• Lab6:进程的调度


    CPU调度

    从就绪队列中挑选下一个占用CPU运行的进程,从多个可用CPU中挑选就绪进程可使用的CPU资源

    调度策略

    比较调度算法的准则

    • CPU使用率
    • 吞吐量
    • 周转时间
    • 就绪等待时间
    • 响应时间

    吞吐量与延迟

    1. 低延迟:喝水的时候想要一打开水龙头水就流出来

    2. 高带宽:给游泳池充水时希望从水龙头里同时流出大量的水,并且不介意是否存在延迟

    处理机调度策略的响应时间目标

    1. 减少响应时间
    2. 减少平均响应时间的波动
    3. 增加吞吐量
    4. 减少等待时间

    调度算法

    先来先服务算法(First Come First Served, FCFS)

    依据进程进入就绪状态的先后顺序排列,进程进入等待或结束状态时,就绪队列中的下一个进程占用CPU

    但是FCFS的平均等待时间波动较大,I/O资源和CPU资源的利用率较低

    短进程优先算法(SPN)

    选择就绪队列中执行时间最短进程占用CPU进入运行状态,用历史的执行时间来预估未来的执行时间,短进程优先算法具有最优平均周转时间

    但是连续的短进程流会使长进程无法获得CPU资源

    最高响应比优先算法(HRRN)

    选择就绪队列中响应比R值最高的进程

    R=(w+s)/s

        w: 等待时间(waiting time)
        s: 执行时间(service time)
    

    时间片轮转算法(RR, Round-Robin)

    利用时间片作为分配处理机资源的基本时间单元,时间片结束时,按FCFS算法切换到下一个就绪进程, 每隔(n – 1)个时间片进程执行一个时间片q

    但是时间片轮转算法需要选择好时间片的大小,过大过小都会导致效率问题

    多级队列调度算法(MQ)

    就绪队列被划分成多个独立的子队列,如:前台–RR、后台–FCFS

    多级反馈队列算法(MLFQ)

    进程可在不同队列间移动的多级队列算法,时间片大小随优先级级别增加而增加,如进程在当前的时间片没有完成,则降到下一个优先级

    公平共享调度(FSS, Fair Share Scheduling)

    FSS控制用户对系统资源的访问,一些用户组比其他用户组更重要,保证不重要的组无法垄断资源,未使用的资源按比例分配,没有达到资源使用率目标的组获得更高的优先级

    代码实现

    这个实验其实有两个,一个是实现Round Robin,一个是Stride Scheduling,两个都非常简单。

    Round Robin调度算法的调度思想是让所有 runnable 态的进程分时轮流使用 CPU 时间。Round Robin 调度器维护当前 runnable进程的有序运行队列。当前进程的时间片用完之后,调度器将当前进程放置到运行队列的尾部,再从其头部取出进程进行调度。

    Stride Scheduling

    具体看一下Stride Scheduling

    1、为每个runnable的进程设置一个当前状态stride,表示该进程当前的调度权。另外定义其对应的pass值,表示对应进程在调度后,stride 需要进行的累加值。
    2、每次需要调度时,从当前 runnable 态的进程中选择 stride最小的进程调度。对于获得调度的进程P,将对应的stride加上其对应的步长pass(只与进程的优先权有关系)。
    3、在一段固定的时间之后,回到步骤2,重新调度当前stride最小的进程

    static void  
    stride_init(struct run_queue *rq) {  
         /* LAB6: YOUR CODE */  
         list_init(&(rq->run_list)); 
         rq->lab6_run_pool = NULL; 
         rq->proc_num = 0;
    }  
    

    首先是队列初始化函数

    static void
    stride_enqueue(struct run_queue *rq, struct proc_struct *proc) {
         /* LAB6: YOUR CODE */
    #if USE_SKEW_HEAP
         rq->lab6_run_pool = skew_heap_insert(rq->lab6_run_pool, &(proc->lab6_run_pool), proc_stride_comp_f);
    #else
         assert(list_empty(&(proc->run_link)));
         list_add_before(&(rq->run_list), &(proc->run_link));
    #endif
         if (proc->time_slice == 0 || proc->time_slice > rq->max_time_slice) {
              proc->time_slice = rq->max_time_slice;
         }
         proc->rq = rq;
         rq->proc_num ++;
    }
    

    然后是入队函数stride_enqueue,根据之前对该调度算法的分析,这里函数主要是初始化刚进入运行队列的进程 proc 的stride属性,然后比较队头元素与当前进程的步数大小,选择步数最小的运行,即将其插入放入运行队列中去,这里并未放置在队列头部。最后初始化时间片,然后将运行队列进程数目加一。

    static void
    stride_enqueue(struct run_queue *rq, struct proc_struct *proc) {
         /* LAB6: YOUR CODE */
    #if USE_SKEW_HEAP
         rq->lab6_run_pool = skew_heap_insert(rq->lab6_run_pool, &(proc->lab6_run_pool), proc_stride_comp_f);
    #else
         assert(list_empty(&(proc->run_link)));
         list_add_before(&(rq->run_list), &(proc->run_link));
    #endif
         if (proc->time_slice == 0 || proc->time_slice > rq->max_time_slice) {
              proc->time_slice = rq->max_time_slice;
         }
         proc->rq = rq;
         rq->proc_num ++;
    }
    
    static void
    stride_dequeue(struct run_queue *rq, struct proc_struct *proc) {
         /* LAB6: YOUR CODE */
    #if USE_SKEW_HEAP
         rq->lab6_run_pool = skew_heap_remove(rq->lab6_run_pool, &(proc->lab6_run_pool), proc_stride_comp_f); //从优先队列中移除
    #else
         assert(!list_empty(&(proc->run_link)) && proc->rq == rq);
         list_del_init(&(proc->run_link));
    #endif
         rq->proc_num --;
    }
    
    static struct proc_struct *
    stride_pick_next(struct run_queue *rq) {
         /* LAB6: YOUR CODE */
    #if USE_SKEW_HEAP
         if (rq->lab6_run_pool == NULL) return NULL;
         struct proc_struct *p = le2proc(rq->lab6_run_pool, lab6_run_pool);
    #else
         list_entry_t *le = list_next(&(rq->run_list));
    
         if (le == &rq->run_list)
              return NULL;
    
         struct proc_struct *p = le2proc(le, run_link);
         le = list_next(le);
         while (le != &rq->run_list)
         {
              struct proc_struct *q = le2proc(le, run_link);
              if ((int32_t)(p->lab6_stride - q->lab6_stride) > 0)
                   p = q;
              le = list_next(le);
         }
    #endif
        //更新对应进程的stride值
         if (p->lab6_priority == 0)
              p->lab6_stride += BIG_STRIDE;
         else p->lab6_stride += BIG_STRIDE / p->lab6_priority;   
         return p;
    }
    

    接下来就是进程的调度函数stride_pick_next,观察代码,它的核心是先扫描整个运行队列,返回其中stride值最小的对应进程,然后更新对应进程的stride值,将步长设置为优先级的倒数,如果为0则设置为最大的步长。

    static void  
    stride_proc_tick(struct run_queue *rq, struct proc_struct *proc) {  
         /* LAB6: YOUR CODE */  
        if (proc->time_slice > 0) 
        {  
            proc->time_slice --;  
        }  
        if (proc->time_slice == 0) 
        {  
            proc->need_resched = 1;  
        }  
    }  
    

    最后是时间片函数stride_proc_tick,主要工作是检测当前进程是否已用完分配的时间片。

    相对于这两个算法我觉得更重要的是明白进程的调度时机

    1. 时钟中断处理函数检测到时间片到期了
    2. 发生阻塞或者睡眠等情况
  • 相关阅读:
    [HAOI2012]高速公路(BZOJ2752)
    [SHOI2008]堵塞的交通traffic(BZOJ1018)
    AHOI2009 行星序列Seq (BZOJ1798)
    java注解问题
    回调方法举例
    log4j将日志文件保存到项目中
    ehCache+springMVC的简单实用
    MyBatis排序时使用order by 动态参数时需要注意,用$而不是#
    mybatis配置log4j
    mybatis ---- 分页 (拦截器)
  • 原文地址:https://www.cnblogs.com/secoding/p/11913817.html
Copyright © 2020-2023  润新知