μC/OS-Ⅱ源码学习(1)---多任务系统的实现(上)

快速回顾

        在之前的文章中通过多个模型来建立对多任务的概念,本文将会从源码角度解析模型的具体实现。μC/OS-Ⅱ中的多任务https://blog.youkuaiyun.com/sherlock_cp/article/details/130077923

任务控制块

        在μC/OS-Ⅱ中,任务控制块类型为OS_TCB,包含了大量的成员:

//ucos_ii.h
typedef struct os_tcb {
    OS_STK          *OSTCBStkPtr;         //任务堆栈当前位置指针

#if OS_TASK_CREATE_EXT_EN > 0u
    void            *OSTCBExtPtr;         //任务扩展块指针
    OS_STK          *OSTCBStkBottom;      //任务堆栈栈底
    INT32U           OSTCBStkSize;        //任务堆栈长度
    INT16U           OSTCBOpt;            //选项字节
    INT16U           OSTCBId;             //任务(0..65535)         
#endif

    struct os_tcb   *OSTCBNext;     //指向下一个任务控制块,用于形成双向链表结构
    struct os_tcb   *OSTCBPrev;     //指向上一个任务控制块,用于形成双向链表结构

#if (OS_EVENT_EN)
    OS_EVENT        *OSTCBEventPtr;     /* 任务等待事件指针,如发生等待,会指向对应事件 */
#endif

#if (OS_EVENT_EN) && (OS_EVENT_MULTI_EN > 0u)
    OS_EVENT       **OSTCBEventMultiPtr;    /* 任务等待多重事件的指针 */
#endif

#if ((OS_Q_EN > 0u) && (OS_MAX_QS > 0u)) || (OS_MBOX_EN > 0u)
/* 通常发送的消息或邮件会存放到对应的消息队列或邮箱中,但若遇到堵塞,则这些信息会到这里 */
    void            *OSTCBMsg;       
#endif

#if (OS_FLAG_EN > 0u) && (OS_MAX_FLAGS > 0u)
#if OS_TASK_DEL_EN > 0u
    OS_FLAG_NODE    *OSTCBFlagNode;         /* 事件标志节点 */
#endif
    OS_FLAGS         OSTCBFlagsRdy;         /* 当前存在的事件标志位 */
#endif

    INT32U           OSTCBDly;            //任务等待执行延时
    INT8U            OSTCBStat;           //任务状态(就绪、pending)
    INT8U            OSTCBStatPend;       //任务pending状态
    INT8U            OSTCBPrio;           //任务优先级

    INT8U            OSTCBX;                /* 任务的组内优先级 */
    INT8U            OSTCBY;                /* 任务的优先级组 */
    OS_PRIO          OSTCBBitX;             /* 任务的组内优先级掩码,用于快速映射和寻找 */
    OS_PRIO          OSTCBBitY;             /* 任务优先级组掩码,用于快速映射和寻找 */

#if OS_TASK_DEL_EN > 0u
    INT8U            OSTCBDelReq;           /* 任务删除请求标志 */
#endif

#if OS_TASK_PROFILE_EN > 0u
    INT32U           OSTCBCtxSwCtr;         /* Number of time the task was switched in                 */
    INT32U           OSTCBCyclesTot;        /* Total number of clock cycles the task has been running  */
    INT32U           OSTCBCyclesStart;      /* Snapshot of cycle counter at start of task resumption   */
    OS_STK          *OSTCBStkBase;          /* Pointer to the beginning of the task stack              */
    INT32U           OSTCBStkUsed;          /* Number of bytes used from the stack                     */
#endif

#if OS_TASK_NAME_EN > 0u
    INT8U           *OSTCBTaskName;    //任务名称
#endif

#if OS_TASK_REG_TBL_SIZE > 0u        //任务寄存器,可以用于存储任务执行过程中的数据
    INT32U           OSTCBRegTbl[OS_TASK_REG_TBL_SIZE];
#endif
} OS_TCB;

