一、进程间通信的目的
因为进程的地址空间都是相互独立的,为了实现进程间的数据传输、资源共享、进程控制(如gdb调试)、通知事件等
二、进程间通信的方式
进程间通信的方式有非常多种,本文仅介绍管道中的匿名管道和命名管道和system V中的消息队列共享内存
2.1管道
内核中的一块缓存,两个用户态通过这段内核态的缓存进行数据传输
匿名管道:使用int pipe(int pipefd[2]);pipefd[0]表示读端,pipefd[1]表示写端,成功返回0失败返回-1,适当的设置错误码。
用两个进程通过管道实现进程间通信,子进程向管道内写入,父进程读出,因此子进程可以关闭读端,父进程可以关闭写端。
读操作使用ssize_t read(int fd, void *buf, size_t count);fd表示要从哪个文件描述符中读取,buf时向哪个缓冲区中放读取出的内容,count为要读多少个字节,成功时返回读到的字节数,失败时返回-1,返回0表示已经读到末尾或者没有可读的文件
写操作使用ssize_t write(int fd, const void *buf, size_t count);buf所指的内存中写count个到指定文件描述符fd的文件中。
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <stdlib.h>
4 #include <string.h>
5
6
7 int main()
8 {
9 int fds[2];
10 if(pipe(fds) == -1)
11 perror("pipe"),exit(1);
12 pid_t pid = fork();
13 if(pid == -1)
14 {
15 perror("fork"),exit(1);
16 }
17 if(pid == 0)
18 {
19 sleep(1);
20 //子进程,关闭读端
21 close(fds[0]);
22 write(fds[1],"abs",3);
23 close(fds[1]);
24 exit(0);
25 }
26 else{
27 //父进程
28 close(fds[1]);
29 char buf[100] = {};
30 int r = read(fds[0],buf,100);//一直阻塞直到子进程的write写入
31 if(r == 0)
32 printf("read EOF\n");
33 else if(r == -1)
34 {
35 perror("read"),exit(1);
36 }
37 else if(r > 0)
38 {
39 printf("buf= [%s]\n",buf);
40 }
41 close(fds[0]);
42 exit(0);
43 //内核中的空间自己回收
44 }
45 }
命名管道:
创建两个命名管道,使用函数int mkfifo[const char *filename,mode_t mode];第一个参数filename为文件名,第二个参数为文件的权限。
创建成功后使用open函数打开管道int open(const char *pathname, int flags, mode_t mode);pathname:打开文件的路径名flags:用来控制打开文件的模式常见的有O_WRONLY写模式O_CREAT创建模式O_RDONLY读模式mode:用来设置创建文件的权限(rwx),当flags中带有O_CREAT时才有效。成功时返回文件描述符,错误时返回-1.
管道的特点:只能用于具有亲缘关系的进程之间的通信;一般而言进程退出,管道就会随之释放,管道的生命周期随进程;管道是半双工的,数据只能单向流动故双方通信时需要建立两个管道。
2.2system V IPC
消息队列:
在内核中消息队列就是一个结构体:
int msgget(key_t key, int msgflg)打开或创建消息队列//key相当于文件名的整数;flag创建队列用选项IPC_CREAT 加权限 | 0644 创建失败返回-1;打开flag用0返回值消息队列的ID,相对于文件描述符
msgctl()释放内核中的消息队列,如不主动释放,进程结束或操作系统关机会一直存在。
msgsnd()消息队列放数据int msgsnd(int msqid,const void *msgp,size_t len,int flag)//msgid是msgget的返回值;msgp指针要发送的消息在哪里?&mb,用结构体struct msgbuf{long channel;//消息类型(通道号,必须>=1);后面都随便};;len消息的字节数(不包括channel的大小),flag 是0达到上限时消息队列就阻塞在这,等被读走了才能放进去;成功返回0,失败返回-1;
msgrcv()消息队列取数据ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg)第一个参数为已打开的消息队列,第二个参数将消息装在哪里,第三个参数不包括消息类型的消息大小(真正接受数据的地方的大小定义的1000就写1000,不是已经接受了多少的数据)取msgtyp通道号为几的数据
const void *msgp,要发送的消息在哪 size_t msgsz,消息的字节数不包括结构体中len,int msgflg);//0必须先定义一个结构体struct msgbuf {long channel;//消息类型(通道号),必须>=1后面的随便定义类型};
删除IPC对象ipcrm 删除用-Q key
ipcs 查看所有systemV的对象(消息队列、共享内存、信号量)都可以显示,只显示消息队列用-q
每个系统中最大创建多少个消息队列MSGMNI cat/proc/sys/kernal/msgmni
每个消息队列中最多能有多少字节MSGMAX cat/proc/sys/kernal/msgmax
1 #include <stdio.h>
2 #include <sys/ipc.h>
3 #include <sys/msg.h>
4 #include <stdlib.h>
5 int main()
6 {
7 int ret = msgget(1234,IPC_CREAT|0644);
8 if(ret == -1)
9 {
10 perror("msgget \n");
11 exit(-1);
12 }
13 else
14 {
15 printf("创建成功\n");
16 }
17 return 0;
18 }
19
使用消息队列来实现一个客户端发送数据服务器读取数据
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <sys/ipc.h>
4 #include <sys/msg.h>
5 #include <string.h>
6 #include <unistd.h>
7 struct msgbuf{
8 long mtype;
9 char mtext[1024];
10 };
11 int main()
12 {
13 //客户端
14 //打开消息队列
15 int id = msgget(1234,0);
16 if(id == -1)
17 {
18 perror("msgget\n");
19 exit(1);
20 }
21 struct msgbuf mb;
22 while(1)
23 {
24 memset(&mb,0x00,sizeof(mb));
25 mb.mtype = 1;
26 *(int *)(mb.mtext) = getpid();
27 //读取键盘
28 char *f = fgets(mb.mtext+sizeof(int),1024-sizeof(int),stdin);
//从标准输入中读到1024-sizeof(int)中
29 if(f == NULL)
30 {
31 break;
32 }
33 //发送给服务器
34 msgsnd(id,&mb,strlen(mb.mtext+sizeof(int))+sizeof(int),0);//发送的数据的大小
35 //读取服务器
36 memset(&mb,0x00,sizeof(mb));
37 msgrcv(id,&mb,1024,getpid(),0);//从进程id的通道中读
38 //显示到屏幕
39 printf("%s\n",mb.mtext+sizeof(int));
40 }
41 }
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <sys/ipc.h>
4 #include <sys/msg.h>
5 #include <string.h>
6 struct msgbuf{
7 long mtype;
8 char mtext[1024];
9 };
10 int main()
11 {
12 //服务端
13 //创建一个消息队列
14 int id = msgget(1234,IPC_CREAT| 0644);
15 if(id == -1)
16 {
17 perror("msgget\n");
18 exit(1);
19 }
20 while(1)
21 {
22 struct msgbuf mb;
23 memset(&mb,0x00,sizeof(mb));
24 //创建成功后读取队列号为msgid的通道1中的数据
25 msgrcv(id,&mb,1024,1,0);
26 //发送给客户端,前四个字节作为通道号发回给客户端
27 mb.mtype =*(int *)(mb.mtext);
28 msgsnd(id,&mb+sizeof(int),strlen(mb.mtext +sizeof(int))+sizeof(int),0);
29 }
30 }
共享内存:
共享存储映射区中用页表映射到同一个物理内存 实现进程间通信,没有用户态到内核态的相互切换时效率最高的一种进程间通信方式。
shmget(key_t key,size_t size//共享内存段的大小,int flag//创建用IPC_CREAT| 0644);
ipcs中的nattch为链接到共享内存的数量
共享内存和进程建立关系void *shmat(int id,const char *shmaddr,flag);//shmaddr由操作系统自己决定关联到哪个虚拟地址空间NULL,flag为0返回虚拟地址空间的起始位置
int shmdt(void *shmaddr)//参数为起始位置,返回值为失败返回-1
两个关联到共享内存的进程一个读一个写最快的进程间通信方式,没有用户态到内核态间的切换
删除共享内存int shmctl(int shmid, int cmd, struct shmid_ds *buf)//cmd IPC_RMID删除共享内存
删除ipcs -M shmget创建时的文件名(key)
使用共享内存实现一个客户端服务器
1 #include <unistd.h>
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <sys/ipc.h>
5 #include <sys/shm.h>
6 #include <string.h>
7 struct stu{
8 int id;
9 char name[20];
10 };
11 int main()
12 {
13 int shmid = shmget(1234,sizeof(struct stu),0);
14 if(shmid == -1)
15 {
16 perror("shmget\n");
17 }
18 printf("create succecss\n");
19 struct stu *p = (struct stu *)shmat(shmid,NULL,0);
20 if(p == NULL)
21 {
22 perror("shmat");
23 exit(1);
24 }
25 p->id = 1;
26 strcpy(p->name ,"jane");
27 sleep(30);
28 shmdt(p);
29 return 0;
30 }
1 #include <unistd.h>
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <sys/ipc.h>
5 #include <sys/shm.h>
6 #include <string.h>
7 struct stu{
8 int id;
9 char name[20];
10 };
11 int main()
12 {
13 int shmid = shmget(1234,sizeof(struct stu),0);
14 if(shmid == -1)
15 {
16 perror("shmget\n");
17 }
18 printf("create succecss\n");
19 struct stu *p = (struct stu *)shmat(shmid,NULL,0);
20 if(p == NULL)
21 {
22 perror("shmat");
23 exit(1);
24 }
25 printf("p->id = %d,p->name = %s",p->id ,p->name);
26 shmdt(p);
27 return 0;
28 }