SylixOS 任务调度源代码分析

在SylixOS 中退出中断和内核都会系统调度任务,任务调度底层切换上下文,底层实现参考链接

在SyliOS默认优先级是0-255,数值越小优先级越高。在SylixOS 任务就绪队列是通过位图的方式去查找,如果当前优先级有任务则对应的位图位就会变成1。在查找到最后优先级后,从对应链表中取出任务控制块tcb,对比是否比当前任务优先级高,决定是否切换任务。

在arm中支持硬件实现前导零计数,所以能够快速的从位图中查找出来。

首先看几个重要的结构体

SylixOS 任务调度源代码分析_第1张图片

第一个结构体体是位图,由于32位并不能够满足0-255优先级的需求 ,所以这里使用了两级位图。

第二个结构体是对应优先级的队列。每个优先级都有一个对应的队列,如果要找此优先级的任务,就从这个队列中查找。

第三个结构体是上面两个结构的结合。将位图,链表结合在一起就实现了查找就绪任务时间复杂度为O(1)。

在内核退出的时候会调用_Schedule函数

/*********************************************************************************************************
** 函数名称: Schedule
** 功能描述: 内核退出时, 会调用此调度函数 (进入内核状态并关中断被调用)
** 输 入  : NONE
** 输 出  : 线程上下文返回值
** 全局变量: 
** 调用模块: 
*********************************************************************************************************/
INT  _Schedule (VOID)
{
    ULONG            ulCPUId;
    PLW_CLASS_CPU    pcpuCur;
    PLW_CLASS_TCB    ptcbCur;
    PLW_CLASS_TCB    ptcbCand;
    INT              iRetVal = ERROR_NONE;
    
    ulCPUId = LW_CPU_GET_CUR_ID();                                      /*  当前 CPUID                  */
    pcpuCur = LW_CPU_GET(ulCPUId);                                      /*  当前 CPU 控制块             */
    ptcbCur = pcpuCur->CPU_ptcbTCBCur;
    
#if LW_CFG_SMP_EN > 0
    if (LW_UNLIKELY(ptcbCur->TCB_plineStatusReqHeader)) {               /*  请求当前任务改变状态        */
        if (__LW_STATUS_CHANGE_EN(ptcbCur, pcpuCur)) {                  /*  是否可以进行状态切换        */
            _ThreadStatusChangeCur(pcpuCur);                            /*  检查是否需要进行状态切换    */
        }
    }
#endif                                                                  /*  LW_CFG_SMP_EN               */

    ptcbCand = _SchedGetCand(pcpuCur, 1ul);                             /*  获得需要运行的线程          */
    if (ptcbCand != ptcbCur) {                                          /*  如果与当前运行的不同, 切换  */
        __LW_SCHEDULER_BUG_TRACE(ptcbCand);                             /*  调度器 BUG 检测             */
        
#if LW_CFG_SMP_EN > 0                                                   /*  SMP 系统                    */
        __LW_SPINLOCK_BUG_TRACE(pcpuCur);
#endif                                                                  /*  LW_CFG_SMP_EN > 0           */
        pcpuCur->CPU_bIsIntSwitch = LW_FALSE;                           /*  非中断调度                  */
        pcpuCur->CPU_ptcbTCBHigh  = ptcbCand;
        
        /*
         *  TASK CTX SAVE();
         *  SWITCH to SAFE stack if in SMP system;
         *  _SchedSwp();
         *  TASK CTX LOAD();
         */
        archTaskCtxSwitch(pcpuCur);                                     /*  线程切换,并释放内核自旋锁   */
#if !defined(__SYLIXOS_ARM_ARCH_M__) || (LW_CFG_CORTEX_M_SVC_SWITCH > 0)
        LW_SPIN_KERN_LOCK_IGNIRQ();                                     /*  内核自旋锁重新加锁          */
#endif                                                                  /*  LW_CFG_CORTEX_M_SVC_SWITCH  */
    }
#if LW_CFG_SMP_EN > 0                                                   /*  SMP 系统                    */
      else {
        __LW_SMP_NOTIFY(ulCPUId);                                       /*  SMP 调度通知                */
        return  (iRetVal);
    }
#endif                                                                  /*  LW_CFG_SMP_EN               */
    
    LW_TCB_GET_CUR(ptcbCur);                                            /*  获得新的当前 TCB            */
    
    iRetVal = ptcbCur->TCB_iSchedRet;                                   /*  获得调度器信号的返回值      */
    ptcbCur->TCB_iSchedRet = ERROR_NONE;                                /*  清空                        */
    
    return  (iRetVal);
}

