Pintos Project1 Thread 报告

本文详细介绍了对Pintos操作系统课程设计的改进,包括重新实现timer_sleep函数以减少CPU资源占用,实现优先级调度以提升高优先级线程执行效率,以及引入优先级捐赠机制以优化锁的并发控制。此外,还实现了多级反馈调度,通过动态调整线程优先级以公平分配CPU时间。整个过程涉及到了线程状态管理、中断处理、时钟中断、线程调度等多个关键组件的修改和优化。

操作系统课程设计

Pintos Project1 Thread



任务1 重新实现timer_sleep函数

timer.c中的原timer_sleep函数实现分析:

intr_get_level返回了intr_level的值

intr_disable获取当前的中断状态, 将当前中断状态改为不能被中断, 然后返回执行之前的中断状态。

ticks从pintos被启动开始计时, 代表操作系统执行单位时间的前进量。

thread_yield是把当前线程放到就绪队列里, 然后重新schedule, 除非ready队列为空,否则当前线程不会继续执行。

timer_elapsed循环在ticks的时间内不断执行thread_yield。

thread_current函数返回指向当前正在运行的线程的指针,其逻辑是

thread_schedule_tail其实就是获取当前线程, 分配恢复之前执行的状态和现场, 如果当前线程死了就清空资源。

schedule是将下一个线程切换过来继续运行。 即先把当前线程丢到就绪队列,然后如果下一个线程和当前线程不一样的话就将线程切换

总结: timer_sleep就是在ticks时间内, 一旦线程处于运行状态就把他扔到就绪队列不让他执行。

分析完这些,我发现原timer_sleep的缺点: 线程不断在cpu就绪队列和running队列之间来回, 占用了太多cpu资源。


timer_sleep函数的重新实现

实现思路: 给线程的结构体加一个成员ticks_blocked来记录线程被sleep的时间。一旦调用timer_sleep就阻塞线程,然后利用操作系统的时钟机制,每次tick将ticks_blocked减1, 减到0就唤醒此线程。

  1. 给线程结构体加入ticks_blocked成员:

     int64_t ticks_blocked;
    
  2. 在线程创建时初始化ticks_blocked为0,在thread_create函数:

     t->ticks_blocked = 0;
    
  3. 修改时钟中断处理函数, 加入线程sleep时间的检测, 在timer_interrupt函数:

     thread_foreach (blocked_thread_check, NULL);
    
  4. 给thread添加一个阻塞线程sleep时间检测函数blocked_thread_check

      void blocked_thread_check (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);//将线程唤醒到就绪队列中。
             }
         }
     }
    
  5. 添加thread_unblock函数,作用是将线程放到就绪队列

     void thread_unblock (struct thread *t)
     {
         enum intr_level old_level;
    
         ASSERT (is_thread (t));
    
         old_level = intr_disable ();
         ASSERT (t->status == THREAD_BLOCKED);
         /* list_push_back (&ready_list, &t->elem); */
         list_insert_ordered (&ready_list, &t->elem, (list_less_func *) &thread_cmp_priority, NULL);
         t->status = THREAD_READY;
         intr_set_level (old_level);
     }
    
  6. 最后的最后,编写timer_sleep函数

     void timer_sleep (int64_t ticks)
     {
         if (ticks <= 0)
         {
             return;//否则没有意义
         }
         ASSERT (intr_get_level () == INTR_ON);
         enum intr_level old_level = intr_disable ();
         struct thread *current_thread = thread_current ();
         current_thread->thread_sleep_tick = ticks;
         //获取运行进程的sleep_tick将其变为运行的tick值
         thread_block ();//阻塞进程
         intr_set_level (old_level);
     }
    

至此,timer_sleep函数已经被重构,任务1完成。

任务2 实现优先级调度和优先级捐赠


优先级调度实现

优先级调度就是优先级高的任务进程优先执行,即可以抢占低优先级的线程。

实现思路: 当一个进程被创建时候,应得到一个优先级,之后如果新的线程的优先级大于运行的线程,则将运行的线程阻塞并放到就绪队列

  1. 修改thread_unblock函数,将list_push_back替换为

     list_insert_ordered (&ready_list, &t->elem, (list_less_func *) &thread_cmp_priority, NULL);
    
  2. 实现线程优先级比较函数thread_cmp_priority

     bool thread_cmp_priority (const struct list_elem *a, const struct   list_elem *b, void *aux UNUSED)
     {
         return list_entry(a, struct thread, elem)->priority > list_entry(b, struct thread, elem)->priority;
     }
    
  3. 记得要修改thread_yield和thread_init里的list_push_back,方法同1

  4. 将信号量的等待队列和condition的waiters队列实现为优先级队列。

    修改cond_signal函数及比较函数cond_sema_cmp_priority

    修改PV操作sema_up和sema_down

至此,优先级调度已经实现。


优先级捐赠实现

优先级捐赠的分析:

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

  2. 如果一个线程被多个线程捐赠,则当前优先级为捐赠优先级中的最大值。

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

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

  5. 在释放锁对一个锁优先级有改变的时候其余被捐赠优先级和当前优先级都应当被计算。