多任务系统模型

       在μC/OS-Ⅱ系统创建之初,下图所展示的多任务模型就已经建立(具体逻辑回顾前文),只不过其中的任务控制块都是空白的。OSTCBFreeList链表可以理解为系统剩余的空白任务控制块,而OSTCBList链表会在每次创建任务时,从OSTCBFreeList截取一个任务控制块并填充信息装载到自身。

         这里涉及到的多个重要的全局变量,都定义在ucos_ii.h文件中:

//ucos_ii.h
OS_EXT OS_TCB *OSTCBCur;          //当前正在运行任务的任务控制块指针
OS_EXT OS_TCB *OSTCBFreeList;     //空白任务控制块链表
OS_EXT OS_TCB *OSTCBHighRdy;      //就绪的最高优先级任务控制块指针
OS_EXT OS_TCB *OSTCBList;         //已注册任务的任务控制块链表
OS_EXT OS_TCB *OSTCBPrioTbl[OS_LOWEST_PRIO + 1u];    //任务优先级数组,可以快速定位对应任务控制块,+1是因为优先级0也是一个任务
OS_EXT OS_TCB  OSTCBTbl[OS_MAX_TASKS + OS_N_SYS_TASKS];   //所有任务(空白+已注册)的任务控制块数组,包含的是任务控制块实体而非指针

         包括五个指针和一个数组,其中数组OSTCBTbl[]是真正包含所有任务控制块的数据实体,其它指针都是对这些实体的引用。

        其中涉及的配置宏定义:

OS_N_SYS_TASKS:系统任务的数量(最多2个,空闲和统计任务),其中空闲任务必须存在,统计任务按需开启;

OS_LOWEST_PRIO:任务的最低优先级,也即空闲任务的优先级;

OS_MAX_TASKS:最大任务数量,注意该数量不包括系统任务数;

初始化多任务系统

        在OSInit()函数运行时,里面有一个OS_InitTCBList()函数专门用来初始化这些任务控制块指针和数组:

//os_core.c
static  void  OS_InitTCBList (void)
{
    INT8U    ix;
    INT8U    ix_next;
    OS_TCB  *ptcb1;
    OS_TCB  *ptcb2;


    OS_MemClr((INT8U *)&OSTCBTbl[0],     sizeof(OSTCBTbl));  //清空所有任务控制块内容
    OS_MemClr((INT8U *)&OSTCBPrioTbl[0], sizeof(OSTCBPrioTbl));  //清空任务优先级数组内容
    for (ix = 0u; ix < (OS_MAX_TASKS + OS_N_SYS_TASKS - 1u); ix++) {
        ix_next =  ix + 1u;
        ptcb1   = &OSTCBTbl[ix];
        ptcb2   = &OSTCBTbl[ix_next];
        ptcb1->OSTCBNext = ptcb2;    //下一个任务控制块指针指向数组下一个元素,即&OSTCBTbl[ix_next]
#if OS_TASK_NAME_EN > 0u     //是否开启任务名称,如开启,则初始化为"?"
        ptcb1->OSTCBTaskName = (INT8U *)(void *)"?";
#endif
    }
    ptcb1                   = &OSTCBTbl[ix];
    ptcb1->OSTCBNext        = (OS_TCB *)0;    //数组最后一个任务控制块的下一个指针指向空(0)
#if OS_TASK_NAME_EN > 0u
    ptcb1->OSTCBTaskName    = (INT8U *)(void *)"?";
#endif
    OSTCBList               = (OS_TCB *)0;    //将任务控制块链表头指向空(0)
    OSTCBFreeList           = &OSTCBTbl[0];   //将剩余空白任务控制块链表头指向数组首元素,即前文创建好的链表头
}

        在该函数里,会将OSTCBTbl[]里的所有空白任务控制块按顺序用成员内部指针OSTCBNext串联起来形成一张链表,最后将链表头,也即&OSTCBTbl[0]赋予OSTCBFreeList,形成一张空白链表。也就是说,OSTCBFreeList空白链表的成员实际上在内存空间就是连续的数组。任务控制块链表OSTCBList由于系统创建之初并无任务,只能初始化为0。

        除此之外,OSInit()函数还对空闲任务(必须存在)和统计任务(如有需要)进行了创建和初始化。具体函数为OS_InitTaskIdle()OS_InitTaskStat()(和常规任务创建一样,此处不展示)。

