OS学习之Day02
整理自bilibill王道考研视频
进程的同步与互斥
问题引入:
- 老渣约会的故事,女一和女二
略
- 管道通信:
管道通信是一种半双工通信,读进程和写进程并法执行。由于并发,必然导致异步性。因此,“写数据”和“读数据”的先后顺序是不确定的。而在实际应用中,必须按照“读数据->写数据”的顺序执行。
信号量机制
- Dijkstra提出的
- 信号量可以分为:整型信号量 和 记录型信号量
- 用户进程可以通过使用操作系统提供的一对原语来对信号量进行操作。从而很方便地实现进程同步、进程互斥。
- 信号量就是一个变量(可以是一个整数,也可以是更负责的记录型变量)。可以用一个信号量来表示 系统中某种资源的数量。比如,系统中只有一台打印机,就可以设置一个初值为1的信号量。
在具有n个进程的系统中,若允许m个进程同时进入某临界区(n>m),其信号量S的取
值上限为___n _______,下限为 ______n-m _________。
- 原语是一种特殊的程序段,由操作系统提供的,执行只能一气呵成,不可被中断。原语是由“开中断,关中断”指令实现的。
- wait(S) 原语 和 signal(S) 原语。 简称为P,V操作(来自荷兰语)。
- wait(S) 等价于 P(S), signal(S) , S代表信号量
整型信号量
用一个整数型的变量 作为信号量,用来表示系统中某种资源的数量。
- 与整型变量的区别在于,对信号量的操作只有三种,即:初始化、P操作、V操作。
/*
* Eg:某计算机中有一台打印机...
*/
int S = 1; //初始化整型信号量,表示当前系统中可用的打印机资源数
void wait(int& S) { //wait原语,相当于"进入区"
while(S<=0); //如果资源数不够,就一直循环等待
S = S - 1; //如果资源数够,则占用一个资源
}
void signal(int& S) { //signal原语,相当于"退出区"
S = S + 1; //使用完资源后,在退出区释放资源
}
void Process0() {
...
wait(S); //进入区,申请资源
使用打印机资源... //临界区,访问资源
signal(S); //退出区,释放资源
...
}
整型信号量存在的问题:不满足”让权等待“原则,存在忙等现象。
记录型信号量
/*记录型信号量的定义*/
typedef struct{
int value; //剩余资源数量
struct process * L; //等待队列
} semaphore;
某进程需要资源时,用wait 原语 申请
void wait(semaphore& S) {
S.value--;
if(S.value < 0) {
bolck(S.L); //如果剩余资源数不够,使用block原语使进程从运行态进入
//阻塞态,并将其挂到信号量S的等待队列(即阻塞队列)中
}
}
进程使用完资源后,通过signal原语 释放
void signal(semphore& S) {
S.value++;
if(S.value <= 0) {
wakeup(S.L); //释放资源后,若还有别的进程在等待这种资源,则使用wakeup原语唤醒
//等待队列中的一个进程,该进程从阻塞态变为就绪态。
}
}
记录型信号量遵循了 “让权等待” 原则,不会出现”忙等“现象。
- 一般说P(S), V(S), 除非特别说明,用的都是记录型信号量,不会出现 “忙等” 的状态。如果临界资源数量不足,进程会被阻塞。
信号量机制实现进程互斥
- 分析并发进程的关键活动,划定临界区(如对临界资源打印机的访问就因该放在临界区)
- 设置互斥信号量 mutex, 初值为1 (通常命名为mutex)
/*
#define P wait
#define V signal
typdef struct {
int value;
process * L;
semaphore(int _value) {
value = _value;
L = NULL;
}
} semaphore;
void wait(semaphore& S) {
S.value--;
if(S.value < 0) {
bolck(S.L);
}
}
void signal(semphore& S) {
S.value++;
if(S.value <= 0) {
wakeup(S.L); //释放资源后,若还有别的进程在等待这种资源,则使用wakeup原语唤醒
//等待队列中的一个进程,该进程从阻塞态变为就绪态。
}
}
*/
semaphore mutex = 1;
P1() {
...
P(mutex); //使用临界资源前需要加锁
临界区代码段...
V(mutex); //使用临界资源后需要解锁
}
P2() {
...
P(mutex);
临界区代码段...
V(mutex);
}
- 对不同的临界资源需要设置不同的互斥信号量。
- P, V 操作必须成对出现。 缺少P(mutex)无法保证临界资源的互斥访问。缺少V(metex)会导致资源永远不被释放,等待进程永远不给唤醒。
信号量机制实现进程同步:
进程同步:要让各个并发的程序按照要求有序地推进。
P1() {
代码1;
代码2;
代码3;
}
P2() {
代码4;
代码5;
代码6;
}
比如P1,P2 并发执行,由于存在异步性,因此,二者交替推进的次序是不确定的。
可能的执行顺序有:(1,2,3,4,5,6), (1,4,2,3,5,6),(1,2,4,5,3,6)…
若P2的 “代码4” 要基于P1的 ”代码1“ 和 “代码2” 的运行结果才能执行,那么我们就必须保证 “代码4” 一定在 “代码2” 之后才执行。 这就是进程同步问题,让本来异步并发的进程 互相配合,有序推进。
用信号量实现进程同步:
- 分析什么地方需要实现”同步关系“,即必须保证"一前一后"执行的两个操作(或者两句代码)
- 设置同步信号量S, 设置初值为0
semphore s = 0;
- 在“前操作”之后执行 V(S)
- 在”后操作“之前执行 P(S)
semphore S = 0;
P1() {
代码1;
代码2;
V(S);
代码3;
}
P2() {
P(S);
代码4;
代码5;
代码6;
}
信号量进程实现前驱关系:
经典的同步互斥问题
1. 生产者消费者问题:
系统中有一组生产者进程 和 一组 消费者进程。
生产者进程每次生产一个产品放入缓冲区,消费者进程每次从缓冲区中取走一个产品并使用。
生产者、消费者进程共享一个初始为空,大小为n的缓冲区。
只有缓冲区没有满时,生产者才能把产品放入缓冲区,否则必须等待。
只有缓冲区不空时,消费者才能从中取出产品,否则必须等待。
缓冲区是临界资源,各个进程必须互斥地访问。
semaphore mutex = 1; //互斥信号量
semaphore full = 0; //同步信号量,表示产品的数量
semaphore empty = n; //同步信号量,表示空闲缓冲区的数量
void producer() {
while(1) {
生产一个产品
P(empty);
P(mutex);
产品放入临界区
V(mutex);
V(full);
}
}
void Consumer() {
while(1) {
P(full);
P(mutex);
从临界区中取走一个产品
V(mutex);
V(empty);
使用产品
}
}
- 实现互斥的P操作一定要在实现同步的P操作之后。
- 不然,当缓冲区已经放慢产品,执行producer()的时候,mutex=0, 循环等待,出现死锁。
多生产者-多消费者问题
桌子上有一个盘子,每次只能往盘子里放一个水果。爸爸专门向盘子放苹果,妈妈专门往盘子里放橘子。儿子专门等着吃盘子中的橘子,女儿专门等着吃盘子中的苹果。仅当盘子里有自己需要的水果时,儿子和女儿可以从盘子里取出水果。请用PV操作实现这个问题。
semaphore mutex = 1;
semaphore empty = 1;
semaphore apple = 0;
semaphore orange = 0;
void father() {
while(1) {
准备一个苹果;
P(empty);
P(mutex);
往盘中放一个苹果;
V(mutex);
V(apple);
}
}
void mother() {
while(1) {
准备一个橘子;
P(empty);
P(mutex);
往盘中放一个桔子;
V(mutex);
V(orange);
}
}
void son() {
while(1) {
P(orange);
P(mutex);
从盘子里拿走橘子;
V(mutex);
V(empty);
吃橘子...
}
}
void daughter() {
while(1) {
P(apple);
P(mutex);
从盘子里拿走苹果;
V(mutex);
V(empty);
吃苹果...
}
}
- 若缓冲区的大小为1, 则可以不设置互斥信号量mutex
吸烟者问题
假设系统中有一个供应者进程和三个吸烟者进程。每个吸烟者不停地卷烟并且抽调它。吸烟者需要有三种材料:烟草,纸,胶水。三个抽烟者中,第一个拥有烟草,第二个拥有纸,第三个拥有胶水。供应者进程无限地提供三种材料。供应者每次将两种材料放桌子上,拥有剩下那种材料的抽烟者卷一根烟,并抽掉它,并给供应者一个信号告诉完成了,供应者就会放另外两种材料在桌上。这个过程一直重复(让三个吸烟者轮流吸烟)。
//我自己写的
semaphore mutex = 1;
semaphore tobacco = 0;
semaphore glue = 0;
semaphore paper = 0;
int cnt = 0;
void provider() {
while(1) {
cnt = (cnt+1)%3;
switch(cnt) {
case 0:
P(empty);
P(mutex);
往桌上放烟草,胶水;
V(mutex);
V(tobacco);
V(glue);
break;
case 1:
P(empty);
P(mutex);
往桌子上放烟草,纸;
V(mutex);
V(tobacco);
V(paper);
break;
case 2:
P(empty);
P(mutex);
往桌子上放胶水,纸;
V(mutex);
V(glue);
V(paper);
break;
}
}
}
读者-写者问题:
有三个进程PA、PB和PC合作解决文件打印问题:PA将文件记录从磁盘读入主存的缓冲区1,每执行一次读一个记录;PB将缓冲区1的内容复制到缓冲区2,每执行一次复制一个记录;PC将缓冲区2的内容打印出来,每执行一次打印一个记录。缓冲区的大小等于一个记录的大小。请用P、V操作来保证文件的正确打印。
semaphore emptyA, emptyB, fullA, fullB;
emptyA = emptyB = 1;
fullA = fullB = 0;
main()
{
Cobegin
PA()
PB()
PC()
Coend;
}
PA()
{
while(1) {
从磁盘读一个记录;
P(emptyA);
将记录存入缓冲区A;
V(fullA);
}
}
PB()
{
While(1)
{
p(fullA);
从缓冲区A中取出记录;
V(emptyA);
p(emptyB);
将记录存入缓冲区B;
V(fullB);
}
}
PC()
{
While(1)
{
P(fullB);
从缓冲区B中取出记录;
V(emptyB);
打印记录;
}
}
1、某银行有人民币储蓄业务,由n个柜员负责。每个顾客进入银行后先取一个号,并等着叫号。当一个柜台人员空闲下来,就叫下一个号。请用PV操作实现柜员进程与顾客进程之间的同步。
semaphore mutex = 1;
semaphore S1 = n; //空闲柜员的个数
semaphore S2 = 0; //未服务顾客的个数
teller() {
while(1) {
P(S2); //找一个未服务的顾客
P(mutex);
叫号;
V(mutex);
服务;
V(S1); //服务完又空闲了
}
}
customer() {
while(1) {
P(mutex);
取号;
V(mutex);
V(S2);
P(S1);
办理业务;
}
}
main() {
cobegin:
teller();
customer();
coend;
}
1、顺序执行的程序具有 顺序性__、封闭性____、可再现性和执行结果确定性。
2、在具有n个进程的系统中,若允许m个进程同时进入某临界区(n>m),其信号量S的取
值上限为___ m_______,下限为__m-n___________。
3、文件的逻辑结构分为:无结构的流式文件、 有结构的记录文件。
4、按设备的使用特性,可将设备分为__输入__设备和_输出__设备两种类型。
5、用户与操作系统之间的接口通常分为___用户__级接口和_程序__级接口。
6、死锁产生的四个必要条件包括:不可剥夺___、环路_____________、互斥____、部分分配____________。
1、程序状态字
为了解决处理器当前工作状态的问题,所有的处理器都具备一个特殊的寄存器,用来表示处理器的状态,称为程序状态字。
设备独立性是指,用户进程中使用逻辑设备名,逻辑设备独立于物理设备。
1、虚拟存储器
进程中代码和数据的虚拟地址组成的虚拟存储空间被称做虚拟存储器。虚存不考虑物理存储器的大小和信息存放的实际位置,只规定每个进程中互相关联的信息的相对位置。每个进程都拥有自己的虚存。虚存可以比主存大,也可以比主存小。
2、设备独立性
用户在编制程序时使用的设备与实际使用的设备无关;即用户使用的是逻辑设备名,而OS将逻辑设备名与物理设备映射起来,逻辑设备和物理设备是独立的。
3、进程控制块
进程控制块,为描述进程及其系统资源的关系,为了刻画一个进程在不同时期所处的状态,os建立一个与进程相联系的数据块,称为进程控制块。PCB是标识进程存在的实体。
3、什么是虚拟设备技术,请举例说明。
SPOOLing
在一种设备基础上模拟实现另一类型的设备的技术;也即在共享型设备上虚拟独占型设备的技术。比如在计算机硬盘上建立虚拟打印机,就是虚拟设备技术的实现。