×
嵌入式 > 嵌入式开发 > 详情

UCOSIII任务堆栈、控制块及就绪表

发布时间:2020-05-20 发布时间:
|

一、UCOSIII任务堆栈


1、任务堆栈的创建


堆栈是在RAM中按照“先进先出(FIFO)”的原则组织的一块连续的存储空间。为了满足任务切换和响应中断时保存CPU寄存器中的内容及任务调用其它函数时的需要,每个任务都应该有自己的堆栈。


如何创建?


#define START_STK_SIZE           512  //堆栈大小


CPU_STK START_TASK_STK[START_STK_SIZE];   //定义一个数组来作为任务堆栈


可查看main.c的29行,跳转可知堆栈大小:


CPU_STK为CPU_INT32U类型,也就是unsigned int类型,为4字节的,那么任务堆栈START_TASK_STK的大小就为:512 X 4=2048字节!


2、任务堆栈的初始化


任务如何才能切换回上一个任务并且还能接着从上次被中断的地方开始运行?


恢复现场即可,现场就是CPU的内部各个寄存器。因此在创建一个新任务时,必须把系统启动这个任务时所需的CPU各个寄存器初始值事先存放在任务堆栈中。这样当任务获得CPU使用权时,就把任务堆栈的内容复制到CPU的各个寄存器,从而可以任务顺利地启动并运行。


把任务初始数据存放到任务堆栈的工作就叫做任务堆栈的初始化,UCOSIII提供了完成堆栈初始化的函数:OSTaskStkInit():


CPU_STK  *OSTaskStkInit (OS_TASK_PTR    p_task,

                         void          *p_arg,

                         CPU_STK       *p_stk_base,

                         CPU_STK       *p_stk_limit,

                         CPU_STK_SIZE   stk_size,

                         OS_OPT         opt)

用户一般不会直接操作堆栈初始化函数,任务堆栈初始化函数由任务创建函数OSTaskCreate()调用。不同的CPU对于的寄存器和对堆栈的操作方式不同,因此在移植UCOSIII的时候需要用户根据各自所选的CPU来编写任务堆栈初始化函数。


可查看UCOSIII_PORT中的os_cpu_c.c中227行


3、如何使用创建的任务堆栈?


作为任务创建函数OSTaskCreate()的参数,函数OSTaskCreate()如下:


void  OSTaskCreate (OS_TCB         *p_tcb,             //任务控制块

                    CPU_CHAR     *p_name,         //任务名字

                    OS_TASK_PTR   p_task,             //任务函数

                    void           *p_arg,             //传递给任务函数的参数

                    OS_PRIO        prio,               //任务优先级

                    CPU_STK       *p_stk_base,    //任务堆栈基地址 一般0

                    CPU_STK_SIZE   stk_limit,   //任务堆栈栈深 一般堆栈大小/10

                    CPU_STK_SIZE   stk_size,    //任务堆栈大小            前面创建的

                    OS_MSG_QTY   q_size,

                    OS_TICK        time_quanta,

                    void           *p_ext,      //用户补充的存储区

                    OS_OPT        opt,

                    OS_ERR        *p_err)      //存放该函数错误时的返回值

可查看main.c中79行的任务开始函数


4、堆栈增长方式


函数OSTaskCreate()中的参数p_stk_base如何确定?


根据堆栈的增长方式,堆栈有两种增长方式:


向上增长:堆栈的增长方向从低地址向高地址增长。


向下增长:堆栈的增长方向从高地址向低地址增长。


函数OSTaskCreate()中的参数p_stk_base是任务堆栈基地址,那么如果CPU的堆栈是向上增长的话那么基地址就&START_TASK_STK[0],如果CPU堆栈是向下增长的话基地址就是&START_TASK_STK[START_STK_SIZE-1]STM32的堆栈是向下增长的!


二、UCOSIII任务控制块


1、任务控制块结构


任务控制块是用来记录与任务相关的信息的数据结构,每个任务都要有自己的任务控制块。任务控制块由用户自行创建,如下代码为创建一个任务控制块:


OS_TCB StartTaskTCB;  //创建一个任务控制块


OS_TCB为一个结构体,描述了任务控制块,任务控制块中的成员变量用户不能直接访问,更不可能改变他们。


OS_TCB为一个结构体,其中有些成员采用了条件编译的方式来确定


struct os_tcb

 

