进程间通信常见方式 Linux环境下,进程地址空间相互独立,每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程和进程之间不能相互访问,要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷贝到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信(IPC,Inter Process Communication). 
在进程间完成数据传递需要借助操作系统提供特殊的方法 ,如:文件、管道、信号、内存共享、消息队列、套接字、命名管道等。常用的进程间通信方式有:
管道(使用最简单)
 
信号(开销最小),只能携带固定的少量信息  
共享映射区
mmap函数的参数使用注意事项 
用于非血缘关系的进程通信  
 
 
本地套接字(最稳定) 
 
管道 基本概念 管道是一种最基本的IPC机制,作用于有血缘关系的进程之间,完成数据传递 。调用pipe系统函数即可创建一个管道。有如下特质:
其本质是一个伪文件 (实为内核缓冲区 ) 
有两个文件描述符引用 ,一个表示读端 ,一个表示写端  
规定数据从管道的写端流入管道,从读端流出  
管道的原理:管道实为内核使用环形队列机 制,借助内核缓冲区 (4K)实现。
管道的局限性:
数据不能自己读自己写  
数据一旦被读走,便不在管道中存在,不可反复读取 由于管道采用半双工通信 方式。因此数据只能在一个方向上流动  
只能在有公共祖先的进程间使用管道  
管道使用方法 pipe函数: 创建并打开 管道。
函数调用成功返回r/w两个文件描述符,无需open,但需手动close。规定:fd[0] :r, fd[1]:w,  类似于0对应标准输入,1对应标准输出 。向管道文件读写数据其实是在读写内核缓冲区。 
1 2 3 4 5 int  pipe (int  pipefd[2 ]) 
刚fork完成时父进程关闭读端,子进程关闭写端,此时数据能在pipe中单向流动,父子进程能够完成通信 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include  <stdio.h>  #include  <stdlib.h>  #include  <sys/types.h>  #include  <unistd.h>  #include  <fcntl.h>  #include  <sys/stat.h>  int  main (int  argc,char * argv[]) 	int  ret=0 ; 	int  pipefd[2 ]; 	pid_t  pid=0 ; 	char * str="loveyou\n" ; 	char  buf[1024 ]; 	 	ret=pipe(pipefd); 	if (ret==-1 ) 		perr_exit("pipe error" ); 	pid=fork(); 	if (pid>0 ){ 		close (pipefd[0 ]);						 		write (pipefd[1 ],str,sizeof (str));		 		close (pipefd[1 ]);						 	}else  if (pid==0 ){ 		close (pipefd[1 ]);						 		ret=read (pipefd[0 ],buf,sizeof (buf));	 		write (STDOUT_FILENO,buf,ret);			 		close (pipefd[0 ]); 	} 	return  0 ; } 
管道读写行为 
读管道
管道中有数据:read返回实际读到的字节数 管道中无数据:
写端全关闭:read返回0 写端没有被全部关闭 (仍有写端打开), read阻塞等待(不久的将来可能会有数据抵达, 此时会让出CPU 
 
 
 
写管道
管道读端全部被关闭, 进程异常终止 (也可以捕捉SIGPIPE信号, 使进程不终止)有读端打开
管道未满:写数据,write将数据写入,返回写入字节数  
管道已满,write阻塞(少见) 
 
 
 
 
 
获取管道缓冲区大小: ulimit -a
父子进程 通信练习:实现 ls | wc -l
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include  <stdio.h>  #include  <stdlib.h>  #include  <sys/types.h>  #include  <unistd.h>  #include  <fcntl.h>  #include  <sys/stat.h>  int  main (int  argc,char * argv[]) 	int  fd[2 ]; 	pid_t  pid; 	int  ret=0 ; 	ret=pipe(fd); 	if (ret==-1 ) 		perr_exit("pipe error" ); 	pid=fork(); 	if (pid==-1 ) 		perr_exit("fork error" ); 	if (pid>0 ){          		close (fd[1 ]); 		dup2(fd[0 ],STDIN_FILENO); 		execlp("wc" ,"wc" ,"-l" ,NULL ); 		perr_exit("execlp wc error" ); 	}else  if (pid==0 ){          		close (fd[0 ]); 		dup2(fd[1 ],STDOUT_FILENO); 		execlp("ls" ,"ls" ,NULL ); 		perr_exit("execlp ls error" ); 	} 	return  0 ; } 
兄弟进程间通信:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 #include  <stdio.h>  #include  <stdlib.h>  #include  <sys/types.h>  #include  <unistd.h>  #include  <fcntl.h>  #include  <sys/stat.h>  int  main (int  argc,char * argv[]) 	int  fd[2 ]; 	pid_t  pid; 	int  ret=0 ; 	int  i=0 ;      	 	ret=pipe(fd); 	if (ret==-1 ) 		perr_exit("pipe error" );      	 	for (i=0 ;i<2 ;++i){ 		pid=fork(); 		if (pid==-1 ) 			perr_exit("fork error" ); 		if (pid==0 ) 			break ; 	}      	 	if (i==2 ){ 		close (fd[0 ]); 		close (fd[1 ]); 		wait(NULL ); 		wait(NULL ); 	}else  if (i==0 ){          		close (fd[0 ]); 		dup2(fd[1 ],STDOUT_FILENO); 		execlp("ls" ,"ls" ,NULL ); 		perr_exit("execlp ls error" ); 	}else  if (i==1 ){          		close (fd[1 ]); 		dup2(fd[0 ],STDIN_FILENO); 		execlp("wc" ,"wc" ,"-l" ,NULL ); 		perr_exit("execlp wc error" ); 	} 	return  0 ; } 
注意:管道可以一个读端, 多个写端, 但是不建议这样做。默认管道的大小是4k。
命名管道FIFO 基本概念 为区分pipe,将FIFO称为命名管道。FIFO可用于不相关进程间的数据交换 。
FIFO是Linux基础文件类型中的一种 , 但是FIFO文件在磁盘上没有数据块, 仅仅用来标识内核中的一条通道, 各进程可以打开这个文件进行read/write, 实际上是在读写内核通道, 这样就实现了进程间通信
创建方式:
1 2 int  mkfifo (const  char * pathname,mode_t  mode) 
用FIFO进行通信几乎只有文件读写操作, 比较简单。
文件通信 读普通文件不会造成read阻塞, 如果子进程睡1秒再写, 父进程由于刚开始读不到数据read直接返回0。
没有血缘关系的进程也可以用文件进行进程间通信。
使用 写进程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include  <stdio.h>  #include  <stdlib.h>  #include  <sys/types.h>  #include  <unistd.h>  #include  <fcntl.h>  #include  <sys/stat.h>  int  main (int  argc,char * argv[]) 	int  fd=0 ; 	int  i=0 ; 	char  buf[4096 ]; 	 	if (argc<2 ){ 		printf ("Enter like this:./a.out fifoname\n" ); 		return  -1 ; 	} 	 	fd=open (argv[1 ],O_WRONLY); 	if (fd==-1 ) 		perr_exit("open error" ); 	while (1 ){          		sprintf (buf,"love you:%d\n" ,i++); 		write (fd,buf,strlen (buf)); 		sleep(1 ); 	} 	close (fd); 	return  0 ; } 
读进程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include  <stdio.h>  #include  <stdlib.h>  #include  <sys/types.h>  #include  <unistd.h>  #include  <fcntl.h>  #include  <sys/stat.h>  int  main (int  argc,char * argv[]) 	int  fd=0 ; 	int  i=0 ; 	int  len=0 ; 	char  buf[4096 ]; 	 	if (argc<2 ){ 		printf ("Enter like this:./a.out fifoname\n" ); 		return  -1 ; 	} 	 	fd=open (argv[1 ],O_RDONLY); 	if (fd==-1 ) 		perr_exit("open error" ); 	 	while (1 ){ 		len=read (fd,buf,sizeof (buf)); 		write (STDOUT_FILENO,buf,len); 		sleep(1 ); 	} 	close (fd); 	return  0 ; } 
MMAP 基本概念 
存储映射I/O使一个磁盘文件与存储空间中的一个缓冲区相映射, 于是当从缓冲区中取数据, 就相当于读文件中的相应字节。 与此类似, 将数据存入缓冲区, 则相应的字节就自动写入文件, 这样就可以在不使用read和write函数的情况下, 使用指针完成I/O操作 。可以借助共享内存和指针来访问磁盘文件。
使用这种方法, 首先应通知内核, 将一个文件映射到存储区域中 , 这个映射工作可以通过mmap函数
基本使用方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void  *mmap (void  *addr, size_t  length, int  prot, int  flags, int  fd, off_t  offset) 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 #include  <stdio.h>  #include  <sys/mman.h>  #include  <sys/types.h>  #include  <sys/stat.h>  #include  <fcntl.h>  #include  <stdlib.h>  #include  <unistd.h>  #include  <string.h>  int  main ()     int  len, ret;     char * p = NULL ;     int  fd = open ("mytest.txt" ,O_CREAT|O_RDWR,0644 );     if (fd < 0 )     {         perror("open" );         exit (0 );     }     len = ftruncate(fd,4 );      if (len == -1 )     {         perror("ftruncate" );         exit (0 );     }     p = mmap(NULL ,4 ,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0 );     if (p == MAP_FAILED)     {         perror("mmap" );         exit (0 );     }          strcpy (p,"abc" );          ret = munmap(p,4 );     if (ret == -1 )     {         perror("munmap" );         exit (0 );     }     close (fd);     return  0 ; } 
od -tcx filename:以16进制查看文件
MMAP使用注意事项 创建映射区的权限应该小于等于文件打开的权限,创建映射区的过程隐含一次对文件的读操作。 
段错误:gdb,直接run就可以抓取到段错误的位置。 
可以open的时候O_CREAT一个新文件来创建映射区,但是要拓展文件大小, 否则会出现总线错误 . 当然 mmap时指定size=0,mmap会报错 
如果open时指定O_RDONLY,mmap时PROT参数指定PROT_READ|PROT_WRITE会报错,无效参数(注意ftruncte()函数需要写权限, 否则无法拓展文件大小 ). 如果都用只读权限, 不会出错. 要创建映射区, 文件必须有读权限  
文件描述符先关闭,对mmap映射没有影响,建立完映射区后fd即可关闭 如果文件偏移量为1000,mmap会报错,因为偏移量必须是4K的整数倍(MMU映射的最小单位为4K)  
对mem进行越界操作:小范围的越界问题不大, 但是最好不要这样(操纵不安全的内存, 操作系统不给保障) 
如果mem++,munmap不会成功(与malloc一样, 释放的内存的指针必须是申请得来的初始的指针, 如果要改变指针的值, 拷贝一份用 ) 
除了第一个参数, 后面的参数都可能导致失败 
无论mmap多复杂,一定要检查mmap的返回值 
 
总结:
创建映射区过程中,隐含一次对映射文件的读操作 
当MAP_SHARED时,要求映射区的权限应该<=文件打开的权限(出于对映射区的保护) ,而MAP_PRIVATE则无所谓因为mmap中的权限是对内存的限制 
特别注意,当映射区文件大小为0时,不能创建映射区,所以:用于映射的文件必须要有实际大小!!mmap使用时候经常会出现总线错误,通常是由于共享文件存储空间大小引起的  
munmap传入的地址一定是mmap的返回地址,坚决杜绝指针++操作文件偏移量必须为4K的整数倍 
mmap创建映射区出错概率很高,一定要检查返回值,确保映射区建立成功后再进行后续操作 
mmap优点 
对文件的读取操作跨过了页缓存,减少了数据的拷贝次数,用内存读写取代I/O读写,提高了文件读取效率。 
实现了用户空间和内核空间的高效交互方式。两空间的各自修改操作可以直接反映在映射的区域内,从而被对方空间及时捕捉。
提供进程间共享内存及相互通信的方式 。不管是父子进程还是无亲缘关系的进程,都可以将自身用户空间映射到同一个文件或匿名映射到同一片区域。从而通过各自对映射区域的改动,达到进程间通信和进程间共享的目的。 同时,如果进程A和进程B都映射了区域C,当A第一次读取C时通过缺页从磁盘复制文件页到内存中;但当B再读C的相同页面时,虽然也会产生缺页异常,但是不再需要从磁盘中复制文件过来,而可以直接使用已经保存在内存中的文件数据。 
可用于实现高效的大规模数据传输。内存空间不足,是制约大数据操作的一个方面,解决方案往往是借助硬盘空间协助操作,补充内存的不足。但是进一步会造成大量的文件I/O操作,极大影响效率。这个问题可以通过mmap映射很好的解决。换句话说,但凡是需要用磁盘空间代替内存的时候,mmap都可以发挥其功效。 
 
mmap父子进程通信 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 #include  <stdio.h>  #include  <stdlib.h>  #include  <sys/mman.h>  #include  <unistd.h>  #include  <sys/wait.h>  #include  <sys/stat.h>  #include  <fcntl.h>  #include  <sys/types.h>  int  var = 100 ;int  main ()     int * p;     pid_t  pid;     int  fd;     fd = open ("temp" ,O_CREAT |O_RDWR|O_TRUNC,0644 );     if (fd < 0 )     {         perror("open error" );         exit (1 );     }     unlink("temp" );      ftruncate(fd,4 );          p = (int *)mmap(NULL ,4 ,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0 );           if (p == MAP_FAILED)      {         perror("mmap error" );         exit (1 );     }     close (fd);      pid = fork();     if (pid == 0 )      {         *p = 2000 ;          var = 1000 ;         printf ("child, *p = %d, var = %d\n" ,*p, var);     } else {         sleep(1 );         printf ("parent, *p = %d, var = %d\n" ,*p, var);         wait(NULL );         int  ret = munmap(p,4 );          if (ret == -1 )         {             perror("munmap error" );             exit (1 );         }     } } 
父子等有血缘关系的进程直接也可以通过mmap建立的映射区来完成数据通信。但相应的要在创建映射区的时候指定对应的标志位参数flags:
MAP_PRIVATE(私有映射): 父子进程各自独占映射区MAP_SHARED(共享映射):父子进程共享映射区 
结论:父子进程共享:
打开的文件 
mmap建立的映射区(但必须使用MAP_SHARED) 
匿名映射 使用映射区来完成文件读写操作十分方便,父子进程间通信也比较容易,但缺陷是:每次创建映射区一定要依赖一个文件才能实现。通常为了建立映射区要open一个temp文件,创建好了再unlink、close,比较麻烦。可以直接使用匿名映射来代替。linux提供了相应的方法,无需依赖一个文件即可创建映射区,同样需要借助标志位参数flags来指定: 
使用MAP_ANONYMOUS(或MAP_ANON),如:
1 int *p = mmap(NULL ,4 ,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1 ,0 );
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include  <stdio.h>  #include  <stdlib.h>  #include  <sys/mman.h>  #include  <unistd.h>  #include  <sys/wait.h>  #include  <sys/stat.h>  #include  <fcntl.h>  #include  <sys/types.h>  int  var = 100 ;int  main ()     int * p;     pid_t  pid;     p = (int *)mmap(NULL ,4 ,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANON,-1 ,0 );           if (p == MAP_FAILED)      {         perror("mmap error" );         exit (1 );     }          pid = fork();     if (pid == 0 )      {         *p = 2000 ;          var = 1000 ;         printf ("child, *p = %d, var = %d\n" ,*p, var);     } else {         sleep(1 );         printf ("parent, *p = %d, var = %d\n" ,*p, var);         wait(NULL );         int  ret = munmap(p,4 );          if (ret == -1 )         {             perror("munmap error" );             exit (1 );         }     } } 
注意:MAP_ANONYMOUS和MAP_ANON两个宏是linux操作系统特有的宏。在类Unix系统中如无该宏定义,可使用如下两步来完成匿名映射区的建立:
1 2 fd = open ("dev/zero" ,O_RDWR); p = mmap(NULL , size ,PROT_READ|PROT_WRITE,MMAP_SHARED,fd,0 ); 
/dev/zero-文件白洞 , 里面有无限量的’\0’, 要多少有多少 
/dev/null-文件黑洞 , 可以写入任意量的数据 
所以在创建映射区时可以用zero文件, 就不用自己创建文件然后拓展大小了
mmap无血缘关系进程间通信 实质上mmap是内核借助文件帮助创建的一个映射区,多个进程之间利用该映射区完成数据传递。由于内核空间多进程共享,因此无血缘关系的进程间也可以使用mmap来完成通信,只要设置相应的标志位参数flags即可。若想实现共享,应该使用MAP_SHARED 
要点:必须是同一个文件 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 #include  <stdio.h>  #include  <stdlib.h>  #include  <unistd.h>  #include  <sys/mman.h>  #include  <sys/types.h>  #include  <string.h>  #include  <sys/types.h>  #include  <sys/stat.h>  #include  <fcntl.h>  struct  STU {    int  id;     char  name[20 ];     char  sex; }; void  sys_err (char * str)     perror(str);     exit (-1 ); } int  main (int  argc, char * argv[])     int  fd;     struct  STU  student  = {10 ,"xiaoming" ,'m' };     char * mm;     if (argc < 2 )     {         printf ("./a.out file_shared\n" );         exit (-1 );     } 	      fd = open (argv[1 ],O_RDWR|O_CREAT,0644 );     if (fd == -1 )     {         sys_err("open error" );     }     ftruncate(fd,sizeof (student)); 	     mm = mmap(NULL , sizeof (student),PROT_READ|PROT_WRITE, MAP_SHARED,fd,0 );     if (mm == MAP_FAILED)     {         sys_err("mmap error" );     }     close (fd);          while (1 )     {         memcpy (mm,&student,sizeof (student));         student.id ++;         sleep(2 );     }          munmap(mm,sizeof (student)); } 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 #include  <stdio.h>  #include  <stdlib.h>  #include  <unistd.h>  #include  <sys/mman.h>  #include  <sys/types.h>  #include  <string.h>  #include  <sys/types.h>  #include  <sys/stat.h>  #include  <fcntl.h>  struct  STU {    int  id;     char  name[20 ];     char  sex; }; void  sys_err (char * str)     perror(str);     exit (-1 ); } int  main (int  argc, char * argv[])     int  fd;     struct  STU  student ;     struct  STU * mm ;     if (argc < 2 )     {         printf ("./a.out file_shared\n" );         exit (-1 );     } 	     fd = open (argv[1 ],O_RDONLY);     if (fd == -1 )     {         sys_err("open error" );     }          mm = mmap(NULL , sizeof (student),PROT_READ, MAP_SHARED,fd,0 );     if (mm == MAP_FAILED)     {         sys_err("mmap error" );     }     close (fd);     while (1 )     {         printf ("id = %d\tname=%s\t%c\n" ,mm->id,mm->name,mm->sex);         sleep(2 );     } } 
无血缘关系进程间通信, 不能用匿名映射 
strace: 追踪一个可执行文件在执行过程中所有的系统调用。