_Schedule函数首先判断当前任务的状态是否需要改变,如果需要切换任务状态。

 然后调用_SchedGetCand函数查找需要运行的任务。

PLW_CLASS_TCB  _SchedGetCand (PLW_CLASS_CPU  pcpuCur, ULONG  ulCurMaxLock)
{
    if (!__COULD_SCHED(pcpuCur, ulCurMaxLock)) {                        /*  当前执行线程不能调度        */
        return  (pcpuCur->CPU_ptcbTCBCur);
        
    } else {                                                            /*  可以执行线程切换            */
#if LW_CFG_SMP_EN > 0
        LW_CPU_CLR_SCHED_IPI_PEND(pcpuCur);
#endif                                                                  /*  LW_CFG_SMP_EN > 0           */
        if (LW_CAND_ROT(pcpuCur)) {                                     /*  产生优先级卷绕              */
            _CandTableUpdate(pcpuCur);                                  /*  尝试更新候选表, 抢占调度    */
        }
        return  (LW_CAND_TCB(pcpuCur));
    }
}

此函数首先通过__COULD_SCHED判断是否能够调度任务,在中断嵌套,或者任务被锁住禁止调度等是不能够调度的。__COULD_SCHED函数这里就不详细展开。

如果是多核SMP 情况下会使用LW_CPU_CLR_SCHED_IPI_PEND 发送一个核间中断,通知其他核心进行调度。先不讨论核间中断的实现细节(不同处理器实现不同)。

_CandTableUpdate函数是实现找到最高优先级任务的函数,函数的内容如下:

VOID _CandTableUpdate (PLW_CLASS_CPU   pcpu)
{
             UINT8              ucPriority;
    REGISTER PLW_CLASS_TCB      ptcbCand;
             PLW_CLASS_PCBBMAP  ppcbbmap;
             BOOL               bNeedRotate = LW_FALSE;
             
#if LW_CFG_SMP_EN > 0                                                   /*  SMP 多核                    */
             PLW_CLASS_TCB      ptcbNew;
#endif

    if (!LW_CPU_IS_ACTIVE(pcpu)) {                                      /*  CPU 必须为激活状态          */
        return;
    }
    
    ptcbCand = LW_CAND_TCB(pcpu);
    if (ptcbCand == LW_NULL) {                                          /*  当前没有候选线程            */
        _CandTableFill(pcpu);
        goto    __update_done;
    }
    
    ppcbbmap = _CandTableSeek(pcpu, &ucPriority);                       /*  当前就绪表中最高优先级      */
    if (ppcbbmap == LW_NULL) {
        LW_CAND_ROT(pcpu) = LW_FALSE;                                   /*  清除优先级卷绕标志          */
        return;
    }
    
#if LW_CFG_SMP_EN > 0
    if (LW_CPU_ONLY_AFFINITY_GET(pcpu) && !ptcbCand->TCB_bCPULock) {    /*  强制运行亲和度任务          */
        bNeedRotate = LW_TRUE;                                          /*  当前普通任务需要让出 CPU    */
    
    } else 
#endif                                                                  /*  LW_CFG_SMP_EN > 0           */
    {
        if (ptcbCand->TCB_usSchedCounter == 0) {                        /*  已经没有时间片了            */
            if (LW_PRIO_IS_HIGH_OR_EQU(ucPriority, 
                                       ptcbCand->TCB_ucPriority)) {     /*  是否需要轮转                */
                bNeedRotate = LW_TRUE;
            }
        
        } else {
            if (LW_PRIO_IS_HIGH(ucPriority, 
                                ptcbCand->TCB_ucPriority)) {
                bNeedRotate = LW_TRUE;
            }
        }
    }
    
    if (bNeedRotate) {                                                  /*  存在更需要运行的线程        */
        _CandTableEmpty(pcpu);                                          /*  清空候选表                  */
        _CandTableResel(pcpu, ppcbbmap, ucPriority);                    /*  重新选择任务执行            */
        
#if LW_CFG_SMP_EN > 0                                                   /*  SMP 多核                    */
        ptcbNew = LW_CAND_TCB(pcpu);
        if ((ptcbNew != ptcbCand) && !ptcbCand->TCB_bCPULock) {         /*  是否需要尝试标记其他 CPU    */
            _CandTableNotify(pcpu, ptcbCand);                           /*  通知其他 CPU 进行调度查看   */
        }
#endif                                                                  /*  LW_CFG_SMP_EN > 0           */
    }
    
__update_done:
    LW_CAND_ROT(pcpu) = LW_FALSE;                                       /*  清除优先级卷绕标志          */
}