创建任务

        在初始化完毕后,用户就可以根据需求创建任务了。任务创建函数有两个版本,老版本的OSTaskCreate()函数仅支持传入四个参数,基本上被弃用了。另一个更新版本的OSTaskCreateExt()函数支持9个传入参数,具有更精细的控制粒度("Ext"即"extend":扩展版本)。下面着重介绍一下后者:

//os_task.c
INT8U OSTaskCreateExt (void   (*task)(void *p_arg),  //任务函数代码
                       void    *p_arg,       //任务初次运行时传入的参数,是一个指针
                       OS_STK  *ptos,        //任务堆栈栈顶,任务堆栈方向与OS_STK_GROWTH相关
                       INT8U    prio,        //任务优先级,数字越小优先级越高
                       INT16U   id,          //任务ID(0-65535),通常没用,但要确保唯一,可以取优先级
                       OS_STK  *pbos,        //任务堆栈栈底
                       INT32U   stk_size,    //任务堆栈大小
                       void    *pext,        //TCB扩展块,通常不用
                       INT16U   opt)         //选项字(16bit),系统规定的有三种
{
    OS_STK    *psp;
    INT8U      err;
#if OS_CRITICAL_METHOD == 3u     //临界区的进入方法等于3时,需要用cpu_sr保存局部变量
    OS_CPU_SR  cpu_sr = 0u;
#endif

#if OS_ARG_CHK_EN > 0u                //使能参数检查
    if (prio > OS_LOWEST_PRIO) {      //确保优先级大于配置的最低优先级
        return (OS_ERR_PRIO_INVALID);
    }
#endif
    OS_ENTER_CRITICAL();         //进入临界区,禁止任务切换
    if (OSIntNesting > 0u) {     //查看中断嵌套,检查是否在中断内,禁止中断内创建任务
        OS_EXIT_CRITICAL();
        return (OS_ERR_TASK_CREATE_ISR);
    }
    if (OSTCBPrioTbl[prio] == (OS_TCB *)0) {    //检查传入的优先级对应的任务控制块是否为空(0)
        OSTCBPrioTbl[prio] = OS_TCB_RESERVED;   //先占用掉该优先级,然后退出临界区防止待在临界区太久,后续再填装任务控制块
        OS_EXIT_CRITICAL();

#if (OS_TASK_STAT_STK_CHK_EN > 0u)             //检查统计任务是否开启
        OS_TaskStkClr(pbos, stk_size, opt);    //如开启了统计任务,则清空任务堆栈
#endif

        psp = OSTaskStkInit(task, p_arg, ptos, opt);     //用传入参数初始化任务堆栈,从栈顶开始填装,并返回填装后所处的位置指针
        err = OS_TCBInit(prio, psp, pbos, id, stk_size, pext, opt);
        if (err == OS_ERR_NONE) {
            if (OSRunning == OS_TRUE) {                        /* Find HPT if multitasking has started */
                OS_Sched();     //任务切换
            }
        } else {
            //创建出错,要进入临界区,将对应优先级的任务控制块清空,再退出临界区
            OS_ENTER_CRITICAL();
            OSTCBPrioTbl[prio] = (OS_TCB *)0; 
            OS_EXIT_CRITICAL();
        }
        return (err);
    }
    OS_EXIT_CRITICAL();
    return (OS_ERR_PRIO_EXIST);
}

        其中任务堆栈的初始化函数OSTaskStkInit(),任务堆栈的填装方式与CPU寄存器相关,同时还填装了用户定义的任务函数入口,以及初次运行传入的参数。

