实验名称: Linux高级开发
system V 进程间通信
实验报告
目录
一.原创性声明..................................................................................................... 1
二.实验要求及完成情况................................................................................ 4
实验要求....................................................................................................... 5
完成情况....................................................................................................... 6
三.程序实现原理及流程图
程序实现原理............................................................................................... 5
流程图........................................................................................................... 6
四.运行结果
消息队列....................................................................................................... 5
Semop()函数测试..................................................................................... 6
五.源码……………………………………………………………………………….….…………….6
一、 原创性声明
1.实验一和实验二代码全部原创
实验室三部分代码来自于百度参考
//这里表明叉子是一个临界资源
#define DELAY (rand() % 5 + 1)
//相当于P操作
void
wait_for_2fork(int no,int semid)
{
//哲学家左边的刀叉号数
int left = no;
//右边的刀叉
int right = (no + 1) % 5;
//刀叉值是两个
//注意第一个参数是编号
struct sembuf buf[2] = {
{left,-1,0},
{right,-1,0}
};
//信号集中有5个信号量,只是对其中的
//资源sembuf进行操作
semop(semid,buf,2);
}
//相当于V操作
void
free_2fork(int no,int semid)
{
int left = no;
int right = (no + 1) % 5;
struct sembuf buf[2] = {
{left,1,0},
{right,1,0}
};
semop(semid,buf,2);
}
2.参考自百度百科:
在操作系统中,P、V操作是进程管理中的难点。这是1968年荷兰人Dijkstra给出的一种解决并发进程间互斥和同步关系的通用方法。
1.P、V操作的意义
定义了信号量及其上的P操作和V操作,来实现并发进程间的同步和互斥,甚至可以用来管理资源的分配。P、V操作因交换的信息量少,属于进程的低级通信。
2. 什么是信号量?
信号量(semaphore)是由一个值和一个指针构成的数据结构。值为整型变量,表示信息量的值;指针指向进程控制块(PCB)队列的队头,表示等待该信号量的下一个进程。如下图所示:
信号量的值与相应资源的使用情况有关。当它的值大于0时,表示当前可用资源的数量;当它的值小于0时,其绝对值表示等待使用该资源的进程个数。注意,信号量的初值不能为负,且其值只能由P、V操作来改变。
3.P、V操作的含义
P、V操作由P操作原语和V操作原语组成(原语是不可中断的过程),对信号量S进行操作,具体定义如下:
P(S):
① 将信号量S的值减1,即S=S-1;
② 如果S≥0,则该进程继续执行;否则该进程状态置为阻塞状态,进程PCB排入信号量PCB队列末尾,放弃CPU,等待V操作的执行。
V(S):
① 将信号量S的值加1,即S=S+1;
② 如果S≤0,释放信号量队列中第一个PCB所对应的进程,将进程状态由阻塞态改为就绪态。执行V操作的进程继续执行。
一般来说,信号量S≥0时,S表示可用资源的数量。执行一次P操作意味着请求分配一个单位资源,因此S的值减1;当S<0时,表示已经没有可用资源,请求者必须等待别的进程释放该类资源,它才能运行下去。而执行一个V操作意味着释放一个单位资源,因此S的值加1;若S?0,表示有某些进程正在等待该资源,因此要唤醒一个阻塞状态的进程,使之成为就绪状态。
4. 利用信号量和P、V操作实现进程互斥
一般地,n个进程利用信号量和P、V操作实现进程互斥的一般模型如下:
进程P1 进程P2 …… 进程Pn
…… …… ……
P(S);P(S); P(S);
临界区; 临界区; 临界区;
V(S);V(S); V(S);
…… …… …… ……
其中S是互斥信号量,初值为1。
使用P、V操作实现进程互斥时应该注意的问题是:
(1)每个程序中,用户实现互斥的P、V操作必须成对出现,先做P操作,进临界区,后做V操作,出临界区。若有多个分支,要认真检查P、V操作的成对性。
(2)P、V操作应分别紧靠临界区的头尾部,临界区的代码应尽可能短,不能有死循环。
(3)互斥信号量的初值一般为1。
5. 利用信号量和P、V操作实现进程同步
P、V操作是典型的进程同步机制之一。用一个信号量与一个消息联系起来,当信号量的值为0时,表示期望的消息尚未产生;当信号量的值为非0时,表示期望的消息已经存在。用P、V操作实现进程同步时,调用P操作测试消息是否到达,调用V操作来发送消息。
使用P、V操作实现进程同步时应该注意的问题是:
(1)分析进程间的制约关系,确定信号量种类。在保持进程间有正确的同步关系情况下,哪个进程应先执行,哪些进程后执行,彼此间通过什么信号量进行协调,从而明确要设置哪些信号量。
(2)信号量的初值与相应资源的数量有关,也与P、V操作在程序代码中出现的位置有关。
(3)同一信号量的P、V操作要成对出现,但它们分别在不同的进程代码中。
二、实验要求及完成情况
1.实验要求:
(1)实验11-3 共享内存实现原理及程序分析
示例程序(p288): shm_sem_example_send.c shm_sem_example_recv.c
简述共享内存实现原理。
运行示例程序,给出运行截图,分析其执行过程。绘制程序流程图
(2)实验11-4 消息队列
发送端向消息队列写入若干消息,类型随机设定。
接收端根据用户输入的消息类型接收消息。
(3)实验11-5 信号量
测试信号量操作函数semop()。
使sem_op分别取不同的值(>0, =0, <0), 跟踪semval、semncnt、semzcnt、sempid值的变化, 查看进程被阻塞和唤醒的情况,分析并得出结论。
(4)实验11-10 哲学家就餐问题
哲学家就餐问题描述:5个哲学家,5个筷子。5个哲学家围坐在一张桌子上,筷子放在分别放在每个哲学家的两旁。如果所有哲学家在某个时刻同时拿起左边的筷子,那么右边的筷子就都被其他的哲学家拿了,造成大家都无法吃饭。但是大家都不想放下左边的筷子(规则是先拿起左边筷子在拿起右边的,吃完饭在放下两个筷子),这就是死锁。
解决这个问题有个办法是在拿起筷子前先判断左右两个筷子是否可用,可用才能拿,而且是同时拿,这样不相邻的哲学家就可以吃上饭,不会造成死锁。
2.完成情况:
均顺利完成了实验,首先,实现了发送端向消息队列写入若干消息,类型随机设定。接收端根据用户输入的消息类型接收消息。测试了信号量操作函数semop()。
使sem_op分别取不同的值(>0, =0, <0), 跟踪semval、semncnt、semzcnt、sempid值的变化, 查看进程被阻塞和唤醒的情况,分析并得出结论。
分析完成了哲学家就餐问题,解决了死锁问题!
三、程序实现原理及流程图(手写)
1.实验11-3
(1)运行结果如下:
(2)流程图如下:
2.自己编写的11-4,11-5,11-10实验原理如下:
四、运行结果
1.消息队列测试
2.测试信号量操作函数semop(),测试如下
3.哲学家就餐问题
五、源码
【实验11-4消息队列】
//send.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/stat.h>
#include<sys/msg.h>
#include<error.h>
structmy_msg
{
long int my_msg_type;
char text[BUFSIZ];
}msgbuf;
intmain(int argc,char *argv[])
{
key_t key;
key=ftok(argv[1],100);
int runningFlg=1;
int msgid;
msgid=msgget((key_t)1234,IPC_CREAT | 0666);
if(msgid==-1)
{
perror("msgget failed!\n");
exit(1);
}
while(runningFlg==1)
{
printf("please inout strings:");
fgets(msgbuf.text,BUFSIZ,stdin);
msgbuf.my_msg_type=1;
if(msgsnd(msgid,(void*)&msgbuf,BUFSIZ,0)==-1)
{
perror("send messagesfailed!\n");
exit(1);
}
if(strncmp(msgbuf.text,"end",3)==0)
{
runningFlg=0;
}
}
return 0;
}
//rec.c
intmain(int argc,char *argv[])
{
//key_t key;
//key=ftok(argv[1],100);
int runningFlg=1;
int msgid;
long int msg_to_receive=0;
msgid=msgget((key_t)1234,IPC_CREAT | 0666);
if(msgid==-1)
{
printf("msgget failed!\n");
exit(1);
}
while(runningFlg==1)
{
if(msgrcv(msgid,(void*)&msgbuf,BUFSIZ,msg_to_receive,0)==-1)
{
perror("msgrcv failed!\n");
exit(1);
}
printf("接收到的字符串是+%s",msgbuf.text);
if(strncmp(msgbuf.text,"end",3)==0)
{
runningFlg=0;
}
}
if(msgctl(msgid,IPC_RMID,0)==-1)
{
perror("msgct(IPC_RMID)failed!\n");
exit(1);
}
return 0;
}
【实验11-5信号量】部分
1.SystemV信号量的继承和销毁
(1)继承
和POSIX的有名信号量一样,父进程中打开的信号量在子进程中仍然是保持着打开状态的。
(2)销毁
对于一个持有该信号量的进程在没有释放该信号量的情况下就终止了。那么该进程所占用的信号量内核是不会进行释放的。
当通过semctl传入IPC_RMID对该信号量集进行删除时,会立即将该信号量从系统中彻底删除,不能再对该信号量进行任何访问。这和POSIX信号量是不一样的。
下面代码进行了测试:
unionsemun
{
int val;
struct semid_ds *buf;
unsigned short int *array;
struct seminfo *__buf;
};
intCreateKey(const char * pathName)
{
int fd = open( pathName, O_CREAT , 0666);
if (fd < 0)
{
cout<<"open fileerror..."<<strerror(errno)<<endl;
return -1;
}
close(fd);
return ftok(pathName, 0);
}
intmain()
{
int semId;
semun arg;
//解决信号量的创建和初始化不是原子操作的一种方案
if ((semId = semget(CreateKey(SEM_PATHNAME),1, IPC_CREAT | IPC_EXCL | 0666)) >= 0)
{
arg.val = 4;
if (semctl(semId, 0, SETVAL, arg) <0)
{
cout<<"semctl error"<<strerror(errno)<<endl;
return -1;
}
}
else if (errno == EEXIST)
{
semId = semget(CreateKey(SEM_PATHNAME),1, 0666);
}
else
{
cout<<"semget error"<<strerror(errno)<<endl;
return -1;
}
cout<<"parent:semvalue:"<<semctl(semId, 0, GETVAL)<<endl;
if (fork() == 0)
{
struct sembuf buffer;
buffer.sem_num = 0;
buffer.sem_op = -2;
buffer.sem_flg = 0;
semop(semId, &buffer, 1);
cout<<"child:semvalue:"<<semctl(semId, 0, GETVAL)<<endl;
exit(0);
}
sleep(1);
cout<<"parent:semvalue:"<<semctl(semId, 0, GETVAL)<<endl;
}
2.下面代码测试信号量集的删除:
intmain()
{
int semId;
semun arg;
//解决信号量的创建和初始化不是原子操作的一种方案
if ((semId = semget(CreateKey(SEM_PATHNAME),1, IPC_CREAT | IPC_EXCL | 0666)) >= 0)
{
arg.val = 4;
if (semctl(semId, 0, SETVAL, arg) <0)
{
cout<<"semctl error"<<strerror(errno)<<endl;
return -1;
}
}
else if (errno == EEXIST)
{
semId = semget(CreateKey(SEM_PATHNAME),1, 0666);
}
else
{
cout<<"semget error"<<strerror(errno)<<endl;
return -1;
}
cout<<"parent:semvalue:"<<semctl(semId, 0, GETVAL)<<endl;
if (fork() == 0)
{
semctl(semId, 0, IPC_RMID);
exit(0);
}
sleep(1);
if (semctl(semId, 0, GETVAL) == -1)
{
cout<<"semctlerror:"<<strerror(errno)<<endl;
return -1;
}
}
3.SystemV信号量的测试
下面是测试代码,信号量集中只有一个信号量,该信号量的值被设成6,每次semop获取两个信号量才能对共享资源(这里用文件代替)进行访问;
intmain()
{
int semId;
semun arg;
//解决信号量的创建和初始化不是原子操作的一种方案
if ((semId = semget(CreateKey(SEM_PATHNAME),1, IPC_CREAT | IPC_EXCL | 0666)) >= 0)
{
arg.val = 4;
if (semctl(semId, 0, SETVAL, arg) <0)
{
cout<<"semctl error"<<strerror(errno)<<endl;
return -1;
}
}
else if (errno == EEXIST)
{
semId = semget(CreateKey(SEM_PATHNAME),1, 0666);
}
else
{
cout<<"semget error"<<strerror(errno)<<endl;
return -1;
}
for (int i = 0; i < 3; ++i)
{
if (fork() == 0)
{
semTest(i + 1, semId);
sleep(1);
exit(0);
}
}
}
【实验11-10哲学家就餐问题】
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/wait.h>
#ifdef _SEM_SEMUN_UNDEFINED
union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
};
#endif
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
int
wait_1fork(int no,int semid)
{
//int left = no;
//int right = (no + 1) % 5;
struct sembuf sb = {no,-1,0};
int ret;
ret = semop(semid,&sb,1);
if(ret < 0) {
ERR_EXIT("semop");
}
return ret;
}
int
free_1fork(int no,int semid)
{
struct sembuf sb = {no,1,0};
int ret;
ret = semop(semid,&sb,1);
if(ret < 0) {
ERR_EXIT("semop");
}
return ret;
}
//这里表明叉子是一个临界资源
#define DELAY (rand() % 5 + 1)
//相当于P操作
void
wait_for_2fork(int no,int semid)
{
//哲学家左边的刀叉号数
int left = no;
//右边的刀叉
int right = (no + 1) % 5;
//刀叉值是两个
//注意第一个参数是编号
struct sembuf buf[2] = {
{left,-1,0},
{right,-1,0}
};
//信号集中有5个信号量,只是对其中的
//资源sembuf进行操作
semop(semid,buf,2);
}
//相当于V操作
void
free_2fork(int no,int semid)
{
int left = no;
int right = (no + 1) % 5;
struct sembuf buf[2] = {
{left,1,0},
{right,1,0}
};
semop(semid,buf,2);
}
void philosophere(int no,int semid)
{
srand(getpid());
for(;;) {
#if 1
//这里采取的措施是当两把刀叉都可用的时候
//哲学家才能吃饭,这样不相邻的哲学家就可
//吃上饭
printf("%d isthinking\n",no);
sleep(DELAY);
printf("%d ishungry\n",no);
wait_for_2fork(no,semid);//拿到叉子才能吃饭
printf("%d iseating\n",no);
sleep(DELAY);
free_2fork(no,semid);//释放叉子
#else
//这段代码可能会造成死锁
int left = no;
int right = (no + 1) % 5;
printf("%d isthinking\n",no);
sleep(DELAY);
printf("%d ishungry\n",no);
wait_1fork(left,semid);
sleep(DELAY);
wait_1fork(right,semid);
printf("%d iseating\n",no);
sleep(DELAY);
free_2fork(no,semid);
#endif
}
}
int
main(int argc,char *argv[])
{
int semid;
//创建信号量
semid =semget(IPC_PRIVATE,5,IPC_CREAT | 0666);
if(semid < 0) {
ERR_EXIT("semid");
}
union semun su;
su.val = 1;
int i;
for(i = 0;i < 5;++i) {
//注意第二个参数也是索引
semctl(semid,i,SETVAL,su);
}
//创建4个子进程
int num = 0;
pid_t pid;
for(i = 1;i < 5;++i) {
pid = fork();
if(pid < 0) {
ERR_EXIT("fork");
}
if(0 == pid) {
num = i;
break;
}
}
//这里就是哲学家要做的事情
philosophere(num,semid);
return 0;
}