从上面的代码中可以看到在进行了一些情况判断后,调用了_CandTableSeek函数。_CandTableSeek函数实现的功能是在0-255优先级中那个有任务需要调度。如果存在有优先级比当前任务高的或者当前任务的时间片已经用完,那么从这个优先级对应的链表中选出任务。_CandTableResel函数就是实现选处任务的。

_CandTableSeek 是一个内联函数

static LW_INLINE PLW_CLASS_PCBBMAP  _CandTableSeek (PLW_CLASS_CPU  pcpu, UINT8 *pucPriority)
{
#if LW_CFG_SMP_EN > 0
    REGISTER UINT8  ucGlobal;
    
    if (_BitmapIsEmpty(LW_CPU_RDY_BMAP(pcpu))) {
        if (LW_CPU_ONLY_AFFINITY_GET(pcpu) ||
            _BitmapIsEmpty(LW_GLOBAL_RDY_BMAP())) {                     /*  就绪表为空                  */
            return  (LW_NULL);
        }
        
        *pucPriority = _BitmapHigh(LW_GLOBAL_RDY_BMAP());
        return  (LW_GLOBAL_RDY_PCBBMAP());                              /*  从全局就绪表选择            */
    
    } else {
        *pucPriority = _BitmapHigh(LW_CPU_RDY_BMAP(pcpu));              /*  本地就绪表最高优先级获取    */
        
        if (LW_CPU_ONLY_AFFINITY_GET(pcpu) || 
            _BitmapIsEmpty(LW_GLOBAL_RDY_BMAP())) {
            return  (LW_CPU_RDY_PCBBMAP(pcpu));                         /*  选择本地就绪任务            */
        }
        
        ucGlobal = _BitmapHigh(LW_GLOBAL_RDY_BMAP());
        if (LW_PRIO_IS_HIGH_OR_EQU(*pucPriority, ucGlobal)) {           /*  同优先级, 优先执行 local    */
            return  (LW_CPU_RDY_PCBBMAP(pcpu));
        
        } else {
            *pucPriority = ucGlobal;
            return  (LW_GLOBAL_RDY_PCBBMAP());
        }
    }
    
#else
    if (_BitmapIsEmpty(LW_GLOBAL_RDY_BMAP())) {                         /*  就绪表中无任务              */
        return  (LW_NULL);
    
    } else {
        *pucPriority = _BitmapHigh(LW_GLOBAL_RDY_BMAP());
        return  (LW_GLOBAL_RDY_PCBBMAP());
    }
#endif                                                                  /*  LW_CFG_SMP_EN > 0           */
}

 因为一个soc上可能有多个核心,所以存在全局还是本地。全局指的是全部soc,本地指的是当前soc。

