一、问题描述
在pintos里,实现进程睡眠是依靠timer_sleep(ticks)函数。
该函数的实现是将进程“睡眠”后,利用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 改动