《操作系统》第二章作业题
T1
设公共汽车上有一个司机和一个售票员,它们的活动如下图所示。请用信号量机制实现他们的同步。
- 定义信号量
int CloseDoor = 1 //初始化门的状态为【关】
int Arrived = 0 //初始化到达状态为【未到达】
- 司机活动
while(true)
{
wait(CloseDoor); //等待售票员发出【门已关】的信号,门关了才能启动汽车
启动汽车;
正常行车;
到站停车;
signal(arrived); //发出【车辆到站】的信号,售票员依据此信号决定是否开门
}
- 售票员
while(true)
{
关车门;
signal(CloseDoor) //发送【门已关】的信号,司机依据此信号决定是否启动汽车
售票;
wait(Arrived); //等待司机发出【车辆到站】的信号,到站了才能开车门让乘客下车
开车门;
}
T2
三个进程P1、P2、P3互斥使用一个包含N(N>0)个单元的缓冲区。
P1每次用produce()生成一个正整数并用put()送入缓冲区的某一空单元中;
P2每次用getodd()从该缓冲区中取出一个奇数并用countodd()统计奇数个数;
P3每次用geteven()从该缓冲区中取出一个偶数并用counteven()统计偶数个数。
请用信号量机制实现这三个进程的同步与互斥活动,并说明所定义的信号量的含义。
- P1
semaphore mutex=1; //缓冲区是互斥共享的,因此定义互斥信号量
semaphore odd=0; //P1与P2的信号量
semaphore even=0; //P1与P3的信号量
semaphore empty=N; //定义一个同步信号量,因此P1与P2、P3具有同步关系(缓冲区有空位前者才能写;缓冲区未满后两者才能读)
P1(){
while(true){
wait(empty); //等到缓冲区有空位了,才开始产出正整数,要不没地儿放嘛
num = produce(); //存下新生的正整数儿
wait(mutex); //要想放入缓冲区,还得给缓冲区加锁才行,避免出现统计错误
put(); //将正整数放入缓冲区
if(num%2 == 0)
signal(even); //产出了偶数,偶数自增1
else
signal(odd); //产出了奇数,奇数自增1
signal(mutex); //写入完成后,可以释放锁
}
}
- P2
P2(){
while(true){
wait(odd); //缓冲区有奇数才能读
wait(mutex); //给缓冲区加锁
getodd(); //从缓冲区取出奇数
countodd(); //统计奇数
//下面的锁释放顺序可对调(因为3个方法在操作缓冲区前,都会检查锁),但为了对称美还是按照下面的写好
signal(mutex); //释放锁
signal(empty); //腾出一个空位
}
}
- P3
//原理同P2,为了代码的简洁性,此处不再注释
P3(){
while(true){
wait(even);
wait(mutex);
geteven();
counteven();
signal(mutex);
signal(empty);
}
}
T3
问题描述
哲学家就餐问题可以这样表述,假设有五位哲学家围坐在一张圆形餐桌旁,准备吃饭。餐桌上有五碗意大利面,每位哲学家之间各有一只筷子。因为用一只筷子很难吃到意大利面,所以假设哲学家必须用两只筷子吃东西。他们只能使用自己左右手边的那两只筷子。
试用写出不会死锁的哲学家进餐问题的算法
-
错误示例
当每个哲学家都拿起左侧的筷子,等待右侧的筷子可用时,就会进入死锁状态,每个哲学家将永远都在等待(右边的)另一个哲学家放下筷子。
semaphore mutex[5] = {1,1,1,1,1}; //初始化信号量,表示五只筷子的可用状态
Error(){
while(true){
wait(mutex[i]); //判断哲学家左边的筷子是否可用
wait(mutex[(i+1)%5]); //判断哲学家右边的筷子是否可用
//进餐
signal(mutex[i]); //释放左筷子,允许别的哲学家使用左边的筷子
signal(mutex[(i+1)%5]); //释放右筷子,允许别的哲学家使用右边的筷子
}
}
-
正确方法1(记录型信号量)
限制至多只允许有四位哲学家同时去拿左边的筷子,我们可以简单的通过增加一个count信号量实现
semaphore mutex[5] = {1,1,1,1,1}; //初始化信号量,表示五只筷子的可用状态
right1(){
while(true){
wait(count); //判断是否超过四人准备进餐
wait(mutex[i]); //判断哲学家左边的筷子是否可用
wait(mutex[(i+1)%5]); //判断哲学家右边的筷子是否可用
//进餐
signal(mutex[i]); //释放左筷子,允许别的哲学家使用左边的筷子
signal(mutex[(i+1)%5]); //释放右筷子,允许别的哲学家使用右边的筷子
}
}
-
正确的方法2(AND型信号量)
也就是使用AND型信号量,同时对哲学家左右两边的筷子同时申请。下面是伪代码:
semaphore mutex[5] = {1,1,1,1,1}; //初始化信号量,表示五只筷子的可用状态
right2(){
while(true){
wait(mutex[i], mutex[(i+1)%5]); //判断哲学家左边和右边的筷子是否同时可用
//进餐
signal(mutex[i], mutex[(i+1)%5]); //进餐完毕,释放哲学家占有的两只筷子
}
}
-
正确的方法3
这种方法原理是限制奇数号哲学家只能先拿左边的筷子;偶数号哲学家只能先拿右边的筷子,这样就一定不会产生死锁了(大家可以画图试试)。
我画了一个,可以看看。比如哲学家4先选了右边的,哲学家3先选择了左边的,那哲学家3和哲学家4之间的那只筷子,哲学家3和哲学家4总有一个人会拿到吧,那样就会有一个人可以吃意面了。
← 1 ∣ 2 → ∣ ← 3 ∣ 4 → ∣ ← 5 ∣ \leftarrow1|2\rightarrow|\leftarrow3|4\rightarrow|\leftarrow5| ←1∣2→∣←3∣4→∣←5∣
semaphore mutex[5] = {1,1,1,1,1}; //初始化信号量,表示五只筷子的可用状态
right3(){
while(true){
if(i%2 == 1){
wait(mutex[i]); //判断奇数号哲学家左边的筷子是否可用
wait(mutex[(i+1)%5]); //判断奇数号哲学家右边的筷子是否可用
}else{
wait(mutex[(i+1)%5]); //判断偶数号哲学家右边的筷子是否可用
wait(mutex[i]); //判断偶数号哲学家左边的筷子是否可用
}
//进餐
signal(mutex[i]); //释放左筷子,允许别的哲学家使用左边的筷子
signal(mutex[(i+1)%5]); //释放右筷子,允许别的哲学家使用右边的筷子
}
}
T4
问题描述:请用信号量解决以下的“过独木桥”问题:同一方向的行人可连续过桥,当某一方向有人过桥时,另一方向的行人必须等待;当某一方向无人过桥时,另一方向的行人可以过桥。
int countA=0, countB=0; // countA、countB 分别表示 A、B 两个方向过桥的行人数量
semaphore bridge=1; // 用来实现不同方向行人对桥的互斥共享
semaphore mutexA=1, mutexB=1; // 分别用来实现对 countA、countB 的互斥共享
PA(){
wait(mutexA); // 等待获取对 countA 的互斥访问权
if(countA == 0) wait(bridge); // 如果当前没有 A 方向的行人过桥,等待获取对桥的互斥访问权
countA++; // 记录 A 方向过桥的行人数量加一
signal(mutexA); // 释放对 countA 的互斥访问权
// 过桥;
wait(mutexA); // 等待获取对 countA 的互斥访问权
countA--; // 记录 A 方向过桥的行人数量减一(因为有人过桥了)
if(countA == 0) signal(bridge); // 如果当前没有 A 方向的行人过桥,释放桥的互斥访问权
signal(mutexA); // 释放对 countA 的互斥访问权
}
T5
试对进程和程序,以及进程和线程进行比较。
-
进程和程序:
- 程序(Program是一组指令的集合,是静态的,通常保存在存储介质中。程序本身是一个静态实体,它只是一个文件,未运行时不占用系统资源,也不执行任何操作。
- 进程(Process)是程序的一次执行过程,是动态的,具有独立的内存空间和系统资源。进程是操作系统进行资源分配和调度的基本单位。
-
进程和线程:
- **进程(Process)**是操作系统中的一个独立的执行单位,拥有独立的内存空间和系统资源,可以进行独立的调度和管理。每个进程都是独立的,彼此之间相互隔离。
- 线程(Thread)是进程中的一个执行单元,是进程的一个实体。同一个进程中的多个线程共享相同的内存空间和系统资源,它们可以并发执行,共同完成进程的任务。线程之间的通信更加容易,但需要注意同步和互斥问题。
-
资源开销:
- 进程相比线程,资源开销更大。因为每个进程都有独立的内存空间和系统资源,创建和销毁进程需要更多的系统资源和时间。
- 线程相比进程,资源开销更小。因为线程共享进程的内存空间和系统资源,创建和销毁线程的开销较小。
-
通信与同步:
- 进程之间的通信比较复杂,通常需要借助于操作系统提供的进程间通信(IPC)机制,如管道、消息队列、共享内存等。
- 线程之间共享同一个进程的地址空间,可以直接访问进程的全局变量和数据,通信更加方便,但需要注意线程安全和同步的问题。建和销毁进程需要更多的系统资源和时间。
- 线程相比进程,资源开销更小。因为线程共享进程的内存空间和系统资源,创建和销毁线程的开销较小。