//os_cpu_c.c
OS_STK *OSTaskStkInit (void (*task)(void *p_arg), void *p_arg, OS_STK *ptos, INT16U opt)
{
    OS_STK *stk;     //局部堆栈变量,OS_STK<=>INT32U,这个长度和单片机地址位数有关

    (void)opt;                       //在这里选项字节并未用到,是为了防止编译警告
    stk       = ptos;                //将传入的任务堆栈栈顶指针赋值到局部变量

#if (__FPU_PRESENT==1)&&(__FPU_USED==1)	   //检查是否有FPU,该配置会影响任务堆栈的结构
	*(--stk) = (INT32U)0x00000000L; //No Name Register  
	*(--stk) = (INT32U)0x00001000L; //FPSCR
	*(--stk) = (INT32U)0x00000015L; //s15
	*(--stk) = (INT32U)0x00000014L; //s14
	*(--stk) = (INT32U)0x00000013L; //s13
	*(--stk) = (INT32U)0x00000012L; //s12
	*(--stk) = (INT32U)0x00000011L; //s11
	*(--stk) = (INT32U)0x00000010L; //s10
	*(--stk) = (INT32U)0x00000009L; //s9
	*(--stk) = (INT32U)0x00000008L; //s8
	*(--stk) = (INT32U)0x00000007L; //s7
	*(--stk) = (INT32U)0x00000006L; //s6
	*(--stk) = (INT32U)0x00000005L; //s5
	*(--stk) = (INT32U)0x00000004L; //s4
	*(--stk) = (INT32U)0x00000003L; //s3
	*(--stk) = (INT32U)0x00000002L; //s2
	*(--stk) = (INT32U)0x00000001L; //s1
	*(--stk) = (INT32U)0x00000000L; //s0
#endif
                                                 /* Registers stacked as if auto-saved on exception    */
    *(stk)    = (INT32U)0x01000000L;             /* xPSR                                               */
    *(--stk)  = (INT32U)task;                    //任务函数入口(R15 PC)
    *(--stk)  = (INT32U)OS_TaskReturn;           /*意外退出时,走这个函数,仅系统调用,用户不可调用(R14 LR) */
    *(--stk)  = (INT32U)0x12121212L;             /* R12                                                */
    *(--stk)  = (INT32U)0x03030303L;             /* R3                                                 */
    *(--stk)  = (INT32U)0x02020202L;             /* R2                                                 */
    *(--stk)  = (INT32U)0x01010101L;             /* R1                                                 */
    *(--stk)  = (INT32U)p_arg;                   /* R0 : argument                                      */

#if (__FPU_PRESENT==1)&&(__FPU_USED==1)	
	*(--stk) = (INT32U)0x00000031L; //s31
	*(--stk) = (INT32U)0x00000030L; //s30
	*(--stk) = (INT32U)0x00000029L; //s29
	*(--stk) = (INT32U)0x00000028L; //s28
	*(--stk) = (INT32U)0x00000027L; //s27
	*(--stk) = (INT32U)0x00000026L; //s26	
	*(--stk) = (INT32U)0x00000025L; //s25
	*(--stk) = (INT32U)0x00000024L; //s24
	*(--stk) = (INT32U)0x00000023L; //s23
	*(--stk) = (INT32U)0x00000022L; //s22
	*(--stk) = (INT32U)0x00000021L; //s21
	*(--stk) = (INT32U)0x00000020L; //s20
	*(--stk) = (INT32U)0x00000019L; //s19
	*(--stk) = (INT32U)0x00000018L; //s18
	*(--stk) = (INT32U)0x00000017L; //s17
	*(--stk) = (INT32U)0x00000016L; //s16
#endif
		
                                                /* Remaining registers saved on process stack         */
    *(--stk)  = (INT32U)0x11111111L;             /* R11                                                */
    *(--stk)  = (INT32U)0x10101010L;             /* R10                                                */
    *(--stk)  = (INT32U)0x09090909L;             /* R9                                                 */
    *(--stk)  = (INT32U)0x08080808L;             /* R8                                                 */
    *(--stk)  = (INT32U)0x07070707L;             /* R7                                                 */
    *(--stk)  = (INT32U)0x06060606L;             /* R6                                                 */
    *(--stk)  = (INT32U)0x05050505L;             /* R5                                                 */
    *(--stk)  = (INT32U)0x04040404L;             /* R4                                                 */

    return (stk);   //返回任务堆栈初始完毕后的指针位置,一定要栈底以上
}

        另一个就是任务控制块的初始化函数OS_TCBInit(),该函数较长,具体看注释。

