任务一 Alarm Clock

用到的相干目录有: pintos/src/devices time.h && time.c

pintos/src/threads thread.h && thread.c

预期pass希望:暂无,安装pintos之后,第一次对threads进行make check操作,可以发现以alarm--开头的前六个文件,以及mlfqs-fair-2和mlfqs-fair-20文件,一开始就测试成功pass。但是为了避免对后期的修改有影响,这里还是要进行修改。

修改目的:可以正确阻塞线程,记录阻塞线程的时间,确保正确唤醒线程

具体修改算法

timer.c中实现了线程休眠的函数thread_sleep(),当线程休眠时必须保证中断是打开的。当前执行进程调用timer_sleep(ticks)时,函数通过不断轮询检查经过时间是否达到ticks,若还没达到则调用thread_yield()函数,达到了ticks就会结束休眠。这个函数作用是实现让某个线程睡眠ticks时间,即让该线程暂时放弃CPU。

实现思路: 调用timer_sleep()的时候直接把线程阻塞掉,然后给线程结构体加一个成员ticks_blocked()来记录这个线程被sleep了多少时间, 然后利用操作系统自身的时钟中断(每个tick会执行一次)加入对线程状态的检测, 每次检测将ticks_blocked减1, 如果减到0就唤醒这个线程。

数据结构修改

<thread.h>

struct thread{
//--------------
//--------------
/* 记录线程被阻塞的时间. */
int64_t ticks_blocked;}

 函数修改

  • <time.c>

    • 修改函数 timer_sleep()

    • 修改函数 timer_interrupt()

  • <thread.c>

    • 修改函数 init_thread()

    • 添加函数 check_blocked_thread()

①在<timer.c>中修改timer_sleep() 函数

检测ticks是否有效 ,记录当前睡眠时间

void
timer_sleep (int64_t ticks)
{
  if (ticks <= 0)//检测ticks是否有效
  {
    return;
  }
  ASSERT (intr_get_level () == INTR_ON);
  enum intr_level old_level = intr_disable ();
  struct thread *current_thread = thread_current ();
  current_thread->ticks_blocked = ticks;//当前线程睡眠的时间
  thread_block ();//进程阻塞
  intr_set_level (old_level);
}

②修改<time.c> 中的timer_interrupt()函数

修改时钟中断处理函数, 加入线程sleep时间的检测, 检测哪些进程使用block状态并且含有剩余的休眠时间。加在timer_interrupt内。此处的检测必须同时满足两个条件,因为操作系统中存在进程因为等待锁而阻塞的状态,而不是主动调取timer_sleep函数的结果

timer_interrupt (struct intr_frame *args UNUSED)
{
  ticks++;
  thread_tick ();
/*添加了 thread_foreach (check_blocked_thread, NULL);这句话*/
thread_foreach (check_blocked_thread, NULL);
}

③修改<thread.c>中的init_thread()函数

添加新属性

/* 记录线程被阻塞的时间. */
int64_t ticks_blocked;

我们利用操作系统自身的时钟中断(每个tick会执行一次)加入对线程状态的检测, 每次检测将ticks_blocked减1, 如果减到0就唤醒这个线程。

为了线程可以顺利创建,所以我们要保证刚创建时线程对应的ticks_blocked为0, 加在init_thread()函数内:

t->ticks_blocked = 0;//初始化为0

④添加<thread.c>新函数 check_blocked_thread()

当前待检进程为阻塞状态并且阻塞时间剩余大于0个计时器刻度时执行,剩余刻度自减。若减为0,调用thread_unblock函数将当前进程加入到就绪队列。aux就是传给这个函数的参数。

/*新函数*/
/* Check the blocked thread */
void
check_blocked_thread(struct thread *t, void *aux UNUSED)
{
  if (t->status == THREAD_BLOCKED && t->ticks_blocked > 0)
  {
      t->ticks_blocked--;
      if (t->ticks_blocked == 0)
      {
          thread_unblock(t);
      }
  }
}

同时在<thread.h>中申明这个函数

void check_blocked_thread(struct thread *t, void *aux UNUSED);

任务二 实现优先级调度

用到的相干目录:pintos/src/threads thread.h && thread.c

预期pass希望: alarm-priority priority-xxx

目的:在Pintos中实现优先级调度。当一个线程被添加到具有比当前运行的线程更高优先级的就绪列表时,当前线程应该立即将处理器放到新线程中。类似地,当线程正在等待锁,信号量或条件变量时,应首先唤醒优先级最高的等待线程。线程可以随时提高或降低自己的优先级,但降低其优先级以使其不再具有最高优先级必须使其立即产生CPU。

