update lab7
This commit is contained in:
parent
0bba4377cf
commit
c9f2d5e9a8
|
@ -4,4 +4,5 @@ assets
|
|||
*.bak
|
||||
*.org
|
||||
*~
|
||||
*.dot
|
||||
autocover
|
||||
|
|
|
@ -158,10 +158,12 @@
|
|||
* [实验内容](lab7/lab7_2_labs.md)
|
||||
* [练习](lab7/lab7_2_1_exercises.md)
|
||||
* [项目组成](lab7/lab7_2_2_files.md)
|
||||
* [同步互斥的设计与实现](lab7/lab7_3_synchronization_implement.md)
|
||||
* [同步互斥机制的设计与实现](lab7/lab7_3_synchronization_implement.md)
|
||||
* [实验执行流程概述](lab7/lab7_3_1_experiment.md)
|
||||
* [计时器的原理和实现](lab7/lab7_3_2_timer_implement.md)
|
||||
* [同步互斥的底层支撑](lab7/lab7_3_2_synchronization_basic_support.md)
|
||||
* [同步互斥机制的底层支撑](lab7/lab7_3_2_synchronization_basic_support.md)
|
||||
* [计时器](lab7/lab7_3_2_1_timer.md)
|
||||
* [屏蔽与使能中断](lab7/lab7_3_2_2_interrupt.md)
|
||||
* [等待队列](lab7/lab7_3_2_3_waitqueue.md)
|
||||
* [信号量](lab7/lab7_3_3_semaphore.md)
|
||||
* [管程和条件变量](lab7/lab7_3_4_monitors.md)
|
||||
* [实验报告要求](lab7/lab7_4_lab_requirement.md)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"plugins": [
|
||||
"autocover"
|
||||
"autocover","grivs"
|
||||
],
|
||||
"pluginsConfig": {
|
||||
"autocover": {
|
||||
|
@ -18,6 +18,8 @@
|
|||
"background": {
|
||||
"color": "#09F"
|
||||
}
|
||||
},
|
||||
"grivs": {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
## 实验目的
|
||||
|
||||
* 熟悉ucore中的进程同步机制,了解操作系统为进程同步提供的底层支持;
|
||||
* 了解定时器(timer)机制的实现;
|
||||
* 在ucore中理解信号量(semaphore)机制的具体实现;
|
||||
* 理解管程机制,在ucore内核中增加基于管程(monitor)的条件变量(condition
|
||||
variable)的支持;
|
||||
|
|
|
@ -13,8 +13,8 @@
|
|||
|
||||
#### 练习1: 理解内核级信号量的实现和基于内核级信号量的哲学家就餐问题(不需要编码)
|
||||
|
||||
完成练习0后,建议大家比较一下(可用kdiff3等文件比较软件)个人完成的lab6和练习0完成后的刚修改的lab7之间的区别,分析了解lab7采用信号量的执行过程。执行make
|
||||
grade,大部分测试用例应该通过。
|
||||
完成练习0后,建议大家比较一下(可用meld等文件diff比较软件)个人完成的lab6和练习0完成后的刚修改的lab7之间的区别,分析了解lab7采用信号量的执行过程。执行`make
|
||||
grade`,大部分测试用例应该通过。
|
||||
|
||||
请在实验报告中给出内核级信号量的设计描述,并说其大致执行流流程。
|
||||
|
||||
|
@ -24,20 +24,24 @@ grade,大部分测试用例应该通过。
|
|||
|
||||
首先掌握管程机制,然后基于信号量实现完成条件变量实现,然后用管程机制实现哲学家就餐问题的解决方案(基于条件变量)。
|
||||
|
||||
执行:make grade
|
||||
执行:`make grade`
|
||||
。如果所显示的应用程序检测都输出ok,则基本正确。如果只是某程序过不去,比如matrix.c,则可执行
|
||||
```
|
||||
make run-matrix
|
||||
命令来单独调试它。大致执行结果可看附录。(使用的是**qemu-1.0.1**)。
|
||||
```
|
||||
命令来单独调试它。大致执行结果可看附录。
|
||||
|
||||
请在实验报告中给出内核级条件变量的设计描述,并说其大致执行流流程。
|
||||
|
||||
请在实验报告中给出给用户态进程/线程提供条件变量机制的设计方案,并比较说明给内核级提供条件变量机制的异同。
|
||||
|
||||
请在实验报告中回答:能否不用基于信号量机制来完成条件变量?如果不能,请给出理由,如果能,请给出设计说明和具体实现。
|
||||
|
||||
#### 扩展练习 Challenge :实现 Linux 的 RCU
|
||||
#### 扩展练习 Challenge : 参考Linux的RCU机制,在ucore中实现简化的RCU机制
|
||||
|
||||
在ucore
|
||||
下实现下Linux的RCU同步互斥机制。可阅读相关Linux内核书籍或查询网上资料,可了解RCU的细节,然后大致实现在ucore中。下面是一些参考资料:
|
||||
下实现下Linux的RCU同步互斥机制。可阅读相关Linux内核书籍或查询网上资料,可了解RCU的设计实现细节,然后简化实现在ucore中。
|
||||
要求有实验报告说明你的设计思路,并提供测试用例。下面是一些参考资料:
|
||||
|
||||
* [http://www.ibm.com/developerworks/cn/linux/l-rcu/](http://www.ibm.com/developerworks/cn/linux/l-rcu/)
|
||||
* [http://www.diybl.com/course/6\_system/linux/Linuxjs/20081117/151814.html](http://www.diybl.com/course/6_system/linux/Linuxjs/20081117/151814.html)
|
||||
|
|
|
@ -51,9 +51,10 @@
|
|||
|
||||
简单说明如下:
|
||||
|
||||
* kern/schedule/{sched.h,sched.c}: 增加了定时器(timer)机制,用于进程/线程的do_sleep功能。
|
||||
* kern/sync/sync.h: 去除了lock实现(这对于不抢占内核没用)。
|
||||
* kern/sync/wait.[ch]:
|
||||
定了为wait结构和waitqueue结构以及在此之上的函数,这是ucore中的信号量semophore机制和条件变量机制的基础,在本次实验中你需要了解其实现。
|
||||
定义了等待队列wait_queue结构和等待entry的wait结构以及在此之上的函数,这是ucore中的信号量semophore机制和条件变量机制的基础,在本次实验中你需要了解其实现。
|
||||
* kern/sync/sem.[ch]:定义并实现了ucore中内核级信号量相关的数据结构和函数,本次试验中你需要了解其中的实现,并基于此完成内核级条件变量的设计与实现。
|
||||
* user/ libs/ {syscall.[ch],ulib.[ch]
|
||||
}与kern/sync/syscall.c:实现了进程sleep相关的系统调用的参数传递和调用关系。
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
互斥是指某一资源同时只允许一个进程对其进行访问,具有唯一性和排它性,但互斥不用限制进程对资源的访问顺序,即访问可以是无序的。同步是指在进程间的执行必须严格按照规定的某种先后次序来运行,即访问是有序的,这种先后次序取决于要系统完成的任务需求。在进程写资源情况下,进程间要求满足互斥条件。在进程读资源情况下,可允许多个进程同时访问资源。
|
||||
|
||||
实验七提供了多种同步互斥手段,包括中断控制、等待队列、信号量、管程机制(包含条件变量设计)等,并基于信号量实现了哲学家问题的执行过程。而练习是要求用管程机制实现哲学家问题的执行过程。在实现信号量机制和管程机制时,需要让无法进入临界区的进程睡眠,为此在ucore中设计了等待队列。当进程无法进入临界区(即无法获得信号量)时,可让进程进入等待队列,这时的进程处于等待状态(也可称为阻塞状态),从而会让实验六中的调度器选择一个处于就绪状态(即RUNNABLE
|
||||
实验七设计实现了多种同步互斥手段,包括时钟中断管理、等待队列、信号量、管程机制(包含条件变量设计)等,并基于信号量实现了哲学家问题的执行过程。而本次实验的练习是要求用管程机制实现哲学家问题的执行过程。在实现信号量机制和管程机制时,需要让无法进入临界区的进程睡眠,为此在ucore中设计了等待队列wait_queue。当进程无法进入临界区(即无法获得信号量)时,可让进程进入等待队列,这时的进程处于等待状态(也可称为阻塞状态),从而会让实验六中的调度器选择一个处于就绪状态(即RUNNABLE
|
||||
STATE)的进程,进行进程切换,让新进程有机会占用CPU执行,从而让整个系统的运行更加高效。
|
||||
|
||||
在实验七中的ucore初始化过程,开始的执行流程都与实验六相同,直到执行到创建第二个内核线程init\_main时,修改了init\_main的具体执行内容,即增加了check\_sync函数的调用,而位于lab7_figs/kern/sync/check\_sync.c中的check\_sync函数可以理解为是实验七的起始执行点,是实验七的总控函数。进一步分析此函数,可以看到这个函数主要分为了两个部分,第一部分是实现基于信号量的哲学家问题,第二部分是实现基于管程的哲学家问题。
|
||||
|
||||
对于check\_sync函数的第一部分,首先实现初始化了一个互斥信号量,然后创建了对应5个哲学家行为的5个信号量,并创建5个内核线程代表5个哲学家,每个内核线程完成了基于信号量的哲学家吃饭睡觉思考行为实现。这部分是给学生作为练习参考用的。学生可以看看信号量是如何实现的,已经如何利用信号量完成哲学家问题。
|
||||
|
||||
对于check\_sync函数的第二部分,首先初始化了管程,然后又创建了5个内核线程代表5个哲学家,每个内核线程要完成基于管程的哲学家吃饭睡觉思考行为实现。这部分需要学生来具体完成。学生需要掌握如何用信号量来实现条件变量,以及包含条件变量的管程如何能够确保哲学家能够正常思考和吃饭。
|
||||
对于check\_sync函数的第二部分,首先初始化了管程,然后又创建了5个内核线程代表5个哲学家,每个内核线程要完成基于管程的哲学家吃饭、睡觉、思考的行为实现。这部分需要学生来具体完成。学生需要掌握如何用信号量来实现条件变量,以及包含条件变量的管程如何能够确保哲学家能够正常思考和吃饭。
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
### 计时器的原理和实现
|
||||
### 定时器
|
||||
|
||||
在传统的操作系统中,计时器是其中一个基础而重要的功能.它提供了基于时间事件的调度机制。在ucore 中,timer 中断(irq0)给操作系统提供了有一定间隔的时间事件,操作系统将其作为基本的调度和计时单位(我们记两次时间中断之间的时间间隔为一个时间片,timer splice)。
|
||||
在传统的操作系统中,定时器是其中一个基础而重要的功能.它提供了基于时间事件的调度机制。在ucore 中,timer 中断(irq0)给操作系统提供了有一定间隔的时间事件,操作系统将其作为基本的调度和计时单位(我们记两次时间中断之间的时间间隔为一个时间片,timer splice)。
|
||||
|
||||
基于此时间单位,操作系统得以向上提供基于时间点的事件,并实现基于时间长度的等待和唤醒机制。在每个时钟中断发生时,操作系统产生对应的时间事件。应用程序或者操作系统的其他组件可以以此来构建更复杂和高级的调度。
|
||||
基于此时间单位,操作系统得以向上提供基于时间点的事件,并实现基于时间长度的睡眠等待和唤醒机制。在每个时钟中断发生时,操作系统产生对应的时间事件。应用程序或者操作系统的其他组件可以以此来构建更复杂和高级的进程管理和调度算法。
|
||||
|
||||
* sched.h, sched.c 定义了有关timer的各种相关接口来使用 timer 服务,其中主要包括:
|
||||
* typedef struct {……} timer\_t: 定义了 timer\_t 的基本结构,其可以用 sched.h 中的timer\_init函数对其进行初始化。
|
||||
* void timer\_init(timer t \*timer, struct proc\_struct \*proc, int expires): 对某计时器 进行初始化,让它在 expires 时间片之后唤醒 proc
|
||||
* void timer\_init(timer t \*timer, struct proc\_struct \*proc, int expires): 对某定时器 进行初始化,让它在 expires 时间片之后唤醒 proc
|
||||
进程。
|
||||
* void add\_timer(timer t \*timer): 向系统添加某个初始化过的timer\_t,该计时器在 指定时间后被激活,并将对应的进程唤醒至runnable(如果当前进程处在等待状态)。
|
||||
* void del\_timer(timer\_t \*time): 向系统删除(或者说取消)某一个计时器。该计时器在取消后不会被系统激活并唤醒进程。
|
||||
* void run\_timer\_list(void): 更新当前系统时间点,遍历当前所有处在系统管理内的计时器,找出所有应该激活的计数器,并激活它们。该过程在且只在每次计时器中断时被调用。在ucore 中,其还会调用调度器事件处理程序。
|
||||
* void add\_timer(timer t \*timer): 向系统添加某个初始化过的timer\_t,该定时器在 指定时间后被激活,并将对应的进程唤醒至runnable(如果当前进程处在等待状态)。
|
||||
* void del\_timer(timer\_t \*time): 向系统删除(或者说取消)某一个定时器。该定时器在取消后不会被系统激活并唤醒进程。
|
||||
* void run\_timer\_list(void): 更新当前系统时间点,遍历当前所有处在系统管理内的定时器,找出所有应该激活的计数器,并激活它们。该过程在且只在每次定时器中断时被调用。在ucore 中,其还会调用调度器事件处理程序。
|
||||
|
||||
一个 timer\_t 在系统中的存活周期可以被描述如下:
|
||||
|
||||
|
@ -19,4 +19,4 @@ add\_timer加入系统管理列表中
|
|||
2. 系统时间被不断累加,直到 run\_timer\_list 发现该 timer\_t到期。
|
||||
3. run\_timer\_list更改对应的进程状态,并从系统管理列表中移除该timer\_t。
|
||||
|
||||
尽管本次实验并不需要填充计时器相关的代码,但是作为系统重要的组件(同时计时器也是调度器的一个部分),你应该了解其相关机制和在ucore中的实现方法。接下来的实验描述将会在一定程度上忽略计时器对调度带来的影响,即不考虑基于固定时间点的调度。
|
||||
尽管本次实验并不需要填充定时器相关的代码,但是作为系统重要的组件(同时定时器也是调度器的一个部分),你应该了解其相关机制和在ucore中的实现方法和使用方法。且在trap_dispatch函数中修改之前对时钟中断的处理,使得ucore能够利用定时器提供的功能完成调度和睡眠唤醒等操作。
|
|
@ -0,0 +1,24 @@
|
|||
### 屏蔽与使能中断
|
||||
|
||||
根据操作系统原理的知识,我们知道如果没有在硬件级保证读内存-修改值-写回内存的原子性,我们只能通过复杂的软件来实现同步互斥操作。但由于有开关中断和test\_and\_set\_bit等原子操作机器指令的存在,使得我们在实现同步互斥原语上可以大大简化。
|
||||
|
||||
在ucore中提供的底层机制包括中断屏蔽/使能控制等。kern/sync.c中实现的开关中断的控制函数local\_intr\_save(x)和local\_intr\_restore(x),它们是基于kern/driver文件下的intr\_enable()、intr\_disable()函数实现的。具体调用关系为:
|
||||
|
||||
```
|
||||
关中断:local_intr_save --> __intr_save --> intr_disable --> cli
|
||||
开中断:local_intr_restore--> __intr_restore --> intr_enable --> sti
|
||||
```
|
||||
|
||||
最终的cli和sti是x86的机器指令,最终实现了关(屏蔽)中断和开(使能)中断,即设置了eflags寄存器中与中断相关的位。通过关闭中断,可以防止对当前执行的控制流被其他中断事件处理所打断。既然不能中断,那也就意味着在内核运行的当前进程无法被打断或被重新调度,即实现了对临界区的互斥操作。所以在单处理器情况下,可以通过开关中断实现对临界区的互斥保护,需要互斥的临界区代码的一般写法为:
|
||||
|
||||
```
|
||||
local_intr_save(intr_flag);
|
||||
{
|
||||
临界区代码
|
||||
}
|
||||
local_intr_restore(intr_flag);
|
||||
……
|
||||
```
|
||||
|
||||
由于目前ucore只实现了对单处理器的支持,所以通过这种方式,就可简单地支撑互斥操作了。在多处理器情况下,这种方法是无法实现互斥的,因为屏蔽了一个CPU的中断,只能阻止本地CPU上的进程不会被中断或调度,并不意味着其他CPU上执行的进程不能执行临界区的代码。所以,开关中断只对单处理器下的互斥操作起作用。在本实验中,开关中断机制是实现信号量等高层同步互斥原语的底层支撑基础之一。
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
## 等待队列
|
||||
|
||||
到目前为止,我们的实验中,用户进程或内核线程还没有睡眠的支持机制。在课程中提到用户进程或内核线程可以转入等待状态以等待某个特定事件(比如睡眠,等待子进程结束,等待信号量等),当该事件发生时这些进程能够被再次唤醒。内核实现这一功能的一个底层支撑机制就是等待队列wait_queue,等待队列和每一个事件(睡眠结束、时钟到达、任务完成、资源可用等)联系起来。需要等待事件的进程在转入休眠状态后插入到等待队列中。当事件发生之后,内核遍历相应等待队列,唤醒休眠的用户进程或内核线程,并设置其状态为就绪状态(PROC_RUNNABLE),并将该进程从等待队列中清除。ucore在kern/sync/{ wait.h, wait.c
|
||||
}中实现了等待项wait结构和等待队列wait
|
||||
queue结构以及相关函数),这是实现ucore中的信号量机制和条件变量机制的基础,进入wait
|
||||
queue的进程会被设为等待状态(PROC_SLEEPING),直到他们被唤醒。
|
||||
|
||||
### 数据结构定义
|
||||
```
|
||||
typedef struct {
|
||||
struct proc_struct *proc; //等待进程的指针
|
||||
uint32_t wakeup_flags; //进程被放入等待队列的原因标记
|
||||
wait_queue_t *wait_queue; //指向此wait结构所属于的wait_queue
|
||||
list_entry_t wait_link; //用来组织wait_queue中wait节点的连接
|
||||
} wait_t;
|
||||
|
||||
typedef struct {
|
||||
list_entry_t wait_head; //wait_queue的队头
|
||||
} wait_queue_t;
|
||||
|
||||
le2wait(le, member) //实现wait_t中成员的指针向wait_t 指针的转化
|
||||
```
|
||||
|
||||
### 相关函数说明
|
||||
与wait和wait queue相关的函数主要分为两层,底层函数是对wait queue的初始化、插入、删除和查找操作,相关函数如下:
|
||||
|
||||
```c
|
||||
void wait_init(wait_t *wait, struct proc_struct *proc); //初始化wait结构
|
||||
bool wait_in_queue(wait_t *wait); //wait是否在wait queue中
|
||||
void wait_queue_init(wait_queue_t *queue); //初始化wait_queue结构
|
||||
void wait_queue_add(wait_queue_t *queue, wait_t *wait); //把wait前插到wait queue中
|
||||
void wait_queue_del(wait_queue_t *queue, wait_t *wait); //从wait queue中删除wait
|
||||
wait_t *wait_queue_next(wait_queue_t *queue, wait_t *wait);//取得wait的后一个链接指针
|
||||
wait_t *wait_queue_prev(wait_queue_t *queue, wait_t *wait);//取得wait的前一个链接指针
|
||||
wait_t *wait_queue_first(wait_queue_t *queue); //取得wait queue的第一个wait
|
||||
wait_t *wait_queue_last(wait_queue_t *queue); //取得wait queue的最后一个wait
|
||||
bool wait_queue_empty(wait_queue_t *queue); //wait queue是否为空
|
||||
```
|
||||
|
||||
高层函数基于底层函数实现了让进程进入等待队列--`wait_current_set`,以及从等待队列中唤醒进程--`wakeup_wait`,相关函数如下:
|
||||
|
||||
```
|
||||
//让wait与进程关联,且让当前进程关联的wait进入等待队列queue,当前进程睡眠
|
||||
void wait_current_set(wait_queue_t *queue, wait_t *wait, uint32_t wait_state);
|
||||
//把与当前进程关联的wait从等待队列queue中删除
|
||||
wait_current_del(queue, wait);
|
||||
//唤醒与wait关联的进程
|
||||
void wakeup_wait(wait_queue_t *queue, wait_t *wait, uint32_t wakeup_flags, bool del);
|
||||
//唤醒等待队列上挂着的第一个wait所关联的进程
|
||||
void wakeup_first(wait_queue_t *queue, uint32_t wakeup_flags, bool del);
|
||||
//唤醒等待队列上所有的等待的进程
|
||||
void wakeup_queue(wait_queue_t *queue, uint32_t wakeup_flags, bool del);
|
||||
```
|
||||
|
||||
### 调用关系举例
|
||||
|
||||
如下图所示,对于唤醒进程的函数`wakeup_wait`,可以看到它会被各种信号量的V操作函数`up`调用,并且它会调用`wait_queue_del`函数和`wakup_proc`函数来完成唤醒进程的操作。
|
||||
```dot
|
||||
digraph "wakeup_wait" {
|
||||
graph [bgcolor="#F7F5F3", fontname="Arial", fontsize="10", label="", rankdir="LR"];
|
||||
node [shape="box", style="filled", color="blue", fontname="Arial", fontsize="10", fillcolor="white", label=""];
|
||||
edge [color="#CC0044", fontname="Arial", fontsize="10", label=""];
|
||||
graph [bgcolor="#F7F5F3"];
|
||||
__N1 [color="red", label="wakeup_wait"];
|
||||
__N2 [label="wait_queue_del"];
|
||||
__N3 [label="wakeup_proc"];
|
||||
__N4 [label="__up"];
|
||||
__N5 [label="up"];
|
||||
__N6 [label="phi_test_sema"];
|
||||
__N7 [label="phi_take_forks_sema"];
|
||||
__N8 [label="cond_signal"];
|
||||
__N9 [label="phi_put_forks_sema"];
|
||||
__N10 [label="cond_wait"];
|
||||
__N11 [label="unlock_mm"];
|
||||
__N12 [label="phi_take_forks_condvar"];
|
||||
__N13 [label="phi_put_forks_condvar"];
|
||||
__N14 [label="wakeup_first"];
|
||||
__N15 [label="wakeup_queue"];
|
||||
__N1 -> __N2;
|
||||
__N1 -> __N3;
|
||||
__N6 -> __N5;
|
||||
__N7 -> __N5;
|
||||
__N8 -> __N5;
|
||||
__N9 -> __N5;
|
||||
__N10 -> __N5;
|
||||
__N11 -> __N5;
|
||||
__N12 -> __N5;
|
||||
__N13 -> __N5;
|
||||
__N5 -> __N4;
|
||||
__N4 -> __N1;
|
||||
__N14 -> __N1;
|
||||
__N15 -> __N1;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
如下图所示,而对于让进程进入等待状态的函数`wait_current_set`,可以看到它会被各种信号量的P操作函数`down`调用,并且它会调用`wait_init`完成对等待项的初始化,并进一步调用`wait_queue_add`来把与要处于等待状态的进程所关联的等待项挂到与信号量绑定的等待队列中。
|
||||
|
||||
```dot
|
||||
digraph "wait_current_set" {
|
||||
graph [bgcolor="#F7F5F3", fontname="Arial", fontsize="10", label="", rankdir="LR"];
|
||||
node [shape="box", style="filled", color="blue", fontname="Arial", fontsize="10", fillcolor="white", label=""];
|
||||
edge [color="#CC0044", fontname="Arial", fontsize="10", label=""];
|
||||
graph [bgcolor="#F7F5F3"];
|
||||
__N1 [color="red", label="wait_current_set"];
|
||||
__N3 [label="wait_init"];
|
||||
__N4 [label="list_init"];
|
||||
__N5 [label="wait_queue_add"];
|
||||
__N6 [label="list_empty"];
|
||||
__N7 [label="list_add_before"];
|
||||
__N8 [label="__down"];
|
||||
__N9 [label="down"];
|
||||
__N10 [label="phi_take_forks_sema"];
|
||||
__N11 [label="cond_signal"];
|
||||
__N12 [label="phi_put_forks_sema"];
|
||||
__N13 [label="cond_wait"];
|
||||
__N14 [label="lock_mm"];
|
||||
__N15 [label="phi_take_forks_condvar"];
|
||||
__N16 [label="phi_put_forks_condvar"];
|
||||
__N3 -> __N4;
|
||||
__N1 -> __N3;
|
||||
__N5 -> __N6;
|
||||
__N5 -> __N7;
|
||||
__N1 -> __N5;
|
||||
__N10 -> __N9;
|
||||
__N11 -> __N9;
|
||||
__N12 -> __N9;
|
||||
__N13 -> __N9;
|
||||
__N14 -> __N9;
|
||||
__N15 -> __N9;
|
||||
__N16 -> __N9;
|
||||
__N9 -> __N8;
|
||||
__N8 -> __N1;
|
||||
}
|
||||
```
|
|
@ -1,77 +1,7 @@
|
|||
### 同步互斥的底层支撑
|
||||
|
||||
**开关中断**
|
||||
由于有处理器调度的存在,且进程在访问某类资源暂时无法满足的情况下,进程会进入等待状态。这导致了多进程执行时序的不确定性和潜在执行结果的不确定性。为了确保执行结果的正确性,本试验需要设计更加完善的进程等待和互斥的底层支撑机制,确保能正确提供基于信号量和条件变量的同步互斥机制。
|
||||
|
||||
根据操作系统原理的知识,我们知道如果没有在硬件级保证读内存-修改值-写回内存的原子性,我们只能通过复杂的软件来实现同步互斥操作。但由于有开关中断和test\_and\_set\_bit等原子操作机器指令的存在,使得我们在实现同步互斥原语上可以大大简化。在atomic.c文件中实现的test\_and\_set\_bit等原子操作。
|
||||
根据操作系统原理的知识,我们知道如果没有在硬件级保证读内存-修改值-写回内存的原子性,我们只能通过复杂的软件来实现同步互斥操作。但由于有定时器、屏蔽/使能中断、等待队列wait_queue支持test\_and\_set\_bit等原子操作机器指令(在本次实验中没有用到)的存在,使得我们在实现进程等待、同步互斥上得到了极大的简化。下面将对定时器、屏蔽/使能中断和等待队列进行进一步讲解。
|
||||
|
||||
在ucore中提供的底层机制包括中断开关控制和test\_and\_set相关原子操作机器指令。kern/sync.c中实现的开关中断的控制函数local\_intr\_save(x)和local\_intr\_restore(x),它们是基于kern/driver文件下的intr\_enable()、intr\_disable()函数实现的。具体调用关系为:
|
||||
|
||||
```
|
||||
关中断:local_intr_save --> __intr_save --> intr_disable --> cli
|
||||
开中断:local_intr_restore--> __intr_restore --> intr_enable --> sti
|
||||
```
|
||||
|
||||
最终的cli和sti是x86的机器指令,最终实现了关中断和开中断,即设置了eflags寄存器中与中断相关的位。通过关闭中断,可以防止对当前执行的控制流被其他中断事件处理所打断。既然不能中断,那也就意味着在内核运行的当前进程无法被打断或被从新调度,即实现了对临界区的互斥操作。所以在单处理器情况下,可以通过开关中断实现对临界区的互斥保护,需要互斥的临界区代码的一般写法为:
|
||||
|
||||
```
|
||||
local_intr_save(intr_flag);
|
||||
{
|
||||
临界区代码
|
||||
}
|
||||
local_intr_restore(intr_flag);
|
||||
……
|
||||
```
|
||||
|
||||
由于目前ucore只实现了对单处理器的支持,所以通过这种方式,就可简单地支撑互斥操作了。在多处理器情况下,这种方法是无法实现互斥的,因为屏蔽了一个CPU的中断,只能阻止本CPU上的进程不会被中断或调度,并不意味着其他CPU上执行的进程不能执行临界区的代码。所以,开关中断只对单处理器下的互斥操作起作用。在本实验中,开关中断机制是实现信号量等高层同步互斥原语的底层支撑基础之一。
|
||||
|
||||
**等待队列**
|
||||
|
||||
到目前为止,我们的实验中,用户进程或内核线程还没有睡眠的支持机制。在课程中提到用户进程或内核线程可以转入休眠状态以等待某个特定事件,当该事件发生时这些进程能够被再次唤醒。内核实现这一功能的一个底层支撑机制就是等待队列(wait
|
||||
queue),等待队列和每一个事件(睡眠结束、时钟到达、任务完成、资源可用等)联系起来。需要等待事件的进程在转入休眠状态后插入到等待队列中。当事件发生之后,内核遍历相应等待队列,唤醒休眠的用户进程或内核线程,并设置其状态为就绪状态(runnable
|
||||
state),并将该进程从等待队列中清除。ucore在kern/sync/{ wait.h, wait.c
|
||||
}中实现了wait结构和wait
|
||||
queue结构以及相关函数),这是实现ucore中的信号量机制和条件变量机制的基础,进入wait
|
||||
queue的进程会被设为睡眠状态,直到他们被唤醒。
|
||||
|
||||
```
|
||||
typedef struct {
|
||||
struct proc_struct *proc; //等待进程的指针
|
||||
uint32_t wakeup_flags; //进程被放入等待队列的原因标记
|
||||
wait_queue_t *wait_queue; //指向此wait结构所属于的wait_queue
|
||||
list_entry_t wait_link; //用来组织wait_queue中wait节点的连接
|
||||
} wait_t;
|
||||
typedef struct {
|
||||
list_entry_t wait_head; //wait_queue的队头
|
||||
} wait_queue_t;
|
||||
le2wait(le, member) //实现wait_t中成员的指针向wait_t 指针的转化
|
||||
```
|
||||
|
||||
与wait和wait queue相关的函数主要分为两层,底层函数是对wait queue的初始化、插入、删除和查找操作,相关函数如下:
|
||||
|
||||
```
|
||||
void wait_init(wait_t *wait, struct proc_struct *proc); //初始化wait结构
|
||||
bool wait_in_queue(wait_t *wait); //wait是否在wait queue中
|
||||
void wait_queue_init(wait_queue_t *queue); //初始化wait_queue结构
|
||||
void wait_queue_add(wait_queue_t *queue, wait_t *wait); //把wait前插到wait queue中
|
||||
void wait_queue_del(wait_queue_t *queue, wait_t *wait); //从wait queue中删除wait
|
||||
wait_t *wait_queue_next(wait_queue_t *queue, wait_t *wait);//取得wait的后一个链接指针
|
||||
wait_t *wait_queue_prev(wait_queue_t *queue, wait_t *wait);//取得wait的前一个链接指针
|
||||
wait_t *wait_queue_first(wait_queue_t *queue); //取得wait queue的第一个wait
|
||||
wait_t *wait_queue_last(wait_queue_t *queue); //取得wait queue的最后一个wait
|
||||
bool wait_queue_empty(wait_queue_t *queue); //wait queue是否为空
|
||||
```
|
||||
|
||||
高层函数基于底层函数实现了让进程进入等待队列,以及从等待队列中唤醒进程,相关函数如下:
|
||||
|
||||
```
|
||||
//让wait与进程关联,且让当前进程关联的wait进入等待队列queue,当前进程睡眠
|
||||
void wait_current_set(wait_queue_t *queue, wait_t *wait, uint32_t wait_state);
|
||||
//把与当前进程关联的wait从等待队列queue中删除
|
||||
wait_current_del(queue, wait);
|
||||
//唤醒与wait关联的进程
|
||||
void wakeup_wait(wait_queue_t *queue, wait_t *wait, uint32_t wakeup_flags, bool del);
|
||||
//唤醒等待队列上挂着的第一个wait所关联的进程
|
||||
void wakeup_first(wait_queue_t *queue, uint32_t wakeup_flags, bool del);
|
||||
//唤醒等待队列上所有的等待的进程
|
||||
void wakeup_queue(wait_queue_t *queue, uint32_t wakeup_flags, bool del);
|
||||
```
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
CPU 的开销。参考教科书“Operating Systems Internals and Design
|
||||
Principles”第五章“同步互斥”中对信号量实现的原理性描述:
|
||||
|
||||
```
|
||||
```c
|
||||
struct semaphore {
|
||||
int count;
|
||||
queueType queue;
|
||||
|
@ -30,12 +30,11 @@ if (s.count<= 0) {
|
|||
|
||||
基于上诉信号量实现可以认为,当多个(\>1)进程可以进行互斥或同步合作时,一个进程会由于无法满足信号量设置的某条件而在某一位置停止,直到它接收到一个特定的信号(表明条件满足了)。为了发信号,需要使用一个称作信号量的特殊变量。为通过信号量s传送信号,信号量的V操作采用进程可执行原语semSignal(s);为通过信号量s接收信号,信号量的P操作采用进程可执行原语semWait(s);如果相应的信号仍然没有发送,则进程被阻塞或睡眠,直到发送完为止。
|
||||
|
||||
ucore中信号量参照上述原理描述,建立在开关中断机制和wait
|
||||
queue的基础上进行了具体实现。信号量的数据结构定义如下:
|
||||
ucore中信号量参照上述原理描述,建立在开关中断机制和wait_queue的基础上进行了具体实现。信号量的数据结构定义如下:
|
||||
|
||||
```
|
||||
```c
|
||||
typedef struct {
|
||||
int value; //信号量的当前值
|
||||
int value; //信号量的当前值
|
||||
wait_queue_t wait_queue; //信号量对应的等待队列
|
||||
} semaphore_t;
|
||||
```
|
||||
|
@ -47,7 +46,7 @@ semaphore)结构,包含了用于计数的整数值value,和一个进程等
|
|||
|
||||
● \_\_down(semaphore\_t \*sem, uint32\_t wait\_state, timer\_t \*timer):具体实现信号量的P操作,首先关掉中断,然后判断当前信号量的value是否大于0。如果是\>0,则表明可以获得信号量,故让value减一,并打开中断返回即可;如果不是\>0,则表明无法获得信号量,故需要将当前的进程加入到等待队列中,并打开中断,然后运行调度器选择另外一个进程执行。如果被V操作唤醒,则把自身关联的wait从等待队列中删除(此过程需要先关中断,完成后开中断)。具体实现如下所示:
|
||||
|
||||
```
|
||||
```c
|
||||
static __noinline uint32_t __down(semaphore_t *sem, uint32_t wait_state) {
|
||||
bool intr_flag;
|
||||
local_intr_save(intr_flag);
|
||||
|
@ -77,7 +76,7 @@ static __noinline uint32_t __down(semaphore_t *sem, uint32_t wait_state) {
|
|||
wait\_state):具体实现信号量的V操作,首先关中断,如果信号量对应的wait
|
||||
queue中没有进程在等待,直接把信号量的value加一,然后开中断返回;如果有进程在等待且进程等待的原因是semophore设置的,则调用wakeup\_wait函数将waitqueue中等待的第一个wait删除,且把此wait关联的进程唤醒,最后开中断返回。具体实现如下所示:
|
||||
|
||||
```
|
||||
```c
|
||||
static __noinline void __up(semaphore_t *sem, uint32_t wait_state) {
|
||||
bool intr_flag;
|
||||
local_intr_save(intr_flag);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
### 管程和条件变量
|
||||
|
||||
#### 原理回顾
|
||||
引入了管程是为了将对共享资源的所有访问及其所需要的同步操作集中并封装起来。Hansan为管程所下的定义:“一个管程定义了一个数据结构和能为并发进程所执行(在该数据结构上)的一组操作,这组操作能同步进程和改变管程中的数据”。有上述定义可知,管程由四部分组成:
|
||||
|
||||
* 管程内部的共享变量;
|
||||
|
@ -9,61 +10,98 @@
|
|||
|
||||
局限在管程中的数据结构,只能被局限在管程的操作过程所访问,任何管程之外的操作过程都不能访问它;另一方面,局限在管程中的操作过程也主要访问管程内的数据结构。由此可见,管程相当于一个隔离区,它把共享变量和对它进行操作的若干个过程围了起来,所有进程要访问临界资源时,都必须经过管程才能进入,而管程每次只允许一个进程进入管程,从而需要确保进程之间互斥。
|
||||
|
||||
但在管程中仅仅有互斥操作是不够用的。进程可能需要等待某个条件C为真才能继续执行。如果采用[忙等](http://zh.wikipedia.org/w/index.php?title=%E5%BF%99%E7%AD%89%E5%BE%85&action=edit&redlink=1 "忙等待(页面不存在)")(busy
|
||||
但在管程中仅仅有互斥操作是不够用的。进程可能需要等待某个条件Cond为真才能继续执行。如果采用[忙等](http://zh.wikipedia.org/w/index.php?title=%E5%BF%99%E7%AD%89%E5%BE%85&action=edit&redlink=1 "忙等待(页面不存在)")(busy
|
||||
waiting)方式:
|
||||
|
||||
```
|
||||
while not( C ) do {}
|
||||
while not( Cond ) do {}
|
||||
```
|
||||
|
||||
在单处理器情况下,将会导致所有其它进程都无法进入[临界区](http://zh.wikipedia.org/wiki/%E4%B8%B4%E7%95%8C%E5%8C%BA "临界区")使得该条件C为真,该管程的执行将会发生[死锁](http://zh.wikipedia.org/wiki/%E6%AD%BB%E9%94%81 "死锁")。为此,可引入条件变量(Condition
|
||||
Variables,简称CV)。一个条件变量CV可理解为一个进程的等待队列,队列中的进程正等待某个条件C变为真。每个条件变量关联着一个[断言](http://zh.wikipedia.org/wiki/%E6%96%B7%E8%A8%80_(%E7%A8%8B%E5%BC%8F) "断言 (程序)")Pc。当一个进程等待一个条件变量,该进程不算作占用了该管程,因而其它进程可以进入该管程执行,改变管程的状态,通知条件变量CV其关联的断言Pc在当前状态下为真。因此对条件变量CV有两种主要操作:
|
||||
在单处理器情况下,将会导致所有其它进程都无法进入[临界区](http://zh.wikipedia.org/wiki/%E4%B8%B4%E7%95%8C%E5%8C%BA "临界区")使得该条件Cond为真,该管程的执行将会发生[死锁](http://zh.wikipedia.org/wiki/%E6%AD%BB%E9%94%81 "死锁")。为此,可引入条件变量(Condition
|
||||
Variables,简称CV)。一个条件变量CV可理解为一个进程的等待队列,队列中的进程正等待某个条件Cond变为真。每个条件变量关联着一个条件,如果条件Cond不为真,则进程需要等待,如果条件Cond为真,则进程可以进一步在管程中执行。需要注意当一个进程等待一个条件变量CV(即等待Cond为真),该进程需要退出管程,这样才能让其它进程可以进入该管程执行,并进行相关操作,比如设置条件Cond为真,改变条件变量的状态,并唤醒等待在此条件变量CV上的进程。因此对条件变量CV有两种主要操作:
|
||||
|
||||
* wait\_cv: 被一个进程调用,以等待断言Pc被满足后该进程可恢复执行.
|
||||
进程挂在该条件变量上等待时,不被认为是占用了管程。
|
||||
* signal\_cv:被一个进程调用,以指出断言Pc现在为真,从而可以唤醒等待断言Pc被满足的进程继续执行。
|
||||
|
||||
#### "哲学家就餐"实例
|
||||
有了互斥和信号量支持的管程就可用用了解决各种同步互斥问题。比如参考《OS
|
||||
Concept》一书中的6.7.2小节“用管程解决哲学家就餐问题”就给出了这样的事例:
|
||||
```c
|
||||
monitor dp
|
||||
{
|
||||
enum {THINKING, HUNGRY, EATING} state[5];
|
||||
condition self[5];
|
||||
|
||||

|
||||
void pickup(int i) {
|
||||
state[i] = HUNGRY;
|
||||
test(i);
|
||||
if (state[i] != EATING)
|
||||
self[i].wait_cv();
|
||||
}
|
||||
|
||||
void putdown(int i) {
|
||||
state[i] = THINKING;
|
||||
test((i + 4) % 5);
|
||||
test((i + 1) % 5);
|
||||
}
|
||||
|
||||
void test(int i) {
|
||||
if ((state[(i + 4) % 5] != EATING) &&
|
||||
(state[i] == HUNGRY) &&
|
||||
(state[(i + 1) % 5] != EATING)) {
|
||||
state[i] = EATING;
|
||||
self[i].signal_cv();
|
||||
}
|
||||
}
|
||||
|
||||
initialization code() {
|
||||
for (int i = 0; i < 5; i++)
|
||||
state[i] = THINKING;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 关键数据结构
|
||||
虽然大部分教科书上说明管程适合在语言级实现比如java等高级语言,没有提及在采用C语言的OS中如何实现。下面我们将要尝试在ucore中用C语言实现采用基于互斥和条件变量机制的管程基本原理。
|
||||
|
||||
ucore中的管程机制是基于信号量和条件变量来实现的。ucore中的管程的数据结构monitor\_t定义如下:
|
||||
|
||||
```
|
||||
```c
|
||||
typedef struct monitor{
|
||||
semaphore_t mutex; // the mutex lock for going into the routines in monitor, should be initialized to 1
|
||||
semaphore_t next; // the next semaphore is used to down the signaling proc itself, and the other OR wakeuped
|
||||
//waiting proc should wake up the sleeped signaling proc.
|
||||
int next_count; // the number of of sleeped signaling proc
|
||||
// the next semaphore is used to
|
||||
// (1) procs which call cond_signal funciton should DOWN next sema after UP cv.sema
|
||||
// OR (2) procs which call cond_wait funciton should UP next sema before DOWN cv.sema
|
||||
semaphore_t next;
|
||||
int next_count; // the number of of sleeped procs which cond_signal funciton
|
||||
condvar_t *cv; // the condvars in monitor
|
||||
} monitor_t;
|
||||
```
|
||||
|
||||
管程中的成员变量mutex是一个二值信号量,是实现每次只允许一个进程进入管程的关键元素,确保了[互斥](http://zh.wikipedia.org/wiki/%E4%BA%92%E6%96%A5 "互斥")访问性质。管程中的条件变量cv通过执行wait\_cv,会使得等待某个条件C为真的进程能够离开管程并睡眠,且让其他进程进入管程继续执行;而进入管程的某进程设置条件C为真并执行signal\_cv时,能够让等待某个条件C为真的睡眠进程被唤醒,从而继续进入管程中执行。管程中的成员变量信号量next和整形变量next\_count是配合进程对条件变量cv的操作而设置的,这是由于发出signal\_cv的进程A会唤醒睡眠进程B,进程B执行会导致进程A睡眠,直到进程B离开管程,进程A才能继续执行,这个同步过程是通过信号量next完成的;而next\_count表示了由于发出singal\_cv而睡眠的进程个数。
|
||||
管程中的成员变量mutex是一个二值信号量,是实现每次只允许一个进程进入管程的关键元素,确保了[互斥](http://zh.wikipedia.org/wiki/%E4%BA%92%E6%96%A5 "互斥")访问性质。管程中的条件变量cv通过执行`wait_cv`,会使得等待某个条件Cond为真的进程能够离开管程并睡眠,且让其他进程进入管程继续执行;而进入管程的某进程设置条件Cond为真并执行`signal_cv`时,能够让等待某个条件Cond为真的睡眠进程被唤醒,从而继续进入管程中执行。
|
||||
|
||||
注意:管程中的成员变量信号量next和整型变量next\_count是配合进程对条件变量cv的操作而设置的,这是由于发出`signal_cv`的进程A会唤醒由于`wait_cv`而睡眠的进程B,由于管程中只允许一个进程运行,所以进程B执行会导致唤醒进程B的进程A睡眠,直到进程B离开管程,进程A才能继续执行,这个同步过程是通过信号量next完成的;而next\_count表示了由于发出`singal_cv`而睡眠的进程个数。
|
||||
|
||||
管程中的条件变量的数据结构condvar\_t定义如下:
|
||||
|
||||
```
|
||||
```c
|
||||
typedef struct condvar{
|
||||
semaphore_t sem; // the sem semaphore is used to down the waiting proc, and the signaling proc should up the waiting proc
|
||||
int count; // the number of waiters on condvar
|
||||
monitor_t * owner; // the owner(monitor) of this condvar
|
||||
semaphore_t sem; // the sem semaphore is used to down the waiting proc, and the signaling proc should up the waiting proc
|
||||
int count; // the number of waiters on condvar
|
||||
monitor_t * owner; // the owner(monitor) of this condvar
|
||||
} condvar_t;
|
||||
```
|
||||
|
||||
条件变量的定义中也包含了一系列的成员变量,信号量sem用于让发出wait\_cv操作的等待某个条件C为真的进程睡眠,而让发出signal\_cv操作的进程通过这个sem来唤醒睡眠的进程。count表示等在这个条件变量上的睡眠进程的个数。owner表示此条件变量的宿主是哪个管程。
|
||||
条件变量的定义中也包含了一系列的成员变量,信号量sem用于让发出`wait_cv`操作的等待某个条件Cond为真的进程睡眠,而让发出`signal_cv`操作的进程通过这个sem来唤醒睡眠的进程。count表示等在这个条件变量上的睡眠进程的个数。owner表示此条件变量的宿主是哪个管程。
|
||||
|
||||
理解了数据结构的含义后,我们就可以开始管程的实现了。ucore设计实现了条件变量wait\_cv操作和signal\_cv操作对应的具体函数,即cond\_wait函数和cond\_signal函数,此外还有cond\_init初始化函数(可直接看源码)。函数cond\_wait(condvar\_t
|
||||
\*cvp, semaphore\_t \*mp)和cond\_signal (condvar\_t
|
||||
\*cvp)的实现原理可参考《OS
|
||||
Concept》一书中的6.7.3小节“用信号量实现管程”的内容:
|
||||
|
||||
** cond_wait的原理描述 **
|
||||
#### 条件变量的signal和wait的设计
|
||||
理解了数据结构的含义后,我们就可以开始管程的设计实现了。ucore设计实现了条件变量`wait_cv`操作和`signal_cv`操作对应的具体函数,即`cond_wait`函数和`cond_signal`函数,此外还有`cond_init`初始化函数(可直接看源码)。函数`cond_wait(condvar_t *cvp, semaphore_t *mp)`和`cond_signal (condvar_t *cvp)`的实现原理参考了《OS Concept》一书中的6.7.3小节“用信号量实现管程”的内容。首先来看`wait_cv`的原理实现:
|
||||
|
||||
```
|
||||
** wait_cv的原理描述 **
|
||||
|
||||
```c
|
||||
cv.count++;
|
||||
if(monitor.next_count > 0)
|
||||
sem_signal(monitor.next);
|
||||
|
@ -74,9 +112,22 @@ cv.count -- ;
|
|||
```
|
||||
|
||||
|
||||
** cond_signal的原理描述 **
|
||||
对照着可分析出`cond_wait`函数的具体执行过程。可以看出如果进程A执行了`cond_wait`函数,表示此进程等待某个条件Cond不为真,需要睡眠。因此表示等待此条件的睡眠进程个数cv.count要加一。接下来会出现两种情况。
|
||||
|
||||
```
|
||||
情况一:如果monitor.next\_count如果大于0,表示有大于等于1个进程执行cond\_signal函数且睡了,就睡在了monitor.next信号量上(假定这些进程挂在monitor.next信号量相关的等待队列S上),因此需要唤醒等待队列S中的一个进程B;然后进程A睡在cv.sem上。如果进程A醒了,则让cv.count减一,表示等待此条件变量的睡眠进程个数少了一个,可继续执行了!
|
||||
|
||||
> 这里隐含这一个现象,即某进程A在时间顺序上先执行了`cond_signal`,而另一个进程B后执行了`cond_wait`,这会导致进程A没有起到唤醒进程B的作用。
|
||||
|
||||
> 问题: 在cond\_wait有sem\_signal(mutex),但没有看到哪里有sem\_wait(mutex),这好像没有成对出现,是否是错误的?
|
||||
> 答案:其实在管程中的每一个函数的入口处会有wait(mutex),这样二者就配好对了。
|
||||
|
||||
情况二:如果monitor.next\_count如果小于等于0,表示目前没有进程执行cond\_signal函数且睡着了,那需要唤醒的是由于互斥条件限制而无法进入管程的进程,所以要唤醒睡在monitor.mutex上的进程。然后进程A睡在cv.sem上,如果睡醒了,则让cv.count减一,表示等待此条件的睡眠进程个数少了一个,可继续执行了!
|
||||
|
||||
然后来看`signal_cv`的原理实现:
|
||||
|
||||
** signal_cv的原理描述 **
|
||||
|
||||
```c
|
||||
if( cv.count > 0) {
|
||||
monitor.next_count ++;
|
||||
sem_signal(cv.sem);
|
||||
|
@ -85,21 +136,19 @@ if( cv.count > 0) {
|
|||
}
|
||||
```
|
||||
|
||||
简单分析一下cond\_wait函数的实现。可以看出如果进程A执行了cond\_wait函数,表示此进程等待某个条件C不为真,需要睡眠。因此表示等待此条件的睡眠进程个数cv.count要加一。接下来会出现两种情况。
|
||||
对照着可分析出`cond_signal`函数的具体执行过程。首先进程B判断cv.count,如果不大于0,则表示当前没有执行cond\_wait而睡眠的进程,因此就没有被唤醒的对象了,直接函数返回即可;如果大于0,这表示当前有执行cond\_wait而睡眠的进程A,因此需要唤醒等待在cv.sem上睡眠的进程A。由于只允许一个进程在管程中执行,所以一旦进程B唤醒了别人(进程A),那么自己就需要睡眠。故让monitor.next\_count加一,且让自己(进程B)睡在信号量monitor.next上。如果睡醒了,这让monitor.next\_count减一。
|
||||
|
||||
情况一:如果monitor.next\_count如果大于0,表示有大于等于1个进程执行cond\_signal函数且睡着了,就睡在了monitor.next信号量上。假定这些进程形成S进程链表。因此需要唤醒S进程链表中的一个进程B。然后进程A睡在cv.sem上,如果睡醒了,则让cv.count减一,表示等待此条件的睡眠进程个数少了一个,可继续执行了!这里隐含这一个现象,即某进程A在时间顺序上先执行了signal\_cv,而另一个进程B后执行了wait\_cv,这会导致进程A没有起到唤醒进程B的作用。这里还隐藏这一个问题,在cond\_wait有sem\_signal(mutex),但没有看到哪里有sem\_wait(mutex),这好像没有成对出现,是否是错误的?其实在管程中的每一个函数的入口处会有wait(mutex),这样二者就配好对了。
|
||||
|
||||
情况二:如果monitor.next\_count如果小于等于0,表示目前没有进程执行cond\_signal函数且睡着了,那需要唤醒的是由于互斥条件限制而无法进入管程的进程,所以要唤醒睡在monitor.mutex上的进程。然后进程A睡在cv.sem上,如果睡醒了,则让cv.count减一,表示等待此条件的睡眠进程个数少了一个,可继续执行了!
|
||||
|
||||
对照着再来看cond\_signal的实现。首先进程B判断cv.count,如果不大于0,则表示当前没有执行cond\_wait而睡眠的进程,因此就没有被唤醒的对象了,直接函数返回即可;如果大于0,这表示当前有执行cond\_wait而睡眠的进程A,因此需要唤醒等待在cv.sem上睡眠的进程A。由于只允许一个进程在管程中执行,所以一旦进程B唤醒了别人(进程A),那么自己就需要睡眠。故让monitor.next\_count加一,且让自己(进程B)睡在信号量monitor.next上。如果睡醒了,这让monitor.next\_count减一。
|
||||
|
||||
#### 管程中函数的入口出口设计
|
||||
为了让整个管程正常运行,还需在管程中的每个函数的入口和出口增加相关操作,即:
|
||||
|
||||
```
|
||||
function (…)
|
||||
function_in_monitor (…)
|
||||
{
|
||||
sem.wait(monitor.mutex);
|
||||
sem.wait(monitor.mutex);
|
||||
//-----------------------------
|
||||
the real body of function;
|
||||
//-----------------------------
|
||||
if(monitor.next_count > 0)
|
||||
sem_signal(monitor.next);
|
||||
else
|
||||
|
|
Loading…
Reference in New Issue