//os_core.c
INT8U  OS_TCBInit (INT8U    prio,
                   OS_STK  *ptos,
                   OS_STK  *pbos,
                   INT16U   id,
                   INT32U   stk_size,
                   void    *pext,
                   INT16U   opt)
{
    OS_TCB    *ptcb;
#if OS_CRITICAL_METHOD == 3u     //临界区的进入方法等于3时,需要用cpu_sr保存局部变量
    OS_CPU_SR  cpu_sr = 0u;
#endif
#if OS_TASK_REG_TBL_SIZE > 0u
    INT8U      i;
#endif

    OS_ENTER_CRITICAL();
    ptcb = OSTCBFreeList;          //从OSTCBFreeList取一个TCB
    if (ptcb != (OS_TCB *)0) {     //确保该TCB不是空白的,否则代表任务数量已满,无法创建
        OSTCBFreeList            = ptcb->OSTCBNext;    //OSTCBFreeList指向下一个TCB,该步骤会卸载头一个空白TCB(用来创建新任务了)
        OS_EXIT_CRITICAL();
        ptcb->OSTCBStkPtr        = ptos;      //将任务堆栈指针指向要创建的任务堆栈栈顶
        ptcb->OSTCBPrio          = prio;      //任务优先级
        ptcb->OSTCBStat          = OS_STAT_RDY;      //任务状态:就绪
        ptcb->OSTCBStatPend      = OS_STAT_PEND_OK;    //pending状态:完成
        ptcb->OSTCBDly           = 0u;                 //任务等待执行延时:0

#if OS_TASK_CREATE_EXT_EN > 0u      //是否使能OSTaskCreateExt(),并将这些额外的参数填装
        ptcb->OSTCBExtPtr        = pext;                 
        ptcb->OSTCBStkSize       = stk_size;             
        ptcb->OSTCBStkBottom     = pbos;                 
        ptcb->OSTCBOpt           = opt;                  
        ptcb->OSTCBId            = id;                   
#else
        pext                     = pext;       /* Prevent compiler warning if not used */
        stk_size                 = stk_size;
        pbos                     = pbos;
        opt                      = opt;
        id                       = id;
#endif

#if OS_TASK_DEL_EN > 0u     //是否使能OSTaskDel()
        ptcb->OSTCBDelReq        = OS_ERR_NONE;    //非0表示要删除任务了
#endif

//计算优先级组(Y)和组内下标(X)
#if OS_LOWEST_PRIO <= 63u   //最低优先级小于63,则用六位来表示,分别为3位优先级组和3位组内优先级数
        ptcb->OSTCBY             = (INT8U)(prio >> 3u);
        ptcb->OSTCBX             = (INT8U)(prio & 0x07u);
#else   //最低优先级大于63,则用八位来表示,分别位4位优先级组和4位组内优先级数                                                          
        ptcb->OSTCBY             = (INT8U)((INT8U)(prio >> 4u) & 0xFFu);
        ptcb->OSTCBX             = (INT8U) (prio & 0x0Fu);
#endif
        //计算掩码,是为了快速定位就绪任务。如果最低优先级小于64,OS_PRIO为8位,否则为16位
        ptcb->OSTCBBitY          = (OS_PRIO)(1uL << ptcb->OSTCBY);
        ptcb->OSTCBBitX          = (OS_PRIO)(1uL << ptcb->OSTCBX);

#if (OS_EVENT_EN)
        ptcb->OSTCBEventPtr      = (OS_EVENT  *)0;         /* Task is not pending on an  event         */
#if (OS_EVENT_MULTI_EN > 0u)
        ptcb->OSTCBEventMultiPtr = (OS_EVENT **)0;         /* Task is not pending on any events        */
#endif
#endif

#if (OS_FLAG_EN > 0u) && (OS_MAX_FLAGS > 0u) && (OS_TASK_DEL_EN > 0u)
        ptcb->OSTCBFlagNode  = (OS_FLAG_NODE *)0;          /* Task is not pending on an event flag     */
#endif

#if (OS_MBOX_EN > 0u) || ((OS_Q_EN > 0u) && (OS_MAX_QS > 0u))
        ptcb->OSTCBMsg       = (void *)0;              /* No message received */
#endif

#if OS_TASK_PROFILE_EN > 0u
        ptcb->OSTCBCtxSwCtr    = 0uL;          /* Initialize profiling variables */
        ptcb->OSTCBCyclesStart = 0uL;
        ptcb->OSTCBCyclesTot   = 0uL;
        ptcb->OSTCBStkBase     = (OS_STK *)0;
        ptcb->OSTCBStkUsed     = 0uL;
#endif

#if OS_TASK_NAME_EN > 0u
        ptcb->OSTCBTaskName    = (INT8U *)(void *)"?";
#endif

#if OS_TASK_REG_TBL_SIZE > 0u                /* Initialize the task variables */
        for (i = 0u; i < OS_TASK_REG_TBL_SIZE; i++) {
            ptcb->OSTCBRegTbl[i] = 0u;
        }
#endif

        OSTCBInitHook(ptcb);        //任务控制块初始化Hook(用户回调函数)

        OSTaskCreateHook(ptcb);     //任务创建Hook(用户回调函数)

        OS_ENTER_CRITICAL();
        OSTCBPrioTbl[prio] = ptcb;      //将填装好的TCB指针填入优先级数组的对应位置
        ptcb->OSTCBNext    = OSTCBList;       //将填装好的TCB挂载到OSTCBList上
        ptcb->OSTCBPrev    = (OS_TCB *)0;     //该TCB的前一个TCB指针指向0
        if (OSTCBList != (OS_TCB *)0) {     //如果当前OSTCBList已经有任务挂载,则将链表头的TCB指向当前创建的新TCB,完成双向链接
            OSTCBList->OSTCBPrev = ptcb;
        }
        OSTCBList               = ptcb;      //OSTCBList链表头指向该TCB,完成新任务的挂载
        OSRdyGrp               |= ptcb->OSTCBBitY;    //将任务优先级组的掩码并入就绪优先级组
        OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;    //将任务组内优先级的掩码并入就绪列表
        OSTaskCtr++;                                 //创建的任务数+1
        OS_EXIT_CRITICAL();
        return (OS_ERR_NONE);
    }
    OS_EXIT_CRITICAL();
    return (OS_ERR_TASK_NO_MORE_TCB);   //没有空白TCB了,即任务数量已满
}

       