实施优先级捐赠,需要考虑需要优先捐赠的所有不同情况。务必处理多个捐赠,其中多个优先级捐赠给单个线程。

将任务二分为两个部分:

①实现优先级捐赠

②维持就绪队列为一个优先级队列,并且能够抢占CPU

具体修改算法:

先总结一下所有测试整合的逻辑:

  1. 在一个线程获取一个锁的时候, 如果拥有这个锁的线程优先级比自己低就提高它的优先级,并且如果这个锁还被别的锁锁着, 将会递归地捐赠优先级, 然后在这个线程释放掉这个锁之后恢复未捐赠逻辑下的优先级。

  2. 如果一个线程被多个线程捐赠, 维持当前优先级为捐赠优先级中的最大值(acquire和release之时)。

  3. 在对一个线程进行优先级设置的时候, 如果这个线程处于被捐赠状态, 则对original_priority进行设置, 然后如果设置的优先级大于当前优先级, 则改变当前优先级, 否则在捐赠状态取消的时候恢复original_priority。

  4. 在释放锁对一个锁优先级有改变的时候应考虑其余被捐赠优先级和当前优先级。

  5. 将信号量的等待队列实现为优先级队列。

  6. 将condition的waiters队列实现为优先级队列。

  7. 释放锁的时候若优先级改变则可以发生抢占

数据结构修改

  • <thread.h>修改thread数据结构, 加入以下成员:

int base_priority;             /* 优先级 */
struct list locks;            /* 当前进程持有的锁*/
struct lock *lock_waiting;   /* 当前进程正在被什么锁阻碍 */
  • <synch.h>修改lock数据结构,加入以下成员:

struct list_elem elem;      /* struct thread中locks的链表元素 */
int max_priority;          /* 获取线程最大优先级 */

优先级捐赠函数功能修改

  • <synch.c>

    • 修改函数lock_acquire ()

    • 修改函数lock_release()

    • 新增函数lock_cmp_priority()

  • <thread.c>

    • 修改函数init_thread()

    • 修改函数thread_set_priority()

    • 新增函数thread_donate_priority()

    • 新增函数thread_remove_lock()

    • 新增函数thread_hold_the_lock()

    • 新增函数thread_cmp_priority ()

    • 修改函数thread_unblock()

    • 修改函数thread_create()

优先级捐赠关键实现函数实现

  • lock_acquire()函数

功能:在P操作之前递归地实现优先级捐赠, 然后在被唤醒之后(此时这个线程已经拥有了这个锁),成为这个锁的拥有者。

void
lock_acquire (struct lock *lock)
{
  struct thread *current_thread = thread_current ();
  struct lock *l;
  enum intr_level old_level;
  ASSERT (lock != NULL);
  ASSERT (!intr_context ());
  ASSERT (!lock_held_by_current_thread (lock));
  if (lock->holder != NULL && !thread_mlfqs)
  {
    current_thread->lock_waiting = lock;//成为锁的拥有者
    l = lock;
    while (l && current_thread->priority > l->max_priority)
    {
      l->max_priority = current_thread->priority;//设置优先级
      thread_donate_priority (l->holder);//捐赠优先级
      l = l->holder->lock_waiting;
    }
  }
  sema_down (&lock->semaphore);
  old_level = intr_disable ();
  current_thread = thread_current ();
  if (!thread_mlfqs)
  {
    current_thread->lock_waiting = NULL;
    lock->max_priority = current_thread->priority;
    thread_hold_the_lock (lock);
  }
  lock->holder = current_thread;
  intr_set_level (old_level);
}
  • lock_release()函数实现

重新实现lock_release函数,在释放锁对一个锁优先级有改变的时候还应考虑其余被捐赠优先级和当前优先级

