【pintos学习笔记】(2)睡眠“忙等待”

时间:2021-12-11 04:34:29

一、问题描述

在pintos里,实现进程睡眠是依靠timer_sleep(ticks)函数。
【pintos学习笔记】(2)睡眠“忙等待”
该函数的实现是将进程“睡眠”后,利用Polling的机制不断中断当前CPU内的进程,使得CPU时间不能够让给别的进程,造成一个”忙等待”的现象。
这有点像一个俗语——“站着茅坑不拉shi”,把CPU时间一直紧紧攥着,然后也不让别的进程去完成任务了。而且这个进程也并没有真正睡着,一直在问我到底还有多久才醒过来,你说是不是很傲娇。


二、问题分析

-为了解决这个傲娇的问题,毫无疑问,我们需要将进程安抚睡着,并帮它掐好表,准时告诉它醒过来。
但不管怎样,我们从timer_sleep这个函数分析起。

/* Sleeps for approximately TICKS timer ticks. Interrupts must be turned on. */
void timer_sleep (int64_t ticks) 
{
  int64_t start = timer_ticks ();
  ASSERT (intr_get_level () == INTR_ON);
  while (timer_elapsed (start) < ticks) thread_yield ();
}

-首先,我们看下面这条语句,它返回了当前timer从os启动后的tick次数。

int64_t start = timer_ticks ();

-然后这个ASSERT()宏定义为了确保系统允许中断,即别的进程可以抢茅坑。

ASSERT (intr_get_level () == INTR_ON);

-这句就是当前这个进程怎么守住这个茅坑的,用一个while循环,不断访问已经过去的ticks次数(timer_elapsed返回从start时刻起的ticks次数)

while (timer_elapsed (start) < ticks) thread_yield ();

-那现在,我们就来改变这种不平等的制度,从timer_sleep函数入手吧~
-为了让进程睡着,我们当然要将它从准备队列(ready_list)里面丢出来,也就是让它不要再去排队上茅厕了。

    thread_block()//将当前进程从准备队列里中止掉,并加入中止队列。

-但是为了不让它从队伍出来时,又被别的进程“抢救回去”(中断),所以我们要禁止中断,确保操作原子性。

    enum intr_level old_level; 
    old_level = intr_disable();
    thread_block();
    intr_set_level(old_level);

-好了,从队伍出来是出来了。但是它在中断队列里面,我们怎么知道它要睡多久后叫起来?
这里插个有趣的事情:还记得之前在HK吃经典小吃鸡蛋仔的时候,那是一家老字号的店,做出来的鸡蛋仔外脆里嫩…想想就很好吃,口水流出来了哈…
-说回正题,那这个和我们有什么关系?我有留意到,为了保证每份的口味能够很好地控制,老板在每个鸡蛋仔的烤具边上装上了我们所熟知得timer!这有什么用?还用说么,就是告诉老板这份鸡蛋仔还要多久才能出炉,要出炉的时候还会“叮叮”响,从而确保了鸡蛋仔的品质,不会太生也不会太焦。
所以,我们要向这个老板学习,给每个要睡眠进程装一个timer,然后随着timer_ticks的变化而递减,直到进程它的timer响了。
-加一个计时器(block_ticks),用于记录当前进程还有多少ticks才唤醒。
改变thread的结构体,增加block_ticks。

struct thread
  {
    /* Owned by thread.c. */
    tid_t tid;                          /* Thread identifier. */
    enum thread_status status;          /* Thread state. */
    char name[16];                      /* Name (for debugging purposes). */
    uint8_t *stack;                     /* Saved stack pointer. */
    int priority;                       /* Priority. */
    struct list_elem allelem;           /* List element for all threads list. */

    /* Shared between thread.c and synch.c. */
    struct list_elem elem;              /* List element. */

#ifdef USERPROG
    /* Owned by userprog/process.c. */
    uint32_t *pagedir;                  /* Page directory. */
#endif

    /* Owned by thread.c. */
    unsigned magic;                     /* Detects stack overflow. */

    int block_ticks ;                   /* ticks to wake up */
  };
/* Sleeps for approximately TICKS timer ticks. Interrupts must be turned on. */
void
timer_sleep (int64_t ticks) 
{
  int64_t start = timer_ticks ();

  ASSERT (intr_get_level () == INTR_ON);
    enum intr_level old_level; 
    struct thread* t = thread_current();
    t->block_ticks = ticks;            /* 记录ticks次数 */

    old_level = intr_disable();
    thread_block();
    intr_set_level(old_level);

    thread_yield();

}

-除此之外,我们要不断去count down,所以我们在timer_interrupt函数里增加检查的过程,即每次tick的时候,再去看哪些程序需要醒来。

/* Timer interrupt handler. */
static void timer_interrupt (struct intr_frame *args UNUSED)
{
  ticks++;
  thread_tick ();

  thread_foreach(block_check, NULL);   /* check the thread to wake up */
}

-至于要怎么检查,就是每次tick的时候,就把要唤醒的程序里的block_ticks减1,然后检查它是否已经用睡够时间,如果睡够了,那么就从中断队列加入等待队列里。

void block_check(struct thread* t, void * aux)
{
    if(t->status == THREAD_BLOCKED && t->block_ticks > 0)
    {
        t->block_ticks--;
        if(t->block_ticks == 0)
            thread_unblock(t);
    }
}

-到这里,忙等待就可以解决了。我们测试pintos后,不会影响通过结果。

三、详细代码变动

-我把pintos的代码git上了CSDN Code了,本身想摆上github,可是最近不知道为什么连不上吧,就只好暂存这里吧。
-git地址:jason\pintos
-睡眠忙等待: pintos commit 改动