任务优先级

        μC/OSⅡ最多支持255个任务,任务的优先级有两种表达方式:

①直接使用一个8位二进制数表示0-254个优先级数,这是最常用的,广泛用于逻辑判断操作;

②分成优先级组和组内优先级,可以理解为一个N*N的矩阵空间,在任务切换时会频繁使用,能迅速定位高优先级的就绪任务,如下图:

        具体类型为OS_PRIO,它和任务最低优先级OS_LOWEST_PRIO有关:

#if OS_LOWEST_PRIO <= 63u
typedef  INT8U    OS_PRIO;
#else
typedef  INT16U   OS_PRIO;
#endif

        看起来很容易理解,系统所需要的优先级数量比较少时,使用8位优先级,能表示8*8=64种优先级;较多时则使用16位优先级,能表示16*16=256种优先级。上图结构对应的全局变量为:

//ucos_ii.h
OS_EXT  OS_PRIO  OSRdyGrp;      /* 优先级组,哪一组有就绪任务就会在对应位置1 */
OS_EXT  OS_PRIO  OSRdyTbl[OS_RDY_TBL_SIZE];    /* 组内优先级,组内哪一个优先级有就绪任务就会在对应位置1 */

         其中:OS_RDY_TBL_SIZE会根据设定的任务数量动态定义,防止浪费。