查找最高优先级是通过_BitmapHigh函数实现的。

UINT8  _BitmapHigh (PLW_CLASS_BMAP  pbmap)
{
    UINT32  uiHigh = (UINT32)archFindLsb((INT)pbmap->BMAP_uiMap);
    UINT32  uiLow;
    
    if (uiHigh == 0) {
        return  (0);
    }
    
    uiHigh--;
    uiLow = (UINT32)archFindLsb((INT)pbmap->BMAP_uiSubMap[uiHigh]) - 1;
    
    return  ((UINT8)((uiHigh << 5) | uiLow));
}

此函数首先通过 archFindLsb查找位图的第一层。第一层的每一位对应的32位。查找到位图的第一层后,再次通过archFindLsb查找位图的第二层。最后将两个组合成最大优先级数值。

archFindLsb


FUNC_DEF(archFindLsb)
    MOV     W1 , #0
    SUB     W1 , W1 , W0
    AND     W0 , W1 , W0
    MOV     W1 , #32
    CLZ     W0 , W0
    SUB     W0 , W1 , W0
    RET
    FUNC_END()

在arm中有个CLZ指令,该指令由硬件实现计算最高位1前面的0个数。比如2前面有30个0,1前面有31个0。我们想找最低位为1的前面有多少个零,所以这里使用

    MOV     W1 , #0
    SUB     W1 , W1 , W0
    AND     W0 , W1 , W0

这三条命令让最低位1保留,其他全部变为0.加入W0传入进来为5,最后W0值是1.因为只保留最低位的1。

调用CLZ命令计算最低位1前面有多少个0.在用32减前面零的个数,就能快速计算出当前最低位1是在哪一位。因为在SylixOS中0-255 值越小优先级越大。所以这里使用的找到最低位为1的位置。

_BitmapHigh函数中位图第一层已经通过上面方式找到,位图的第二层

 uiLow = (UINT32)archFindLsb((INT)pbmap->BMAP_uiSubMap[uiHigh]) - 1;

可以看出也是调用archFindLsb函数查找到最低位1位置。

最后使用下面组合

((UINT8)((uiHigh << 5) | uiLow));

组合成当前最高优先级(也就是0-255当前存在最小值)。

此时查找最高优先级数值的任务就结束,继续回到_CandTableUpdate函数,下面需要从对应的优先级链表中找到任务控制块tcb,以下为_CandTableUpdate函数的部分

    ppcbbmap = _CandTableSeek(pcpu, &ucPriority);                       /*  当前就绪表中最高优先级      */
    if (ppcbbmap == LW_NULL) {
        LW_CAND_ROT(pcpu) = LW_FALSE;                                   /*  清除优先级卷绕标志          */
        return;
    }
    
#if LW_CFG_SMP_EN > 0
    if (LW_CPU_ONLY_AFFINITY_GET(pcpu) && !ptcbCand->TCB_bCPULock) {    /*  强制运行亲和度任务          */
        bNeedRotate = LW_TRUE;                                          /*  当前普通任务需要让出 CPU    */
    
    } else 
