操作系统实验三进程间通信

文末也可直接获取实验文档

实验三 进程间通信

1实验目的

1、了解linux系统中进程通信的基本原理。
2、分析进程竞争资源现象,学习解决进程互斥的方法。

2 实验内容

(1)消息的创建,发送和接收。
①使用系统调用msgget (), msgsnd (), msgrcv (), 及msgctl () 等编制一长度为1k的消息的发送和接收程序。
②观察程序,说明控制消息队列系统调用msgctl () 在此起什么作用?
(2)编程实现进程的管道通信。
使用系统调用pipe()建立一条管道,两个子进程p1和P2分别向管道各写一句话,而父进程则从管道中读出来自于两个子进程的信息并显示在屏幕上。
①观察程序中的sleep(1)起什么作用?
②子进程1和2为什么也能对管道进行操作?
(3)比较上述(1),(2)两种通信机制的不同特点。

3实验详细操作步骤及程序清单:

3.1通过Linux提供的系统调用signal()来说明如何执行一个预先安排好的信号处理函数。(选做)
signal()的返回值是指向一个函数的指针,该函数的参数为一个整数。

#include<stdio.h>
#include<signal.h>
#include<unistd.h>
intctrl_c_count=0;
void(*old_handler)(int);
voidctrl_c(int);
voidmain()
{
intc;
/*设置ctrl+c产生的信号SIGINT的处理程序为ctrl_c.取代系统原来默认的处理程序*/
old_handler=singal(SIGINT,ctrl_c);
/*不断输入字符,同时按多次ctrl+c,直到按回车结束*/
while((c=getchar())!=’\n’));
printf(“ctrl_c_count=%d\n”,ctrl_c_count);
/*设置信号处理程序为原来的处理程序*/
(void)singal(SIGINT,old_hander);
/*不断输入字符,ctrl+c结束*/
while((c=getchar())!=’\n’));
}
/*对SIGINT信号的处理程序*/
voidctrl_c(intsignum)
{
(void)signal(SIGINT,ctrl_c);
++ctrl_c;
}

程序说明:这个程序是从键盘获得字符,直到换行符为止,然后进入无限循环。这里,程序安排了捕获ctrl_c信号(SIGINT),并且利用SIGINT来执行一个ctrl_c的处理函数。当在键盘上敲入一个换行符时,SIGINT原来的操作(很可能是默认操作)才被恢复。Main()函数中的第一个语句完成设置信号处理程序:
old_handler=signal(SIGINT,ctrl_c);
signal()的两个参数是:信号值,这里是键盘中断信号SIGINT;以及一个指向函数的指针,这里是ctrl_c,当这个中断信号出现时,将调用该函数。Signal()调用返回旧的信号处理程序的地址,在此它被赋给变量older_handler,使得原来的信号处理程序稍后可以被恢复。
1写入程序
在这里插入图片描述
2编译并运行
在这里插入图片描述

结果分析:程序中的ctrl_c函数的功能是记录SIGINT(CTRL+c)的次数,所以ctrl_c_count=10,当使用默认的函数操作时,CTRL+c退出程序

3.2软中断通信(选做)
编制一段程序,使用系统调用fork()创建两个子进程,再用系统调用signal()让父进程捕捉键盘上来的中断信号(即按ctrl+c键),当捕捉到中断信号后,父进程用系统调用kill()向两个子进程发出信号,子进程捕捉到信号后,分别输出下列信息后终止:
childprocess1iskilledbyparent!
childprocess2iskilledbyparent!
父进程等待两个子进程终止后,输出以下信息后终止:
parentprocessiskilled!
源程序:

#include<stdio.h>
#include<signal.h>
#include<unistd.h>

voidwaiting(),stop();
intwait_mark;
main()
{
intp1,p2;
if(p1=fork())/*创建子进程p1*/
{
if(p2=fork())/*创建子进程p2*/
{
wait_mark=1;
signal(SIGINT,stop);/*接收到^c信号,转stop*/
waiting();
kill(p1,16);/*向p1发软中断信号16*/
kill(p2,17);/*向p2发软中断信号17*/
wait(0);/*同步*/
wait(0);
printf("parentprocessiskilled!\n");
exit(0);
}
else
{
wait_mark=1;
signal(SIGINT,stop);
waiting();
lockf(1,0,0);
printf("childprocess2iskilledbyparent!\n");
lockf(1,0,0);
exit(0);
}
}
else
{
wait_mark=1;
signal(SIGINT,stop);
waiting();
lockf(1,0,0);
printf("childprocess1iskilledbyparent!\n");
lockf(1,0,0);
exit(0);
}
}
voidwaiting()
{
while(wait_mark!=0);
}
voidstop()
{
wait_mark=0;
}

