pintos project (2) Project 1 Thread -Mission 1 Explanation

一 开篇

  这篇博客开始介绍pintos project的第一个任务:

  • Project 1 Threads 

  

  操作系统这门课对我个人而言算是比较有难度很有挑战的一门课,但是我非常的喜欢,关于一些算法性质的东西,在课上的时候可能不是很了解,但是通过pintos这个实验,很多东西自然而然的就明白了。

  实验一从一开始我参考了这篇博客 Pintos-斯坦福大学操作系统Project详解-Project1 原博客的博主对于实验一讲解的非常细致,对于一些函数基本上也是一个个的讲解,所以如果觉得我这篇博客写的不是很好的朋友,请移步原博,希望你能有所收获。

  之所以在原博客已经存在的情况下再写一篇这样的博客,是因为我觉得,在同一个实验中每个人的收获是不同的,比如一些地方的修改可能使你豁然开朗,有些函数该如何调用,有些变量如何使用,很多程度上让我觉得,这个实验让我学到了很多的东西。

  OK,闲话不多说,开始任务。

二 任务分解

  Project thread 的主要实验目标是使我们make check的时候,27个test全部pass。在不进行任何修改的情况下,make check 得到的结果是这样的:

  pintos project 1 thread 可具体的划分成三个子任务

  • Mission 1  重新实现 timer_sleep()函数。
  • Mission 2 实现优先级调度
  • Mission 3 实现多级反馈调度

Mission 1:

  重新实现timer_sleep()函数

  pintos系统使用的是busy waiting的方式实现,即: 线程在不停的循环,寻求进入CPU的机会,直至时间片耗尽,进程被饿死。我们需要做的就是更改timer_sleep()的实现。

  timer_sleep()函数在src/devices/timer.c中。

    

   我们可以看到 int64_t start = timer_ticks(); 这一行,调用了timer_ticks()函数,让我们找到这个函数:

    

   在这个函数里面使用了一个枚举类型 intr_lever,让我们找到这个枚举值。在interrupt.h中,我们可以看到: 

    

 

   很明显的我们可以知道,这个枚举值是一个控制中断的值,当INTR_OFF的时候,禁止中断,当INTR_ON的时候,允许中断。

   在timer_ticks()中我们看到了intr_disable()函数,继续找到intr_disable(),在interrupt.c中:

    

   通过注释我们可以得知,这个函数的作用是,在调用的时候,禁止中断并且放回上一个中断状态。

   而在这里我们看到了一个intr_get_level()函数,虽然通过名字我们基本上已经判断出来它的作用,但是我们还是要看一下实现:

    

  intr_get_level()的作用是: 返回当前中断的状态。(具体的实现使用了汇编,然而我并没有学过汇编,因此放在一边,我们只需要明白它的作用就好,具体的汇编实现我们并不需要去了解。如果有兴趣了解的,请寻找相关资料。)

   到这里为止我们分析完了timer_sleep()函数,让我们来整理一下。

  首先,intr_get_level()返回了intr_level的值;

  然后,intr_disable()获取了当前的中断状态给变量old_level,并将当前的中断状态设置为OFF 即不能中断,再返回操作之前的中断状态,即old_level。

  继续分析timer_ticks(), 我们可以看到,用一个变量t获取了全局变量ticks的值,然后返回这个变量t,中间调用了一个intr_set_level()函数,继续分析这个函数

  

  函数的return语句用了一个三元运算符, return level == INTR_ON ? intr_enable () : intr_disable (); 

  作用是:如果level之前允许中断,那么设置成enable, 否则就设置成disable。而这里的intr_enable()函数和intr_disable()函数,则可以继续查看下去:

  

  

  好的,到这里为止我们算是分析完了timer_ticks() 函数,函数的核心作用其实就一个:

      让t获取ticks的当前值。

    而之所以先要用old_level来保存当前中断状态,最后intr_set_level(old_level)返回到之前的状态,

    是为了让 t = ticks这个过程不被打断。

 

  都分析到这里了,我们还没看全局变量ticks到底是什么,搜索一下ticks的声明:

  

/* Number of timer ticks since OS booted. */
static int64_t ticks;

  根据注释我们知道,ticks是一个计数器,记录了从pintos启动开始,一共进行了多少个时间单位。

  到这里我们再来看看timer_sleep()函数,

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

  找到timer_elapsed()函数:

/* Returns the number of timer ticks elapsed since THEN, which
   should be a value once returned by timer_ticks(). */
int64_t
timer_elapsed (int64_t then)
{
  return timer_ticks () - then;
}

  这个函数返回了一个当前时间距离then的时间间隔,现在我们明白那句while是什么意思了:

    在一个时间间隔里,不停的执行thread_yield(),直到当前这个时间片被耗尽。

  那我们现在只需要分析thread_yield()就可以了,继续看代码:

/* Yields the CPU.  The current thread is not put to sleep and
   may be scheduled again immediately at the scheduler's whim. */
void
thread_yield (void)
{
  struct thread *cur = thread_current ();
  enum intr_level old_level;

  ASSERT (!intr_context ());

  old_level = intr_disable ();
  if (cur != idle_thread)
    list_push_back (&ready_list, &cur->elem);
  cur->status = THREAD_READY;
  schedule ();
  intr_set_level (old_level);
}
/* Returns the running thread.
   This is running_thread() plus a couple of sanity checks.
   See the big comment at the top of thread.h for details. */
struct thread *
thread_current (void)
{
  struct thread *t = running_thread ();

  /* Make sure T is really a thread.
     If either of these assertions fire, then your thread may
     have overflowed its stack.  Each thread has less than 4 kB
     of stack, so a few big automatic arrays or moderate
     recursion can cause stack overflow. */
  ASSERT (is_thread (t));
  ASSERT (t->status == THREAD_RUNNING);

  return t;
}
/* Returns true if T appears to point to a valid thread. */
static bool
is_thread (struct thread *t)
{
  return t != NULL && t->magic == THREAD_MAGIC;
}

  从上面几个函数和注释,我们可以得到thread_yield()的作用:让当前的进程放弃CPU,然后进入ready队列继续等着下一次的执行,如果ready队列是空的就让它继续执行。

  

  分析了上面这么多,我们可以得到一个结论:timer_sleep()函数的作用就是,在一个ticks的周期内,如果线程处于running,就把它扔到ready队列去。

这么做的缺点是很明显的,线程在CPU的running队列和ready队列中不断切换,耗费了大量的资源,我们要做的第一件事情,就是要改善这种实现方式。

  

  现在我们看到的pintos的实现方式里,thread的状态一直在running和ready队列中,我们现在来思考一种实现方式:

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

  这样一来,在线程被阻塞并且ticks_blocked的值大于0的时候,线程不再能够切换到running,即进入真正的sleep中,直至被系统时钟唤醒。

 

  具体的实现见下一篇博客。

posted @ 2015-05-26 15:36  crayygy  阅读(1637)  评论(0编辑  收藏  举报