{

 

    CPU_STK             *StkPtr;  //指向当前任务堆栈栈顶                         

 

    void                *ExtPtr; //指向用户可定义的数据取                           

 

    CPU_STK             *StkLimitPtr;//可指向任务堆栈中的某个位置

 

    OS_TCB              *NextPtr; //NexPtr和PrevPtr用于在任务就绪表建立OS_TCB

 

    OS_TCB              *Prev

 

    ……                              //此处省略N个成员变量

 

 #if OS_CFG_DBG_EN > 0u

 

    OS_TCB              *DbgPrevPtr;//下面三个成员变量用于调试

 

    OS_TCB              *DbgNextPtr;

 

    CPU_CHAR            *DbgNamePtr;

 

 #endif

 

}

可查看main.c中26行进行跳转,里面有很多的成员变量


2、任务控制块初始化


函数OSTaskCreate()在创建任务的时候会对任务的任务控制块进行初始化。


函数OS_TaskInitTCB()用与初始化任务控制块。用户不需要自行初始化任务控制块。


可查看os_task.c中341行


三、UCOSIII任务就绪表


1、优先级:


UCOSIII中任务优先级数由宏OS_CFG_PRIO_MAX来配置,UCOSIII中数值越小,优先级越高,最低可用优先级就是OS_CFG_PRIO_MAX-1。


可查看UCOSIII_CONFIG中os_cfg.c中47行


2、就绪表


    UCOSIII中就绪表由2部分组成:


    1、优先级位映射表OSPrioTbl[]:用来记录哪个优先级下有任务就绪。


    2、就绪任务列表OSRdyList[]:用来记录每一个优先级下所有就绪的任务。


OSPrioTbl[]在os_prio.c中有定义:


CPU_DATA   OSPrioTbl[OS_PRIO_TBL_SIZE]; //UCOSIII_CORE中os_prio.c中41行


跳转可知:


在STM32中CPU_DATA为unsigned int,有4个字节,32位。因此表OSPrioTbl每个参数有32位,其中每个位对应一个优先级。


OS_PRIO_TBL_SIZE=((OS_CFG_PRIO_MAX - 1u) / DEF_INT_CPU_NBR_BITS)+ 1)


其中OS_CFG_PRIO_MAX由用户自行定义,默认为64。


DEF_INT_CPU_NBR_BITS= CPU_CFG_DATA_SIZE * DEF_OCTET_NBR_BITS


CPU_CFG_DATA_SIZE=CPU_WORD_SIZE_32=4。


DEF_OCTET_NBR_BITS=8。


所以,当系统有64个优先级的时候:


OS_PRIO_TBL_SIZE=((64-1)/(4*8)+1)=2。


2.1、如何找到已经就绪了的最高优先级的任务?


函数OS_PrioGetHighest()用于找到就绪了的最高优先级的任务:源码在UCOSIII_CORE中os_prio.c中85行


OS_PRIO  OS_PrioGetHighest (void)

 

{

 

    CPU_DATA  *p_tbl;

 

    OS_PRIO    prio;

 

    prio  = (OS_PRIO)0;

 

    p_tbl = &OSPrioTbl[0];//从OSProTb[0]开始扫描映射表,一直遇到非零项

 

while (*p_tbl == (CPU_DATA)0) {

 

//当数组OSProTb[]中某个元素为0时,就继续扫描下一个素组元素,prio加DEF_INT_CPU_NBR_BITS位

 

       prio += DEF_INT_CPU_NBR_BITS;

 

       p_tbl++;// p_tbl加一,继续寻找OSProTb[]中下一个元素

 

}

 

//一旦找到一个非0项,再加上该项的前导0数量就找到了最高优先级任务了

 

    prio += (OS_PRIO)CPU_CntLeadZeros(*p_tbl); //计算前导0:计算0的个数

 

    return (prio);

2.2就绪任务列表


通过上一步我们已经知道了哪个优先级的任务已经就绪了,但是UCOSIII支持时间片轮转调度,同一个优先级下可以有多个任务,因此我们还需要在确定是优先级下的哪个任务就绪了


struct  os_rdy_list {

 

    OS_TCB           *HeadPtr    //用于创建链表,指向链表头

 

    OS_TCB           *TailPtr;   //用于创建链表,指向链表尾

 



『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
发明专利在疫情影响下的逆势增长