1, 写入参考程序
在这里插入图片描述

2编译和运行
在这里插入图片描述

使用系统调用fork()创建两个子进程,再用系统调用signal()让父进程捕捉键盘上的中断信号(即按DEL键);当捕捉到中断信号后,父进程用系统调用Kill()向两个子进程发出信号,子进程捕捉到信号后分别输出下列信息后终止: Child Proeess 1 is Killed by Parent! Child Process 2 is Killed by Parent! 父进程等待两个子进程终止后,输出如下的信息后终止: Parent Process is Killed!

3.3消息通信(必做)
使用系统调用msgget(),msgsnd(),msgrcv()及msgctl()编制一长度为1K的消息发送和接收的程序。
〈程序设计〉
(1)为了便于操作和观察结果,用一个程序为“引子”,先后fork()两个子进程,SERVER和CLIENT,进行通信。
(2)SERVER端建立一个Key为75的消息队列,等待其他进程发来的消息。当遇到类型为1的消息,则作为结束信号,取消该队列,并退出SERVER。SERVER每接收到一个消息后显示一句“(server)received”。
(3)CLIENT端使用Key为75的消息队列,先后发送类型从10到1的消息,然后退出。最后的一个消息,既是SERVER端需要的结束信号。CLIENT每发送一条消息后显示一句“(client)sent”。
(4)父进程在SERVER和CLIENT均退出后结束。
参考程序:

#include<stdio.h>
#include<sys/types.h>
#include<sys/msg.h>
#include<sys/ipc.h>
#defineMSGKEY75/*定义关键词MEGKEY*/
structmsgform/*消息结构*/
{
longmtype;
charmtxt[1030];/*文本长度*/
}msg;
intmsgqid,i;

voidCLIENT()
{
inti;
msgqid=msgget(MSGKEY,0777);
for(i=10;i>=1;i--)
{
msg.mtype=i;
printf("(client)sent\n");
msgsnd(msgqid,&msg,1024,0);/*发送消息msg入msgid消息队列*/
}
exit(0);
}

voidSERVER()
{
msgqid=msgget(MSGKEY,0777|IPC_CREAT);/*由关键字获得消息队列*/
do
{
msgrcv(msgqid,&msg,1030,0,0);/*从队列msgid接受消息msg*/
printf("(server)receive\n");
}while(msg.mtype!=1);/*消息类型为1时,释放队列*/
msgctl(msgqid,IPC_RMID,0);
exit(0);
}

main()
{
if(fork())SERVER();
elseCLIENT();
wait(0);
wait(0);
}

1, 输入参考程序
在这里插入图片描述

2, 编译并运行
在这里插入图片描述

<结果>

从理想的结果来说,应当是每当Client发送一个消息后,server接收该消息,Client再发送下一条。也就是说“(Client)sent”和“(server)received”的字样应该在屏幕上交替出现。实际的结果大多是,先由Client发送两条消息,然后Server接收一条消息。此后Client、Server交替发送和接收消息.最后一次接收两条消息.Client和Server分别发送和接收了10条消息,与预期设想一致。

<分析>
message的传送和控制并不保证完全同步,当一个程序不再激活状态的时候,它完全可能继续睡眠,造成上面现象,在多次sendmessage后才receivemessage.这一点有助于理解消息转送的实现机理。

消息通信的特点:消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。消息的传递,自身就带有同步的控制.当等到消息的时候,进程进入睡眠状态,不再消耗CPU资源。
3.4进程的管道通信(必做)
编制一段程序,实现进程的管道通信。使用系统调用pipe()建立一条管道先。两个子进程p1和分别向管道各写一句话:
Child1processissendingamessage!
Child2processissendingamessage!
而父进程则从管道中读出来自于两个子进程的信息并显示在屏幕上。

#include<unistd.h>
#include<stdio.h>
#include<signal.h>
Intpid1,pid2;
Main()
{
Intfd[3];
Charoutpipe[100],inpipe[100];
Pipe(fd);/*创建一个管道*/While((pid1=fork())==-1);
If(pid1==0)
{
Printf”p1\n”);
Lockf(fd[1],1,0);
Sprintf(outpipe,”child1processissendingamessage!);
/*把串放入数组outpipe中*/
Write(fd[1],outpipe,50);/*向管道写长为50字节的串*/
Sleep(1);  /*自我阻塞1秒*/
Lockf(fd[1],0,0);
Exit(0);
}
Else
{
While((pid2=fork())==-1);
If(pid2==0)
{
Printf(“p2\n”);
Lockf(fd[1],1,0);/*互斥*/
Sprintf(outpipe,”child2processissendingamessage!);
Write(fd[1],outpipe,50);
Sleep(1);
Lockf(fd[1],0,0);
Exit(0);
}
Else
{
Printf(“parent\n”);
Wait(0);/*同步*/
Read(fd[0],inpipe,50);/*从管道中读长为50字节的串*/
Printf(%s\n”,inpipe);
Wait(0);
Read(fd[0],inpipe,50);
Printf(%s\n”,inpipe);
Exit(0);
}
}
}