if(!thread_mlfqs) {
    //将锁从相应的队列中移除
    list_remove(&(lock->holder_elem));
    lock->lock_priority = PRI_MIN-1;
  }
  sema_up (&lock->semaphore);
  //pro1 added
  if(!thread_mlfqs) {
    //如果当前的线程持有的锁的队列为空,就恢复到原来的优先级
    if(list_empty(&curr->locks)) {
      curr->donated = false;
      thread_set_priority (curr->old_priority);
    }
    //否则将锁队列头(优先级最高)的优先级捐赠给它——donate-multiple
    else
    {
      I = list_front(&curr->locks);
      another = list_entry(I, struct lock, holder_elem);
      thread_set_other_priority (thread_current(), another->lock_priority, false);
    }
  • list_insert_ordered()函数

功能:实现队列的线程按照优先级高低排序

同时这里对thread_unblock() && thread_yield()进行修改,把list_push_back改成

list_insert_ordered (&ready_list, &t->elem, (list_less_func *) &thread_cmp_priority, NULL);
  • thread_hold_the_lock()函数

功能: 如果一个线程被多个线程捐赠, 维持当前优先级为捐赠优先级中的最大值

enum intr_level old_level = intr_disable ();
//实现优先级排序,选最大的
list_insert_ordered (&thread_current ()->locks, &lock->elem, lock_cmp_priority, NULL);
  
if (lock->max_priority > thread_current ()->priority)
{//维持最大捐赠优先级
  thread_current ()->priority = lock->max_priority;
  thread_yield ();
}
  • thread_donate_priority()函数介绍

  功能:实现优先级捐赠,通过直接修改锁的最高优先级, 然后调用update的时候把现成优先级更 新实现的

 //前面需判断是否是多级反馈调度,如果是就要禁止优先级捐赠
enum intr_level old_level = intr_disable ();
  thread_update_priority (t);
//如果设置的线程是ready的状态,因为优先级改变了,需要重新按照优先级的顺序放入ready队列中
   if (t->status == THREAD_READY)
   {
     list_remove (&t->elem);
     list_insert_ordered (&ready_list, &t->elem, thread_cmp_priority, NULL);
   }
  • priority_higher()函数介绍

    功能:实现判断两个线程的优先级高低函数

  • thread_unblock() && thread_create() &&thread_yield修改

    实现抢占式调度,如果新建的线程优先级高于正在运行的线程,则当前线程需要让出CPU。这里只需要在thread_create函数中的thread_unblock之后比较一下新创建的线程的优先级与当前正在运行的线程的优先级的高低,然后决定是否需要让出CPU。

    创建的线程的优先级比当前线程的高的话,当前线程就要让出CPU,在thread_create函数中添加一下代码

   if (thread_current ()->priority < priority)
   {
       thread_yield ();
    }
  • lock_cmp_priority()函数介绍

    功能:实现锁队列排序

  • thread_set_priority()函数实现

    功能:为确保最高优先级的线程运行,需要重新计算调度的时刻有:创建新线程,设置线程优先级

void
thread_set_priority (int new_priority)
{
  if (thread_mlfqs)
    return;

  enum intr_level old_level = intr_disable ();

  struct thread *current_thread = thread_current ();
  int old_priority = current_thread->priority;
  current_thread->base_priority = new_priority;

  if (list_empty (&current_thread->locks) || new_priority > old_priority)
  {
    current_thread->priority = new_priority;
    thread_yield ();
  }

  intr_set_level (old_level);
}

优先级队列函数实现

  • <synch.c>

    • 修改函数cond_signal()

    • 修改函数sema_up()

    • 修改函数sema_down()

    • 新增函数cond_sema_cmp_priority ()

cond_signal()函数修改

功能:调用list_sort()函数,将condition的waiters队列实现为优先级队列

 list_sort (&cond->waiters, cond_sema_cmp_priority, NULL);

cond_sema_cmp_priority()函数介绍

功能:信号量比较函数

sema_up()函数

内部调用list_sort ()函数,将sema的waiters队列实现为优先级队列

void
sema_up (struct semaphore *sema) //相当于signal操作
{
  enum intr_level old_level;

  ASSERT (sema != NULL);
  //pro1 added
  struct thread *wake_up; //需要唤醒的线程
  wake_up = NULL;
  //pro1 added

  old_level = intr_disable ();
  if (!list_empty (&sema->waiters)) {
     /*thread_unblock (list_entry (list_pop_front (&sema->waiters),
                                struct thread, elem));*/
     //pro1 added
     //对sema的waiters队列按照优先级进行排序
     list_sort (&sema->waiters, priority_higher, NULL); 
     //唤醒队列头,也就是队列中优先级最高的线程
     wake_up = list_entry (list_pop_front (&sema->waiters), struct thread, elem);
     thread_unblock (wake_up);
     //pro1 added
  }
  sema->value++;    //V
  //pro1 added
  //如果当前线程的优先级比唤醒的线程的低,就要放弃CPU
  if(wake_up != NULL && thread_current()->priority < wake_up->priority)
     thread_yield();
  //pro1 added
  intr_set_level (old_level);
}

sema_down()函数实现

实现wait操作,使value--

任务三 实现反馈调度

涉及到的目录:pintos/src/threads/ thread.h && thread.c 预期pass:mlfqs-xxx

目的:实现多级反馈队列调度程序,减少在系统上运行作业的平均响应时间。

具体修改算法

实现思路: 在timer_interrupt中固定一段时间计算更新线程的优先级,这里是每TIMER_FREQ时间更新一次系统load_avg和所有线程的recent_cpu, 每4个timer_ticks更新一次线程优先级, 每个timer_tick running线程的recent_cpu加一, 虽然这里说的是维持64个优先级队列调度, 其本质还是优先级调度, 我们保留之前写的优先级调度代码即可, 去掉优先级捐赠。

计算公式

  • priority = PRI_MAX - (recent_cpu / 4) - (nice * 2)

    nice是线程自己控制的一个值,范围在-20~20。优先级每四个tick重新计算并排列一次。

  • recent_cpu=(2load_avg)/(2load_avg+1)*recent_cpu+nice

    recent_cpu测试当前线程在当前时钟周期(或当前很小的一个时间单位里)被分到的CPU时间。每次时钟tickrecent_cpu加1,recent_cpud的值每秒钟刷新一次。

  • load_avg = (59/60)*load_avg + (1/60)*ready_threads

    load_avg估计CPU需要处理的线程数目,其值初始为0,每秒钟重新计算一次

    定点小数及其计算

定点小数及其计算

在上面的公式中,priorityniceready_thread是整数,但是recent_cpuload_avg是实数。 但是,Pintos不支持内核中的浮点运算,因为它会使内核变得复杂和变慢。 出于同样的原因,真正的内核通常具有相同的限制。 这意味着必须使用整数模拟实数的计算。 本节介绍基础知识。

其基本思想是将整数的最右边的几位视为表示分数。例如,我们可以将带符号的32位整数的最低14位指定为小数位,这样整数x代表实数 x/214 。这个叫做 17.14 定点数,最大能够表示 (231-1)/214 ,近似 131071.999 。

我们知道bool变量thread_mlfqs指示是否启用高级调度程序,并且高级调度程序不应包含优先级捐赠的内容,所以,在Mission_2中实现的优先级捐赠代码,需要使用if判断以保证在使用高级调度程序时,不启用优先级捐赠。随后,我们可以根据实验指导中的详细说明一步步完成本次实验。

首先,根据上述给定点数的计算方法,编写定点数的计算方法

/* Basic definitions of fixed point. */
typedef int fixed_t;
/* 16 LSB used for fractional part. */
#define FP_SHIFT_AMOUNT 16
/* Convert a value to fixed-point value. */
#define FP_CONST(A) ((fixed_t)(A << FP_SHIFT_AMOUNT))
/* Add two fixed-point value. */
#define FP_ADD(A,B) (A + B)
/* Add a fixed-point value A and an int value B. */
#define FP_ADD_MIX(A,B) (A + (B << FP_SHIFT_AMOUNT))
/* Substract two fixed-point value. */
#define FP_SUB(A,B) (A - B)
/* Substract an int value B from a fixed-point value A */
#define FP_SUB_MIX(A,B) (A - (B << FP_SHIFT_AMOUNT))
/* Multiply a fixed-point value A by an int value B. */
#define FP_MULT_MIX(A,B) (A * B)
/* Divide a fixed-point value A by an int value B. */
#define FP_DIV_MIX(A,B) (A / B)
/* Multiply two fixed-point value. */
#define FP_MULT(A,B) ((fixed_t)(((int64_t) A) * B >> FP_SHIFT_AMOUNT))
/* Divide two fixed-point value. */
#define FP_DIV(A,B) ((fixed_t)((((int64_t) A) << FP_SHIFT_AMOUNT) / B))
/* Get integer part of a fixed-point value. */
#define FP_INT_PART(A) (A >> FP_SHIFT_AMOUNT)
/* Get rounded integer of a fixed-point value. */
#define FP_ROUND(A) (A >= 0 ? ((A + (1 << (FP_SHIFT_AMOUNT - 1))) >> FP_SHIFT_AMOUNT) \
        : ((A - (1 << (FP_SHIFT_AMOUNT - 1))) >> FP_SHIFT_AMOUNT))

