操作系统实验报告 lab7
2017-06-12 09:35
267 查看
练习0填写已有实验
练习1 理解内核级信号量的实现和基于内核级信号量的哲学家就餐问题
0x0 哲学家问题
0x1 信号量介绍
0x2 P操作V操作实现
0x3 代码分析
0x4 信号量性质
练习2 完成内核级条件变量和基于内核级条件变量的哲学家就餐问题
0x1 管程机制
0x2 数据结构
0x3 组成函数
实验截图
实验感悟
这里把需要填充的文件罗列如下:
基于上诉信号量实现可以认为,当多个(>1)进程可以进行互斥或同步合作时,一个进程会由于无法满足信号量设置的某条件而在某一位置停止,直到它接收到一个特定的信号(表明条件满足了)。为了发信号,需要使用一个称作信号量的特殊变量。为通过信号量s传送信号,信号量的V操作采用进程可执行原语semSignal(s);为通过信号量s接收信号,信号量的P操作采用进程可执行原语semWait(s);如果相应的信号仍然没有发送,则进程被阻塞或睡眠,直到发送完为止。
具体实现信号量的P操作,首先关掉中断,然后判断当前信号量的value是否大于0。如果是>0,则表明可以获得信号量,故让value减一,并打开中断返回即可;如果不是>0,则表明无法获得信号量,故需要将当前的进程加入到等待队列中,并打开中断,然后运行调度器选择另外一个进程执行。如果被V操作唤醒,则把自身关联的wait从等待队列中删除(此过程需要先关中断,完成后开中断)。具体实现如下所示:
V操作
具体实现信号量的V操作,首先关中断,如果信号量对应的wait queue中没有进程在等待,直接把信号量的value加一,然后开中断返回;如果有进程在等待且进程等待的原因是semophore设置的,则调用wakeup_wait函数将waitqueue中等待的第一个wait删除,且把此wait关联的进程唤醒,最后开中断返回。具体实现如下所示:
第一部分是实现基于信号量的哲学家问题,第二部分是实现基于管程的哲学家问题
第一部分就是本实验内容
首先实现初始化了一个互斥信号量,然后创建了对应5个哲学家行为的5个信号量,并创建5个内核线程代表5个哲学家,每个内核线程完成了基于信号量的哲学家吃饭睡觉思考行为实现。现在我们继续跟进
value>0,表示共享资源的空闲数
vlaue<0,表示该信号量的等待队列里的进程数
value=0,表示等待队列为空
管程,即定义了一个数据结构和能为并发进程所执行(在该数据结构上)的一组操作,这组操作能同步进程和改变管程中的数据。
管程相当于一个隔离区,它把共享变量和对它进行操作的若干个过程围了起来,所有进程要访问临界资源时,都必须经过管程才能进入,而管程每次只允许一个进程进入管程,从而需要确保进程之间互斥。
管程主要由这四个部分组成
所谓条件变量,即将等待队列和睡眠条件包装在一起,就形成了一种新的同步机制,称为条件变量。个条件变量CV可理解为一个进程的等待队列,队列中的进程正等待某个条件C变为真。
每个条件变量关联着一个断言Pc。当一个进程等待一个条件变量,该进程不算作占用了该管程,因而其它进程可以进入该管程执行,改变管程的状态,通知条件变量CV其关联的断言Pc在当前状态下为真。
因而条件变量两种操作如下:
- wait_cv: 被一个进程调用,以等待断言Pc被满足后该进程可恢复执行. 进程挂在该条件变量上等待时,不被认为是占用了管程。
- 被一个进程调用,以指出断言Pc现在为真,从而可以唤醒等待断言Pc被满足的进程继续执行。
ucore中的管程机制是基于信号量和条件变量来实现的。管程的数据结构monitor_t如下:
管程中的条件变量cv通过执行wait_cv,会使得等待某个条件C为真的进程能够离开管程并睡眠,且让其他进程进入管程继续执行;而进入管程的某进程设置条件C为真并执行signal_cv时,能够让等待某个条件C为真的睡眠进程被唤醒,从而继续进入管程中执行。发出signal_cv的进程A会唤醒睡眠进程B,进程B执行会导致进程A睡眠,直到进程B离开管程,进程A才能继续执行,这个同步过程是通过信号量next完成的;而next_count表示了由于发出singal_cv而睡眠的进程个数。
条件变量condvar_t的数据结构如下:
分析完数据结构之后,我们开始分析管程的实现。
ucore设计实现了条件变量wait_cv操作和signal_cv操作对应的具体函数,即cond_wait函数和cond_signal函数,此外还有cond_init初始化函数。
先看看cond_signal函数,实现如下:
cond_wait函数
首先进程B判断cv.count,如果不大于0,则表示当前没有睡眠的进程,因此就没有被唤醒的对象了,直接函数返回即可;
如果大于0,这表示当前有睡眠的进程A,因此需要唤醒等待在cv.sem上睡眠的进程A。由于只允许一个进程在管程中执行,所以一旦进程B唤醒了别人(进程A),那么自己就需要睡眠。故让monitor.next_count加一,且让自己(进程B)睡在信号量monitor.next上。如果睡醒了,这让monitor.next_count减一。
同样,再来看看cond_wait函数,实现如下:
可以看出如果进程A执行了cond_wait函数,表示此进程等待某个条件C不为真,需要睡眠。因此表示等待此条件的睡眠进程个数cv.count要加一。接下来会出现两种情况。
情况一:如果monitor.next_count如果大于0,表示有大于等于1个进程执行cond_signal函数且睡着了,就睡在了monitor.next信号量上。假定这些进程形成S进程链表。因此需要唤醒S进程链表中的一个进程B。然后进程A睡在cv.sem上,如果睡醒了,则让cv.count减一,表示等待此条件的睡眠进程个数少了一个,可继续执行。
情况二:如果monitor.next_count如果小于等于0,表示目前没有进程执行cond_signal函数且睡着了,那需要唤醒的是由于互斥条件限制而无法进入管程的进程,所以要唤醒睡在monitor.mutex上的进程。然后进程A睡在cv.sem上,如果睡醒了,则让cv.count减一,表示等待此条件的睡眠进程个数少了一个,可继续执行了!
这样我们就可以在此基础上继续完成哲学家就餐问题的解决了,主要是就是如下的两个函数:
实验成功
练习1 理解内核级信号量的实现和基于内核级信号量的哲学家就餐问题
0x0 哲学家问题
0x1 信号量介绍
0x2 P操作V操作实现
0x3 代码分析
0x4 信号量性质
练习2 完成内核级条件变量和基于内核级条件变量的哲学家就餐问题
0x1 管程机制
0x2 数据结构
0x3 组成函数
实验截图
实验感悟
练习0:填写已有实验
使用meld的软件进行对比即可这里把需要填充的文件罗列如下:
proc.c default_pmm.c pmm.c swap_fifo.c vmm.c trap.c sche.c
练习1 理解内核级信号量的实现和基于内核级信号量的哲学家就餐问题
0x0 哲学家问题
哲学家就餐问题,即有五个哲学家,他们的生活方式是交替地进行思考和进餐。哲学家们公用一张圆桌, 周围放有五把椅子,每人坐一把。在圆桌上有五个碗和五根筷子,当一个哲学家思考时,他不与其他人交 谈,饥饿时便试图取用其左、右最靠近他的筷子,但他可能一根都拿不到。只有在他拿到两根筷子时,方 能进餐,进餐完后,放下筷子又继续思考。
0x1 信号量介绍
struct semaphore { int count; queueType queue; }; void P(semaphore S){ S.count--; if (S.count<0) { 把进程置为睡眠态; 将进程的PCB插入到S.queue的队尾; 调度,让出CPU; } } void V(semaphore S){ S.count++; if (S.count≤0) { 唤醒在S.queue上等待的第一个进程; } }
基于上诉信号量实现可以认为,当多个(>1)进程可以进行互斥或同步合作时,一个进程会由于无法满足信号量设置的某条件而在某一位置停止,直到它接收到一个特定的信号(表明条件满足了)。为了发信号,需要使用一个称作信号量的特殊变量。为通过信号量s传送信号,信号量的V操作采用进程可执行原语semSignal(s);为通过信号量s接收信号,信号量的P操作采用进程可执行原语semWait(s);如果相应的信号仍然没有发送,则进程被阻塞或睡眠,直到发送完为止。
0x2 P操作&V操作实现
P操作具体实现信号量的P操作,首先关掉中断,然后判断当前信号量的value是否大于0。如果是>0,则表明可以获得信号量,故让value减一,并打开中断返回即可;如果不是>0,则表明无法获得信号量,故需要将当前的进程加入到等待队列中,并打开中断,然后运行调度器选择另外一个进程执行。如果被V操作唤醒,则把自身关联的wait从等待队列中删除(此过程需要先关中断,完成后开中断)。具体实现如下所示:
static __noinline uint32_t __down(semaphore_t *sem, uint32_t wait_state) { bool intr_flag; local_intr_save(intr_flag); //关掉中断 if (sem->value > 0) {//当前信号量value大于0 sem->value --;//直接让value减一 local_intr_restore(intr_flag);//开中断返回 return 0; } //当前信号量value小于等于0,表明无法获得信号量 wait_t __wait, *wait = &__wait; wait_current_set(&(sem->wait_queue), wait, wait_state);//将当前的进程加入到等待队列中 local_intr_restore(intr_flag);//打开中断 schedule();//运行调度器选择其他进程执行 local_intr_save(intr_flag);//关中断 wait_current_del(&(sem->wait_queue), wait);//被V操作唤醒,从等待队列移除 local_intr_restore(intr_flag);//开中断 if (wait->wakeup_flags != wait_state) { return wait->wakeup_flags; } return 0; }
V操作
具体实现信号量的V操作,首先关中断,如果信号量对应的wait queue中没有进程在等待,直接把信号量的value加一,然后开中断返回;如果有进程在等待且进程等待的原因是semophore设置的,则调用wakeup_wait函数将waitqueue中等待的第一个wait删除,且把此wait关联的进程唤醒,最后开中断返回。具体实现如下所示:
static __noinline void __up(semaphore_t *sem, uint32_t wait_state) { bool intr_flag; local_intr_save(intr_flag);//关闭中断 { wait_t *wait; if ((wait = wait_queue_first(&(sem->wait_queue))) == NULL) {//没有进程等待 sem->value ++;//信号量的value加一 } else {//有进程在等待 assert(wait->proc->wait_state == wait_state); wakeup_wait(&(sem->wait_queue), wait, wait_state, 1);//将`wait_queue`中等待的第一个wait删除,并将该进程唤醒 } } local_intr_restore(intr_flag);//开启中断返回 }
0x3 代码分析
check_sec
第一部分是实现基于信号量的哲学家问题,第二部分是实现基于管程的哲学家问题
void check_sync(void){ int i; //check semaphore sem_init(&mutex, 1); for(i=0;i<N;i++){d sem_init(&s[i], 0); int pid = kernel_thread(philosopher_using_semaphore, (void *)i, 0); if (pid <= 0) { panic("create No.%d philosopher_using_semaphore failed.\n"); } philosopher_proc_sema[i] = find_proc(pid); set_proc_name(philosopher_proc_sema[i], "philosopher_sema_proc"); } //check condition variable monitor_init(&mt, N); for(i=0;i<N;i++){ state_condvar[i]=THINKING; int pid = kernel_thread(philosopher_using_condvar, (void *)i, 0); if (pid <= 0) { panic("create No.%d philosopher_using_condvar failed.\n"); } philosopher_proc_condvar[i] = find_proc(pid); set_proc_name(philosopher_proc_condvar[i], "philosopher_condvar_proc"); } }
第一部分就是本实验内容
首先实现初始化了一个互斥信号量,然后创建了对应5个哲学家行为的5个信号量,并创建5个内核线程代表5个哲学家,每个内核线程完成了基于信号量的哲学家吃饭睡觉思考行为实现。现在我们继续跟进
philosopher_using_semaphore函数观察它的具体实现。
int philosopher_using_semaphore(void * arg) /* i:哲学家号码,从0到N-1 */ { int i, iter=0; i=(int)arg; cprintf("I am No.%d philosopher_sema\n",i); while(iter++<TIMES)/* 无限循环 */ { cprintf("Iter %d, No.%d philosopher_sema is thinking\n",iter,i); // 哲学家正在思考 do_sleep(SLEEP_TIME); phi_take_forks_sema(i); // 需要两只叉子,或者阻塞 cprintf("Iter %d, No.%d philosopher_sema is eating\n",iter,i); // 进餐 do_sleep(SLEEP_TIME); phi_put_forks_sema(i); // 把两把叉子同时放回桌子 } cprintf("No.%d philosopher_sema quit\n",i); return 0; }
phi_take_forks_sema和phi_put_forks_sema
void phi_take_forks_sema(int i) /* i:哲学家号码从0到N-1 */ { down(&mutex); /* 进入临界区 */ state_sema[i]=HUNGRY; /* 记录下哲学家i饥饿的事实 */ phi_test_sema(i); /* 试图得到两只叉子 */ up(&mutex); /* 离开临界区 */ down(&s[i]); /* 如果得不到叉子就阻塞 */ } void phi_put_forks_sema(int i) /* i:哲学家号码从0到N-1 */ { down(&mutex); /* 进入临界区 */ state_sema[i]=THINKING; /* 哲学家进餐结束 */ phi_test_sema(LEFT); /* 看一下左邻居现在是否能进餐 */ phi_test_sema(RIGHT); /* 看一下右邻居现在是否能进餐 */ up(&mutex); /* 离开临界区 */ }
0x4 信号量性质
我们可以看出信号量的计数器value具有有如下性质:value>0,表示共享资源的空闲数
vlaue<0,表示该信号量的等待队列里的进程数
value=0,表示等待队列为空
练习2 完成内核级条件变量和基于内核级条件变量的哲学家就餐问题
0x1 管程机制
即要求首先掌握管程机制,然后基于信号量实现完成条件变量实现,然后用管程机制实现哲学家就餐问题的解决方案。管程,即定义了一个数据结构和能为并发进程所执行(在该数据结构上)的一组操作,这组操作能同步进程和改变管程中的数据。
管程相当于一个隔离区,它把共享变量和对它进行操作的若干个过程围了起来,所有进程要访问临界资源时,都必须经过管程才能进入,而管程每次只允许一个进程进入管程,从而需要确保进程之间互斥。
管程主要由这四个部分组成
1、管程内部的共享变量; 2、管程内部的条件变量; 3、管程内部并发执行的进程; 4、对局部于管程内部的共享数据设置初始值的语句。
所谓条件变量,即将等待队列和睡眠条件包装在一起,就形成了一种新的同步机制,称为条件变量。个条件变量CV可理解为一个进程的等待队列,队列中的进程正等待某个条件C变为真。
每个条件变量关联着一个断言Pc。当一个进程等待一个条件变量,该进程不算作占用了该管程,因而其它进程可以进入该管程执行,改变管程的状态,通知条件变量CV其关联的断言Pc在当前状态下为真。
因而条件变量两种操作如下:
- wait_cv: 被一个进程调用,以等待断言Pc被满足后该进程可恢复执行. 进程挂在该条件变量上等待时,不被认为是占用了管程。
- 被一个进程调用,以指出断言Pc现在为真,从而可以唤醒等待断言Pc被满足的进程继续执行。
0x2 数据结构
大概了解了原理之后,接下来我们开始分析具体的代码。ucore中的管程机制是基于信号量和条件变量来实现的。管程的数据结构monitor_t如下:
typedef struct monitor{ semaphore_t mutex; // 二值信号量,只允许一个进程进入管程,初始化为1 semaphore_t next; //配合cv,用于进程同步操作的信号量 int next_count; // 睡眠的进程数量 condvar_t *cv; // 条件变量cv } monitor_t;
管程中的条件变量cv通过执行wait_cv,会使得等待某个条件C为真的进程能够离开管程并睡眠,且让其他进程进入管程继续执行;而进入管程的某进程设置条件C为真并执行signal_cv时,能够让等待某个条件C为真的睡眠进程被唤醒,从而继续进入管程中执行。发出signal_cv的进程A会唤醒睡眠进程B,进程B执行会导致进程A睡眠,直到进程B离开管程,进程A才能继续执行,这个同步过程是通过信号量next完成的;而next_count表示了由于发出singal_cv而睡眠的进程个数。
条件变量condvar_t的数据结构如下:
typedef struct condvar{ semaphore_t sem; //用于发出wait_cv操作的等待某个条件C为真的进程睡眠 int count; // 在这个条件变量上的睡眠进程的个数 monitor_t * owner; // 此条件变量的宿主管程 } condvar_t;
0x3 组成函数
cond_signa函数分析完数据结构之后,我们开始分析管程的实现。
ucore设计实现了条件变量wait_cv操作和signal_cv操作对应的具体函数,即cond_wait函数和cond_signal函数,此外还有cond_init初始化函数。
先看看cond_signal函数,实现如下:
void cond_signal (condvar_t *cvp) { cprintf("cond_signal begin: cvp %x, cvp->count %d, cvp->owner->next_count %d\n", cvp, cvp->count, cvp->owner->next_count); if(cvp->count>0) { //当前存在睡眠的进程 cvp->owner->next_count ++;//睡眠的进程总数加一 up(&(cvp->sem));//唤醒等待在cv.sem上睡眠的进程 down(&(cvp->owner->next));//把自己睡眠 cvp->owner->next_count --;//睡醒后等待此条件的睡眠进程个数减一 } cprintf("cond_signal end: cvp %x, cvp->count %d, cvp->owner->next_count %d\n", cvp, cvp->count, cvp->owner->next_count); }
cond_wait函数
首先进程B判断cv.count,如果不大于0,则表示当前没有睡眠的进程,因此就没有被唤醒的对象了,直接函数返回即可;
如果大于0,这表示当前有睡眠的进程A,因此需要唤醒等待在cv.sem上睡眠的进程A。由于只允许一个进程在管程中执行,所以一旦进程B唤醒了别人(进程A),那么自己就需要睡眠。故让monitor.next_count加一,且让自己(进程B)睡在信号量monitor.next上。如果睡醒了,这让monitor.next_count减一。
同样,再来看看cond_wait函数,实现如下:
void cond_wait (condvar_t *cvp) { //LAB7 EXERCISE1: YOUR CODE cprintf("cond_wait begin: cvp %x, cvp->count %d, cvp->owner->next_count %d\n", cvp, cvp->count, cvp->owner->next_count); cvp->count++;//需要睡眠的进程个数加一 if(cvp->owner->next_count > 0) up(&(cvp->owner->next));//唤醒进程链表中的下一个进程 else up(&(cvp->owner->mutex));//否则唤醒睡在monitor.mutex上的进程 down(&(cvp->sem));//将自己睡眠 cvp->count --;//睡醒后等待此条件的睡眠进程个数减一 cprintf("cond_wait end: cvp %x, cvp->count %d, cvp->owner->next_count %d\n", cvp, cvp->count, cvp->owner->next_count); }
可以看出如果进程A执行了cond_wait函数,表示此进程等待某个条件C不为真,需要睡眠。因此表示等待此条件的睡眠进程个数cv.count要加一。接下来会出现两种情况。
情况一:如果monitor.next_count如果大于0,表示有大于等于1个进程执行cond_signal函数且睡着了,就睡在了monitor.next信号量上。假定这些进程形成S进程链表。因此需要唤醒S进程链表中的一个进程B。然后进程A睡在cv.sem上,如果睡醒了,则让cv.count减一,表示等待此条件的睡眠进程个数少了一个,可继续执行。
情况二:如果monitor.next_count如果小于等于0,表示目前没有进程执行cond_signal函数且睡着了,那需要唤醒的是由于互斥条件限制而无法进入管程的进程,所以要唤醒睡在monitor.mutex上的进程。然后进程A睡在cv.sem上,如果睡醒了,则让cv.count减一,表示等待此条件的睡眠进程个数少了一个,可继续执行了!
这样我们就可以在此基础上继续完成哲学家就餐问题的解决了,主要是就是如下的两个函数:
void phi_take_forks_condvar(int i) { down(&(mtp->mutex)); //通过P操作进入临界区 state_condvar[i]=HUNGRY; //记录下哲学家i是否饥饿,即处于等待状态拿叉子 phi_test_condvar(i); while (state_condvar[i] != EATING) { cprintf("phi_take_forks_condvar: %d didn't get fork and will wait\n",i); cond_wait(&mtp->cv[i]);//如果得不到叉子就睡眠 } //如果存在睡眠的进程则那么将之唤醒 if(mtp->next_count>0) up(&(mtp->next)); else up(&(mtp->mutex)); } void phi_put_forks_condvar(int i) { down(&(mtp->mutex));//通过P操作进入临界区 state_condvar[i]=THINKING;//记录进餐结束的状态 phi_test_condvar(LEFT);//看一下左边哲学家现在是否能进餐 phi_test_condvar(RIGHT);//看一下右边哲学家现在是否能进餐 //如果有哲学家睡眠就予以唤醒 if(mtp->next_count>0) up(&(mtp->next)); else up(&(mtp->mutex)); }
实验截图
实验成功
实验感悟
本次实验使我对互斥以及同步有了更深的认识, 实验七提供了多种同步互斥手段,包括中断控制、等待队列、信号量、管程机制(包含条件变量设计)等,并基于信号量实现了哲学家问题的执行过程。而练习是要求用管程机制实现哲学家问题的执行过程。使得学到的知识得到了实践。下一步还是要提高自己的动手能力。相关文章推荐
- 中科大信息安全操作系统课程lab7实验报告
- ucore操作系统lab7——实验报告
- 操作系统ucore lab6实验报告
- 操作系统实验报告 lab5
- ucore操作系统lab2实验报告
- 计算机操作系统实验之_进程观测_实验报告
- 操作系统ucore lab7实验报告
- 操作系统ucore lab7实验报告
- 计算机操作系统实验之_进程观测_实验报告
- 操作系统实验报告
- 操作系统ucore lab8实验报告
- 操作系统实验报告:ucore_lab2~5
- ucore操作系统lab4实验报告(理论部分)
- 操作系统课程实验报告(四)
- 操作系统实验报告 lab3
- 操作系统实验报告:ucore-lab1
- “Linux内核分析”实验报告(七)Linux 操作系统如何装载链接并执行程序
- 操作系统实验报告 lab8
- 操作系统实验报告 lab1
- 操作系统ucore lab2实验报告