SylixOS 翼辉操作系统 RMS 机制源码探究
1. RMS 的定位
RMS 在 SylixOS 中用于辅助周期性线程按固定周期运行。它更像是一个周期控制器,而不是完整意义上的 Rate Monotonic Scheduling 调度算法实现。
它主要负责:
记录周期起点 -> 计算本周期已执行时间 -> 未到周期则睡眠 -> 到期后重新就绪
线程真正能否运行,仍由 SylixOS 普通调度器决定;如果线程策略是 RR,则唤醒后参与同优先级时间片轮转;如果线程策略是 FIFO,则唤醒后继续遵循 FIFO 调度语义。
| 文件 | 作用 |
|---|---|
RmsCreate.c(<projectPath>/SylixOS/libsylixos/SylixOS/kernel/interface/RmsCreate.c) | 创建 RMS 对象 |
RmsPeriod.c(<projectPath>/SylixOS/libsylixos/SylixOS/kernel/interface/RmsPeriod.c) | 周期控制主入口 |
_RmsLib.c(<projectPath>/SylixOS/libsylixos/SylixOS/kernel/core/_RmsLib.c) | RMS 核心内部逻辑 |
_TimeTick.c(<projectPath>/SylixOS/libsylixos/SylixOS/kernel/core/_TimeTick.c) | tick 唤醒等待线程 |
k_scanlink.h(<projectPath>/SylixOS/libsylixos/SylixOS/kernel/include/k_scanlink.h) | 等待链操作宏 |
k_sched.h(<projectPath>/SylixOS/libsylixos/SylixOS/kernel/include/k_sched.h) | 就绪表和调度相关宏 |
2. RMS 控制块的关键字段
RMS 控制块定义在 [k_class.h],重点字段如下:
typedef struct {
UINT8 RMS_ucType;
UINT8 RMS_ucStatus;
ULONG RMS_ulTickNext;
ULONG RMS_ulTickSave;
PLW_CLASS_TCB RMS_ptcbOwner;
UINT16 RMS_usIndex;
CHAR RMS_cRmsName[LW_CFG_OBJECT_NAME_SIZE];
} LW_CLASS_RMS;
| 字段 | 含义 |
|---|---|
RMS_ucType | RMS 对象是否已分配 |
RMS_ucStatus | RMS 当前运行状态 |
RMS_ulTickSave | 当前周期起点 tick |
RMS_ulTickNext | 预计下一次唤醒 tick |
RMS_ptcbOwner | 当前因 RMS 周期等待而阻塞的线程 |
RMS_usIndex | RMS 对象池索引 |
3. RMS 状态
RMS 的核心状态只有三个:
| 状态 | 含义 |
|---|---|
LW_RMS_INACTIVE | 已创建,但尚未开始周期计时 |
LW_RMS_ACTIVE | 正在计量当前周期执行时间 |
LW_RMS_EXPIRED | 当前线程已进入周期等待 |
整体状态机:
4. 创建 RMS:只创建,不启动计时
API_RmsCreate()创建 RMS 对象后,关键字段初始化如下:
prms->RMS_ucType = LW_RMS_USED;
prms->RMS_ucStatus = LW_RMS_INACTIVE;
prms->RMS_ulTickNext = 0ul;
prms->RMS_ulTickSave = 0ul;
API_RmsCreate 只是分配 RMS 对象,不开始周期计时。
创建后的初始状态是 LW_RMS_INACTIVE。
5. 第一次 API_RmsPeriod:激活 RMS
API_RmsPeriod()中,如果状态是 LW_RMS_INACTIVE:
case LW_RMS_INACTIVE:
_RmsActive(prms);
__KERNEL_EXIT_IRQ(iregInterLevel);
return (ERROR_NONE);
_RmsActive()的核心代码:
VOID _RmsActive (PLW_CLASS_RMS prms)
{
prms->RMS_ucStatus = LW_RMS_ACTIVE;
__KERNEL_TIME_GET_IGNIRQ(prms->RMS_ulTickSave, ULONG);
}
第一次 API_RmsPeriod() 的作用:
-
状态从
INACTIVE变为ACTIVE; -
记录当前 tick 到
RMS_ulTickSave; -
直接返回,不睡眠。
所以第一次调用相当于启动周期计时。
6. 后续 API_RmsPeriod:周期控制核心
第二次及以后调用API_RmsPeriod()时,状态通常是 LW_RMS_ACTIVE:
case LW_RMS_ACTIVE:
ulErrorCode = _RmsInitExpire(prms, ulPeriod, &ulWaitTime);
核心逻辑在_RmsInitExpire(),它先计算当前周期已经执行了多少 tick:
ulThreadExecTime = (ulKernelTime >= prms->RMS_ulTickSave) ?
(ulKernelTime - prms->RMS_ulTickSave) :
(ulKernelTime + (__ARCH_ULONG_MAX - prms->RMS_ulTickSave) + 1);
这里顺带处理了 ULONG tick 回绕问题。
然后根据执行时间和周期 ulPeriod 的关系分三种情况。
6.1 执行时间大于周期
if (ulThreadExecTime > ulPeriod) {
__KERNEL_TIME_GET_IGNIRQ(prms->RMS_ulTickSave, ULONG);
return (ERROR_RMS_TICK);
}
含义:任务超周期运行,返回 ERROR_RMS_TICK。
状态仍保持 LW_RMS_ACTIVE,并重新记录周期起点,避免后续一直累计错误。
6.2 执行时间等于周期
if (ulThreadExecTime == ulPeriod) {
*pulWaitTick = 0;
__KERNEL_TIME_GET_IGNIRQ(prms->RMS_ulTickSave, ULONG);
return (ERROR_NONE);
}
含义:刚好到达周期,不需要睡眠,直接进入下一周期。
6.3 执行时间小于周期
*pulWaitTick = ulPeriod - ulThreadExecTime;
prms->RMS_ucStatus = LW_RMS_EXPIRED;
prms->RMS_ptcbOwner = ptcbCur;
__KERNEL_TIME_GET_IGNIRQ(prms->RMS_ulTickNext, ULONG);
prms->RMS_ulTickNext += *pulWaitTick;
含义:任务提前完成,需要睡眠剩余 tick。
此时 RMS 状态变为 LW_RMS_EXPIRED,并记录:
-
当前等待线程:
RMS_ptcbOwner; -
预计唤醒时间:
RMS_ulTickNext; -
需要等待的 tick:
pulWaitTick。
6.4 核心流程
7. 周期未到:线程如何进入等待
当 _RmsInitExpire() 得到 ulWaitTime > 0 后,API_RmsPeriod()会把当前线程从就绪系统中移除,并加入等待链:
ppcb = _GetPcb(ptcbCur);
__DEL_FROM_READY_RING(ptcbCur, ppcb);
ptcbCur->TCB_ulDelay = ulWaitTime;
__ADD_TO_WAKEUP_LINE(ptcbCur);
这里是 RMS 和内核调度系统发生联系的关键点。
7.1 从就绪表移除
__DEL_FROM_READY_RING:
#define __DEL_FROM_READY_RING(ptcb, ppcb) \
do { \
if (!(ptcb)->TCB_bIsCand) { \
_DelTCBFromReadyRing((ptcb), (ppcb)); \
} \
_CandTableTryDel((ptcb), (ppcb)); \
} while (0)
作用:让当前线程不再参与调度选择。
它会尝试从:
- ready ring;
- candidate table;
中移除当前线程。
7.2 加入等待链
__ADD_TO_WAKEUP_LINE:
#define __ADD_TO_WAKEUP_LINE(ptcb) \
do { \
ptcb->TCB_usStatus |= LW_THREAD_STATUS_DELAY; \
_WakeupAdd(&_K_wuDelay, &ptcb->TCB_wunDelay, LW_FALSE); \
} while (0)
作用:
-
给线程设置
LW_THREAD_STATUS_DELAY; -
将线程挂入
_K_wuDelay等待扫描链。
此时线程状态不再是 ready,调度器不会选中它。
流程图:
8. Tick 到期:线程如何重新 ready
RMS 自己不扫描等待链。线程重新 ready 依赖系统 tick。
API_KernelTicks()每个 tick 会调用:
_ThreadTick();
_ThreadTick()会扫描 _K_wuDelay。到期后执行:
ptcb = _LIST_ENTRY(pwun, LW_CLASS_TCB, TCB_wunDelay);
__DEL_FROM_WAKEUP_LINE(ptcb);
__DEL_FROM_WAKEUP_LINE:
#define __DEL_FROM_WAKEUP_LINE(ptcb) \
do { \
ptcb->TCB_usStatus &= ~LW_THREAD_STATUS_DELAY; \
_WakeupDel(&_K_wuDelay, &ptcb->TCB_wunDelay, LW_FALSE); \
} while (0)
注意:它不是直接写:
ptcb->TCB_usStatus = LW_THREAD_STATUS_RDY;
而是清除 LW_THREAD_STATUS_DELAY 位。
因为 LW_THREAD_STATUS_RDY是 0x0000:
#define LW_THREAD_STATUS_RDY 0x0000
所以如果线程只有 DELAY 这一种等待状态,那么清除后自然变为 ready:
TCB_usStatus = LW_THREAD_STATUS_DELAY
TCB_usStatus &= ~LW_THREAD_STATUS_DELAY
TCB_usStatus = 0
TCB_usStatus == LW_THREAD_STATUS_RDY
随后_ThreadTick()检查:
if (__LW_THREAD_IS_READY(ptcb)) {
ptcb->TCB_ucSchedActivate = LW_SCHED_ACT_OTHER;
ppcb = _GetPcb(ptcb);
__ADD_TO_READY_RING(ptcb, ppcb);
}
如果线程已经 ready,就重新加入就绪系统。
__ADD_TO_READY_RING:
#define __ADD_TO_READY_RING(ptcb, ppcb) \
do { \
if (!_CandTableTryAdd((ptcb), (ppcb))) { \
_AddTCBToReadyRing((ptcb), (ppcb), LW_FALSE); \
} \
} while (0)
唤醒链路:
9. 睡眠返回后:_RmsEndExpire
线程被 tick 唤醒并重新运行后,API_RmsPeriod()会继续执行_RmsEndExpire():
ULONG _RmsEndExpire (PLW_CLASS_RMS prms)
{
if (prms->RMS_ucStatus != LW_RMS_EXPIRED) {
return (ERROR_RMS_WAS_CHANGED);
}
prms->RMS_ucStatus = LW_RMS_ACTIVE;
__KERNEL_TIME_GET(prms->RMS_ulTickSave, ULONG);
if (prms->RMS_ulTickNext != prms->RMS_ulTickSave) {
return (ERROR_THREAD_WAIT_TIMEOUT);
} else {
return (ERROR_NONE);
}
}
作用:
-
确认 RMS 仍处于
EXPIRED; -
状态恢复为
ACTIVE; -
将当前 tick 作为新周期起点;
-
检查实际唤醒 tick 是否等于预计唤醒 tick。
如果:
RMS_ulTickNext != RMS_ulTickSave
说明实际唤醒时间和预期不同,返回 ERROR_THREAD_WAIT_TIMEOUT。
10. API_RmsPeriod 总流程
11. 典型使用方式
#include <SylixOS.h>
/* RMS 调度器句柄 */
static LW_HANDLE hRms;
/* 线程入口函数 */
static PVOID rmsThread (PVOID pvArg)
{
ULONG ret;
ULONG ulPeriod = 5; // 执行周期5 ticks
while (1) {
/*
* 调用 Lw_Rms_Period 函数,使 RMS 调度器按照指定周期开始工作。
* 此函数会阻塞当前线程,直到下一个周期到来。
* 如果线程执行时间超过周期,函数会返回错误。
*/
ret = Lw_Rms_Period(hRms, ulPeriod);
if (ret != ERROR_NONE) {
/* 处理周期超时或其他错误 */
fprintf(stderr, "RMS period error: 0x%lx\n", ret);
break;
}
/* 在此处执行周期性任务代码 */
fprintf(stdout, "RMS thread is running, period = %ld ticks, current system time = %ld ticks\n", ulPeriod, Lw_Time_Get());
}
return (LW_NULL);
}
int main (int argc, char *argv[])
{
LW_HANDLE hThreadId;
LW_CLASS_THREADATTR threadAttr;
// 步骤 1: 创建 RMS 调度器
hRms = Lw_Rms_Create("my_rms", 0, LW_NULL);
if (hRms == LW_HANDLE_INVALID) {
fprintf(stderr, "Failed to create RMS scheduler.\n");
return (PX_ERROR);
}
// 步骤 2: 创建并配置线程属性
Lw_ThreadAttr_Build(&threadAttr,
4 * LW_CFG_KB_SIZE,
LW_PRIO_NORMAL,
LW_OPTION_THREAD_STK_CHK,
LW_NULL);
// 步骤 3: 使用 Lw_Thread_Create 创建线程
hThreadId = Lw_Thread_Create("t_rms", rmsThread, &threadAttr, LW_NULL);
if (hThreadId == LW_OBJECT_HANDLE_INVALID) {
fprintf(stderr, "Failed to create thread.\n");
Lw_Rms_Delete(&hRms);
return (PX_ERROR);
}
// 等待线程结束 (此示例中线程不会主动结束,因此会一直等待)
Lw_Thread_Join(hThreadId, LW_NULL);
// 线程结束后,清理 RMS 调度器
Lw_Rms_Delete(&hRms);
return (ERROR_NONE);
}
运行逻辑:
12. RMS 与 RR / FIFO 调度的关系
RMS 和线程调度策略是间接耦合。
RMS 控制线程什么时候睡眠、什么时候重新 ready。
RR/FIFO 控制线程 ready 之后,调度器如何选择它运行。
也就是说,RMS 不直接决定线程运行顺序。它只负责把线程从 ready 状态切到 delay 等待,再由 tick 唤醒回 ready。线程回到 ready 后,才进入普通调度器,由 RR、FIFO 等策略决定后续运行行为。
12.1 RR:同优先级按时间片轮转
RR 时间片逻辑位于_SchedTick():
if (ptcb->TCB_ucSchedPolicy == LW_OPTION_SCHED_RR) {
if (ptcb->TCB_usSchedCounter == 0) {
LW_CAND_ROT(pcpu) = LW_TRUE;
} else {
ptcb->TCB_usSchedCounter--;
}
}
含义:如果当前运行线程是 RR 策略,就在 tick 中递减时间片,时间片耗尽后,触发同优先级候选线程轮转。
因此 RMS 线程如果使用 RR 策略:
-
周期未到时,RMS 会让它进入 delay 等待;
-
tick 到期后,它重新进入 ready/candidate;
-
若同优先级还有其他 RR 线程,它会参与时间片轮转;
-
它不一定唤醒后立刻运行。
12.2 FIFO:同优先级不主动按时间片轮转
FIFO 策略和 RR 的区别在于:FIFO 线程不会因为时间片耗尽而主动让出 CPU。
从 _SchedTick() 的逻辑可以看出,只有 LW_OPTION_SCHED_RR 分支会递减 TCB_usSchedCounter 并触发 LW_CAND_ROT(pcpu)。如果线程不是 RR,比如 FIFO,就不会走这段时间片轮转逻辑。
因此 FIFO 的典型行为是:
-
高优先级 FIFO 线程优先运行;
-
同优先级 FIFO 线程之间不会像 RR 那样定期轮转;
-
当前 FIFO 线程通常要主动阻塞、睡眠、等待资源、调用让步接口,或被更高优先级线程抢占,其他同优先级线程才有机会运行。
对于 RMS + FIFO 线程:
RMS 仍然只负责周期等待;
FIFO 决定线程 ready 后是否能继续占用 CPU。
也就是说:
-
RMS 周期未到:线程被移出 ready,加入
_K_wuDelay; -
RMS 周期到期:线程重新 ready;
-
若它是 FIFO 策略,唤醒后不会参与 RR 时间片轮转;
-
它是否马上运行,取决于优先级以及当前 CPU 上是否有更高优先级线程。
12.3 RR 与 FIFO 对 RMS 的影响对比
| 调度策略 | RMS 唤醒后的行为 |
|---|---|
| RR | 重新进入 ready/candidate 后,参与同优先级时间片轮转 |
| FIFO | 重新进入 ready/candidate 后,不因时间片耗尽而轮转 |
更直观地说:
RMS 管“什么时候回来”。
RR/FIFO 管“回来之后怎么排队运行”。
关系图:
因此需要注意:
RMS 周期到期,只表示线程重新具备运行资格,不保证立即运行。
如果有更高优先级线程正在运行,RMS 线程仍可能等待。若同优先级存在多个 RR 线程,RMS 线程唤醒后会参与 RR 轮转;若是 FIFO,则不会因为时间片机制主动轮转。
13. 总结
RMS 的核心逻辑可以压缩为:
Create:创建 RMS,状态为 INACTIVE。
First Period:切换到 ACTIVE,记录周期起点。
Next Period:计算已执行时间。
超周期:返回 ERROR_RMS_TICK。
刚好到周期:直接进入下一周期。
未到周期:线程进入 DELAY 等待。
Tick:等待到期后清除 DELAY,线程重新 ready。
Resume:_RmsEndExpire 将 RMS 状态恢复为 ACTIVE。
最重要的两条链路:
RMS 周期等待链路:
ACTIVE -> EXPIRED -> DELAY 等待 -> tick 唤醒 -> ACTIVE
调度接入链路:
__DEL_FROM_READY_RING -> __ADD_TO_WAKEUP_LINE -> __DEL_FROM_WAKEUP_LINE -> __ADD_TO_READY_RING
RMS调度器为每个周期性任务设定了固定的周期(Lw_Rms_Period)。当任务调用此函数后,它会根据周期长度和当前执行时间,精确控制该线程进入阻塞(delay)状态的时长。周期越短的任务,其delay等待时间越短,因此能更快地从阻塞状态回到就绪队列。
线程返回就绪状态后,由 SylixOS 普通调度器以及 RR/FIFO 等策略决定何时运行。由于具有更短周期的任务能够更频繁地回到就绪队列,它自然会在同优先级竞争中更早获得调度机会,从而体现为“更高优先级”的效果。
简言之,RMS通过时间控制实现“周期越短、唤醒越频繁”,而非直接修改线程优先级。
如果对你有帮助,欢迎点赞收藏,有相关问题也可以评论讨论,后续将继续更新该系列。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐
所有评论(0)