#endif                                                                  /*  LW_CFG_SMP_EN > 0           */
    {
        if (ptcbCand->TCB_usSchedCounter == 0) {                        /*  已经没有时间片了            */
            if (LW_PRIO_IS_HIGH_OR_EQU(ucPriority, 
                                       ptcbCand->TCB_ucPriority)) {     /*  是否需要轮转                */
                bNeedRotate = LW_TRUE;
            }
        
        } else {
            if (LW_PRIO_IS_HIGH(ucPriority, 
                                ptcbCand->TCB_ucPriority)) {
                bNeedRotate = LW_TRUE;
            }
        }
    }
    
    if (bNeedRotate) {                                                  /*  存在更需要运行的线程        */
        _CandTableEmpty(pcpu);                                          /*  清空候选表                  */
        _CandTableResel(pcpu, ppcbbmap, ucPriority);                    /*  重新选择任务执行            */
        
#if LW_CFG_SMP_EN > 0                                                   /*  SMP 多核                    */
        ptcbNew = LW_CAND_TCB(pcpu);
        if ((ptcbNew != ptcbCand) && !ptcbCand->TCB_bCPULock) {         /*  是否需要尝试标记其他 CPU    */
            _CandTableNotify(pcpu, ptcbCand);                           /*  通知其他 CPU 进行调度查看   */
        }
#endif                                                                  /*  LW_CFG_SMP_EN > 0           */
    }

 _CandTableSeek执行完毕后,就要查看找出的优先级是否大于当前任务的,还有当前任务的时间片是否到,满足其中一个条件就进行任务的切换。调用_CandTableResel函数找对应的任务控制块tcb。

/*********************************************************************************************************
** 函数名称: _CandTableResel
** 功能描述: 选择一个最该执行线程放入候选表.
** 输 入  : pcpu          CPU 结构
**           ppcbbmap      优先级位图
**           ucPriority    需要运行任务的优先级
** 输 出  : NONE
** 全局变量: 
** 调用模块: 
*********************************************************************************************************/
static VOID  _CandTableResel (PLW_CLASS_CPU   pcpu, PLW_CLASS_PCBBMAP  ppcbbmap, UINT8  ucPriority)
{
    REGISTER PLW_CLASS_TCB      ptcb;
    REGISTER PLW_CLASS_PCB      ppcb;
    
    ptcb = _CandTableNext(ppcbbmap, ucPriority);                        /*  确定可以候选运行的线程      */
    ppcb = &ppcbbmap->PCBM_pcb[ucPriority];
    
    LW_CAND_TCB(pcpu) = ptcb;                                           /*  保存新的可执行线程          */
    ptcb->TCB_ulCPUId = LW_CPU_GET_ID(pcpu);                            /*  记录 CPU 号                 */
    ptcb->TCB_bIsCand = LW_TRUE;                                        /*  进入候选运行表              */
    _DelTCBFromReadyRing(ptcb, ppcb);                                   /*  从就绪环中退出              */
    
    if (_PcbIsEmpty(ppcb)) {
        __DEL_RDY_MAP(ptcb);                                            /*  从就绪表中删除              */
    }
}

在此函数中首先调用了  _CandTableNext函数,函数是根据优先级,从队列中取出一个任务,函数内容如下:

static PLW_CLASS_TCB  _CandTableNext (PLW_CLASS_PCBBMAP  ppcbbmap, UINT8  ucPriority)
{
    REGISTER PLW_CLASS_PCB      ppcb;
    REGISTER PLW_CLASS_TCB      ptcb;
    
    ppcb = &ppcbbmap->PCBM_pcb[ucPriority];
    ptcb = _LIST_ENTRY(ppcb->PCB_pringReadyHeader, 
                       LW_CLASS_TCB, 
                       TCB_ringReady);                                  /*  从就绪环中取出一个线程      */
    
    if (ptcb->TCB_ucSchedPolicy == LW_OPTION_SCHED_FIFO) {              /*  如果是 FIFO 直接运行        */
        return  (ptcb);
    
    } else if (ptcb->TCB_usSchedCounter == 0) {                         /*  缺少时间片                  */
        ptcb->TCB_usSchedCounter = ptcb->TCB_usSchedSlice;              /*  补充时间片                  */
        _list_ring_next(&ppcb->PCB_pringReadyHeader);                   /*  下一个                      */
        ptcb = _LIST_ENTRY(ppcb->PCB_pringReadyHeader, 
                           LW_CLASS_TCB, 
                           TCB_ringReady);
    }
    
    return  (ptcb);
}

从就绪链表中取出一个任务后,首先判断调度方式,如果是先进先出则执行执行,如果不是判断是否时间片用完,时间片用完补充时间片,然后从优先级链表中在取出其下一个来执行。