//ucos_ii.h
#define  OS_RDY_TBL_SIZE   ((OS_LOWEST_PRIO) / 8u + 1u) /* 组内优先级数组 */

       如上所述,μC/OSⅡ系统同时使用了两种方式表示优先级,那么两种方式如何进行转换呢?答案就在本文创建和切换的代码中。

        另外,OS_LOWEST_PRIO的定义范围为0-254,为什么没有255呢(刚好用满8位)?这是因为255即0xFF是一个特殊优先级OS_PRIO_SELF,用于在任务操作时传入表示任务自身(类似对线程进行批量操作时可以用到,因为是动态的,难以知道当时运行任务的优先级)。

任务切换

        在μC/OSⅡ中,任务切换也可以称为上下文切换(context switch),进行上下文切换的底层函数有两个:常态切换OSCtxSw()和中断切换OSIntCtxSw(),对于用户来说,这种切换是无感的,用户也不用手动去调用这些接口,系统会在合适的时机自动进行切换,比如创建任务后、等待信号量或事件等等。

        在系统执行上下文切换之前,都要先进行一系列的准备,其中最重要的就是获取下一个任务。OS_Sched()就是做这个事情的,简单来说,就是获取就绪任务的最高优先级,并在完成一切准备工作后,调用OSCtxSw()执行上下文切换(前提是有切换的必要):

//os_core.c
void  OS_Sched (void)
{
#if OS_CRITICAL_METHOD == 3u    /* 进入临界区的准备 */
    OS_CPU_SR  cpu_sr = 0u;
#endif

    OS_ENTER_CRITICAL();
    if (OSIntNesting == 0u) {       /* 该方法不允许在中断中使用 */
        if (OSLockNesting == 0u) {       /* 调度器不能上锁 */
            OS_SchedNew();        //找出当前就绪的最高优先级任务
            OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];    //获取就绪的最高优先级任务
            if (OSPrioHighRdy != OSPrioCur) {       /* 就绪的最高优先级任务不是当前运行的任务,才需要切换,否则继续运行 */
#if OS_TASK_PROFILE_EN > 0u
                OSTCBHighRdy->OSTCBCtxSwCtr++;         /* Inc. # of context switches to this task      */
#endif
                OSCtxSwCtr++;      /* 切换次数加1,这个变量好像没什么用 */
                OS_TASK_SW();      /* 上下文切换,底层调用OSCtxSw(),由汇编指令实现 */
            }
        }
    }
    OS_EXIT_CRITICAL();
}

        这里面的OS_SchedNew()用于获取当前就绪任务中的最高优先级OSPrioHighRdy

