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_ucTypeRMS 对象是否已分配
RMS_ucStatusRMS 当前运行状态
RMS_ulTickSave当前周期起点 tick
RMS_ulTickNext预计下一次唤醒 tick
RMS_ptcbOwner当前因 RMS 周期等待而阻塞的线程
RMS_usIndexRMS 对象池索引

3. RMS 状态

RMS 的核心状态只有三个:

状态含义
LW_RMS_INACTIVE已创建,但尚未开始周期计时
LW_RMS_ACTIVE正在计量当前周期执行时间
LW_RMS_EXPIRED当前线程已进入周期等待

整体状态机:

API_RmsCreate

第一次 API_RmsPeriod
_RmsActive

执行时间 >= 周期
不睡眠

执行时间 < 周期
线程睡眠等待

tick 到期唤醒
_RmsEndExpire

API_RmsCancel

API_RmsDelete

API_RmsDelete

INACTIVE

ACTIVE

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() 的作用:

  1. 状态从 INACTIVE 变为 ACTIVE;

  2. 记录当前 tick 到 RMS_ulTickSave;

  3. 直接返回,不睡眠。

所以第一次调用相当于启动周期计时。

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 核心流程

是

否

是

否

_RmsInitExpire

计算已执行时间

已执行时间 > 周期?

返回 ERROR_RMS_TICK
状态保持 ACTIVE

已执行时间 == 周期?

ulWaitTick = 0
状态保持 ACTIVE

计算剩余等待 tick

状态改为 EXPIRED

记录 owner 和 TickNext

返回 ERROR_NONE

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)

作用:

  1. 给线程设置 LW_THREAD_STATUS_DELAY;

  2. 将线程挂入 _K_wuDelay 等待扫描链。

此时线程状态不再是 ready,调度器不会选中它。

流程图:

RMS 周期未到

__DEL_FROM_READY_RING

从 ready/candidate 移除

设置 TCB_ulDelay

__ADD_TO_WAKEUP_LINE

设置 DELAY 状态

加入 _K_wuDelay

退出内核并调度其他线程

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)

唤醒链路:

否

是

否

是

Tick 中断

API_KernelTicks

_ThreadTick

扫描 _K_wuDelay

等待是否到期?

继续等待

__DEL_FROM_WAKEUP_LINE

清除 DELAY 状态

TCB_usStatus == RDY?

仍有其他等待状态
暂不加入就绪表

__ADD_TO_READY_RING

进入 ready ring 或 candidate table

普通调度器选择运行

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);
    }
}

作用:

  1. 确认 RMS 仍处于 EXPIRED;

  2. 状态恢复为 ACTIVE;

  3. 将当前 tick 作为新周期起点;

  4. 检查实际唤醒 tick 是否等于预计唤醒 tick。

如果:

RMS_ulTickNext != RMS_ulTickSave

说明实际唤醒时间和预期不同,返回 ERROR_THREAD_WAIT_TIMEOUT。

10. API_RmsPeriod 总流程

INACTIVE

ACTIVE

大于周期

等于周期

小于周期

API_RmsPeriod

RMS 状态

_RmsActive

记录 TickSave

返回 ERROR_NONE

_RmsInitExpire

执行时间与周期比较

返回 ERROR_RMS_TICK
状态保持 ACTIVE

不睡眠
状态保持 ACTIVE

状态变 EXPIRED

从就绪表删除

加入等待链

线程睡眠

Tick 到期唤醒

_RmsEndExpire

状态恢复 ACTIVE

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);
}

运行逻辑:

小于周期

等于周期

大于周期

创建 RMS

第一次 API_RmsPeriod

开始周期计时

执行任务主体

再次 API_RmsPeriod

任务耗时与周期比较

睡眠剩余 tick

返回 ERROR_RMS_TICK

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 策略:

  1. 周期未到时,RMS 会让它进入 delay 等待;

  2. tick 到期后,它重新进入 ready/candidate;

  3. 若同优先级还有其他 RR 线程,它会参与时间片轮转;

  4. 它不一定唤醒后立刻运行。

12.2 FIFO:同优先级不主动按时间片轮转

FIFO 策略和 RR 的区别在于:FIFO 线程不会因为时间片耗尽而主动让出 CPU。

从 _SchedTick() 的逻辑可以看出,只有 LW_OPTION_SCHED_RR 分支会递减 TCB_usSchedCounter 并触发 LW_CAND_ROT(pcpu)。如果线程不是 RR,比如 FIFO,就不会走这段时间片轮转逻辑。

因此 FIFO 的典型行为是:

  1. 高优先级 FIFO 线程优先运行;

  2. 同优先级 FIFO 线程之间不会像 RR 那样定期轮转;

  3. 当前 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

设置 DELAY

_K_wuDelay 等待链

Tick 到期

清除 DELAY

READY

ready ring / candidate table

普通调度器

调度策略

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通过时间控制实现“周期越短、唤醒越频繁”,而非直接修改线程优先级。

如果对你有帮助,欢迎点赞收藏,有相关问题也可以评论讨论,后续将继续更新该系列。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