运行结果:
child1processissendingamessage!
child2processissendingamessage!
1输入参考程序
在这里插入图片描述

2编译并运行
在这里插入图片描述

相关问题及思考:
(1)无名管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
(2)有名管道(namedpipe):有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
(3)管道通信的特点:管道只能承载无格式字节流。
(4)观察程序中的sleep(1)起什么作用?
答:延长子进程占用管道的时间,并没有让子进程1先输出而子进程2后输出的作用。
(5)子进程1和2为什么也能对管道进行操作?
答:因为该pipe管道属于无名管道,调用pipe()的父进程及其子孙进程均能识别此文件描述符,能利用该文件(管道)进行通信。

3.5共享存储区通信(选做)
使用系统调用shmget(),sgmat(),smgdt(),shmctl(),编制一个与上述相同功能的程序。
参考程序:

#include<sys/types.h>
#include<sys/msg.h>
#include<sys/ipc.h>
#defineSHMKEY75
Intshmid,I;
Int*addr;
Voidclient()
{
IntI;
Shmid=shmget(SHMKEY,1024,0777);/*获取共享区,长度为1024*/
Addr=shmat(shmid,0,0);/*共享区起始地址为addr*/
For(i=9;i>=0;i--)
{
While(*addr!=-1);
Printf((client)sent\n”);/*打印(client)sent*/
*addr=I;
}
Exit(0);
}
Voidserver()
{
Shmid=shmget(SHMKEY,1024,0777|IPC_CREAT);/*创建共享区*/
Addr=shmat(shmid,0,0);/*共享区起始地址为addr*/
Do
{*addr=-1;
While(*addr==-1);
Printf((server)received\n”);/*服务进程使用共享区*/
}while(*addr);
Shmctl(shmid,IPC_RMID,0);
Eixt(0);
}
Voidmain()
{
While((i=fork())==-1);
If(!i)server();
While((i=fork())==-1);
If(!i)client();
Wait(0);
Wait(0);
}

1输入修改后的参考代码
在这里插入图片描述

2编译并运行
在这里插入图片描述

结果:
运行结果和预想的完全一样,但在运行过程中,发现每当client发送一次数据后,server要等待大学0.1秒才有响应。同样,client又需要等待约0.1秒后才发送下一个数据。

分析:
当client发送一个数据后,并没有任何措施通知server数据已经发出,需要由client的查询才能知道。此时client并没有放弃系统控制权,只有当系统进行调度时,切换到了server进程,再进行应答。

4相关问题及思考

(1)消息队列方式是由消息的链表,存在内核中并由消息队列标识符标识。克服了信号传递信息少,管道只能承载无格式字节流及缓冲区大小受限的缺点。
(2)管道通信是一种半双工的通信方式,数据只能单向流动,只能在有父子进程关系的进程间使用

观察程序中的sleep(1)起什么作用?
答:延长子进程占用管道的时间,并没有让子进程1先输出而子进程2后输出的作用。

子进程1和2为什么也能对管道进行操作?
答:因为该pipe管道属于无名管道,调用pipe()的父进程及其子孙进程均能识别此文件描述符,能利用该文件(管道)进行通信。

5总结

这次实验让我了解了linux系统中进程通信的基本原理,通过分析进程竞争资源现象,学习了解决进程互斥的方法。
在实验过程中对实验内容提出的问题感到吃力,但在查看代码和查阅相关资料后得到了解决。

6背景知识

1、fork()
创建一个新进程
intfork()
其中返回int取值意义如下:
0:创建子进程,从子进程返回的id值
大于0:从父进程返回的子进程id值
-1:创建失败
2、lockf(files,function,size):
用作锁定文件的某些段或者整个文件,本函数适用的头文件为:

#include<unistd.h>
参数定义:
intlockf(files,function,size)
intfiles,function;
longsize;
其中:files是文件描述符:function是锁定和解锁;1表示锁定,0表示解锁。size是锁定和解锁的字节数,若用0,表示从文件的当前位置到文件尾。

3、msgget(key,flag):
获得一个消息的描述符,该描述符指定一个消息队列以便用于其他系统调用。
该函数使用偷文件如下:

#include<sy/types.h>
#include<sy/ipc.h>
#include<sy/msg.h>
参数定义
intmsgget(key,flag)
key_tkey;
intflag;
语法格式:msgqid=msgget(key,flag)
其中:msgid是该系统调用返回的描述符,失败则返回-1;flag本身由操作允许权和控制命令值相“或”得到。
如:IP_CREAT|0400是否该队列应被创建;
IP_EXCL|0400是否该队列的创建应是互斥的;等。

4、msgsnd(id,msgp,size,flag):
发送一消息。
该函数是用头文件如下:

#include<sy/types.h>
#include<sy/ipc.h>
#include<sy/msg.h>
参数定义
intmsgnd(id,msgp,size,flag)
intid,size,flag;
structmsgbuf*msgp;
其中:id是返回消息队列的描述符;msgp是指向用户存储区的一个构造体指针,size指示由msgp指向的数据结构中字符数组的长度;即消息的长度。这个数组的最大值由MSG-MAX系统可调用参数来确定。flag规定当核心用尽内部缓冲空间时应执行的动作;若在标志flag中末设置IPC_NOWAIT位,则当该消息队列中字节数超过一最大值时,或系统范围的消息数超过某一最大值时,调用msgsnd进程睡眠。若是设置IPC_NOWAIT,则在此情况下,msgsnd立即返回。

5、msgrcv(id,msgp,size,type,flag):
接受一消息。
该函数调用使用头文件如下:

#include<sy/types.h>
#include<sy/ipc.h>
#include<sy/msg.h>
参数定义
intmsgrcv(id,msgp,size,type,flag)
intid,size,type,flag;
structmsgbuf*msgq;
structsgbuf{longmtpe;chatmtext[];};
语法格式:
count=msgrcv(id,msgp,size,type,flag)
其中:id是用来存放欲接收消息的拥护数据结构的地址;size是msgp中数据数组的大小;type是用户要读的消息类型:
type为0:接收该队列的第一个消息;
type为正:接收类型type的第一个消息;
type为负:接收小于或等于type绝对值的最低类型的第一个消息。
flag规定倘若该队列无消息,核心应当做什么事,如果此时设置了IPC_NOWAIT标志,则立即返回,若在flag中设置了MSG_NOERROR,且所接收的消息大小大于size,核心截断所接受的消息。
count是返回消息正文的字节数。

6、msgctl(id,cmd,buf):
查询一个消息描述符的状态,设置它的状态及删除一个消息描述符。
调用该函数使用头文件如下:

#include<sy/types.h>
#include<sy/ipc.h>
#include<sy/msg.h>
参数定义
intmsgctl(id,cmd,buf)
intid,cmd;
structmsgbuf*msgq;
structmsqid_ds*buf;
其中:函数调用成功时返回0,调用不成功时返回-1。id用来识别该消息的描述符;cmd规定命令的类型。
IPC_START将与id相关联的消息队列首标读入buf。
IPC_SET为这个消息序列设置有效的用户和小组标识及操作允许权和字节的数量。
IPC_RMID删除id的消息队列。
buf是含有控制参数或查询结果的用户数据结构的地址。
附:msgid_ds结构定义如下:
structmsgid_ds
{structipc_permmsg_perm;/*许可权结构*/
shotpadl[7];/*由系统使用*/
ushortonsg_qnum;/*队列上消息数*/
ushortmsg_qbytes;/*队列上最大字节数*/
ushortmsg_lspid;/*最后发送消息的PID*/
ushortmsg_lrpid;/*最后接收消息的PID*/
time_tmsg__stime;/*最后发送消息的时间*/
time_tmsg_rtime;/*最后接收消息的时间*/
me_tmsg_ctime;/*最后更改时间*/
};
structipc_perm
{ushortuid;/*当前用户id*/
ushortgid;/*当前进程组id*/
ushortcuid;/*创建用户id*/
ushortcgid/*创建进程组id*/
ushortmode;/*存取许可权*/
{shotpatl;longpad2}/*由系统使用*/
}

7、shmget(key,size,flag):
获得一个共享存储区。
该函数使用头文件如下:

#include<sy/types.h>
#include<sy/ipc.h>
#include<sy/shm.h>
语法格式:
shmid=shmaget(key,size,flag)
参数定义:
intshmaget(key,size,flag)
key_tkey;
intsize,flag;
其中:size是存储区的字节数,key和flag与系统调用msgget中的参数含义相同。
附:
操作允许权八进制数
用户可读00400
用户可读00200
小组可读00040
小组可读00020
其他可读00004
其他可读00002
控制命令值
IPC_CREAT0001000
IPC_EXCL0002000