在_CandTableNext执行完成后,最高优先级任务的tcb已经找到,然后在_CandTableResel函数中调用

 LW_CAND_TCB(pcpu) = ptcb; 
#define LW_CAND_TCB(pcpu)   pcpu->CPU_cand.CAND_ptcbCand

此宏是将当前tcb加入到对用cpu的候选任务中。

然后在_CandTableResel函数中调用_DelTCBFromReadyRing函数,将任务从优先级队列中删除。最后调用如下函数

if (_PcbIsEmpty(ppcb)) {
        __DEL_RDY_MAP(ptcb);                                            /*  从就绪表中删除              */
    }

功能是判断当前优先级队列是否为空,如果为空就将位图的对应优先级的位变为0.

在_CandTableUpdate函数最后,根据情况是否触发核间中断通知其他核心调度。_CandTableUpdate函数执行完毕后

在_SchedGetCand函数执行如下函数

 if (LW_CAND_ROT(pcpuCur)) {                            /*  产生优先级卷绕              */
    _CandTableUpdate(pcpuCur);                          /*  尝试更新候选表, 抢占调度    */
}
return  (LW_CAND_TCB(pcpuCur));

最后返回了当前cpu的就绪任务,因为前面通过_CandTableResel函数查找到最高优先级任务加入到了对应cpu的就绪中。

如果找到的线程不是当前线程此时就会进行任务切换,切换函数如下

FUNC_DEF(archTaskCtxSwitch)
    LDR     X18, [X0]                                                   ;/*  获取当前 TCB 的 REG_CTX 地址*/
    
    SAVE_SMALL_REG_CTX                                                  ;/*  保存小寄存器上下文          */

    MOV     X19 , X0                                                    ;/*  X19 暂存 X0                 */

#if LW_CFG_SMP_EN > 0    
    BL      _SchedSafeStack                                             ;/*  _SchedSafeStack();          */
    MOV     SP , X0                                                     ;/*  设置 SP                     */
    MOV     X0 , X19                                                    ;/*  恢复 X0                     */
#endif

    BL      _SchedSwp                                                   ;/*  _SchedSwp();                */

    LDR     X18, [X19]                                                  ;/*  获取当前 TCB 的 REG_CTX 地址*/
    LDR     X9 , [X18, #CTX_TYPE_OFFSET]                                ;/*  获得上下文类型              */
    CMP     X9 , #0
    B.NE    _RestoreSmallCtx

    RESTORE_BIG_REG_CTX                                                 ;/*  恢复大寄存器上下文          */

LINE_LABEL(_RestoreSmallCtx)
    RESTORE_SMALL_REG_CTX                                               ;/*  恢复小寄存器上下文          */
    FUNC_END()

首先保存上下文,然后获得调度栈,跳转到_SchedSwp执行,_SchedSwp函数最主要是如下一段代码

    REGISTER PLW_CLASS_TCB      ptcbCur      = pcpuCur->CPU_ptcbTCBCur;
    REGISTER PLW_CLASS_TCB      ptcbHigh     = pcpuCur->CPU_ptcbTCBHigh;
    REGISTER LW_OBJECT_HANDLE   ulCurId      = ptcbCur->TCB_ulId;
    REGISTER LW_OBJECT_HANDLE   ulHighId     = ptcbHigh->TCB_ulId;
    BOOL               bIsIntSwitch = pcpuCur->CPU_bIsIntSwitch;
............
省略
...........
pcpuCur->CPU_ptcbTCBCur = ptcbHigh;                                 /*  切换任务                    */

将pcpuCur->CPU_ptcbTCBHigh保存的任务保存到pcpuCur->CPU_ptcbTCBCur。在前面的函数将查找到高优先级的任务保存到了  pcpuCur->CPU_ptcbTCBHigh中。

执行完_SchedSwp 函数后,就是进行任务的切换。

你可能感兴趣的:(SylixOS)