由于本实验涉及到的公式均与时钟中断有关,所以,我们将上述的公式迭代计算的相关代码,写入我们在任务一中详细介绍过的timer_interrupt()时钟中断处理函数中。

数据结构修改

<thread.h>

struct thread{
   int nice;    /*mlfqs中和线程优先级相关的变量,记录线程的友好程度 */
   fixed_t recent_cpu;  /*最近使用CPU的时间 */
}

线程初始化时,给这两个变量赋初值 修改init_thread()

t->nice = 0;
t->recent_cpu = FP_CONST (0);

函数功能修改

  • <thread.c>

    • 添加函数thread_mlfqs_increase_recent_cpu () 计算recent_cpu

    • 添加函数thread_mlfqs_update_priority() 更新优先级

    • 添加函数thread_mlfqs_update_load_avg_and_recent_cpu()

    • 添加函数thread_set_nice()

    • 添加函数 thread_get_nice ()

    • 添加函数thread_get_load_avg () 计算load_avg

    • 添加函数thread_get_recent_cpu() 计算recent_cpu

    • 添加全局变量                                                                                                                       

      fixed_t load_avg;//全局变量

      初始化

      load_avg = FP_CONST (0);//在thread_start中
  • <timer.c>

    • 修改函数timer_interrupt

      功能:每隔一段时间计算当前优先级并刷新

  if (thread_mlfqs)
  {
    thread_mlfqs_increase_recent_cpu();
    if (ticks % TIMER_FREQ==0)
      //刷新所有线程的load_avg和recent_cpu参数
      thread_mlfqs_update_load_avg_and_recent_cpu ();
    else if (ticks%4 == 0)
      thread_mlfqs_update_priority (thread_current ());
  }

