一、实验目的
1、了解什么是管道
2、熟悉UNIX/LINUX支持的管道通信方式
3、了解什么是消息
4、熟悉消息传送的机理
二、实验内容
1.编写程序实现进程的管道通信。用系统调用pipe( )建立一管道,二个子进程P1和P2分别向管道各写一句话:
Child 1 is sending a message!
Child 2 is sending a message!
父进程从管道中读出二个来自子进程的信息并显示。
2.消息的创建、发送和接收。使用系统调用msgget( ),msgsnd( ),msgrev( ),及msgctl( )编制一长度为1k的消息发送和接收的程序。
三、实验代码及注释
(一)进程的管道通信实验
1、pipe( )
建立一无名管道。
系统调用格式
pipe(filedes)
参数定义
1. int pipe(filedes); 2. int filedes[2];
其中,filedes[1]是写入端,filedes[0]是读出端。
该函数使用头文件如下:
1. #include <unistd.h> 2. #inlcude <signal.h> 3. #include <stdio.h>
2、read( )
系统调用格式
read(fd,buf,nbyte)
功能:从fd所指示的文件中读出nbyte个字节的数据,并将它们送至由指针buf所指示的缓冲区中。如该文件被加锁,等待,直到锁打开为止。
参数定义
1. int read(fd,buf,nbyte); 2. int fd; 3. char *buf; 4. unsigned nbyte;
3、write( )
系统调用格式
write(fd,buf,nbyte)
功能:把nbyte 个字节的数据,从buf所指向的缓冲区写到由fd所指向的文件中。如文件加锁,暂停写入,直至开锁。
参数定义同read( )。
参考程序
1. #include <unistd.h> 2. #include <signal.h> 3. #include <stdio.h> 4. int pid1,pid2; 5. main( ) 6. { 7. int fd[2]; 8. char outpipe[100],inpipe[100]; 9. pipe(fd); /*创建一个管道*/ 10. while ((pid1=fork( ))= =-1); 11. if(pid1= =0) 12. { 13. lockf(fd[1],1,0); 14. sprintf(outpipe,"child 1 process is sending message!"); 15. /*把串放入数组outpipe中*/ 16. write(fd[1],outpipe,50); /*向管道写长为50字节的串*/ 17. sleep(5); /*自我阻塞5秒*/ 18. lockf(fd[1],0,0); 19. exit(0); 20. } 21. else 22. { 23. while((pid2=fork( ))= =-1); 24. if(pid2= =0) 25. { lockf(fd[1],1,0); /*互斥*/ 26. sprintf(outpipe,"child 2 process is sending message!"); 27. write(fd[1],outpipe,50); 28. sleep(5); 29. lockf(fd[1],0,0); 30. exit(0); 31. } 32. else 33. { wait(0); /*同步*/ 34. read(fd[0],inpipe,50); /*从管道中读长为50字节的串*/ 35. printf("%s\n",inpipe); 36. wait(0); 37. read(fd[0],inpipe,50); 38. printf("%s\n",inpipe); 39. exit(0); 40. } 41. } 42. }
(二)消息的发送与接收实验
1. msgget( )
获得一个消息的描述符,该描述符指定一个消息队列以便用于其他系统调用。如果希望创建一个新的消息队列,或者希望存取一个已经存在的消息队列,你可以使用系统调用msgget()。
该函数使用头文件如下:
1. #include<sys/types.h> 2. #include<sys/ipc.h> 3. #include<sys/msg.h>
(msgget()、msgsnd()、msgrcv ()、msgctl()所用头文件相同)
原型:int msgget (key-t key, int msgflag)
返回值:如果成功,返回消息队列标识符,如果失败,则返回-1。
其中key是类似一个长整型,可由用户设定也可通过ftok()获得。msgflg的值是八进制的消息队列操作权和控制命令的组合。附:
操作允许权 |
八进制整数 |
用户可读 |
0400 |
用户可写 |
0200 |
同组可读 |
0040 |
同组可写 |
0020 |
其它可读 |
0004 |
其它可写 |
0002 |
操作权可相加而派生,如用户“可读”、“可写” 的 权 限 为0400|0200=0600。
控制命令可取IPC_CREAT或IPC_EXCL。IPC_CREAT如果内核中没有此队列,则创建它。IPC_EXCL当和IPC_CREAT一起使用时,如果队列已经存在,则失败。
如果单独使用IPC_CREAT,则msgget()要么返回一个新创建的消息队列的标识符,要么返回具有相同关键字值的队列的标识符。如果IPC_EXCL和IPC_CREAT一起使用,则msgget()要么创建一个新的消息队列,要么如果队列已经存在则返回一个失败值-1。IPC_EXCL单独使用是没有用处的。
如果要创建一个key=888且属主和同组可读写的消息队列,执行以下系统调用msgget(888,0660|IPC_CREAT)。下面看一个打开和创建一个消息队列的例子:
1. int open_queue(key_t keyval){ 2. int qid; 3. if((qid=msgget(keyval,IPC_CREAT|0660))= =-1){ 4. return(-1); 5. } 6. return(qid); 7. }
2. msgsnd()
发送一消息。向指定的消息队列发送一个消息,并将该消息链接到该消息队列的尾部。
系统调用格式:
msgsnd(msgqid,msgp,size,flag)
该函数使用头文件如下:
1. #include <sys/types.h> 2. #include <sys/ipc.h> 3. #include <sys/msg.h>
参数定义:
1. int msgsnd(msgqid,msgp,size,flag) 2. int msgqid,size,flag; 3. struct msgbuf * msgp;
其中msgqid是返回消息队列的描述符;msgp是指向用户消息缓冲区的一个结构体指针。缓冲区中包括消息类型和消息正文,即
1. { 2. long mtype; /*消息类型*/ 3. char mtext[ ]; /*消息的文本*/ 4. }
size指示由msgp指向的数据结构中字符数组的长度;即消息的长度。这个数组的最大值由MSG-MAX( )系统可调用参数来确定。flag规定当核心用尽内部缓冲空间时应执行的动作:进程是等待,还是立即返回。若在标志flag中未设置IPC_NOWAIT位,则当该消息队列中的字节数超过最大值时,或系统范围的消息数超过某一最大值时,调用msgsnd进程睡眠。若是设置IPC_NOWAIT,则在此情况下,msgsnd立即返回。
对于msgsnd( ),核心须完成以下工作:
(1)对消息队列的描述符和许可权及消息长度等进行检查。若合法才继续执行,否则返回;
(2)核心为消息分配消息数据区。将用户消息缓冲区中的消息正文,拷贝到消息数据区;
(3)分配消息首部,并将它链入消息队列的末尾。在消息首部中须填写消息类型、消息大小和指向消息数据区的指针等数据;
(4)修改消息队列头中的数据,如队列中的消息数、字节总数等。最后,唤醒等待消息的进程。
3. msgrcv( )
接受一消息。从指定的消息队列中接收指定类型的消息。
系统调用格式:
msgrcv(msgqid,msgp,size,type,flag)
本函数使用的头文件如下:
1. #include <sys/types.h> 2. #include <sys/ipc.h> 3. #include <sys/msg.h>
参数定义:
1. int msgrcv(msgqid,msgp,size,type,flag) 2. int msgqid,size,flag; 3. struct msgbuf *msgp; 4. long type;
其中,msgqid,msgp,size,flag与msgsnd中的对应参数相似,type是规定要读的消息类型,flag规定倘若该队列无消息,核心应做的操作。如此时设置了IPC_NOWAIT标志,则立即返回,若在flag中设置了MS_NOERROR,且所接收的消息大于size,则核心截断所接收的消息。
对于msgrcv系统调用,核心须完成下述工作:
(1)对消息队列的描述符和许可权等进行检查。若合法,就往下执行;否则返回;
(2)根据type的不同分成三种情况处理:
type=0,接收该队列的第一个消息,并将它返回给调用者;
type为正整数,接收类型type的第一个消息;
type为负整数,接收小于等于type绝对值的最低类型的第一个消息。
(3)当所返回消息大小等于或小于用户的请求时,核心便将消息正文拷贝到用户区,并从消息队列中删除此消息,然后唤醒睡眠的发送进程。但如果消息长度比用户要求的大时,则做出错返回。
4. msgctl( )
消息队列的操纵。读取消息队列的状态信息并进行修改,如查询消息队列描述符、修改它的许可权及删除该队列等。
系统调用格式:
msgctl(msgqid,cmd,buf);
本函数使用的头文件如下:
1. #include <sys/types.h> 2. #include <sys/ipc.h> 3. #include <sys/msg.h>
参数定义:
1. int msgctl(msgqid,cmd,buf); 2. int msgqid,cmd; 3. struct msgqid_ds *buf;
其中,函数调用成功时返回0,不成功则返回-1。buf是用户缓冲区地址,供用户存放控制参数和查询结果;cmd是规定的命令。命令可分三类:
(1)IPC_STAT。查询有关消息队列情况的命令。如查询队列中的消息数目、队列中的最大字节数、最后一个发送消息的进程标识符、发送时间等;
(2)IPC_SET。按buf指向的结构中的值,设置和改变有关消息队列属性的命令。如改变消息队列的用户标识符、消息队列的许可权等;
(3)IPC_RMID。消除消息队列的标识符。
msgqid_ds 结构定义如下:
1. struct msgqid_ds 2. { struct ipc_perm msg_perm; /*许可权结构*/ 3. short pad1[7]; /*由系统使用*/ 4. ushort msg_qnum; /*队列上消息数*/ 5. ushort msg_qbytes; /*队列上最大字节数*/ 6. ushort msg_lspid; /*最后发送消息的PID*/ 7. ushort msg_lrpid; /*最后接收消息的PID*/ 8. time_t msg_stime; /*最后发送消息的时间*/ 9. time_t msg_rtime; /*最后接收消息的时间*/ 10. time_t msg_ctime; /*最后更改时间*/ 11. }; 12. struct ipc_perm 13. { ushort uid; /*当前用户*/ 14. ushort gid; /*当前进程组*/ 15. ushort cuid; /*创建用户*/ 16. ushort cgid; /*创建进程组*/ 17. ushort mode; /*存取许可权*/ 18. { short pid1; long pad2;} /*由系统使用*/ 19. }
参考程序
1、client.c
1. #include <sys/types.h> 2. #include <sys/msg.h> 3. #include <sys/ipc.h> 4. #define MSGKEY 75 5. struct msgform 6. { long mtype; 7. char mtext[1000]; 8. }msg; 9. int msgqid; 10. void client() 11. { 12. int i; 13. msgqid=msgget(MSGKEY,0777); /*打开75#消息队列*/ 14. for(i=10;i>=1;i--) 15. { 16. msg.mtype=i; 17. printf(“(client)sent\n”); 18. msgsnd(msgqid,&msg,1024,0); /*发送消息*/ 19. } 20. exit(0); 21. } 22. main( ) 23. { 24. client( ); 25. }
2、server.c
1. #include <sys/types.h> 2. #include <sys/msg.h> 3. #include <sys/ipc.h> 4. #define MSGKEY 75 5. struct msgform 6. { long mtype; 7. char mtext[1000]; 8. }msg; 9. int msgqid; 10. void server( ) 11. { 12. msgqid=msgget(MSGKEY,0777|IPC_CREAT); /*创建75#消息队列*/ 13. do 14. { 15. msgrcv(msgqid,&msg,1030,0,0); /*接收消息*/ 16. printf(“(server)received\n”); 17. }while(msg.mtype!=1); 18. msgctl(msgqid,IPC_RMID,0); /*删除消息队列,归还资源*/ 19. exit(0); 20. } 21. main( ) 22. { 23. server( ); 24. }
四、运行结果截图
五、调试和运行程序过程中产生的问题及采取的措施
问题:文件名创建不正确,路径使用错误。
措施:请教老师,在同学的讨论下完成。