1、1实验三 进程间通信一、实验目的Linux 系统的进程通信机构 (IPC) 允许在任意进程间大批量地交换数据。本实验的目的是了解和熟悉 Linux 支持的消息通讯机制及信息量机制。二、实验学时2 学时三、实验内容1. 编写程序实现进程的管道通信。用系统调用pipe( )建立一管道,二个子进程P1和P2分别向管道各写一句话:Child 1 is sending a message!Child 2 is sending a message!父进程从管道中读出二个来自子进程的信息并显示(要求先接收 P1,后 P2) 。2.利用 msgget( )、msgsnd( )、msgrcv( )、msgctl
2、( )等系统调用编写两个程序 client.c 和 server.c,分别用于消息的发送和接收。server 建立一个 key 为 75 的消息队列,等待其它进程发来的消息。当遇到类型为 1 的消息,则作为结束信号,取消该队列,并退出 server。server 每接收到一个消息后显示一句“(server)received” 。client 使用 key 为 75 的消息队列,先后发送类型从 10 到 1 的消息,然后退出。最后一个消息,即是 server 端需要的结束信号。client 每发送一条消息后显示一句“(client)sent” 。四、实验要求阅读 Linux 系统的 msg.c、
3、sem.c 和 shm.c 等源码文件,熟悉 Linux 的三种机制。五、实验步骤实验1:(1)什么是管道UNIX系统在OS的发展上,最重要的贡献之一便是该系统首创了管道(pipe)。这也是UNIX 系统的一大特色。2所谓管道,是指能够连接一个写进程和一个读进程的、并允许它们以生产者消费者方式进行通信的一个共享文件,又称为pipe文件。由写进程从管道的写入端(句柄1)将数据写入管道,而读进程则从管道的读出端(句柄0)读出数据。句柄 fd0句柄 fd1读出端 写入端(2)管道的类型:1、有名管道一个可以在文件系统中长期存在的、具有路径名的文件。用系统调用mknod( )建立。它克服无名管道使用上
4、的局限性,可让更多的进程也能利用管道进行通信。因而其它进程可以知道它的存在,并能利用路径名来访问该文件。对有名管道的访问方式与访问其他文件一样,需先用 open( )打开。2、无名管道一个临时文件。利用 pipe( )建立起来的无名文件(无路径名)。只用该系统调用所返回的文件描述符来标识该文件,故只有调用 pipe( )的进程及其子孙进程才能识别此文件描述符,才能利用该文件(管道)进行通信。当这些进程不再使用此管道时,核心收回其索引结点。二种管道的读写方式是相同的,本实验只讲无名管道。3、pipe 文件的建立分配磁盘和内存索引结点、为读进程分配文件表项、为写进程分配文件表项、分配用户文件描述符
5、4、读/写进程互斥内核为地址设置一个读指针和一个写指针,按先进先出顺序读、写。为使读、写进程互斥地访问 pipe 文件,需使各进程互斥地访问 pipe 文件索引结点中的直接地址项。因此,每次进程在访问 pipe 文件前,都需检查该索引文件是否已被上锁。若是,进程便睡眠等待,否则,将其上锁,并进行读/写。操作结束后解锁,并唤醒因该索引结点上锁而睡眠的进程。(3)所涉及的系统调用 31、pipe( )建立一无名管道。系统调用格式pipe(filedes)参数定义int pipe(filedes);int filedes2;其中,filedes1 是写入端,filedes0 是读出端。该函数使用头文
6、件如下:#include #inlcude #include 2、read( )系统调用格式read(fd,buf,nbyte)功能:从 fd 所指示的文件中读出 nbyte 个字节的数据,并将它们送至由指针buf 所指示的缓冲区中。如该文件被加锁,等待,直到锁打开为止。参数定义int read(fd,buf,nbyte);int fd;char *buf;unsigned nbyte;3、write( )系统调用格式write(fd,buf,nbyte)功能:把 nbyte 个字节的数据,从 buf 所指向的缓冲区写到由 fd 所指向的文件中。如文件加锁,暂停写入,直至开锁。参数定义同 re
7、ad( )。(4)参考程序#include 4#include #include int pid1,pid2;main( )int fd2;char outpipe100,inpipe100;pipe(fd); /*创建一个管道 */while (pid1=fork( )=-1);if(pid1=0)lockf(fd1,1,0); /*互斥*/sprintf(outpipe,“child 1 process is sending message!“);/*把串放入数组 outpipe 中*/write(fd1,outpipe,50); /*向管道写长为 50 字节的串*/sleep(5); /
8、*自我阻塞 5 秒*/lockf(fd1,0,0);exit(0);elsewhile(pid2=fork( )=-1);if(pid2=0) lockf(fd1,1,0); /*互斥*/sprintf(outpipe,“child 2 process is sending message!“);write(fd1,outpipe,50);sleep(5);lockf(fd1,0,0);exit(0);else wait(0); /*同步*/5read(fd0,inpipe,50); /*从管道中读长为 50 字节的串 */printf(“%s/n“,inpipe);wait(0);read(
9、fd0,inpipe,50);printf(“%s/n“,inpipe);exit(0);(5)请写出运行结果,分析原因并完成下述思考题。1、程序中的sleep(5) 起什么作用? 2、子进程 1 和 2 为什么也能对管道进行操作?实验2:(1)涉及到的系统调用:1、msgget( )系统调用格式int msgget(key_t key, int msgflg);功能:获取与某个键关联的消息队列标识。消息队列被建立的情况有两种:(1)如果键的值是 IPC_PRIVATE。 (2)或者键的值不是 IPC_PRIVATE,并且键所对应的消息队列不存在,同时标志中指定 IPC_CREAT。参数定义k
10、ey:消息队列关联的键。 msgflg:消息队列的建立标志和存取权限。返回说明: 成功执行时,返回消息队列标识值。失败返回-1,errno 被设为以下的某个值:EACCES:指定的消息队列已存在,但调用进程没有权限访问它,而且不拥有 CAP_IPC_OWNER 权限 6EEXIST:key 指定的消息队列已存在,而 msgflg 中同时指定 IPC_CREAT 和IPC_EXCL 标志 ENOENT:key 指定的消息队列不存在同时 msgflg 中不指定 IPC_CREAT 标志 ENOMEM:需要建立消息队列,但内存不足 ENOSPC:需要建立消息队列,但已达到系统的限制该函数使用头文件如
11、下:#include #include #include 2、msgsnd( )和 msgrcv( )系统调用格式int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);功能:在消息队列上进行收发消息。为了发送消息,调用进程对消息队列必须有写权限。接收消息时必须有读权限。参数定义msqid:消息队列的识别码。msgp:指向消息缓冲区的指针,此位置用来暂时存储发送和接收
12、的消息,是一个用户可定义的通用结构,形态如下struct msgbuf long mtype; /* 消息类型,必须 0 */char mtext100; /* 消息文本 */;msgsz:消息的大小。msgtyp:从消息队列内读取的消息形态。如果值为零,则表示消息队列中的所有消息都会被读取。msgflg:用来指明核心程序在队列没有数据的情况下所应采取的行动。如果 msgflg 和常数 IPC_NOWAIT 合用,则在 msgsnd()执行时若是消息队列已满,则 msgsnd()将不会阻塞,而会立即返回-1,如果执行的是 msgrcv(),则在消息队列呈空时,不做等待马上返回-1,并设定错误码
13、为 ENOMSG。当 msgflg 为 0时,msgsnd()及 msgrcv()在队列呈满或呈空的情形时,采取阻塞等待的处理模式。该函数使用头文件如下:#include 7#include #include 返回说明:成功执行时,msgsnd()返回 0,msgrcv()返回拷贝到 mtext 数组的实际字节数。失败两者都返回-1,errno 被设为以下的某个值:对于 msgsnd EACCES:调用进程在消息队列上没有写权限,同时没有 CAP_IPC_OWNER 权限EAGAIN:由于消息队列的 msg_qbytes 的限制和 msgflg 中指定 IPC_NOWAIT标志,消息不能被发送
14、EFAULT:msgp 指针指向的内存空间不可访问EIDRM:消息队列已被删除EINTR:等待消息队列空间可用时被信号中断EINVAL:参数无效ENOMEM:系统内存不足,无法将 msgp 指向的消息拷贝进来对于 msgrcvE2BIG:消息文本长度大于 msgsz,并且 msgflg 中没有指定 MSG_NOERROREACCES:调用进程没有读权限,同时没具有 CAP_IPC_OWNER 权限EAGAIN:消息队列为空,并且 msgflg 中没有指定 IPC_NOWAITEFAULT:msgp 指向的空间不可访问EIDRM:当进程睡眠等待接收消息时,消息已被删除EINTR:当进程睡眠等待接
15、收消息时,被信号中断EINVAL:参数无效ENOMSG:msgflg 中指定了 IPC_NOWAIT,同时所请求类型的消息不存在3、msgctl( )系统调用格式int msgctl(int msqid, int cmd, struct msqid_ds *buf);功能:在指定的消息队列上执行某种控制操作。参数定义msqid:消息队列识别码。cmd:操作命令,可能值在下面给出:IPC_STAT:将 msqid 所指定的消息队列的信息拷贝一份到 buf 指针所指向的地址。调用者必须对消息队列有读权限。IPC_SET:将由 buf 所指向的 msqid_ds 结构的一些成员写入到与这个消息队列关
16、联的内核结构。同时更新的字段有 msg_ctime。结构体的以下成员会被8更新:msg_qbytes,msg_perm.uid,msg_perm.gid 和 msg_perm.mode 的低九位。调用进程的有效标识必须匹配消息队列属主(msg_perm.uid)或建立者(msg_perm.uid),或者调用者必须拥有相关的特权(如 Linux 下的CAP_IPC_RESOURCE)。IPC_RMID:删除指定的消息队列,唤醒所有等待中的读者和写者进程。IPC_INFO:(Linux 特有命令)获取系统范围内消息队列的制约和其它参数,并储存在 buf 指向的结构。这一操作需要将 msginfo
17、类型造型成msqid_ds,msginfo 定义于,下面列出它的原型:struct msginfo int msgpool; /* 用于保存消息数据的缓冲池大小,字节为单位,尚未被使用 */int msgmap; /* 消息映射中的最大入口,尚未被使用 */int msgmax; /* 能够写入单个消息的最大字节数 */int msgmnb; /* 能够写入消息队列的最大字节数; 队列建立期间用于初始化 msg_qbytes 字段 */int msgmni; /* 系统允许的最大消息队列数 */int msgssz; /* 消息段大小,尚未被使用 */int msgtql; /* 队列上能够存
18、放的最大消息数,尚未被使用 */unsigned short int msgseg; /* 消息可用的最大段数,尚未被使用 */;MSG_INFO:(Linux 特有命令)返回和 IPC_INFO 命令相同的信息。除了下面字段用消息队列耗费的系统资源信息填充外:msgpool 字段包含有当前系统存在的消息数。msgmap 字段包含有系统范围内所有队列的消息总量。msgtql 字段包含有系统范围所有消息的总字节数。MSG_STAT:(Linux 特有命令)返回和 IPC_STAT 命令相同的信息。区别在于msqid 参数并非队列标识,而是内核内部存放所有消息队列信息数组的索引。buf:用于描述某
19、个消息队列的元数据,下面给出它的原型:struct msqid_ds struct ipc_perm msg_perm; /* 队列的属主和权限 */time_t msg_stime; /* 最后一次 msgsnd()操作的时间 */time_t msg_rtime; /* 最后一次 msgrcv()操作的时间 */time_t msg_ctime; /* 最后一次队列改变的时间 */9unsigned long _msg_cbytes; /* 当前队列上包含的字节数 */msgqnum_t msg_qnum; /* 当前队列上包含的消息数 */msglen_t msg_qbytes; /*
20、队列上允许的最大字节数 */pid_t msg_lspid; /* 最后一次执行 msgsnd()的进程标识 */pid_t msg_lrpid; /* 最后一次执行 msgrcv()的进程标识 */;ipc_perm 结构定义在,原型如下:struct ipc_perm key_t key; /* 提供给 msgget()的键 */uid_t uid; /* 属主的有效 UID */gid_t gid; /* 属主的有效 GID */uid_t cuid; /* 建立者的有效 UID */gid_t cgid; /* 建立者的有效 GID */unsigned short mode; /*
21、权限位 */unsigned short seq; /* 系列号 */;该函数使用头文件如下:#include #include #include 返回说明: 成功执行时,IPC_STAT,IPC_SET 和 IPC_RMID 返回 0。IPC_INFO 或 MSG_INFO 返回内核内部记录所有消息队列信息数组的最高可用入口索引。MSG_STAT 返回队列标识。失败返回-1,errno 被设为以下的某个值: EACCES:权限不足 EFAULT:buf 所指向的空间不可访问EIDRM:消息队列已被删除EINVAL:参数无效EPERM:操作不允许server.c 参考程序如下:#include
22、 10#include #include #include #include #define MSGKEY 75struct msgformlong mtype;char mtext1000;msg;int msgqid;void server()msgqid=msgget(MSGKEY,0777|IPC_CREAT); /*创建 75#消息队列*/domsgrcv(msgqid, /*接收消息*/printf(“(server)receivedn“);while(msg.mtype!=1);msgctl(msgqid,IPC_RMID,0); /*删除消息队列,归还资源*/exit(0);main()server();client.c 参考程序如下:#include #include #include #include #include #define MSGKEY 75struct msgform