对ucosii数据结构的理解

 

前言

主要是分析两个文件: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表,结构体数组

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猪熊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值