具体实现:

  1. 修改thread数据结构, 增加以下成员

     int base_priority;
     struct list locks;                  
     struct lock *lock_waiting;
    

    修改lock数据结构,增加以下成员

     struct list_elem elem;
     int max_priority;  
    
  2. 修改lock_acquire函数:

    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);
    }
    
  3. 编写thread_donate_priority函数

     void thread_donate_priority (struct thread *t)
     {
     enum intr_level old_level = intr_disable ();
     thread_update_priority (t);
    
     if (t->status == THREAD_READY)
     {
         list_remove (&t->elem);
         list_insert_ordered (&ready_list, &t->elem, thread_cmp_priority, NULL);
     }
     intr_set_level (old_level);
     }
    

4.编写thread_hold_the_lock函数,在wait之前实现优先级捐赠, 然后在被唤醒,也就是线程拥有了这个锁之后,成为这个锁的拥有者。

    void thread_hold_the_lock(struct lock *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 ();
    }

    intr_set_level (old_level);
    }
  1. 编写锁队列的排序函数lock_cmp_priority

     bool lock_cmp_priority (const struct list_elem *a, const struct list_elem *b, void *aux UNUSED)
     {
         return list_entry (a, struct lock, elem)->max_priority > list_entry (b, struct lock, elem)->max_priority;
     }
    
  2. 编写锁的释放函数thread_remove_lock

     void thread_remove_lock (struct lock *lock)
     {
         enum intr_level old_level = intr_disable ();
         list_remove (&lock->elem);
         thread_update_priority (thread_current ());
         intr_set_level (old_level);
     }
    
  3. 编写释放锁后优先级的更新函数thread_update_priority

     void thread_update_priority (struct thread *t)
     {
         enum intr_level old_level = intr_disable ();
         int max_priority = t->base_priority;
         int lock_priority;
    
         if (!list_empty (&t->locks))
         {
             list_sort (&t->locks, lock_cmp_priority, NULL);
             lock_priority = list_entry (list_front (&t->locks), struct lock, elem)->max_priority;
             if (lock_priority > max_priority)
             max_priority = lock_priority;
         }
    
         t->priority = max_priority;
         intr_set_level (old_level);
     }
    
  4. 如果释放这个锁后后这个线程还有别的锁, 就先获取这个线程拥有的锁的最大优先级,再然后如果这个优先级比base_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);
     }
    

至此,优先级捐赠已经实现。


任务3 实现多级反馈调度

实现思路:

维护一个CPU使用时间和一个调度队列,当一个thread run完一个时间片后下降一个优先级,然后从优先级高的队列中选择出一个线程继续运行。

实现方法:

在timer_interrupt中固定一段时间计算更新线程的优先级,每tick时间更新一次系统load_avg和所有线程的recent_cpu, 每4个ticks更新一次线程优先级, 每个timer_tick running线程的recent_cpu加一, 这里我们保留之前写的优先级调度代码即可, 但之前优先级捐赠相关代码已经对需要的地方加了thread_mlfqs的判断了,所以要去掉。

从官网获得公式:

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

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

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

关于浮点数运算:

Pintos不支持内核中的浮点运算,因为这会使内核复杂化并减慢速度。由于同样的原因,真正的内核通常也有同样的局限性。这意味着对实际量的计算必须使用整数进行模拟。
要用整数操作模拟出浮点数的运算,方法是将整数的最右边的位视为表示分数。

具体实现:

  1. 在thread.c中加入加入全局变量load_avg ,并修改thread 结构体如下所示.

     int nice;
     fixed_t recent_cpu;
    
  2. 在线程初始化的时候,也对其进行初始化操作

     t->nice=0;
     t->recent_cpu=FP_CONST(0);
    
  3. 在时钟中断中编写每4个tick更新一次优先级,每1 ticks更新一次recent_cpu、load_avg、nice 。这里调用的运算都是浮点运算。

     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 = 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)
             {
             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);
             }
         }
         }
    
         void
         thread_mlfqs_update_priority (struct thread *t)
         {
         if (t == idle_thread)
             return;
    
         ASSERT (thread_mlfqs);
         ASSERT (t != idle_thread);
    
         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;
     }
    
  4. 修改时钟中断函数

     static void timer_interrupt (struct intr_frame *args UNUSED)
     {
         ticks++;
         enum intr_level old_level = intr_disable ();
         if (thread_mlfqs)
         {
             thread_mlfqs_increase_recent_cpu_by_one ();
             if (ticks % TIMER_FREQ == 0)
             thread_mlfqs_update_load_avg_and_recent_cpu ();
             else if (ticks % 4 == 0)
             thread_mlfqs_update_priority (thread_current ());
         }
         thread_foreach (blocked_thread_check, NULL);
         intr_set_level (old_level);
         thread_tick ();
     }
    
  5. 编写thread_mlfqs_increase_recent_cpu_by_one函数,每一tick都更新所有线程的load_avg和recent_cpu

     void thread_mlfqs_increase_recent_cpu_by_one (void)
     {
         ASSERT (thread_mlfqs);
         ASSERT (intr_context ());
    
         struct thread *current_thread = thread_current ();
         if (current_thread == idle_thread)
             return;
         current_thread->recent_cpu = FP_ADD_MIX (current_thread->recent_cpu, 1);
     }
    
  6. 编写thread_mlfqs_update_priority函数,用来更新优先级

     void thread_mlfqs_update_priority (struct thread *t)
     {
         if (t == idle_thread)
             return;
         ASSERT (thread_mlfqs);
         ASSERT (t != idle_thread);
    
         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;
     }
    

至此,多级反馈调度已经实现。

至此,Project1已经完成。

感谢老师的答疑和万能的CSDN,提供了很多帮助。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值