1. 实验目的
- 系统调用的进一步理解
- 进程上下文切换
- 同步与通信方法
2. Task 1
通过fork的方式,产生4个进程P1,P2,P3,P4,每个进程打印输出自己的名字,例如P1输出“I am the process P1”。要求P1最先执行,P2、P3互斥执行,P4最后执行。通过多次测试验证实现是否正确。
2.1. 实验代码
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>
sem_t *x1_signal=NULL;
sem_t *x2_signal=NULL;
sem_t *x3_signal=NULL;
int main()
{
pid_t P2,P3,P4;
x1_signal=sem_open("x1_signal",O_CREAT,0666,1);//控制P2与P3之间的互斥访问,信号量初始值为1
x2_signal=sem_open("x2_signal",O_CREAT,0666,0);//控制P2与P4之间的先后访问,信号量初始值为0
x3_signal=sem_open("x3_signal",O_CREAT,0666,0);//控制P3与P4之间的先后访问,信号量初始值为0
P2=fork();//创建进程P2
if(P2<0)
{
perror("创建进程P2出错!");
}
if(P2==0)
{
//当前进程为P2
sem_wait(x1_signal);//实现P2、P3互斥访问,锁定信号量x1
printf("I am the process P2!\n");
sem_post(x1_signal);//实现P2、P3互斥访问,释放信号量x1
sem_post(x2_signal);//实现P2、P4先后访问,P2完成后释放x2信号
}
if(P2>0)
{
//当前进程为P1
P3=fork();//创建P3进程
if(P3<0)
{
perror("创建进程P3出错!");
}
if(P3==0)
{
//当前进程为P3
sem_wait(x1_signal);//实现P2、P3互斥访问,锁定信号量x1
printf("I am the process P3!\n");
sem_post(x1_signal);//实现P2、P3互斥访问,释放信号量x1
sem_post(x3_signal);//实现P3、P4先后访问,P3完成后释放x3信号
}
if(P3>0)
{
//当前进程为P1
printf("I am the process P1!\n");//由于P1是P2、P3、P4的父进程所以P1一定会是第一个执行
P4=fork();//创建P4进程
if(P4<0)
{
perror("创建进程P4出错!");
}
if(P4==0)
{
//当前进程为P4
sem_wait(x2_signal);//实现P2与P4先后访问,等待x2信号
sem_wait(x3_signal);//实现P3与P4先后访问,等待x3信号
printf("I am the process P4!\n");
}
}
}
sem_close(x1_signal);
sem_close(x2_signal);
sem_close(x3_signal);
sem_unlink("x1_signal");
sem_unlink("x2_signal");
sem_unlink("x3_signal");
return 0;
}
2.2. 实验分析
2.2.1. 前驱图
前驱关系:P={P1、P2、P3、P4}={(P1,P2),(P1,P3),(P2,P4),(P3,P4)}
2.2.2. 进程实现
将主函数作为P1进程,进程创建的流程图如下。
得到进程树如下。
2.2.3. 信号量机制
- 根据上述进程实现分析可知,主函数作为P1进程,P1进程的子进程包括P2、P3、P4进程,所以可以通过fork函数,我们就可以实现,P1进程最先执行的目标
- 使用信号量x1实现P2、P3的互斥执行,信号量初始值为1
- 使用信号量x2实现P2、P4的先后执行,信号量初始值为0
- 使用信号量x3实现P3、P4的先后执行,信号量初始值为0
(1) 利用信号量实现进程互斥
设x1为互斥信号量,初值为1,取值范围为(-1,0,1)。当x1=1时,表示两个进程P2、P3皆未进入需要互斥的临界资源区;当x1=0时,表示有一个进程进入临界区运行,另外一个必须等待,挂入阻塞队列;当x1=-1时,表示有一个进程进入临界区运行,另一个进程因为等待而阻塞在信号量队列中需要被当前已在临界区运行的进程退出时唤醒。
- 代码(伪)描述如下。
P2() {
sem_wait(x1_signal);//实现P2、P3互斥访问,锁定信号量x1
printf("I am the process P2!\n");//临界资源
sem_post(x1_signal);//实现P2、P3互斥访问,释放信号量x1
}
P3() {
sem_wait(x1_signal);//实现P2、P3互斥访问,锁定信号量x1
printf("I am the process P3!\n");//临界资源
sem_post(x1_signal);//实现P2、P3互斥访问,释放信号量x1
}
在利用信号量机制实现进程互斥时,应该注意,wait(x1)和signal(x2)应该成对出现。缺少wait将会导致系统混乱,不能保证临界资源的互斥访问;缺少signal江湖导致临界资源永远不被释放,从而使因等待该资源而阻塞的进程不能被唤醒。
(2) 利用信号量实现前驱关系
现有两组并发执行的进程,分别为P2->P4、P3->P4。我们使P2、P4共享一个公用信号量x2,P3、P4共享一个公共信号量x3,并赋初值为0,将signal(x2)放在P2语句后面,signal(x3)放在P3语句后面,wait(x2)、wait(x3)放在P4语句前面。
代码(伪)描述如下。
P2() {
printf("I am the process P2!\n");//临界资源
sem_post(x2_signal);//实现P2、P4先后访问,P2完成后释放x2信号
}
P3() {
printf("I am the process P3!\n");//临界资源
sem_post(x3_signal);//实现P3、P4先后访问,P3完成后释放x3信号
}
P4() {
sem_wait(x2_signal);//实现P2与P4先后访问,等待x2信号
sem_wait(x3_signal);//实现P3与P4先后访问,等待x3信号
printf("I am the process P4!\n");
}
由于x2、x3被初始化为0,若P4先执行必定阻塞,只有在进程P2、P3完成执行,signal(x2)、signal(x3)使得x2、x3的值增为1,P4进程才能成功执行。
2.3. 实验结果
编译并运行程序,每个程序成功打印出自己的名字。并发现存在两种运行结果分别为:
P1->P2->P3->P4或是P1->P3->P2->P4。均符合题目要求。
3. Task 2
火车票余票数ticketCount 初始值为1000,有一个售票线程,一个退票线程,各循环执行多次。添加同步机制,使得结果始终正确。要求多次测试添加同步机制前后的实验效果。(说明:为了更容易产生并发错误,可以在适当的位置增加一些pthread_yield(),放弃CPU,并强制线程频繁切换,例如售票线程的关键代码。
temp=ticketCount;
pthread_yield();
temp=temp-1;
pthread_yield();
ticketCount=temp;
退票线程的关键代码:
temp=ticketCount;
pthread_yield();
temp=temp+1;
pthread_yield();
ticketCount=temp;
3.1. 未添加同步机制
3.1.1. 实验代码
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<semaphore.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
volatile int ticketCount=1000;
void *Sale(void *arg){
int num,temp;
num=atoi(arg);
for(int i=0;i<num;i++){
temp=ticketCount;
pthread_yield();
temp=temp-1;
pthread_yield();
ticketCount=temp;
}
}
void *Refund(void *arg){
int num,temp;
num=atoi(arg);
for(int i=0;i<num;i++){
temp=ticketCount;
pthread_yield();
temp=temp+1;
pthread_yield();
ticketCount=temp;
}
}
int main(int argc,char *argv[]){
if(argc!=3){
printf("请输入正确参数!\n");
exit(0);
}
printf("初始化总票数为:%d\n",ticketCount);
printf("总售票票数为:%s\n",argv[1]);
printf("总退票票数为:%s\n",argv[2]);
pthread_t P1,P2;
pthread_create(&P1,NULL,Sale,argv[1])