thread_mlfqs_update_load_avg_and_recent_cpu()函数实现

功能:首先根据就绪队列的大小计算load_avg的值,随后根据load_avg的值,更新所有进程的recent_cpu值及priority

/* 每秒刷新所有线程的 load_avg 和 recent_cpu */
void
thread_mlfqs_update_load_avg_and_recent_cpu (void)
{
  ASSERT (thread_mlfqs);
  ASSERT (intr_context ());

  size_t ready_threads = list_size (&ready_list);
  if (thread_current () != idle_thread)
    ready_threads++;
    //重新计算load_avg
  load_avg = FP_ADD (FP_DIV_MIX (FP_MULT_MIX (load_avg, 59), 60), FP_DIV_MIX (FP_CONST (ready_threads), 60));

  struct thread *t;
  struct list_elem *e = list_begin (&all_list);
  for (; e != list_end (&all_list); e = list_next (e))
  {
    t = list_entry(e, struct thread, allelem);
    if (t != idle_thread)
    {
        //重新计算recent_cpu
      t->recent_cpu = FP_ADD_MIX (FP_MULT (FP_DIV (FP_MULT_MIX (load_avg, 2), FP_ADD_MIX (FP_MULT_MIX (load_avg, 2), 1)), t->recent_cpu), t->nice);
        //更新优先级
      thread_mlfqs_update_priority (t);
    }
  }
}

thread_mlfqs_update_priority()函数实现

功能:更新当前进程的priority值,注意,一定要保证每个线程的优先级介于0(PRI_MIN)到63(PRI_MAX)之间

t->priority = FP_INT_PART (FP_SUB_MIX (FP_SUB (FP_CONST (PRI_MAX), FP_DIV_MIX (t->recent_cpu, 4)), 2 * t->nice));
t->priority = t->priority < PRI_MIN ? PRI_MIN : t->priority;
//计算出来的优先级如果比最大值大就赋为最大值,比最小值小就赋为最小值
t->priority = t->priority > PRI_MAX ? PRI_MAX : t->priority;

thread_set_nice() &&thread_get_nice()函数设置

thread_set_nice()功能:将当前线程的 nice 值设置为 NICE

设置当前线程的nice,并根据nice的值来设置更新recent_cpu和优先级

如果优先级改变后当前线程的优先级比ready队列里的线程的低,就要让出CPU

thread_get_nice()功能:返回当前进程的nice值

thread_get_load_avg

return FP_ROUND (FP_MULT_MIX (load_avg, 100));//计算公式实现

thread_get_recent_cpu()函数

return FP_ROUND (FP_MULT_MIX (thread_current ()->recent_cpu, 100));//计算公式

⑥变量添加

fixed_t load_avg;

thread_start中初始化:

load_avg = FP_CONST (0);

结果:27 pass 代码可以私信我,带上邮箱号

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