RR 调度算法实现

RR调度算法的调度思想 是让所有runnable态的进程分时轮流使用CPU时间。RR调度器维护当前runnable进程的有序运行队列。当前进程的时间片用完之后,调度器将当前进程放置到运行队列的尾部,再从其头部取出进程进行调度。RR调度算法的就绪队列在组织结构上也是一个双向链表,只是增加了一个成员变量,表明在此就绪进程队列中的最大执行时间片。而且在进程控制块proc_struct中增加了一个成员变量time_slice,用来记录进程当前的可运行时间片段。这是由于RR调度算法需要考虑执行进程的运行时间不能太长。在每个timer到时的时候,操作系统会递减当前执行进程的time_slice,当time_slice为0时,就意味着这个进程运行了一段时间(这个时间片段称为进程的时间片),需要把CPU让给其他进程执行,于是操作系统就需要让此进程重新回到rq的队列尾,且重置此进程的时间片为就绪队列的成员变量最大时间片max_time_slice值,然后再从rq的队列头取出一个新的进程执行。下面来分析一下其调度算法的实现。

RR_enqueue的函数实现如下表所示。即把某进程的进程控制块指针放入到rq队列末尾,且如果进程控制块的时间片为0,则需要把它重置为rq成员变量max_time_slice。这表示如果进程在当前的执行时间片已经用完,需要等到下一次有机会运行时,才能再执行一段时间。

  1. static void
  2. RR_enqueue(struct run_queue *rq, struct proc_struct *proc) {
  3. assert(list_empty(&(proc->run_link)));
  4. list_add_before(&(rq->run_list), &(proc->run_link));
  5. if (proc->time_slice == 0 || proc->time_slice > rq->max_time_slice) {
  6. proc->time_slice = rq->max_time_slice;
  7. }
  8. proc->rq = rq;
  9. rq->proc_num ++;
  10. }

RR_pick_next的函数实现如下表所示。即选取就绪进程队列rq中的队头队列元素,并把队列元素转换成进程控制块指针。

  1. static struct proc_struct *
  2. FCFS_pick_next(struct run_queue *rq) {
  3. list_entry_t *le = list_next(&(rq->run_list));
  4. if (le != &(rq->run_list)) {
  5. return le2proc(le, run_link);
  6. }
  7. return NULL;
  8. }

RR_dequeue的函数实现如下表所示。即把就绪进程队列rq的进程控制块指针的队列元素删除,并把表示就绪进程个数的proc_num减一。

  1. static void
  2. FCFS_dequeue(struct run_queue *rq, struct proc_struct *proc) {
  3. assert(!list_empty(&(proc->run_link)) && proc->rq == rq);
  4. list_del_init(&(proc->run_link));
  5. rq->proc_num --;
  6. }

RR_proc_tick的函数实现如下表所示。即每次timer到时后,trap函数将会间接调用此函数来把当前执行进程的时间片time_slice减一。如果time_slice降到零,则设置此进程成员变量need_resched标识为1,这样在下一次中断来后执行trap函数时,会由于当前进程程成员变量need_resched标识为1而执行schedule函数,从而把当前执行进程放回就绪队列末尾,而从就绪队列头取出在就绪队列上等待时间最久的那个就绪进程执行。

  1. static void
  2. RR_proc_tick(struct run_queue *rq, struct proc_struct *proc) {
  3. if (proc->time_slice > 0) {
  4. proc->time_slice --;
  5. }
  6. if (proc->time_slice == 0) {
  7. proc->need_resched = 1;
  8. }
  9. }