如:shmid=shmget(key,size,(IPC_CREAT|0400));
创建一个关键字为key,长度为size的共享存储区。
8、shmat(id,addr,flag):
从逻辑上将一个共享存储区附接到进程的虚拟地址空间上。
该函数调用使用头文件如下:

#include<sy/types.h>
#include<sy/ipc.h>
#include<sy/msg.h>
参数定义:
char*shmat(id,addr,flag)
intid,flag;
char*addr;
语法格式:virtaddr=shmat(id,addr,flag)

其中:id是共享存储区的标识符,addr是用户要使用共享存储区附接的虚地址,若addr是0,系统是否对应用户规定的地址做舍入操作。如果flag中设置了shm_rnd即表示操作系统在必要时舍去这个地址。如果设置了shm_rdonly,即表示只允许读操作。viraddr是附接的虚地址。
9、shmdt(addr):
把一个共享存储区从指定进程的虚地址空间分开。
调用该函数使用头文件:

#include<sy/types.h>
#include<sy/ipc.h>
#include<sy/mhm.h>
参数定义:
intshmdt(addr)
char*addr

其中,当调用成功时,返回0值,调用不成功,返回-1,addr是系统调用shmat所返回的地址。
10、shmctl(id,cmd,buf):
对与共享存储区关联的各种参数进行操作,从而对共享存储区进行控制。
调用该函数使用头文件:

#include<sy/types.h>
#include<sy/ipc.h>
#include<sy/shm.h>
参数定义:
intshmctl(id,cmd,buf)
intid,cmd;
structshmid_ds*buf;
其中:调用成功返回0,否则返回-1。id为被共享存储区的标识符。cmd规定操作的类型。规定如下:
IPC_STAT:返回包含在指定的shmid相关数据结构中的状态信息,并且把它放置在用户存储区中的*but指针所指的数据结构中。执行此命令的进程必须有读取允许权。
IPC_SET:对于指定的shmid,为它设置有效用户和小组标识和操作存取权。
IPC_RMID:删除指定的shmid以及与它相关的共享存储区的数据结构。
SHM_LOCK:在内存中锁定指定的共享存储区,必须是超级用户才可以进行此项操作。
Buf是一个用户级数据结构地址。
附:
shmid_ds
{structipc_permshm_perm;/*允许权结构*/
intshm_segsz;/*段大小*/
intpadl;/*由系统使用;*/
ushortshm_lpid;/*最后操作的进程id;*/
ushortshm_cpid;/*创建者的进程id;*/
ushortshm_nattch;/*当前附界数;*/
shortpad2;/*由系统使用;*/
time_tshm_atime;/*最后附接时间*/
time_tshm_dtime;/*最后段接时间*/
time_tshm_ctime;/*最后修改时间*/
}

11、signal(sig,function):
允许调用进程控制软中断信号的处理。
头文件为:

#include<signal.h>
参数定义:
signal(sig,function);
intsig;
void(*func)();
其中:sig的值是:
SIGHVP挂起
SIGINT键盘按^c键或break键
SIGQUIT键盘按quit键
SIGILL非法指令
SIGIOTIOT指令
SIGEMTEMT指令
SIGFPE浮点运算溢出
SIGKILL要求终止进程
SIGBUS总线错
SIGSEGV段违例
SIGSYS系统调用参数错
SIGPIPE向无读者管道上写
SIGALRM闹钟
SIGTERM软件终结
SIGUSRI用户定义信号
SIGUSR2第二个用户定义信号
SIGCLD子进程死
SIGPWR电源故障
function的解释如下:
SIG_DEL:缺省操作。

对除SIGPWR和SIGCLD外所有信号的缺省操作是进程终结对信号SIGQUIT,SIGILL,SIGTRA,SIGIOT,SIGEMT,SIGFPE,SIGBUS,SIGSEGV和SIGSYS它产生一内存映像文件。
SIG_IGN:忽视该信号的出现。
Function:在该进程中的一个函数地址,在核心返回用户态时,它以软中断信号的序号作为参数调用该函数,对除了信号SIGILL,SIGTRAP和SIGTWR以外的信号,核心自动地重新设置软中断信号处理程序的值为SIG_DEL,一个进程不能捕获SIGKILL信号。

关注公众号:Time木
回复:操作系统实验
可获得相关代码,数据,文档
回复:操作系统试卷
可获取操作系统试卷整理
更多大学课业实验实训可关注公众号回复相关关键词
学艺不精,若有错误还望指点

  • 17
    点赞
  • 123
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Time木

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值