SylixOS 调度浅析
1、简介
每个 CPU 如果需要被调度,即进行 CPU 任务上下文切换,有两种情况:主动调度、被动调度
对于多核 CPU 来说,如何通知某个 CPU,它需要被调度?
答:通过 IPI 中断
2、调度时机
2.1、主动调度
主动调度,即 CPU 当前运行的线程主动去调用可能产生调度的相关函数,如
- API_SemaphoreBRelease、API_SemaphoreBPost 等释放信号量操作
- 释放信号量时,通常会伴随着唤醒阻塞在这个信号量上的任务
- Sleep、Usleep 等主动睡眠操作
- 当前任务主动放弃 CPU 使用权
- API_SemaphoreBPend、API_SemaphoreCPend 等尝试获取信号量操作
- 当前任务申请信号量、尝试访问临界区,且临界区已经被占据,这时就产生了阻塞
上述接口,都会直接调用到 _Schedule()或者 _ScheduleInt()函数,进行 CPU 调度。
_Schedule()函数可以分为两部分:
- 获得需要运行的线程
- 如果当前 CPU 被设置卷绕标志,说明有线程需要被调度到当前核运行。调用 _CandTableUpdate 更新当前 CPU 候选线程
- 如果没有被设置卷绕表示,则直接获取当前 CPU 的候选线程
关于 CPU 核的 “卷绕标志”,可以暂时理解为,经过一系列调度算法,准备给目标线程安排一个 CPU 运行,同时给该 CPU 设置卷绕标志,在合适时机给设置了该标志的 CPU 发送 IPI 中断,通知其进行线程调度、线程上下文切换。
- 如果和 CPU 当前运行线程不同,则进行线程上下文切换;如果和 CPU 当前运行线程相同,则当前 CPU 不需要调度,但是需要向其它 CPU 发送 IPI 中断,以通知其它 CPU 进行调度。当然,并不是向所有 CPU 发送 IPI 中断,只会向被设置了卷绕标志的 CPU 发送 IPI 中断。
IPI 中断为什么会引起调度?因为 SylixOS 内核中任何中断在处理结束、退出时,都会去调用 __KERNEL_SCHED_INT-> _ScheduleInt(),_ScheduleInt()函数中进行中断上下文中线程切换。
2.2、被动调度
被动调度又可以分为两种情况
- IPI 中断:当有其他高优先级线程需要被安排到本核,即会向本核发出核间中断,中断退出时,调用 _ScheduleInt 切换到高优先级线程。
- Tick 中断(RR 调度策略情况下):_SchedTick 函数,会循环检查每个 CPU 当前运行的线程的时间片是否耗尽,如果耗尽,则对当前 CPU 设置卷绕标志。在 Tick 中断退出时,调用的 _ScheduleInt,向这些被设置卷绕标志的 CPU 发送 IPI 中断。
- 在调用 _CandTableNext 接口,从就绪表中确定一个最需运行的线程时,如果找到的是一个时间片已经用完的线程,则为其补充时间片,同时跳过该线程,寻找下一个。
2.3 任务状态
SylixOS 支持三种任务状态,分别是就绪态、运行态、阻塞态。
这其中,阻塞态,又可以详细分为等待和休眠。
- 等待:任务等待某个条件或事件发生(例如信号量、消息队列、互斥锁、事件标志、tick 等)
- 休眠:入休眠态是任务的主动过程,这主要是任务调用了内核提供的休眠函数(例如 sleep、delay 等)
3、任务调度链表
3.1 任务就绪表
/*********************************************************************************************************位图表
*********************************************************************************************************/
#ifdef __SYLIXOS_KERNELtypedef struct {volatile UINT32 BMAP_uiMap; /* 主位图掩码 */volatile UINT32 BMAP_uiSubMap[(LW_PRIO_LOWEST >> 5) + 1]; /* 辅位图掩码 */
} LW_CLASS_BMAP;
typedef LW_CLASS_BMAP *PLW_CLASS_BMAP;/*********************************************************************************************************优先级控制块
*********************************************************************************************************/typedef struct {LW_LIST_RING_HEADER PCB_pringReadyHeader; /* 就绪非运行线程环表 */UINT8 PCB_ucPriority; /* 优先级 */
} LW_CLASS_PCB;
typedef LW_CLASS_PCB *PLW_CLASS_PCB;/*********************************************************************************************************就绪表
*********************************************************************************************************/typedef struct {LW_CLASS_BMAP PCBM_bmap;LW_CLASS_PCB PCBM_pcb[LW_PRIO_LOWEST + 1];
} LW_CLASS_PCBBMAP;
typedef LW_CLASS_PCBBMAP *PLW_CLASS_PCBBMAP;
SylixOS 系统启动的过程会初始化一个任务优先级就绪表,如果是单核系统,初始化的是全局就绪表 _K_pcbbmapGlobalReady 。多核的话,除了全局就绪表,还把每一个核对应的本地就绪表 &((pcpu)->CPU_pcbbmapReady 初始化。根据任务是否绑核、CPU 是否设置强亲和性,加入到对应 CPU 核的优先级就绪表。
/*********************************************************************************************************全局就绪位图表
*********************************************************************************************************/
__KERNEL_EXT LW_CLASS_PCBBMAP _K_pcbbmapGlobalReady;
就绪表可以分为两部分:
-
位图表
-
优先级控制块
-
位图表的作用,就是找到所有就绪线程中,最高的优先级号码
-
根据优先级号码(也就是优先级控制块的索引),寻找指定的优先级控制块
-
根据优先级控制块,就能找到运行在该优先级下的所有线程(TCB)
下面讲解几个关键函数:
_BitmapHigh
函数,就是从指定位图表中,找到最高优先级号码。
/*********************************************************************************************************
** 函数名称: _BitmapHigh
** 功能描述: 获得位图表中的最高优先级
** 输 入 : pbmap 位图控制块
** 输 出 : 优先级
** 全局变量:
** 调用模块:
*********************************************************************************************************/
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));
}
_GetPcb
函数,获取优先级控制块。根据线程是否绑核,选择获取的是全局优先级控制块还是指定 CPU 的优先级控制块。从这里也可以看到,不绑核的情况下,默认都是获取的全局优先级控制块。
static LW_INLINE PLW_CLASS_PCB _GetPcb (PLW_CLASS_TCB ptcb)
{
#if LW_CFG_SMP_EN > 0if (ptcb->TCB_bCPULock) { /* 锁定 CPU *//* 寻找指定 CPU 的优先级控制块 */return (LW_CPU_RDY_PPCB(LW_CPU_GET(ptcb->TCB_ulCPULock), ptcb->TCB_ucPriority));} else
#endif /* LW_CFG_SMP_EN > 0 */{/* 寻找全局优先级控制块 */return (LW_GLOBAL_RDY_PPCB(ptcb->TCB_ucPriority));}
}
以 _CandTableSeek
函数为例,讲解从就绪表中,找到优先级最高的那个,返回值为就绪表。
/*********************************************************************************************************
** 函数名称: _CandTableSeek
** 功能描述: 调度器查询有就绪线程的最高优先级内联函数.
** 输 入 : pcpu CPU
** pucPriority 优先级返回值
** 输 出 : 就绪表.
** 全局变量:
** 调用模块:
*********************************************************************************************************/
static LW_INLINE PLW_CLASS_PCBBMAP _CandTableSeek (PLW_CLASS_CPU pcpu, UINT8 *pucPriority)
{
#if LW_CFG_SMP_EN > 0REGISTER UINT8 ucGlobal;if (_BitmapIsEmpty(LW_CPU_RDY_BMAP(pcpu))) {/* 当前核就绪表为空 *//* 如果当前 CPU 被设置强亲和性,或者全局优先级就绪表为空 */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 {/* 当前核就绪表不为空,获取当前 CPU 就绪表中的最高优先级 */*pucPriority = _BitmapHigh(LW_CPU_RDY_BMAP(pcpu)); /* 本地就绪表最高优先级获取 *//* 如果当前 CPU 被设置强亲和性,或者全局优先级就绪表为空 */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 */
}
找到就绪表后,调用 _CandTableNext 函数,从指定就绪表中获取最需要运行的线程
/*********************************************************************************************************
** 函数名称: _CandTableNext
** 功能描述: 从就绪表中确定一个最需运行的线程.
** 输 入 : ppcbbmap 就绪表
** ucPriority 优先级
** 输 出 : 在就绪表中最需要运行的线程.
** 全局变量:
** 调用模块:
*********************************************************