文章目录
1 创建任务函数对TCB和堆栈的初始化
每个任务都必须拥有自己的栈空间,任务栈永远位于RAM中用来保存局部变量、函数调用以及可能的ISR嵌套;每个任务都有属于自己的一套CPU寄存器
OSTaskCreate()
void OSTaskCreate (OS_TCB *p_tcb, /*任务控制块TCB地址*/
CPU_CHAR *p_name, /*任务名称(字符串)*/
OS_TASK_PTR p_task, /*指向任务函数的指针*/
void *p_arg, /*任务函数的参数*/
OS_PRIO prio, /*任务优先级*/
CPU_STK *p_stk_base, /*任务堆栈的基地址,*/
CPU_STK_SIZE stk_limit, /*任务堆栈深度*/
CPU_STK_SIZE stk_size, /*任务堆栈的大小*/
OS_MSG_QTY q_size, /*任务内部消息队列的最大长度*/
OS_TICK time_quanta, /*时间片轮转调度时该任务所要花费的时间片数*/
void *p_ext, /*任务控制块的补充存储区*/
OS_OPT opt, /*任务创建的选项*/
OS_ERR *p_err) /*函数返回值信息*/
2 创建任务的钩子函数
该函数由用户自己定义,可以将刚才创建的TCB的各个成员的数值显示到终端上,用于调试
3 任务栈空间大小的确定
任务堆栈大小的计算包括:计算任务内所有函数的嵌套调用对存储器的总需求(每个函数的返回地址占用一个指针变量),加上这些函数调用的所有传递参数所需的存储容量,加上任务切换时存储CPU运行环境所需要的存储容量(具体数量于CPU相关),加上每个嵌套的ISR所需要的用来存储CPU运行环境的存储容量(如果CPU没有独立的栈空间来处理ISR,就需要考虑此点),再加上这些ISR所需要的其他各种存储容量。
计算一个任务的栈空间大小往往不是那么容易,在这种情况下,可以给任务分配一块很大的存储空间并且启动多任务管理系统,然后,通过监视任务在运行时所使用的栈空间,就可以知道任务在系统运行以后实际所使用的栈空间大小,其次,分配栈空间时应该保留一定的安全余量,在栈空间的计算值上乘以一个安全系数。
4 任务栈溢出检测
如果使用的处理器有存储器管理单元MMU(Memory Management Unit)或存储器保护单元MPU(Memory Protection Unit),那么检测就会变得简单,MMU与MPU时域CPU集成在一起的特殊硬件设备,经过配置后,能够检测到任务在访问代码、数据或堆栈时对非法存储地址的访问企图。
4.1 使用具有堆栈溢出检测功能的CPU
有一些处理器的确有简单的堆栈指针溢出检测寄存器。当CPU的堆栈指针低于或者高于(具体与堆栈的入栈方向有关)该寄存器的设置值时,一个异常就会产生,异常处理程序可确保(例如发出有关错误代码的警告,甚至结束错误代码的执行)有问题代码不进一步对系统造成伤害。任务控制块TCB的成员.StkLimitPtr就是为这种目的而设计的,堆栈溢出检测限位一般应该设在任务堆栈的有效区域内,并且应该在堆栈中留出足够的空间来处理可能出现的异常。
改变堆栈限位值
首先应该改变堆栈溢出检测寄存器的数值,使其指向NULL,然后改变CPU堆栈指针寄存器的数值,最后再该改变堆栈溢出检测寄存器的数值,如果不这样做,一旦改变堆栈指针寄存器或者改变堆栈溢出检测寄存器的数值就会产生异常。通过改变堆栈溢出检测寄存器的数值,使其指向一个永远不会造成堆栈指针无效的位置, 就可以避免以上情况的发生。
4.2 基于软件的堆栈溢出检测
当系统切换任务时,系统会调用一个介入函数,该函数可以对任务切换的功能进行扩展。通过在介入函数中添加代码可以模拟实现硬件堆栈溢出检测功能。在切换一个任务之前,介入函数中的代码必须确保载入CPU的堆栈指针不能超过.StkLimitPtr中存放的限制值,一旦超出,介入函数就无法正常工作,因此,在设定.StkLimitPtr在堆栈中的位置时使其远离堆栈基地址就显得很重要,软件仿真无法做到硬件那么可靠,但任然可用来防止可能的堆栈溢出。
5 任务管理函数
5.1 任务的内部管理
- 任务的休眠态(dormant state):任务已经存在于存储器中,但还不受μC/OS-III管理
- 任务的就绪态(ready state):当一个任务准备运行时,该任务处于就绪态,系统中允许有任意个就绪态任务,它们被按照优先级在就绪表中被管理
- 任务的运行态(running state):最重要的就绪态任务进入运行态
- 任务的等待态(pending state):当任务调用一个能够使其进入等待态的函数,并且它需等待的事件还未发生,任务就会进入等待态,等待态的任务被放入一个于该任务所等待的事件相对应的等待表中,任务在等待态并不会消耗CPU,当等待事件发生,任务被放回到就绪表中,并且系统会判断这个任务是否是最重要的就绪任务,如果是则CPU的使用权被剥夺。
- 任务的中断服务态(Interrupted state):如果允许CPU被中断,当发生中断时,当前正在执行的任务会被挂起。一般来说,ISR应当只是通知任务某个事件已经发生,而事件的处理则交给任务。ISR应当尽可能短,中断处理的大部分工作应当在μC/OS可管理的任务中进行。ISR仅仅允许调用与post相关的这一类函数。唯一一个不允许ISR调用的post函数是OSMutexPost(),该函数仅允许在任务级调用。
5.2 任务的状态追踪(内部状态机)
任务被挂起时,只能通过OSTaskResume()函数使其恢复
5.3 任务控制块TCB
struct os_tcb {
CPU_STK *StkPtr; /* 指针变量指向当前任务栈的栈顶*/
void *ExtPtr; /* 指针变量指向任务可定义的扩展区*/
CPU_STK *StkLimitPtr; /* 指针变量指向任务栈的栈深度位置*/
OS_TCB *NextPtr; /* 任务就绪表中的下一个任务控制块*/
OS_TCB *PrevPtr; /* 任务就绪表中的上一个任务控制块*/
OS_TCB *TickNextPtr; /* 正在延时后等待某个事件任务的下一个任务控制块*/
OS_TCB *TickPrevPtr; /* 正在延时后等待某个事件任务的上一个任务控制块*/
OS_TICK_SPOKE *TickSpokePtr; /* 通过该指针可以知道该任务挂在时钟节拍列表的哪个位置*/
CPU_CHAR *NamePtr; /* 给任务起名字(ASCII字符串)*/
CPU_STK *StkBasePtr; /* 任务栈的基地址*/
OS_TASK_PTR TaskEntryAddr; /* 任务函数的入口地址*/
void *TaskEntryArg; /* 任务函数的入口参数*/
OS_PEND_DATA *PendDataTblPtr; /* μC/OS允许任务挂起在任意数目的信号量或消息队列上,该指针指向任务挂起表*/
OS_STATE PendOn; /* 表示任务等待的事件类型/
OS_STATUS PendStatus; /* 表示任务等待的状态 */
OS_STATE TaskState; /* 任务当当前状态(内部状态机的8个状态之一)*/
OS_PRIO Prio; /* 任务的优先级*/
CPU_STK_SIZE StkSize; /* 任务栈的大小*/
OS_OPT Opt; /* 任务创建时的选项*/
OS_OBJ_QTY PendDataTblEntries; /* 表示同时等待的事件对象的述目与PendDataTblPtr配合使用*/
CPU_TS TS; /* 时间戳*/
OS_SEM_CTR SemCtr; /* 任务内建的计数型信号量的计数值*/
/* DELAY / TIMEOUT */
OS_TICK TickCtrPrev; /* 任务开始延时时的时钟值*/
OS_TICK TickCtrMatch; /* 任务开始延时时的时钟值与延时时间的总和,用来确定是否延时结束*/
OS_TICK TickRemain; /* 剩余延时时间*/
/* ... run-time by OS_StatTask() */
OS_TICK TimeQuanta; /*时间片轮转调度时,给任务分配的时间片(时钟节拍数)*/
OS_TICK TimeQuantaCtr; /*当前任务的时间片剩余值*/
void *MsgPtr; /* 当任务接收消息时,该指针指向消息*/
OS_MSG_SIZE MsgSize; /*表示接收消息的长度*/
OS_MSG_Q MsgQ; /* 任务内建消息队列*/
CPU_TS MsgQPendTime; /* 记录一条消息到达所花费的时间*/
CPU_TS MsgQPendTimeMax; /* 记录一条消息到达所花费的最长时间*/
OS_REG RegTbl[OS_CFG_TASK_REG_TBL_SIZE]; /* 每个任务的专用寄存器不同于CPU寄存器,用来存储任务专用的消息,
例如:任务ID、errno信息,还可以存储在系统运行时与任务相关联的数据,数据类型必须为OS_REG */
OS_FLAGS FlagsPend; /*等待事件组标志位*/
OS_FLAGS FlagsRdy; /* 等待事件组标志位中的就绪位*/
OS_OPT FlagsOpt; /* 等待的类型*/
OS_NESTING_CTR SuspendCtr; /* 记录任务挂起的次数*/
OS_CPU_USAGE CPUUsage; /* 统计任务记录的任务CPU利用率(0.00-100.00%)*/
OS_CPU_USAGE CPUUsageMax; /* CPU Usage of task (0.00-100.00%) - Peak*/
OS_CTX_SW_CTR CtxSwCtr; /* 任务执行的频繁程度(每次调用就递增)*/
CPU_TS CyclesDelta; /* 此次执行任务所花费的时间,切换时计算value of OS_TS_GET()-.CyclesStart*/
CPU_TS CyclesStart; /*任务切换时,记录的当前任务运行开始的时间*/
OS_CYCLES CyclesTotal; /* 任务运行的总时间sum of CyclesDelta*/
OS_CYCLES CyclesTotalPrev; /* Snapshot of previous # of cycles */
CPU_TS SemPendTime; /* 信号量发送到接收所花费的时间OSTaskSemPost()开始计时,OSTaskSemPend()时返回*/
CPU_TS SemPendTimeMax; /* SemPendTime的最大值,即花费的最长时间*/
CPU_STK_SIZE StkUsed; /* 由OSTaskStkChk()统计的当前任务的堆栈使用字节数*/
CPU_STK_SIZE StkFree; /* 由OSTaskStkChk()统计的当前任务的堆栈未使用字节数*/
CPU_TS IntDisTimeMax; /* 最大中断关闭时间*/
CPU_TS SchedLockTimeMax; /* 锁定调度器的最长时间*/
OS_TCB *DbgPrevPtr; /* 指向OS_TCB双向链表中的前一个OS_TCB,当创建任务时,相应的TCB被插入到双向链表中*/
OS_TCB *DbgNextPtr; /* 指向OS_TCB双向链表中的后一个OS_TCB,当创建任务时,相应的TCB被插入到双向链表中*/
CPU_CHAR *DbgNamePtr; /* 当任务在等待一个时间标志组、一个信号量、一个互斥信号量或一个消息队列时,该指针指向任务所等待对象的名字*/
};
5.4 初始化系统内部任务
- 空闲任务(必须创建)
- 系统创建的第一个任务
- 优先级最低OS_CFG_PRIO_MAX-1
- 空闲任务不能调用任何使其进入等待态的服务函数
- 空闲任务所作的事情
- 对空闲任务计数器不断累加,表示空闲任务的活跃度
- 由统计任务控制的计数器,用来表征统计任务运行时CPU的利用情况
- 空闲任务的钩子函数
- 该函数可以在运行电池供电的任务时,用来进入低功耗模式
- 或作其他的事情
- 时钟节拍任务(必须创建)
- 任务优先级为1
- 用来跟踪任务延时以及任务等待超时
- 该任务为一个周期任务,等待时钟节拍ISR发来的信号
- ISR中断调用OSTimeTick发送一个信号给时钟节拍任务,使时钟节拍任务处于就绪态
- 时钟节拍任务执行时,会遍历所有在等待延时结束或在指定时间内等待某个内核对象的任务
- 这些任务构成的列表称为时钟节拍列表(tick list)
- 时钟节拍任务会使时钟节拍列表中的那些延时已经结束或等待已经超时的任务进入就绪态
- 时钟节拍任务按照任务调用延时时OSTickCtr的值与延迟时间的总和与时钟节拍列表的大小取模进行插入
- 被插入的任务的控制块会记录OSTickCtr的值与延迟时间的总和,每当到达表时,都会比较,相同则从表中删除
- 如果该任务在等待延时结束,会将它放入任务就绪列表
- 如果在等待某个事件,还需从该事件的任务等待表中删除
- 用户必须在多任务系统启动以后再激活时钟节拍信号发生器,也就是在调用OSStart()之后。换言之,在调用OSStart()之后做的第一件事是初始化定时器中断。
- 任务优先级为1
- 统计任务
- 任务优先级为OS_CFG_PRIO_MAX-2
- 统计总的CPU利用率(0.00%~100.00%)
- 各任务的CPU利用率(0.00~100.00%)
- 各任务的堆栈使用量
- 如果需要使用统计任务则需要main()创建的第一个也是唯一一个用用任务中调用OSStatTaskCPUUsageInit()函数
- 通常,μC/OS-III允许用户在调用OSStart()创建任意数目的任务,但是,在需要使用统计任务来计算总的CPU利用率时,必须只创建一个任务。
- 系统会将每个人的运行统计结果存入每个任务的控制块中
- 任务优先级为OS_CFG_PRIO_MAX-2
- 定时任务
- 定时器是一个递减的计数器,当计数器为0时,可执行一个操作,该操作由用户在创建定时器时的回调函数设定。
- 该任务的优先级在os_cfg_app.h中的OS_CFG_TMR_TASK_PRIO设定,一般设置为中等优先级
- 与时钟节拍任务使用相同的中断源,定时器的更新频率一般会慢一些
- 中断服务管理任务
- 最高优先级
- 该任务负责“延迟”(deferring)在ISR中调用的系统post服务函数(OS post service)的行为