1.RMS调度简介
任务按单调速率优先级分配(RMPA)的调度算法,称为单调速率调度(RMS)。RMPA是指任务的优先级按任务周期T来分配。它根据任务的执行周期的长短来决定调度优先级,那些具有小的执行周期的任务具有较高的优先级,周期长的任务优先级低。
2.RMS调度实现介绍
SylixOS目前关于RMS调度分为创建、删除、调度三个部分组成。创建和删除就不予介绍。重点关注下调度算法的实现。调度有两个去完成,一是计算调度前用掉的时间etime,二是睡眠剩余调度的时间temp,如程序清单 21所示。
程序清单2-1 RMS实现源码
/******************************************************************************************** ** 函数名称: sched_rms_period ** 功能描述: RMS 调度器 ** 输 入 : prms RMS 调度器 ** period RMS 周期 ** 输 出 : 0 表示正确 ** error == EINTR 表示被信号激活. ** 全局变量: ** 调用模块: API 函数 ********************************************************************************************/ LW_API int sched_rms_period (sched_rms_t *prms, const struct timespec *period) { struct timespec temp; struct timespec etime; if (!prms || !period) { errno = EINVAL; return (PX_ERROR); } switch (prms->PRMS_iStatus) { case PRMS_STATUS_INACTIVE: lib_clock_gettime(CLOCK_MONOTONIC, &prms->PRMS_tsSave); prms->PRMS_iStatus = PRMS_STATUS_ACTIVE; return (ERROR_NONE); case PRMS_STATUS_ACTIVE: lib_clock_gettime(CLOCK_MONOTONIC, &temp); etime = temp; __timespecSub(&etime, &prms->PRMS_tsSave); if (__timespecLeftTime(period, &etime)) { lib_clock_gettime(CLOCK_MONOTONIC, &prms->PRMS_tsSave); errno = EOVERFLOW; return (PX_ERROR); } temp = *period; __timespecSub(&temp, &etime); __timespecAdd(&prms->PRMS_tsSave, period); return (nanosleep(&temp, LW_NULL)); default: errno = ENOTSUP; return (PX_ERROR); } }
1. 计算调度时间
首先通过系统函数获取准确的时间etime,在和上一次保存的时间PRMS_toSave相减获得在进入调度函数之前所用掉的时间etime。利用etime和需要调度的时间period比较进行处理。计算出需要睡眠的时间temp。
2. 睡眠剩余的调度时间
通过函数nanosleep计算剩余的时间,函数nanosleep具体实现可参考SylixOS源码,其流程如图 21所示。
图2-1 nanosleep流程图
3.RMS调度分析
3.1 RMS调度优势
调度算法中利用PRMS_tsSave保存时间点,为下一次调度作为时间参考,调度按照确定周期运行,这样可以避免因多次调度而累积的误差。代码如下:
… __timespecSub(&temp, &etime); /* * 注意: 这里直接加上周期是为了让每次测算都是以一个固定周期律进行 * 提高周期精度. (不使用 lib_clock_gettime()) */ __timespecAdd(&prms->PRMS_tsSave, period); /* 以确定周期运行 */ return (nanosleep(&temp, LW_NULL)); …
3.2 误差分析一
调度算法中存在这样的问题,需要延迟的时间time和延迟函数function是分开的,这就会导致一个问题,在计算出time后有可能进程被调度,而没有进入function。
等下一次调度到进程后再进入function后会导致时间不准确,该进程从被调度到再次被调度中的时间没有被计算。这种情况出现在调度函数sched_rms_period,在计算
出睡眠时间后temp后进程被调度,等下一次被调度后才进入睡眠时间。代码如下:
__timespecSub(&temp, &etime); /* * 注意: 这里直接加上周期是为了让每次测算都是以一个固定周期律进行 * 提高周期精度. (不使用 lib_clock_gettime()) */ __timespecAdd(&prms->PRMS_tsSave, period); /* 以确定周期运行 */ return (nanosleep(&temp, LW_NULL));
nanosleep也同样会出现此类问题,在计算平滑过度时间后,也有可能出现进程调度而导致时间计算不准确。代码如下:
if (__timespecLeftTime(&tvTemp, rqtp)) { /* 还有剩余时间需要延迟 */ struct timespec tvNeed = *rqtp; __timespecSub(&tvNeed, &tvTemp); __timePassSpec(&tvNeed); /* 平静度过 */ }
3.3 误差分析二
同时我们在该函数中获取高精度时间没有放在函数最开始运行处,在运行到获取高精度时间前也可能出现进程调度。代码如下:
ulTick = __timespecToTick((struct timespec *)rqtp); if (!ulTick) { /* 不到一个 tick */ __timePassSpec(rqtp); /* 平静度过 */ if (rmtp) { rmtp->tv_sec = 0; /* 不存在时间差别 */ rmtp->tv_nsec = 0; } return (ERROR_NONE); } __timeGetHighResolution(&tvStart); /* 记录开始的时间 */
改进措施:尽量将__timeGetHighResolution获取开始延迟的时间点函数靠前,避免nanosleep在执行获取高精度时间之前出现进程调度的情况;
可以将要延迟的时间起始点以参数的形式传递给要睡眠的函数,比如nanosleep这类函数就可以在调用者处确定延迟的时间点tvStart,而不是在
nanosleep中去获取