从任务的存储结构来看,uC/OS-II 的任务由三个部分构成:
- 任务程序代码,任务的执行部分
- 任务堆栈,用来保存任务工作环境
- 任务控制块,用来保存任务属性
每一个任务都作为一个节点,组成一个双向的任务链表。
用户任务:
从程序代码上看,用户任务似乎就是一个C语言函数,但是这个函数不是一般的C语言函数,它是一个任务(线程)。因此,它不是被主函数或其他函数调用的,主函数mian()只是负责创建和启动它们,而由操作系统负责来调度它们。
系统任务:
uC/OS-II 预定义了两个为应用程序服务的系统任务:
- 空闲任务(OSTaskIdle)
- 统计任务(OSTaskStat)
其中空闲任务是每个应用程序必须使用的。OSTaskIdle由系统自动创建,在系统初始化时,
- OSInit --> OS_InitTaskIdle --> OSTaskIdle
如果用户应用程序要使用统计任务,则必须把定义在OS_CFG.H中的OS_TASK_STAT_EN设置为1,并且必须在创建统计任务之前调用函数OSStatInit( ) 对统计任务进行初始化。
任务优先级:
优先级数目通过OS_CFG.H中的OS_LOWEST_PRIO配置。
固定地,系统总是把最低优先级别OS_LOWEST_PRIO自动赋给空闲任务。如果应用程序中还使用了统计任务,则系统会把OS_LOWEST_PRIO - 1 自动赋给统计任务。
由于每个任务都具有唯一的优先级别,因此uC/OS-II 通常也用任务的优先级别来作为这个任务的标识。
任务堆栈:
堆栈的增长方向随系统所使用的处理器不同而不同,为提高应用程序的可移植性,可利用OS_CFG.H 中的 OS_STK_GROWTH 作为选择开关。
把CPU启动任务时所需的初始数据事先存放在任务的堆栈中,当任务获得CPU使用权时,就可以把堆栈中的初始数据复制到CPU的各寄存器中,使任务顺利地启动并运行。
任务堆栈的初始化工作是由系统通过在OSTaskCreate ( )中调用OSTaskStkInit ( )来完成的。
任务控制块:
任务控制块用来记录一些与任务有关的属性,负责把任务代码和任务堆栈进行关联,而使任务控制块/任务代码/任务堆栈成为一个整体,并且系统要通过这个任务控制块来感知和管理一个任务。
uC/OS-II用两条双向链表来管理任务控制块:
- 空闲链表(OSTCBFreeList):在OSInit ( ) --> OS_InitTCBList ( ) 时建立
- 任务块链表(OSTCBList) :在OSTaskCreate ( ) --> OS_TCBInit ( ) 时建立
两个数组,一个指针:
- OS_TCB OSTCBTbl[ ] : 每个元素就是一个任务控制块,用于构建OSTCBFreeList
- OS_TCB *OSTCBPrioTbl[ ] : 以优先级为顺序存放指向任务控制块的指针,加快访问速度
- OS_TCB *OSTCBCur : 指向当前运行任务的任务控制块
uC/OS-II允许用函数OSTaskDel ( )删除一个任务,实质上就是把该任务的任务控制块从OSTCBList中删掉,并把它归还给OSTCBFreeList 。
任务就绪表:
OSRdyTbl[0] ~ OSRdyTbl[7] 中的每一位对应优先级0~63
OSRdyGrp 中的每一位对应OSRdyTbl[0] ~ OSRdyTbl[7]中的一组
64个优先级需要用6位二进制数标识,高三位(D5D4D3)指明OSRdyTbl[0~7] 中的某一组y,而低三位(D2D1D0)指明OSRdyTbl[y] 中的某一位x
两个用于加快运算速度的数组:
- OSMapTbl[ ] : 用于加快获取某个特定位,OS_TCB中的OSTCBBitX/OSTCBBitY也是出于此目的
- OSUnMapTbl[ ] : 用于加快获取x和y,OS_TCB中的OSTCBX/OSTCBY也是出于此目的
获取最高优先级的就绪任务:
- y = OSUnMapTbl[ OSRdyGrp];
- x = OSUnMapTbl[ OSRdyTbl[ y ] ];
- prio = (y << 3) + x;
- 这里的OSUnMapTbl[ ] 不太好理解,我看了很久才想明白的,其实它是这样构成的:
从00000000到00111111,每一种情况对应OSUnMapTbl[ ] 中的一个元素,自己算一遍就知道了。
任务的调度:
任务调度器的主要工作有两项:
- 在任务就绪表中查找具有最高优先级的就绪任务
- 实现任务的切换
uC/OS-II有两种调度器:
- 任务级调度器(OSSched): uC/OS-II 允许应用程序调用OSSchedLock( )(OSLockNesting + 1) 和 OSSchedUnlock ( ) (OSLockNesting - 1)给调度器上锁和解锁。在确认未被上锁(OSLockNesting == 0)并且不是中断服务程序调用调度器(OSIntNesting == 0)的情况下,OSSched获取最高级别的任务控制块,再根据OSTCBHIghRdy 和 OSTCBCur在宏OS_TASK_SW ( )中实施任务切换。
- 中断级调度器(OSIntExt)
任务切换宏OS_TASK_SW ( ) :
任务切换的工作是靠OSCtxSw ( ) 完成的。
任务的切换其实就是断点数据的切换,断点数据的切换也就是CPU堆栈指针的切换。堆栈指针SP是保存在控制块成员(OS_TCB)的OSTCBStkPtr中的。
OSCtxSw ( ) 要依次完成如下7项任务:
- 把被中止任务的断点指针PC保存到任务堆栈中
- 把CPU通用寄存器的内容保存到任务堆栈中
- 把被中止任务的任务堆栈指针SP保存到该任务的任务控制块OSTCBStkPtr中
- 获得待运行任务的任务控制块
- 使CPU通过任务控制块获得待运行任务的任务堆栈指针SP
- 把待运行任务堆栈中通用寄存器的内容恢复到CPU的通用寄存器中
- 使CPU获得待运行任务的断点指针PC
由于任务切换时需要对CPU的寄存器进行操作,因此在一般情况下,中断服务程序OSCtxSw ( ) 都要用汇编语言来编写。具体的实现与处理器有关,需要根据情况编写。
任务的创建:
uC/OS-II有两个用来创建任务的函数:OSTaskCreate ( ) 和 OSTaskCreateExt ( ) OSTaskCreateExt ( ) 是OSTaskCreate ( ) 的扩展,并提供了一些附加功能。
OSTaskCreate ( ) 首先进行优先级判断,随后调用OSTaskStkInit ( ) 和 OSTCBInit ( ) 进行初始化。初始化成功后,把任务计数器加1,如果 uC/OS-II 处于运行状态(OSRunning == 1),则调用OSSched ( ) 进行任务调度。
- uC/OS-II 有一个规定:在调用启动任务函数OSStart ( ) 之前,必须已经创建了至少一个任务。 uC/OS-II 不允许在中断服务程序中创建任务。
任务的挂起和恢复:
- 挂起任务(OSTaskSuspend):删除任务就绪表中被挂起任务的就绪标志,并在任务控制块成员OSTCBStat中做好挂起记录
- 恢复任务(OSTaskResume) :清除任务控制块成员OSTCBStat中的挂起记录并使任务就绪。
任务优先级的修改:OSTaskChangePrio ( )
任务的删除:
所谓删除一个任务,就是把该任务置于睡眠状态。具体做法是,把被删除任务的OSTCB从OSTCBList中删除,并归还给OSTCBFreeList,然后在OSRdyTbl中把相应的就绪任务位置0 。
在任务中,可以掉用OSTaskDel ( ) 来删除任务自身或者除了空闲任务之外的其他任务。
如果要删除一个占用资源的任务时,提出删除任务请求的任务通过调用OSTaskDelReq ( ) 提出请求,把被删除任务的OSTCB中的OSTCBDelReq置为OS_TASK_DEL_REQ。被删除任务可以在适当的时间检测自己的OSTCB中的OSTCBDelReq,如果被置为OS_TASK_DEL_REQ,则释放资源并且删除自身。提出请求的任务可以通过OSTaskDelReq ( ) 返回值是否为OS_TASK_NOT_EXIST来确定被删除任务是否已经被删除。
查询任务的信息:OSTaskQuery ( )
uC/OS-II 的初始化:
函数OSInit ( ) 将对 uC/OS-II 的所有全局变量和数据结构进行初始化:
任务:
- OSTaskIdle : 赋予最低优先级并永远处于就绪状态
- OSStat : 如果OS_TASK_STAT_EN = 1,则以优先级OS_LOWEST_PRIO - 1创建统计任务。
数据结构:
- 包括空任务控制块链表在内的5个空数据缓冲区。
- OSTCBPrioTbl[ OS_LOWEST_PRIO + 1]
全局变量:
变量 | 值 | 变量的说明 |
OSPrioCur | 0 | INT8U,正在运行的任务的优先级 |
OSPrioHighRdy | 0 | INT8U,具有最高优先级别的就绪任务的优先级 |
OSTCBCur | NULL | OS_TCB *,指向正在运行任务的OSTCB的指针 |
OSTCBHighRdy | NULL | OS_TCB *,指向最高优先级就绪任务OSTCB的指针 |
OSTime | 0L | INT32U,系统当前时间(节拍数) |
OSIntNesting | 0 | INT8U,中断嵌套的层数(0~255) |
OSLockNesting | 0 | INT8U,调用了OSSchedLock的嵌套层数 |
OSCtxSwCtr | 0 | INT32U,上下文切换的次数 |
OSTaskCtr | 2 | INT8U,已经建立了的任务数 |
OSRunning | FALSE | BOOLEAN,uC/OS-II核是否正在运行的标志 |
OSCPUUsage | 0 | INT8S,CPU的利用率(%) |
OSIdleCtrMax | 0L | INT32U,每秒空闲任务计数的最大值 |
OSIdleCtrRun | 0L | INT32U,空闲任务的计数器每秒的计数值 |
OSIdleCtr | 0L | INT32U,空闲任务计数器 |
OSStatRdy | FALSE | BOOLEAN,统计任务是否就绪的标志 |
OSIntExity | 0 | INT8U,用于OSInitExt() |
uC/OS-II 的启动:
uC/OS-II 进行任务的管理是从调用启动函数OSStart ( ) 开始的,前提是在调用该函数之前至少创建了一个任务。
OSStartHighRdy ( ) 在多任务系统启动函数OSStart ( ) 中调用。实现的功能是,设置系统运行标志OSRunning = TRUE,将就绪表中最高优先级任务的栈指针加载到SP中,并强制中断返回。这样就绪的最高优先级任务就如同从中断里返回到运行态一样,使得整个系统得以运转