声明:该系列例程解读完全基于野火<<uCos-III 内核实现和应用开发实战>>一书详解以及配套实验例程,本人只是当作笔记在此记录,如有侵犯,请联系我,下架该文章.且本人能力有限,对代码理解难免出现纰漏以及错误的地方,敬请留言指正.
在读操作系统的时候,脑海中总是能冒出来一句话:
江湖是一张珠帘。 大人物小人物,是珠子,大故事小故事,是串线。 情义二字,则是那些珠子的精气神.
---------<<雪中悍刀行>>
事实上当工程的代码量超过一定范围时,不能够完整的将函数的执行过程中的相互调用关系理清楚,难免会造成一叶障目不见泰山的感觉,有些不是庐山真面目,只缘身在此山中的无力.在此借用思维导图希望从宏观上理清RTOS的代码
书写格式约定: 对于文章中需要留心的地方用红色书写 ,由于该系列文章属于迭代过程,基本上后一节的大都是在前一节的基础上完成的,对每个例程迭代过程中添加的以蓝色表示
例程目录
例程1 任务的定义与切换
例程1 任务的定义与切换
功能说明: 基本构建uCos-III系统雏形,对工程下文件的作用进行初步认识.
工程文件树:
工程分为三部分
一 文件解析 :描述工程下各个文件的宏定义 类型typedef重命名 函数声明
二 函数主体 : 理解各个文件(.c/.s)函数主体
三 思维导图: 从宏观上看工程的程序走向
一 文件解析
在此根据工程下文件夹来分别列出:工程下繁多的宏定义和变量类型(全局变量 局部静态变量)以及函数声明
为方便阅读 建立锚点
Template
---------User
---------uCos-III Source
---------uCos-III Ports
---------uC/CPU
user文件夹 :暂时仅包含main.c
[1] 宏定义:
#define TASK1_STK_SIZE 20 //任务堆栈的大小 字节为单位
#define TASK2_STK_SIZE 20
[2] 变量定义:全局变量 静态局部变量
static CPU_STK Task1Stk[TASK1_STK_SIZE]; //定义静态变量 任务1堆栈
static CPU_STK Task2Stk[TASK2_STK_SIZE];
static OS_TCB Task1TCB;//定义静态变量 任务控制块
static OS_TCB Task2TCB;
[3] 函数声明:
void Task1( void *p_arg); //任务函数主体 即实现功能代码部分
void Task2( void *p_arg );
void delay(uint32_t count); //延时函数
uCos-III Source文件夹
该文件夹下 文件目录
(1)os.h
(2)os_cfg.h
(3)os_core.c
(4)os_task.c
(5)os_type.c
(6)os_var.c
[1]宏定义
(1)os.h文件
//定义状态值 类型为OS_STATE---> 无符号字符类型
#define OS_STATE_OS_STOPPED (OS_STATE)(0u)
#define OS_STATE_OS_RUNNING (OS_STATE)(1u)
(2)os_cfg.h
//系统支持优先级的最大值 常数
#define OS_CFG_PRIO_MAX 32u
(3)os_core.c以及对应的os_core.h文件
该文件仅包含关于内核部分的部分函数 且声明均在os.h中完成
(4)os_task.c
该文件仅包含关于任务部分的部分函数 且声明均在os.h中完成
(5)os_type.c
该文件仅包含关于任务部分的部分函数 且声明均在os.h中完成
(6)os_var.c
仅完成变量的定义
[2]变量定义:全局变量 静态局部变量 数据结构类型参考下方定义
(1)os.h文件
//结构类型:任务控制块指针 指向OS当前执行的任务控制块
OS_EXT OS_TCB *OSTCBCurPtr;
//结构类型:任务控制块指针 指向OS当前处于就绪状态且优先级为最高的任务控制块
OS_EXT OS_TCB *OSTCBHighRdyPtr;
//结构类型: 双向链表结构体数组 表示就绪队列 数组成员为双向链表
//分别连接相同处于就绪状态的优先级相同的TCB
OS_EXT OS_RDY_LIST OSRdyList[OS_CFG_PRIO_MAX];
// uchar类型变量 表当前系统运行状态
OS_EXT OS_STATE OSRunning;
(2)os_cfg.h 无
(3)os_core.c 无
(4)os_task.c 无
(5)os_type.c 无
(6)os_var.c
该文件的点睛之笔在于在os.h文件 文件首处存在如下预编译:
#ifdef OS_GLOBALS
#define OS_EXT
#else
#define OS_EXT extern
#endif
而在该文件首先 宏定义 #define OS_GLOBALS 后包含头文件#include "os.h"
由基本的编译知识: #include "xxxxx.h"命令使得编译器在生成可执行文件的过程
中,将头文件内容经过条件编译判别嵌入到源程序中,真正有效的部分为源程序.c 文
件.在os.h头文件中包含大量前缀OS_EXT 在进行嵌入os_var.c文件时:由于之前宏定
义OS_GLOBALS,故在该文件中视为空-->" ",进行变量的定义,即分配内存空间,但在
其他xxx.c文件(无宏定义OS_GLOBALS )嵌入os.h文件时,通过条件编译,将OS_EXT简单
替换为extern,完成应用外部变量的声明.
[3]函数声明
(1)os.h
//OS任务创建
void OSTaskCreate (OS_TCB *p_tcb,
OS_TASK_PTR p_task,
void *p_arg,
CPU_STK *p_stk_base,
CPU_STK_SIZE stk_size,
OS_ERR *p_err);
void OSInit(OS_ERR *p_err); //系统初始化
void OSStart (OS_ERR *p_err); //启动系统 赋值OSTCBHighRdyPtr变量
void OSSched (void); //任务切换
//OS任务堆栈初始化
CPU_STK *OSTaskStkInit (OS_TASK_PTR p_task,
void *arg,
CPU_STK *p_stk_base,
CPU_STK_SIZE stk_size);
//OS就绪链表初始化
void OS_RdyListInit (void);
(2)os_cfg.h 无
(3)os_core.c
(4)os_task.c
(5)os_type.c .c文件中函数声明均在os.h文件中声明 对应.h文件 仅起到部分作用
(6)os_var.c 无
数据结构 该类型只在os.h中进行定义
// 枚举 RTOS在运行过程中可能出现的各种错误码 枚举结构类型为OS_ERR
typedef enum os_err {
OS_ERR_NONE = 0u,
OS_ERR_A = 10000u,
OS_ERR_ACCEPT_ISR = 10001u,
//...其实还有很多 限于篇幅 不作处理
OS_ERR_Z = 35000u
} OS_ERR;
// 建立双向链表结构体 成员[1]前驱任务控制块类型指针 [2]后继任务控制块类型指针
struct os_rdy_list
{
OS_TCB *HeadPtr;
OS_TCB *TailPtr;
};
struct os_tcb //结构体: 任务控制块主体 [1]堆栈指针 [2]堆栈大小
{
CPU_STK *StkPtr;
CPU_STK_SIZE StkSize;
};
typedef重命名
(1)os.h
typedef struct os_rdy_list OS_RDY_LIST; //重命名 双向链表结构体
// 在此定重定义了一种数据类型OS_TASK_PTR 为函数指针 且该函数的返回值为 void 类型 函数传入参数为 void类型指针的
//变量(其实不存在 void类型的指针 在此引用只是为了说明对该函数的传入参数的数据结构并没有做硬性规定 但至少是个指针)
typedef void (*OS_TASK_PTR)(void *p_arg);
typedef struct os_tcb OS_TCB; //重定义 任务控制块结构体
(2)os_type.h
typedef CPU_INT16U OS_OBJ_QTY;
typedef CPU_INT08U OS_PRIO;//重定义 OS_PRIO为char类型
typedef CPU_INT08U OS_STATE;
uCos-III Ports文件夹
该文件夹下,包含子文件:
(1)os_cpu.h
(2)os_cpu_a.s
(3)os_cpu_c.c
[1] 宏定义:
(1)os_cpu.h
//条件编译 决定定义NVIC_INT_CTRL 中断控制寄存器
#ifndef NVIC_INT_CTRL
#define NVIC_INT_CTRL *((CPU_REG32 *)0xE000ED04) /* 中断控制及状态寄存器 SCB_ICSR */
#endif
//条件编译 决定定义NVIC_PENDSVSET
#ifndef NVIC_PENDSVSET
#define NVIC_PENDSVSET 0x10000000 /* 触发PendSV异常的值 Bit28:PENDSVSET */
#endif
//将宏伪装成函数的形式 改变中断控制及状态寄存器的数值 触发中断
#define OS_TASK_SW() NVIC_INT_CTRL = NVIC_PENDSVSET
#define OSIntCtxSw() NVIC_INT_CTRL = NVIC_PENDSVSET
(2)os_cpu_a.s 汇编文件中定义的常量类型
NVIC_INT_CTRL EQU 0xE000ED04 ; 中断控制及状态寄存器 SCB_ICSR。
NVIC_SYSPRI14 EQU 0xE000ED22 ; 系统优先级寄存器 SCB_SHPR3:bit16~23
NVIC_PENDSV_PRI EQU 0xFF ; PendSV 优先级的值(最低)。
NVIC_PENDSVSET EQU 0x10000000 ; 触发PendSV异常的值 Bit28:PENDSVSET。
[2] 变量定义: 均无
[3] 函数声明:
(1)os_cpu.h
void OSStartHighRdy(void); //完成OS
void PendSV_Handler(void); //系统内部PendSV 中断服务函数主体
(2)os_cpu_a.s
EXPORT OSStartHighRdy ; 该文件定义的函数
EXPORT PendSV_Handler
(3)os_cpu_c.c OS任务堆栈初始化函数 在os.h中声明
CPU_STK *OSTaskStkInit (OS_TASK_PTR p_task,
void *p_arg,
CPU_STK *p_stk_base,
CPU_STK_SIZE stk_size)
uC/CPU文件夹
该文件夹下,包含子文件:
(1)cpu.h
[1] 宏定义: 无
[2] 变量定义: 无
[3] 函数声明: 无
[4] 重命名typedef:
(1)cpu.h
typedef unsigned short CPU_INT16U;
typedef unsigned int CPU_INT32U;
typedef unsigned char CPU_INT08U;
//由于STM32的地址总线是32的 故用int描述地址
typedef CPU_INT32U CPU_ADDR;
/* 堆栈数据类型重定义 */
typedef CPU_INT32U CPU_STK;
typedef CPU_ADDR CPU_STK_SIZE;
//增加关键字 volatile 来描述寄存器的 异变特性 使其变量在内存分配时尽量占用 寄存器
typedef volatile CPU_INT32U CPU_REG32;
二 函数主体
(1)main.c文件
int main(void)
{
//定义局部变量 通过各函数利用指针作为传入形参记录在OS运行过程中可能出现的错误码
OS_ERR err;
OSInit(&err); //OS初始化函数
//OS任务创建函数 传入参数[1]TCB指针 [2]函数指针: 函数名 [3]传入参数
//[4]任务堆栈指针 [5]任务堆栈大小 [6]全局变量:错误码
OSTaskCreate ((OS_TCB*) &Task1TCB,
(OS_TASK_PTR ) Task1,
(void *) 0,
(CPU_STK*) &Task1Stk[0],
(CPU_STK_SIZE) TASK1_STK_SIZE,
(OS_ERR *) &err);
OSTaskCreate ((OS_TCB*) &Task2TCB,
(OS_TASK_PTR ) Task2,
(void *) 0,
(CPU_STK*) &Task2Stk[0],
(CPU_STK_SIZE) TASK2_STK_SIZE,
(OS_ERR *) &err);
// 将任务TCB 挂接到 就绪队列(数组)中
OSRdyList[0].HeadPtr = &Task1TCB;
OSRdyList[1].HeadPtr = &Task2TCB;
OSStart(&err);
}
void delay (uint32_t count)
{
for(; count!=0; count--);
}
void Task1( void *p_arg )
{
for( ;; )
{
flag1 = 1;
delay( 100 );
flag1 = 0;
delay( 100 );
OSSched();
}
}
(2)os_core.c
void OSInit (OS_ERR *p_err)
{
//该变量 是在os.h中声明的OS_STATE 类型变量 用来指示OS运行状态
//右边为宏常量
OSRunning = OS_STATE_OS_STOPPED;
//设置全局变量 TCB指针初始指向空
OSTCBCurPtr = (OS_TCB *)0;
OSTCBHighRdyPtr = (OS_TCB *)0;
OS_RdyListInit(); //调用OS就绪列表初始化函数
//通过指针改变传入参数 实时记录OS运行状态 之后不做讲解
*p_err = OS_ERR_NONE;
}
void OS_RdyListInit(void)
{
OS_PRIO i;
OS_RDY_LIST *p_rdy_list;
//遍历 就绪列表(双向链表结构体数组) 使得每一双向链表在开始时前驱指针和后继指针为空
for( i=0u; i<OS_CFG_PRIO_MAX; i++ )
{
p_rdy_list = &OSRdyList[i];
p_rdy_list->HeadPtr = (OS_TCB *)0;
p_rdy_list->TailPtr = (OS_TCB *)0;
}
}
void OSStart (OS_ERR *p_err)
{ //改变全局变量:系统运行状态 OSRunning
if( OSRunning == OS_STATE_OS_STOPPED )
// 将就绪队列[0] 指向的TCB ----> 处于就绪状态的最高优先级的TCB
OSTCBHighRdyPtr = OSRdyList[0].HeadPtr;
//系统 从就绪状态的最高优先级指向的TCB开始运行 ---汇编函数
OSStartHighRdy();
/* 不会运行到这里,运行到这里表示发生了致命的错误 */
*p_err = OS_ERR_FATAL_RETURN;
}
else
{
*p_err = OS_STATE_OS_RUNNING;
}
}
(3)os_task.c
//通过传入参数 初始化任务的
void OSTaskCreate (OS_TCB *p_tcb,
OS_TASK_PTR p_task,
void *p_arg,
CPU_STK *p_stk_base,
CPU_STK_SIZE stk_size,
OS_ERR *p_err)
{
CPU_STK *p_sp;
//传入 [1]函数指针(地址) [2]p_arg [3]堆栈基地址 [4]堆栈大小
p_sp = OSTaskStkInit (p_task, p_arg,p_stk_base, stk_size);
//配置TCB结构体成员
p_tcb->StkPtr = p_sp;
p_tcb->StkSize = stk_size;
*p_err = OS_ERR_NONE;
}
(4)os_cpu_c.c
//该函数的主要作用 将任务函数地址和ARM芯片内部使用的 寄存器映射值(对应4G空间内的地址) 压入堆栈中
CPU_STK *OSTaskStkInit (OS_TASK_PTR p_task,
void *p_arg,
CPU_STK *p_stk_base,
CPU_STK_SIZE stk_size)
{
CPU_STK *p_stk;
p_stk = &p_stk_base[stk_size];
/* 异常发生时自动保存的寄存器 */
*--p_stk = (CPU_STK)0x01000000u; /* xPSR的bit24必须置1 */
*--p_stk = (CPU_STK)p_task; /* 任务的入口地址 */
*--p_stk = (CPU_STK)0x14141414u; /* R14 (LR) */
*--p_stk = (CPU_STK)0x12121212u; /* R12 */
*--p_stk = (CPU_STK)0x03030303u; /* R3 */
*--p_stk = (CPU_STK)0x02020202u; /* R2 */
*--p_stk = (CPU_STK)0x01010101u; /* R1 */
*--p_stk = (CPU_STK)p_arg; /* R0 : 任务形参 */
/* 异常发生时需手动保存的寄存器 */
*--p_stk = (CPU_STK)0x11111111u; /* R11 */
*--p_stk = (CPU_STK)0x10101010u; /* R10 */
*--p_stk = (CPU_STK)0x09090909u; /* R9 */
*--p_stk = (CPU_STK)0x08080808u; /* R8 */
*--p_stk = (CPU_STK)0x07070707u; /* R7 */
*--p_stk = (CPU_STK)0x06060606u; /* R6 */
*--p_stk = (CPU_STK)0x05050505u; /* R5 */
*--p_stk = (CPU_STK)0x04040404u; /* R4 */
return (p_stk);
}
(5) os_cpu_a.s 基本选用 野火例程 针对汇编文件的讲解
; 外部文件引人的全局变量 [1]OS当前运行控制块指针 [2]处于就绪状态的最高优先级TCB指针
IMPORT OSTCBCurPtr
IMPORT OSTCBHighRdyPtr
; 该文件定义的函数 导出 使这些函数能够被外部文件所使用 最终在os_cpu.h文件中声明 使其被调用
EXPORT OSStartHighRdy
EXPORT PendSV_Handler
; 字节对齐
PRESERVE8
THUMB ;指令格式为THUMB指令 通常为16位的
AREA CODE, CODE, READONLY ;建立代码段 只读类型
OSStartHighRdy
;通过 汇编直接寻址方式 对寄存器操作 设置 PendSV 异常优先级为最低
LDR R0, = NVIC_SYSPRI14
LDR R1, = NVIC_PENDSV_PRI
STRB R1, [R0] ;NVIC_PENDSV_PRI--->[NVIC_SYSPRI14] 配置中断优先级
MOVS R0, #0 ; 设置psp的值为0,开始第一次上下文切换
MSR PSP, R0 ; 通用寄存器--->特殊功能寄存器
LDR R0, =NVIC_INT_CTRL ; 触发PendSV异常
LDR R1, =NVIC_PENDSVSET
STR R1, [R0] ;NVIC_PENDSVSET-->[NVIC_INT_CTRL] 触发中断
CPSIE I ; 开中断
OSStartHang
B OSStartHang ; 程序应永远不会运行到这里
PendSV_Handler
; 任务的保存,即把CPU寄存器的值存储到任务的堆栈中
CPSID I ; 关中断,NMI和HardFault除外,防止上下文切换被中断
MRS R0, PSP ; 将psp的值加载到R0
CBZ R0, OS_CPU_PendSVHandler_nosave ; 判断R0,如果值为0则跳转到OS_CPU_PendSVHandler_nosave
; 进行第一次任务切换的时候,R0肯定为0
; 在进入PendSV异常的时候,当前CPU的xPSR,PC(任务入口地址),R14,R12,R3,R2,R1,R0会自动存储到当前任务堆栈,同时递减PSP的值
STMDB R0!, {R4-R11} ; 后变址操作:手动存储CPU寄存器R4-R11的值到当前任务的堆栈
LDR R1, = OSTCBCurPtr ; 加载 OSTCBCurPtr 指针的地址到R1,这里LDR属于伪指令
LDR R1, [R1] ; 加载 OSTCBCurPtr 指针到R1,这里LDR属于ARM指令
STR R0, [R1] ; 存储R0的值到 OSTCBCurPtr->OSTCBStkPtr,这个时候R0存的是任务空闲栈的栈顶
; 任务的切换,即把下一个要运行的任务的堆栈内容加载到CPU寄存器中
OS_CPU_PendSVHandler_nosave
; OSTCBCurPtr = OSTCBHighRdyPtr;
LDR R0, = OSTCBCurPtr ; 加载 OSTCBCurPtr 指针的地址到R0,这里LDR属于伪指令
LDR R1, = OSTCBHighRdyPtr ; 加载 OSTCBHighRdyPtr 指针的地址到R1,这里LDR属于伪指令
LDR R2, [R1] ; 加载 OSTCBHighRdyPtr 指针到R2,这里LDR属于ARM指令
STR R2, [R0] ; 存储 OSTCBHighRdyPtr 到 OSTCBCurPtr
LDR R0, [R2] ; 加载 OSTCBHighRdyPtr 到 R0
LDMIA R0!, {R4-R11} ; 加载需要手动保存的信息到CPU寄存器R4-R11--->针对该任务的相关参数传至 寄存器组中
MSR PSP, R0 ; 更新PSP的值,这个时候PSP指向下一个要执行的任务的堆栈的栈底(这个栈底已经加上刚刚手动加载到CPU寄存器R4-R11的偏移)
ORR LR, LR, #0x04 ; 确保异常返回使用的堆栈指针是PSP,即LR寄存器的位2要为1
CPSIE I ; 开中断
BX LR ; 异常返回,这个时候任务堆栈中的剩下内容将会自动加载到xPSR,PC(任务入口地址),R14,R12,R3,R2,R1,R0(任务的形参)
; 同时PSP的值也将更新,即指向任务堆栈的栈顶。在STM32中,堆栈是由高地址向低地址生长的。
NOP ; 为了汇编指令对齐,不然会有警告
END ; 汇编文件结束