//os_core.c
static  void  OS_SchedNew (void)
{
#if OS_LOWEST_PRIO <= 63u            /* 小于64个任务,使用8位优先级 */
    INT8U   y;

    //相当于对组号和组内编号进行解码,还原成原始的优先级序号
    y             = OSUnMapTbl[OSRdyGrp];
    OSPrioHighRdy = (INT8U)((y << 3u) + OSUnMapTbl[OSRdyTbl[y]]);
#else             /* 大于等于64个任务,使用16位优先级 */
    INT8U     y;
    OS_PRIO  *ptbl;


    if ((OSRdyGrp & 0xFFu) != 0u) {
        y = OSUnMapTbl[OSRdyGrp & 0xFFu];
    } else {
        y = OSUnMapTbl[(OS_PRIO)(OSRdyGrp >> 8u) & 0xFFu] + 8u;
    }
    ptbl = &OSRdyTbl[y];
    if ((*ptbl & 0xFFu) != 0u) {
        OSPrioHighRdy = (INT8U)((y << 4u) + OSUnMapTbl[(*ptbl & 0xFFu)]);
    } else {
        OSPrioHighRdy = (INT8U)((y << 4u) + OSUnMapTbl[(OS_PRIO)(*ptbl >> 8u) & 0xFFu] + 8u);
    }
#endif
}

        里面还涉及一个const映射数组OSUnMapTbl[],用于将优先级的组机制还原成原始的优先级序号(快速映射的算法,这里不多介绍)。

        除此之外,我们通常会在中断中成对调用OSIntEnter()OSIntExit(),其中OSIntExit()就会执行中断切换,其内容和上面的常态任务切换OS_Sched()基本一致,只是对中断嵌套做了处理,上下文切换函数有所区别(OSCtxSw()OSIntCtxSw()):

//os_core.c
void  OSIntExit (void)
{
#if OS_CRITICAL_METHOD == 3u                               /* 进入临界区的准备 */
    OS_CPU_SR  cpu_sr = 0u;
#endif



    if (OSRunning == OS_TRUE) {
        OS_ENTER_CRITICAL();
        if (OSIntNesting > 0u) {     /* 将中断嵌套-1 */
            OSIntNesting--;
        }
        if (OSIntNesting == 0u) {      /* 如果还有中断嵌套(不止一层),就退出 */
            if (OSLockNesting == 0u) {     /* 检查调度器是否上锁,上锁后无法切换 */
                OS_SchedNew();     //获取就绪的最高优先级
                OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
                if (OSPrioHighRdy != OSPrioCur) {          /* 就绪的最高优先级任务不是当前运行的任务,才进行切换 */
#if OS_TASK_PROFILE_EN > 0u
                    OSTCBHighRdy->OSTCBCtxSwCtr++;         /* Inc. # of context switches to this task  */
#endif
                    OSCtxSwCtr++;        /* 切换次数加1,这个变量好像没什么用 */
                    OSIntCtxSw();        /* 进行上下文切换 */
                }
            }
        }
        OS_EXIT_CRITICAL();
    }
}

        获取了下一个要执行任务的优先级后,就可以进行上下文切换了,具体的切换代码是使用汇编指令编写的,它们都会主动触发PendSV中断,并在中断处理函数内进行任务切换:

os_cpu_a.asm
PendSV_Handler
    CPSID   I                  ; 上下文切换过程中禁止中断
    MRS     R0, PSP            ; PSP is process stack pointer 如果在用PSP堆栈,则可以忽略保存寄存器,参考CM3权威中的双堆栈
    CBZ     R0, PendSV_Handler_Nosave		; Skip register save the first time
	
	;Is the task using the FPU context? If so, push high vfp registers.
	TST 	R14, #0x10
	IT 		EQ
	VSTMDBEQ R0!, {S16-S31} 
	
    SUBS    R0, R0, #0x20        ; 保存寄存器值到当前任务的堆栈中
    STM     R0, {R4-R11}

    LDR     R1, =OSTCBCur       ; OSTCBCur首地址即待切换任务的堆栈指针
    LDR     R1, [R1]
    STR     R0, [R1]            ; R0 is SP of process being switched out

       在这里不进一步探究,在上一篇文章中有对该过程进行图形化的解析。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值