进程间通信目的:
<1>.数据传输:一个进程需要将它的数据发送给另一个进程。
<2>.资源共享:多个进程之间共享同样的资源。
<3>.通知事件:一个进程需要向另一个进程发送消息,通知它发生了某种事件(如进程终止时要通知父进程)。
<4>.进程控制:有些进程需要完全控制另一个进程的执行(如debug进程)。
进程间通信分类:
管道:
.匿名管道pipe
.命名管道
管道:管道是Unix中最古老的进程间通信的形式。
把从一个进程连接到另一个进程的一个数据统称为一个“管道”。
一、匿名管道:
#include<unistd.h>
功能:创建无名管道
原型:
int pipe(int fd[2]);
参数:
fd:文件描述符数组,其中fd[0]表示读端,fd[1]表示写端
返回值:成功返回0,失败返回错误码。
例1:从键盘上读取数据,写入管道,读取管道,写入屏幕
#include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 #include<string.h>
5
6 int main()
7 {
8 int fds[2];
9 char buf[1024]={0};
10 int len=0;
11 if(pipe(fds)==-1){
12 perror("pipe"),exit(1);}
13 //read from stdin
14 printf("pipe ok\n");
15 while(1){
16 ssize_t read_size=read(0,buf,sizeof(buf)-1);
17 if(read_size<0){
18 perror("read");
19 return 1;
20 }
21 if(read_size==0){//EOF
22 printf("read done\n");
23 return 0;
24 }
25 buf[read_size]='\0';
26 //write to pipe
27 write(fds[1],buf,strlen(buf));
28 char out_buf[1024]={0};
29 //read from pipe
30 read_size=read(fds[0],out_buf,sizeof(out_buf)-1);
31 if(read_size<0){
32 perror("read");
33 return 1;
34 }
35 if(read_size==0){
36 printf("read done\n");
37 return 0;
38 }
39 out_buf[read_size]='\0';
40 //write to stdout
41 write(1,out_buf,strlen(out_buf));
42 }
43 return 0;
44 }
管道如何实现进程间的通信
(1)父进程创建管道,得到两个⽂件描述符指向管道的两端
(2)父进程fork出子进程,⼦进程也有两个⽂件描述符指向同⼀管道。
(3)父进程关闭fd[0],子进程关闭fd[1],即⽗进程关闭管道读端,⼦进程关闭管道写端(因为管道只支持单向通信)。⽗进程可以往管道⾥写,⼦进程可以从管道⾥读,管道是⽤环形队列实现的,数据从写端流⼊从读端流出,这样就实现了进程间通信。
例2:用代码实现管道通信
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 #include<string.h>
5
6 int main()
7 {
8 int fds[2];
9 if(pipe(fds)==-1){
10 perror("pipe");
11 exit(1);}
12 pid_t pid=fork();
13 if(pid<0){
14 perror("fork");
15 return 1;
16 }
17 else if(pid>0){//parent
18 close(fds[0]);//close read
19 write(fds[1],"hello xiaodu",12);
20 }
21 else{//child
22 close(fds[1]);
23 char buf[1024]={0};
24 read(fds[0],buf,13);
25 printf("%s\n",buf);
26 }
27 return 0;
28 }
管道读取数据的四种的情况
(1).读端不读数据且fd[0]没有关闭(读端的引用计数>0),写端一直写。
#include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 #include<string.h>
5 int main()
6 {
7 int fds[2];
8 if(pipe(fds)==-1){
9 perror("pipe");
10 exit(1);}
11 pid_t pid=fork();
12 if(pid<0){
13 perror("fork");
14 return 1;
15 }
16 else if(pid==0){//child
17 close(fds[0]);//close read
18 int count=0;
19 char*msg="hello xiaodu\n";
20 while(1){
21 write(fds[1],msg,strlen(msg));
22 printf("%d\n",++count);
23 }
24
25 }
26 else{//parent
27 close(fds[1]);
28 char buf[1024]={0};
29 int count=0;
30 while(1){
31 }
32 }
33 return 0;
34 }
(2).写端不写数据且fd[1]没有关闭(写端的引用计数>0),读端一直读数据。
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 #include<string.h>
5 int main()
6 {
7 int fds[2];
8 if(pipe(fds)==-1){
9 perror("pipe");
10 exit(1);}
11 pid_t pid=fork();
12 if(pid<0){
13 perror("fork");
14 return 1;
15 }
16 else if(pid==0){//child
17 close(fds[0]);//close read
18 int count=5;
19 char*msg="hello xiaodu\n";
20 while(count--){
21 sleep(10);
22 write(fds[1],msg,strlen(msg));
23 }
24
25 }
26 else{//parent
27 close(fds[1]);
28 char buf[1024]={0};
29 int count=0;
30 while(1){
31 ssize_t s=read(fds[0],buf,sizeof(buf)-1);
32 if(s>0){
33 buf[s]=0;
34 printf("f get msg:%s\n",buf);
35 }
36 else if(s==0){
37 printf("child quit\n");
38 }
39 else{
40 perror("read");
41 break;
42 }
43 }
44 }
45 return 0;
46 }
(3).写端写了一部分数据后不再写数据且关闭fd[1](写端的引用计数=0),读端一直读且fd[0]没有关闭。
#include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 #include<string.h>
5 int main()
6 {
7 int fds[2];
8 if(pipe(fds)==-1){
9 perror("pipe");
10 exit(1);}
11 pid_t pid=fork();
12 if(pid<0){
13 perror("fork");
14 return 1;
15 }
16 else if(pid==0){//child
17 close(fds[0]);//close read
18 int count=5;
19 char*msg="hello xiaodu\n";
20 while(count--){
21 // sleep(10);
22 write(fds[1],msg,strlen(msg));
23 sleep(10);
24 close(fds[1]);
25 }
26
27 }
28 else{//parent
29 close(fds[1]);
30 char buf[1024]={0};
31 int count=0;
32 while(1){
33 ssize_t s=read(fds[0],buf,sizeof(buf)-1);
34 if(s>0){
35 buf[s]=0;
36 printf("f get msg:%s\n",buf);
37 }
38 else if(s==0){
39 printf("child quit\n");
40 }
41 else{
42 perror("read");
43 break;
44 }
45 }
46 }
47 return 0;
48 }
(4).读端读了一部分数据后不再读数据且关闭了fd[0](读端的引用计数=0),写端一直写且fd[1]没有关闭。
..下边代码是父进程负责读,子进程负责写:
#include<stdio.h>
2 #include <unistd.h>
3 #include<stdlib.h>
4 #include<string.h>
5 int main()
6 {
7 int pipefd[2]={0};
8 int ret=pipe(pipefd);
9 if(ret==-1)
10 {
11 perror("pipe");
12 }
13 pid_t id=fork();
14 if(id==0)
15 {
16 int i=0;
17 const char*msg="hello xiaodu\n";
18 close(pipefd[0]);
19 while(i<10)
20 {
21
22 write(pipefd[1],msg,strlen(msg));
23 sleep(1);
24 i++;
25 }
26 }
27 else
28 {
29 char buf[1024];
30 close(pipefd[1]);
31 int j=0;
32 while(j<3)
33 {
34
35 ssize_t s=read(pipefd[0],buf,sizeof(buf)-1);
36
37 if(s>0)
38 {
39 buf[s]=0;
40 printf("father: %s\n",buf);
41 j++;
42 }
43
44 // sleep(10);
45 // close(pipefd[0]);
46 }
47 sleep(5);
48 close(pipefd[0]);
49 int status=0;
50 wait(&status);
51 printf("child process quit not normal !\n,sig is:%d\n",status&0X7F);
52 }
53 return 0;
54 }
发现进程是异常退出,退出信号为13号,即为SIGPIPE信号。
匿名管道特点:
(1).只能用于具有共同祖先(具有亲缘关系)的进程之间进行通信(如父子进程,父进程创建管道,fork出子进程)。
(2).进程退出,管道释放,所以管道的生命周期随进程。
(3).内核对管道操作具有同步与互斥机制。
(4).管道在通信时是基于字节流的。
(5).管道是半手工的,数据只能从一个方向流动,需要双向通信时,则需建立两个管道。
命名管道
如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,被称为命名管道。
适用于无任何关系的通信。
命名管道是一个特殊类型的文件。
创建命名管道:mkfifo filename
函数为:int mkfifo(const char*filename,mode_t mode);
举例:创建命名管道:
int main(int argc,char*argv[])
{
mkfifo("p1",0644);
return 0;
}
例3:用命名管道实现server&client 通信
client.c文件:用户发送信息
#include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 #include<sys/types.h>
5 #include<sys/stat.h>
6 #include<fcntl.h>
7 #include<string.h>
8
9 #define ERR_EXIT(m)\
10 do{\
11 perror(m);\
12 exit(EXIT_FAILURE);\
13 }while(0)
14
15 int main()
16 {
17 int wfd=open("mypipe",O_WRONLY);
18 if(wfd<0){
19 ERR_EXIT("open");
20 }
21 char buf[1024]={0};
22 while(1){
23 buf[0]=0;
24 printf("please Enter# ");
25 fflush(stdout);
26 ssize_t s=read(0,buf,sizeof(buf)-1);
27 if(s>0){
28 buf[s]=0;
29 write(wfd,buf,strlen(buf));
30 }else if(s<=0){
31 ERR_EXIT("read");
32 }
33 }
34 close(wfd);
35 return 0;
36 }
~
server.c:服务器接收数据
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 #include<sys/types.h>
5 #include<sys/stat.h>
6 #include<fcntl.h>
7 #define ERR_EXIT(m)\
8 do{\
9 perror(m);\
10 exit(EXIT_FAILURE);\
11 }while(0)
12
13 int main()
14 {
15 umask(0);//创建一个文件时将文件权限减了一个umask值
16 int ret=mkfifo("mypipe",0644);
17 if(ret<0){
18 ERR_EXIT("mkfifo");
19 }
20 int rfd=open("mypipe",O_RDONLY);
21 if(rfd<0){
22 ERR_EXIT("open");
23 }
24 char buf[1024]={0};
25 while(1){
26 buf[0]=0;
27 printf("please wait...\n");
28 ssize_t s=read(rfd,buf,sizeof(buf)-1);
29 if(s>0){
30 buf[s-1]=0;
31 printf("client says# %s\n",buf);
32 }else if(s==0){
33 printf("client quit,exit now\n");
34 exit(EXIT_SUCCESS);
35 }else{
36 ERR_EXIT("read");
37 }
38 }
39 close(rfd);
40 return 0;
41 }
在另一个终端下打开./client
这样就可以通过命名管道将两个毫无关系的进程实现进程间通信。
Linux下管道的容量及管道的数据结构
<1>.查看管道的容量
查看Linux下管道的默认大小可以使用命令ulimit -a
可以看到pipe size一次原子性写入为8*512=4096bytes,8是内核设定的管道缓冲区的大小。
我们通过ulimit -a命令查看到的pipo size定义的是内核管道缓冲区的大小,这个值的大小是由内核设定的;而pipe capacity指的是管道的最大值,即容量,是内核内存中的一个缓冲区。
pipe缓冲区是64kB。
尽管命令ulimit -a看到管道大小8块,缓冲区的大小不是4 k,因为内核动态分配最大16“缓冲条目”,即64 k。
即可算出管道容量为:16*4096bytes=65536bytes.
即为:64*1024bytes=65536bytes.
测试管道容量大小只需要将写端一直写,读端不读且不关闭fd[0],即可。
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 #include<string.h>
5 #include<errno.h>
6 #include<fcntl.h>
7 int main()
8 {
9 int fd[2];
10 int ret=pipe(fd);
11 if(ret<0){
12 perror("pipe");
13 exit(1);}
14 pid_t pid=fork();
15 if(pid<0){
16 perror("fork");
17 exit(1);}
18 else if(pid>0){
19 close(fd[0]);
20 long long count=0;
21 char*msg="a";
22 while(1){
23 write(fd[1],msg,strlen(msg));
24 ++count
25 printf("count=%lld\n",count);
26 }
27 }
**28 *else{//child
29 close(fd[1]);
30 char _msg[10];
31 while(1){
32 memset(_msg,'\0',sizeof(_msg));
33 }
34 }***
35 return 0;
36
37 }
~
即可观察到管道的最大容量为65536。
<2>.查看管道的数据结构
在Linux 中,管道的实现并没有使用专门的数据结构,而是借助了文件系统的file 结构和VFS 的索引节点inode。通过将两个 file 结构指向同一个临时的 VFS 索引节点,而这个 VFS 索引节点又指向一个物理页面而实现的。如下图所示。
两个 file 数据结构,但它们定义文件操作例程地址是不同的,其中一个是向管道中写入数据的例程地址,而另一个是从管道中读出数据的例程地址。这样,用户程序的系统调用仍然是通常的文件操作,而内核却利用这种抽象机制实现了管道这一特殊操作。一个普通的管道仅可供具有共同祖先的两个进程之间共享,并且这个祖先必须已经建立了供它们使用的管道。