前言
主要是分析两个文件:os_cpu.h和ucosii.h,对其中的数据结构做一个汇总分析。
ucosii源码的是2.92.11版本的。
1. os_cpu.h
1.1 typedef
typedef unsigned char INT8U;
typedef unsigned int OS_STK;
typedef unsigned int OS_CPU_SR;
很经典的用法,见名知义,并且符合单片机中对bit的精确要求。
同时也是提高程序的可移植性,很TM重要。
1.2 OS_CRITICAL_METHOD
OS_CRITICAL_METHOD是个宏定义,名字些微有点长,英译是“系统临界段模式”的意思。
如果宏定义成3,表示利用编译器的扩展功能,将程序状态字保存到一个局部变量中;
为什么要用这个,看了源码之后你会发现,ucosii几乎每一个文件中都定义了这样的一个变量: OS_CPU_SR cpu_sr = 0u
,这样做是为了给OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()进出临界区的两个函数提供上面所说的局部变量。
有了这个局部变量,就能实现屏蔽中断和恢复中断的功能,进而来保证修改共享资源时不被中断或异常打断。
1.3 汇编函数的声明
OS_CPU_SR OS_CPU_SR_Save (void);
void OS_CPU_SR_Restore (OS_CPU_SR cpu_sr);
void OSCtxSw (void);
void OSIntCtxSw (void);
void OSStartHighRdy (void);
void OS_CPU_PendSVHandler (void);
void OS_CPU_SysTickHandler (void);
void OS_CPU_SysTickInit (INT32U cnts);
如果只是将ucosii拿来用,一般不会需要去探究这些汇编函数是怎么实现的,但是需要了解这些功能最终实在汇编代码里实现的。
而如果是进行差异化移植驱动的话,可能就需要研究这些是如何实现的,这里需要对ARM架构和汇编指令集有所了解,可以参考《Cortex-M3权威指南·宋岩译》。
2. ucosii.h
ucosii.h和os_core.c是系统中篇幅最大的两个文件。
在ucosii.h文件中,篇幅最大的则是预处理,其一来实现预编译-实现内核可裁剪,其二是预编译中的错误判断和输出,但都不是最重要的。
对ucosii.h文件格式优化之后,可以发现:
系统错误码宏定义近100行;
系统中的所有函数的声明,100多行;
结构体定义和相关宏定义,全局变量大概400行。
围绕着结构体定义,该文件的内容分两大块,事件和任务。
2.1 Event
ucosii中的通信机制,把信号量、消息邮箱、互斥量、消息队列、事件标志组5个方式纳为一个类型。
但是,这里不对互斥量、事件标志组做研究,只关注2个:信号量和消息队列。
所以,定义了如下结构体OS_EVENT和OS_Q:
typedef struct os_event {
INT8U OSEventType;//表示事件类型,不同的事件将其对应的位置1或清0
void *OSEventPtr;//它有两个作用,1在初始化时形成一个单向链表,2指向消息邮箱或消息队列的控制块
INT16U OSEventCnt;//计数器,信号量会用到
OS_PRIO OSEventGrp;
OS_PRIO OSEventTbl[OS_EVENT_TBL_SIZE];//“组”和“表”,见优先级算法
} OS_EVENT;
OS_EXT OS_EVENT *OSEventFreeList; //指向表的空闲处
OS_EXT OS_EVENT OSEventTbl[OS_MAX_EVENTS]; //事件控制块表,结构体数组
typedef struct os_q {
struct os_q *OSQPtr;
void **OSQStart;
void **OSQEnd;
void **OSQIn;
void **OSQOut;
INT16U OSQSize;
INT16U OSQEntries;
} OS_Q;
OSQPtr是结构体指针用来在初始化时形成链接(单向链表);
OSQStart、OSQEnd指向消息队列的起始地址和结束地址;
OSQIn、OSQOut指向队头和队尾;
OSQSize和OSQEntries表示消息队列的容量和当前数目。
OS_Q是个很典型的队列控制结构。此外,因为消息本质上就是个指针,我们要做的是读取该数据指向的内容;消息队列,则是一个指针数组,里面存放了多条消息,所以成员中定义成二级指针。
2.2 Task
typedef struct os_tcb {
OS_STK *OSTCBStkPtr;
OS_STK *OSTCBStkBottom;
INT32U OSTCBStkSize;
INT16U OSTCBOpt;
struct os_tcb *OSTCBNext;
struct os_tcb *OSTCBPrev;
OS_EVENT *OSTCBEventPtr;
void *OSTCBMsg;
INT32U OSTCBDly;
INT8U OSTCBStat;
INT8U OSTCBStatPend;
INT8U OSTCBPrio;
INT8U OSTCBX;
INT8U OSTCBY;
OS_PRIO OSTCBBitX;
OS_PRIO OSTCBBitY;
} OS_TCB;
如上,都是是结构体成员中比较重要的。要素过多,一个一个注释。
OSTCBStkPtr,是任务堆栈的栈顶指针,在任务被调度执行时,汇编代码会用到。ucos中每个任务都有自己的堆栈空间,任务进行切换时,将上个任务推到老任务的堆栈区,然后读取新任务的堆栈区指针,开始执行。
OSTCBStkBottom、OSTCBStkSize会在统计任务中用到,统计任务会用来检查每个任务的堆栈空间是否异常。OSTCBOpt一般用于在创建任务时进行堆栈检查。
OSTCBNext和OSTCBPrev,很明显是用作双向链接,方便实现一个插入和删除操作。
OSTCBEventPtr,用来执行任务所拥有的事件;OSTCBMsg,用来给消息队列传递消息。
OSTCBDly,是任务延时节拍,或是任务自身延时,或是任务因等待事件而延时,最终都会在OSTimeTick()中进行滴答递减实现调度。
OSTCBStat和OSTCBStatPend,是程序状态字和等待状态字。
ucosii中任务,有五态:就绪、运行、等待、被中断和睡眠。
每个任务创建之后都默认就绪态,且等待OS调度;任务由等待态和被中断态结束时,也是先返回就绪态。
OS调度永远从就绪的任务中,提取优先级最高的任务开始执行,运行态。
如果任务正在运行,突然被中断打断,则处于被中断态。
睡眠态,要么任务未被创建,要么任务被删除,要么任务被高高挂起。
基于这个逻辑,宏定义了如下几个来表示OSTCBStat(刚好8个位全部用完):
#define OS_STAT_RDY 0x00u
#define OS_STAT_SEM 0x01u
#define OS_STAT_MBOX 0x02u
#define OS_STAT_Q 0x04u
#define OS_STAT_SUSPEND 0x08u
#define OS_STAT_MUTEX 0x10u
#define OS_STAT_FLAG 0x20u
#define OS_STAT_MULTI 0x80u
2.3 优先级算法
OSTCBPrio,是8bit的无符号变量,用来表示优先级,但是ucosii只用了6bit来表示优先级(即最大63(111 111))。
高三bit表示OSTCBY,低三bit表示成OSTCBX,则值在0~7之间。
那么就有如下数学关系:
关系式1:OSTCBPrio = OSTCBY << 3 + OSTCBX;
关系式2:OSTCBY = OSTCBPrio >> 3; OSTCBX = OSTCBPrio & 0x07;
且从0到63,OSTCBY 和OSTCBX 的值非常有规律性,同一组内OSTCBY的值相等,不同组但是间隔为8的倍数的OSTCBX 相等。
所以就有了“组”和“表”的概念,“组”刚好分8个,“表”刚好分8个;再利用位操作来实现,“组”刚好用一个8bit的无符号变量来表示,“表”刚好有一个[8]的数组来表示,且数组的索引就是“OSTCBY”。
ucosii巧妙的是,利用这个规律定义了OSTCBBitX和OSTCBBitY:
用OSTCBBitY表示,8个组的状态;
用OSTCBBitX表示,同一组内8个任务的状态;
每有任意一个组或任务就绪,就让对应的位置1,则值在0到255之间。
那么就有如下数学关系:
关系式3:OSTCBBitX = 1 << OSTCBX; OSTCBBitY= 1 << OSTCBY;
通过这三个位操作关系式,我们就能得到总体的任务就绪状态。
但是,ucosii更巧妙的是,用一个索引表OSUnMapTbl[256]来实现了提取其中的最高优先级,只需要计算出0到255这256个数-每一个数的二进制值中从右边开始第一个出现1的位,因为其代表了最高的就绪位(你品,你细品)
例如,利用这个映射表,
先用Group作为索引,在索引表中提取出最高优先级任务的OSTCBY值 -- y = OSUnMapTbl[Group]
那么,有了OSTCBY索引,就能得到“表Tbl”的值 x = OSUnMapTbl[ Tbl[y] ]
由此,优先级prio = y << 3 + x;
同理,在实现任务的就绪和等待时,我们只需要提取当前任务的优先级,对Bit值进行置1或清0。
小尾巴:
一些相关的全局变量:
OS_EXT OS_TCB *OSTCBCur; //指向正在运行任务的TCB
OS_EXT OS_TCB *OSTCBFreeList; //指向空闲的TCB块
OS_EXT OS_TCB *OSTCBHighRdy; //指向就绪表中的最高优先级TCB
OS_EXT OS_TCB *OSTCBList; //指向最后一次创建的任务,用于遍历TCB表
OS_EXT OS_TCB *OSTCBPrioTbl[OS_LOWEST_PRIO + 1u]; //指针数组,按照优先级指向TCB
OS_EXT OS_TCB OSTCBTbl[OS_MAX_TASKS + OS_N_SYS_TASKS]; //TCB表,结构体数组