SylixOS --- RMS调度详解

1.RMS调度简介
任务按单调速率优先级分配(RMPA)的调度算法,称为单调速率调度(RMS)。RMPA是指任务的优先级按任务周期T来分配。它根据任务的执行周期的长短来决定调度优先级,那些具有小的执行周期的任务具有较高的优先级,周期长的任务优先级低。

2.RMS调度实现介绍
SylixOS目前关于RMS调度分为创建、删除、调度三个部分组成。创建和删除就不予介绍。重点关注下调度算法的实现。调度有两个去完成,一是计算调度前用掉的时间etime,二是睡眠剩余调度的时间temp,如程序清单 2‑1所示。

程序清单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。

  1. 睡眠剩余的调度时间

通过函数nanosleep计算剩余的时间,函数nanosleep具体实现可参考SylixOS源码,其流程如图 2‑1所示。

图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中去获取

猜你喜欢

转载自blog.csdn.net/qq_40390825/article/details/117327029