深入浅出PINTOS
又名
零基础学PINTOS
——哈哈重在恶搞。温帅哥出品。
忙活了一个周末,废寝忘食地啃特那保姆写的《现代操作作系统》,pintos的第一个实验才仅仅算是初见端倪。。。斯坦福阿斯坦福,你把这个pintos搞的这么难,坑爹阿!能独立完成这个实验的孩子你们伤不起阿!
言归正传,把我知道的一点点pintos实验project1的做法全都抖落出来,分享一下。已经会的大牛不要嫌我嘚瑟阿。。。
一、在pintos源码中找到最基本的概念:
本次实验至少需要阅读thread.h,tread.c,interrup.h,time.c这四个文件。
pintos在thread.h中定义了一个结构体struct thread,这个结构体就存方了有关进程的基本信息。
struct thread
{
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. */
};
大家不要被这么庞大的结构体所吓倒,其实他说的事情很简单,无非是这个线程的几个基本信息。值得注意的是enum thread_status这个枚举类型的变量,他的意思就是这个线程现在所处的状态。
enum thread_status
{
THREAD_RUNNING, /* Running thread. */
THREAD_READY, /* Not running but ready to run. */
THREAD_BLOCKED, /* Waiting for an event to trigger. */
THREAD_DYING /* About to be destroyed. */
};
还有一个最最重要的概念是中断。所谓中断其实分两种,一种是IO设备向CPU发出的中断的信息,另一种是CPU决定切换到另一个进程时(轮换时间片)发出的指令。我们现在处理第二种。pintos的中断在interrupt.h和interrupt.c之中。其中这个枚举类型intr_lverl会在后面被反复提到:
enum intr_level
{
INTR_OFF, /* Interrupts disabled. */
INTR_ON /* Interrupts enabled. */
};
其实这个intr_level表达的意思更简单,就是有两个单词,intr_off表示关中断,on表示开中断。大家都知道,执行原子级别操作的时候,中断必须是关着的。
最后还要说以下,pintos是以ticks作为基本时间单位的,每秒有TIMER_FREQ个ticks:
/* Number of timer interrupts per second. */
#define TIMER_FREQ 100 //系统默认这个宏为100
还有一点,pintos默认每一个ticks调用一次时间中断。换句话说,每一个线程最多可以占据CPU一个ticks的时长,之后就必须放手。
二、掌握thread的基本操作(函数):
以下函数在thread.c中都可以找到。
1.thread_current()获取当前当前的线程的指针。
2.thread_foreach(thread_action_func *func, void *aux) 遍历当前ready queue中的所有线程,并且对于每一个线程执行一次func操作。注意到这里的func是一个任意给定函数的指针,参数aux则是你想要传给这个函数的参数。实际上pintos没有多么高深,所有ready的线程被保存在一个链表中。这个函数做得不过是遍历了一遍链表而已。注意这个函数只能在中断关闭的时候调用。
3.thread_block()和thread_unblock(thread *t)。 这是一对儿函数,区别在于第一个函数的作用是把当前占用cpu的线程阻塞掉(就是放到waiting里面),而第二个函数作用是将已经被阻塞掉的进程t唤醒到ready队列中。
4.timer_interrupt (struct intr_frame *args UNUSED)这个函数在timer.c中,pintos在每次时间中断时(即每一个时间单位(ticks))调用一次这个函数。
5. intr_disable () 这个函数在interrupt.c中,作用是返回关中断,然后返回中断关闭前的状态。(其实说白了状态不就是INTR_OFF,INTR_ON 这两种么。 )
三、代码分析与timer_sleep()函数的重新设计:
timer_sleep的作用是让此线程等待ticks单位时长,然后再执行。函数原型:
void
timer_sleep (int64_t ticks) //参数的意思是你想要等待的时间长度
{
int64_t start = timer_ticks (); //记录开始时的系统时间
ASSERT (intr_get_level () == INTR_ON);
while (timer_elapsed (start) < ticks) //如果elapse(流逝)的时间>=ticks时就返回。否则将持续占用cpu。
thread_yield ();
}
原本的timer_sleep函数是对的,只不过它不好。它使用的方法(忙等待)是利用一个while循环不断地请求CPU来判断是否经过了足够的时间长度。这样做得坏处是很显然的,通常cpu在一个ticks时间内可以处理10000次这样的循环,而timer_elapsed()函数只会在ticks+1时更新一次。所以我们可以做得就是改进这个函数,使得它每一个ticks才检查一次时间,而不是每一个ticks检查一万次。
我的设计思路是:在timer_sleep()函数中让该进程暂时阻塞(调用thread_block()),然后过了ticks个时间段后再把它加回到ready queue中。
至于因为每一次时间中断的时候恰好是ticks加一的时候,因此我们可以改进timer_interrup()函数,使得系统每次调用他的时候都检查一下我的这个进程是否已经等待了足够长得时间了。如果还没有够,则不管它,如果已经足够长了,则调用thread_unblock()函数将它召唤回ready_queue中。
这个时候又有一个问题出现了:我怎么样才能得到一个线程被阻塞了多长时间呢?事实上pintos本身没有这样一个功能,需要我们对thread这个结构题搞糟,加入一个整形变量int block_ticks就可以了。当这个线程被block的时候,将block_ticks记录为需要等待的时间长度。之后每次中断的时候检查它一次,并且顺便使其自减。当它小到等于0的时候,把线程调到ready queue中。
四、源代码搞起!
1.将thread改为:
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. */
int block_ticks; /* 存储该进程已经被block多久了*/
/* 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. */
};
2.改动timer.c中的timer_sleep函数
void
timer_sleep (int64_t ticks)
{
/*
int64_t start = timer_ticks ();*/
ASSERT (intr_get_level () == INTR_ON);
/*while (timer_elapsed (start) < ticks)
thread_yield ();*/
enum intr_level old_level; //定义变量保存原先的中断状态
struct thread *t;
t=thread_current ();
t->block_ticks = ticks; //将等待时长设置为需要等待的时间
old_level = intr_disable (); // thread_block()方法需要关闭中断
thread_block(); //阻塞进程
intr_set_level (old_level); //恢复中断
}
3.为timer.c增加函数用来检查每个进程中的block_ticks是否为零
void
block_check(struct thread *t, void *aux UNUSED)
{
if (t->status == THREAD_BLOCKED&&t->block_ticks>0)//看这个线程是不是阻塞态
{
t->block_ticks--; //将等待的时间自减
if (t->block_ticks == 0)
{
thread_unblock (t); //把t线程重新”解锁“
}
}
}
4.修改timer_interrupt函数,使其每次中断都对所有线程执行block_check函数
static void
timer_interrupt (struct intr_frame *args UNUSED)//UNUSED是一个宏,表示这个参数没用
{
enum intr_level old_level; //和先前一样,记录原来的中断状态
old_level=intr_disable(); //forreach函数要求关中断。
ticks++;
thread_foreach (block_check, 0);
intr_set_level (old_level); //恢复中断
thread_tick (); //交给操作系统出发中断并且调度新的线程进驻cpu。
}
本文来源:https://www.2haoxitong.net/k/doc/3ecadb71f242336c1eb95ee1.html
文档为doc格式