进程间通信

  每个进程各有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到。    

  所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信 (IPC,InterProcess Communication) 

如图:  

wKioL1cN29iTVjnFAABmiApm85A046.png

1.进程间的通信方式

  1. 无名管道( pipe )管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。

  2. 高级管道(popen):将另一个程序当做一个新的进程在当前程序进程中启动,则它算是当前程序的子进程,这种方式我们成为高级管道方式。

  3. 有名管道 (named pipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。

  4. 消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。

  5. 信号量( semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。

  6. 信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。

  7. 共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。

  8. 套接字( socket ) : 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。


                管道(pipe)

 管道是一种最基本的IPC 机制,由pipe 函数创建:   

    #include <unistd.h>

    int pipe(int filedes[2]);  

  调用pipe 函数时在内核中开辟一块缓冲区(称为管道)用于通信,它有一个读端一个写端,然后通filedes参数传出给用户程序两个文件描述符,filedes[0] 指向管道的读端,filedes[1] 指向管道的写端(很好记,就像0是标准输入,1是标准输出一样)。所以管道在用户程序看起来就像一个打开的文件,通过read(filedes[0]);或者write(filedes[1]);向这个文件读写数据其实是在读写内核缓冲区。pipe 函数调用成功返回0, 调用失败返回-1 。 

  开辟了管道之后如何实现两个进程间的通信呢?如图可以按下列的步骤通信。

wKiom1cN3S6i38ORAACStekaLLw664.png   

 <1. 父进程调pipe 开辟管道,得到两个文件描述符指向管道的两端。  

 <2. 父进程调fork 创建子进程,那么该进程也有两个文件描述符指向同一管道。

 <3. 子进程关闭管道读端,父进程关闭管道写端。子进程可以往管道写,父进程可以从管道读,管道是环形队列实现的,数据从写端流入从读端流出,这样就实现了进程间通信。 

       

1. 匿名管道

#include <stdio.h> 
#include <unistd.h> 
#include <errno.h> 
#include <string.h> 
int main()  
{ 
	 int _pipe[2];           
	 int ret = pipe(_pipe);           
	 if(ret == -1){           
	      printf("create pipe error! errno code is : %d\n",errno);                           return 1;                     
	 }           
	 pid_t id = fork();           
	 if( id < 0 ){           
	 	 printf("fork error!");                     
	 	 return 2;                     
	 }else if( id == 0 ){ //child           
	 	 close(_pipe[0]);                     
	 	 int i =0;                     
	 	 char *_mesg_c=NULL;                     
	 	 while(i<100){                     
	 	     _mesg_c="i am child!";                     
	 	 	 write(_pipe[1], _mesg_c, strlen(_mesg_c)+1);              
	 	 	 sleep(1);                               
	 	 	 i++;                               
	 	 }
	  }else{ //father           
	 	 close(_pipe[1]);                     
	 	 char _mesg[100];                     
	 	 int j = 0;                     
	 	 while(j<100){                     
     	 	 memset(_mesg, '\0', sizeof(_mesg));               
     	 	 read(_pipe[0], _mesg, sizeof(_mesg));               
     	 	 printf("%s\n",_mesg);               
	 	 	 j++;                               
	 	 }                     
	 }           
	 return 0;           
}

使用管道有些限制:   

  两个进程通过一个管道只能实现单向通信。如上的例子,子进程写父进程读,如果有时也需要父进程写给子进程读,则需要开辟一条新的管道。


  思考,如果只开一个管道,但是子进程不关闭读端,父进程也不关闭写端,双都有读端和写端,为什么不能实现双向通信?

 解答(个人意见):管道数据是面向字节流的环形结构环形buf 详解),父进程只需从管道读端读数据,子进程只从管道写端写数据,这样就保证了无论是读、写数据的完整性 和有序性。假设所有端口开启,父、子进程同时读写数据,那么管道中的信息就会参杂在一块,读出的数据必定不是完整的。有序的。这便违背了通信的初衷,因此不能这样。管道的单向通信,既是优点(简单,高效)也是缺点(无法实现相互通信)

 

  管道的读写端通过打开的文件描述符来传递,因此要通信的两个进程必须从它们的公共祖先那继承管道文件描述符。上面的例子是父进程把文件描述符传给子进程之后父、子进程之间通信,也可以父进程fork 两次,把文件描述符传给两个子进程,然后两个子进程之间通信, 总之需要通过fork 传递文件描述符使两个进程都能访问同一管道,它们才能通信。 也就是说,道通信是需要进程之间有关系。 

  使用管道需要注意以下4种特殊情况(假设都是阻塞I/O 操作,没有设置O_NONBLOCK标志):  

  <1. 如果所有指向管道写端的文件描述符都关闭了(管道写端的引用计数等于0), 仍然有进程从管道的读端读数据,那么管道中剩余的数据都被读取后,再次read 会返回0,就像读到文件末尾一样。

代码如下:

#include <stdio.h> 
#include <unistd.h> 
#include <errno.h> 
#include <string.h> 
#include <sys/wait.h> 
int main() 
{ 
	 int _pipe[2];           
	 int ret = pipe(_pipe);           
	 if(ret == -1){           
	 	 printf("create pipe error! errno code is : %d\n",errno); 	                      
     return 1; 
	 }           
	 pid_t id = fork();           
	 if( id < 0 ){           
	 	 printf("fork error!");                     
	 	 return 2;                     
	 }else if( id == 0 ){ //child           
	 	 close(_pipe[0]);                     
	 	 int i =0;                     
	 	 char *_mesg_c=NULL;                     
	 	 while(i<10){                     
	 	     _mesg_c="i am child!";                     
	 	 	 write(_pipe[1], _mesg_c, strlen(_mesg_c)+1);                               
	 	 	 sleep(1);                               
	 	 	 i++;                               
	 	 }                     
	 	 close(_pipe[1]);
	 	 }else{ //father           
	 	 close(_pipe[1]);                     
	 	 char _mesg[100];                     
	 	 int j = 0;                     
	 	 while(j<100){                     
     	 	 memset(_mesg, '\0', sizeof(_mesg));               
     	 	 int ret = read(_pipe[0], _mesg, sizeof(_mesg));               
     	 	 printf("%s : code is : %d\n",_mesg, ret);               
	 	 	 j++;                               
	 	 }                     
	 	 if  ( waitpid(id, NULL, 0)< 0)                     
	 	 {                     
	 	 	 return 3;                               
	 	 }                     
	 }           
	 return 0;           
}

 

  <2.如果有指向管道写端的文件描述符没关闭(管道写端的引用计数大于0), 持有管道写 端的 进程也没有向管道中写数据,这时有进程从管道读端读数据,那么管道中剩余的数据都被读取后,再次read 会阻塞,直到管道中有数据可读了才读取数据并返回。

代码如下:

#include <unistd.h> 
#include <errno.h> 
#include <string.h> 
#include <sys/wait.h> 
int main() 
{ 
	 int _pipe[2];           
	 int ret = pipe(_pipe);           
	 if(ret == -1){           
	 	 printf("create pipe error! errno code is : %d\n",errno);                     
	 	 return 1;                     
	 }	                    
         pid_t id = fork(); 
	 if( id < 0 ){           
	 	 printf("fork error!");                     
	 	 return 2;                     
	 }else if( id == 0 ){ //child           
	 	 close(_pipe[0]);                     
	 	 int i =0;                     
	 	 char *_mesg_c=NULL;                     
	 	 while(i<20){                     
	 	 	 if( i < 10 ){                               
	 	         _mesg_c="i am child!";                     
	 	 	     write(_pipe[1], _mesg_c, strlen(_mesg_c)+1);                               
	 	 	 }                               
	 	 	 sleep(1);       
	 	 	 i++;                               
	 	 }                     
	 	 close(_pipe[1]);                     
	 }else{ //father           
	 	 close(_pipe[1]);                     
	 	 char _mesg[100];                     
	 	 int j = 0;                     
	 	 while(j<20){                     
     	 	 memset(_mesg, '\0', sizeof(_mesg));               
     	 	 int ret = read(_pipe[0], _mesg, sizeof(_mesg));               
     	 	 printf("%s : code is : %d\n",_mesg, ret);               
	 	 	 j++;                               
	 	 }                     
	 	 if  ( waitpid(id, NULL, 0)< 0)                     
	 	 {                     
	 	 	 return 3;                               
	 	 }                     
	 }           
	 return 0;           
}


  <3. 如果所有指向管道读端的文件描述符都关闭了(管道读端的引用计数等于0), 这时有进程向管道的写端write,那么该进程会收到信号SIGPIPE,通常会导致进程异常终止。 

#include <stdio.h> 
#include <unistd.h> 
#include <errno.h> 
#include <string.h> 
#include <sys/wait.h> 
int main() 
{ 
	 int _pipe[2];           
	 int ret = pipe(_pipe);           
	 if(ret == -1){           
	 	 printf("create pipe error! errno code is : %d\n",errno);                     
	 	 return 1;                     
	 }           
	 pid_t id = fork();	                    
if( id < 0 ){ 
	 	 printf("fork error!");                     
	 	 return 2;                     
	 }else if( id == 0 ){ //child           
	 	 close(_pipe[0]);                     
	 	 int i =0;                     
	 	 char *_mesg_c=NULL;                     
	 	 while(i<20){                     
	 	 	 if( i < 10 ){                               
	 	         _mesg_c="i am child!";                     
	 	 	     write(_pipe[1], _mesg_c, strlen(_mesg_c)+1);                               	 }                               
	 	 	 sleep(1);                               
	 	 	 i++;                               
	 	 }                     
	 }else{ //father           
	 	 close(_pipe[1]);                     
	 	 char _mesg[100];                     
	 	 int j = 0;                     
	 	 while(j<3){                     
     	 	 memset(_mesg, '\0', sizeof(_mesg));               
     	 	 int ret = read(_pipe[0], _mesg, sizeof(_mesg));               
     	 	 printf("%s : code is : %d\n",_mesg, ret);               
	 	 	 j++;                               
	 	 }                     
	 	 close(_pipe[0]);                     
	 	 sleep(10);                     
	 	 if  ( waitpid(id, NULL, 0)< 0)                     
	 	 {                     
	 	 	 return 3;                               
	 	 }                     
	 }           
	 return 0;           
}


  <4. 如果有指向管道读端的文件描述符没关闭(管道读端的引用计数大于0),持有管道读端的进程也没有从管道中读数据,这时有进程向管道写端写数据,那么在管道被写满时再次write会阻塞,直到管道中有空位置了才写入数据并返回。(代码类似上)

2.命名管道 (FIFO)

 <1.概念     

    管道的一个不足之处是没有名字,因此,只能用于具有亲缘关系的进程间通信,在命名管道(named pipe 或FIFO)提出后,该限制得到了克服。FIFO不同于管道之处在于它提供一个路径名与之关联以FIFO的文件形式存储于文件系统中。命名管道是一个设备文件,因此,即使进程与创建FIFO的进程不存在亲缘关系,只要可以访问该路径,就能够通过FIFO相互通信。值得注意的是,FIFO(first input first output)总是按照先进先出的原则操作(保留匿名管道的特性),第一个被写入的数据将先从管道中读出。 

<2.命名管道的创建与读写 

     Linux 下有两种方式创建命名管道。一是在Shell 下交互地建一个命名管道,二是在程序中使用系统函数建命名管道。Shell 方式下可使用mknod mkfifo命令,下面命令使 mknod 创建了一个命名管道:

      mknod namedpipe 

     创建命名管道的系统函数有两个:mknod 和mkfifo。两个函数均定义在头件sys/stat.h ,

函数原型如下:

   #include <sys/types.h> 

    #include <sys/stat.h> 

    int mknod(const char *path,mode_t mod,dev_t dev); 

    int mkfifo(const char *path,mode_t mode);  

 函数mknod 参数中path 为创建的命名管道的全路径名mod 为创建的命名管道的模式,指明其存取权限dev为设备值,该值取决于件创建的种类,它只在创建设备件时才会用。这两个函数调用成功都返回0,失败都返回-1。下面使用 mknod 函数创建了个命名管道: 

    umask(0); 

    if  (mknod("/tmp/fifo",S_IFIFO | 0666) == -1) 

   { 

    perror("mkfifo error"); 

    exit(1); 

    } 

  函数mkfifo前两个参数的含义和mknod 相同。下面是使用 mkfifo的代例代码: 

    umask(0); 

    if  (mkfifo("/tmp/fifo",S_IFIFO|0666) == -1) 

    { 

    perror("mkfifo error!"); 

    exit(1); 

    }


  “S_IFIFO|0666”指明创建一个命名管道且存取权限为0666即创建者、与创建者同组的用户、其他用户对该命名管道的访问权限都是可读可写(这里要注意umask对生成的

管道文件权限的影响)。 

   命名管道创建后就可以使用了,命名管道和管道的使用方法基本是相同的。只是使用命名管道时,必须先调open()将其打开。因为命名管道是一个存在于硬盘上的文件,匿名管道是存在于内存中的特殊文件。 

   需要注意的是,调用open()打开命名管道的进程可能会被阻塞。但如果同时用读、写方式(O_RDWR)打开,则一定不会导致阻塞;如果以只读方式(O_RDONLY)打开,则调open()函数的进程将会被阻塞,直到有写方式打开管道;同样只以写方(O_WRONLY)打开也会阻塞直到有读方式打开管道。


  <3.总结 

 件系统中的路径名是全局的,各进程都可以访问,因此可以用文件系统中的路径名来标识一个IPC 通道。  

   命名管道也被称为FIFO文件,它是一种特殊类型的文件,它在文件系统中以文件名的形式存在,但是它的行为却和之前所讲的没有名字的管道(匿名管道)类似。 

   由于Linux中所有的事物都可被视为文件,所以对命名管道的使用也就变得与文件操作非常的统一,也使它的使用非常方便,同时我们也可以像平常的文件名一样在命令中使

   mknod mkfifo这两个函数都能创建一个FIFO文件,注意是创建一个真实存在于文件系统中的文件,

  filename指定了文件名,mode则指定了文件的读写权限。mknod 是较老的函数。    使用mkfifo函数更加简单和规范,所以建议在可能的情况下,尽量使用mkfifo不是mknod 。 

   mkfifo函数的作用是在文件系统中创建一个文件,该文件用于提供FIFO功能,即命名管道。

   前边讲的那些管道都没有名字,因此它们被称为匿名管道,或简称管道。对文件系统来说,匿名管道是不可见的,它的作用仅限于在父进程和子进程两个进程间进行通信。

   命名管道是个可见的文件,因此,它可以用于任何两个进程之间的通信,不管这两个进程是不是父子进程,也不管这两个进程之间有没有关系。

程序实例: 

  read端:

int main() 
{ 
	 int fd = open(_PATH_, O_RDONLY);           
	 if(fd < 0){           
	 	 printf("open file error!\n");                     
	 	 return 1;                     
	 }           
	 char buf[_SIZE_];           
	 memset(buf, '\0', sizeof(buf));           
	 while(1){           
	 	 int ret = read(fd, buf, sizeof(buf));                     
	 	 if  (ret <= 0)//error or end of  file                     
	 	 {                     
	 	 	 printf("read end or error!\n");                         
	 	 	 break;                         
	 	 }                     
	 	 printf("%s\n", buf);                     
	 	 if( strncmp(buf, "quit", 4) == 0 ){                     
	 	 	 break;                         
	 	 }
	 	 }           
    close(fd);  
	 return 0;           
}

  write 端:

#include <stdio.h> 
#include <sys/types.h> 
#include <sys/stat.h> 
#include <unistd.h> 
#include <string.h> 
#include <fcntl.h> 
#define _PATH_ "/tmp/file.tmp" 
#define _SIZE_ 100 
int main() 
{ 
	 int ret = mkfifo(_PATH_,0666|S_IFIFO);           
	 if(ret == -1){           
	 	 printf("mkfifo error\n");                     
	 	 return 1;                     
	 }           
	 int fd = open(_PATH_, O_WRONLY);           
	 if(fd < 0){           
	 	 printf("open error\n");                     
	 }           
	 char buf[_SIZE_];           
	 memset(buf, '\0', sizeof(buf));           
	 while(1){           
	 	 scanf("%s", buf);                     
	 	 int ret = write(fd, buf, strlen(buf)+1);                     
	 	 if(ret < 0){                     
	 	 	 printf("write error\n");                               
	 	 	 break;                               
	 	 }                     
	 	 if( strncmp(buf, "quit", 4) == 0 ){                     
	 	 	 break;                               
	 	 }                     
	 }           
    close(fd);  
	 return 0;           
}

           XSI IPC(消息队列,信号量,共享内存) 

  简介

    XSI IPC 包括消息队列、信号量以及共享内存,他们都依托标识符和键来实现的,这就像是管道靠文件描述符来实现一样

IPC 主题一:消息队列

 一、什么是消息队列 

   消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法。  每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类  型值。我们可以通过发送消息来避免命名管道的同步和阻塞问题。消息队列与  管道不同的是,消息队列是基于消息的管道是基于字节流的,且消息队列的  读取不一定是先进先出。消息队列与  命名管道有一样的不足,就是每个消  息的最大长度是有上限的(MSGMAX),  每个消息队列的总的字节数是有上  限的(MSGMNB)系统上消息队列的总数  也有一个上限(MSGMNI)

wKiom1cN9KiQSc9gAABdkhsd_TI743.png

二、IPC 对象数据结构 

  内核为每个IPC 对象维护个数据结构(/usr/include/linux/ipc.h) 

  struct ipc_perm { 

   key_t    __key;        /* Key supplied to xxxget(2) */ 

   uid_t    uid;         /* Effective UID of  owner */ 

   gid_t    gid;         /* Effective GID of  owner */  

   uid_t    cuid;        /* Effective UID of  creator */ 

   gid_t    cgid;        /* Effective GID of  creator */ 

   unsigned short mode;     /* Permissions */ 

   unsigned short __seq;    /* Sequence number */ 

};  

wKioL1cN9gjjOBqoAAB2bhfoniA698.png    消息队列,共享内存和信号量都有这样一个共同的数据结构。

三、消息队列结构(/usr/include/linux/msg.h ) 

 wKioL1cN9nOiL15rAADJ8ChZvG8107.png

   可以看到第一个条目就是IPC 结构体,即是共有的,后面的都是消息队列所私有的成员。 

   消息队列是用链表实现的

四、函数 

  1. 创建新消息队列或取得已存在消息队列 

    原型:int msgget(key_t key, int msgflg); 

    参数: 

       key:可以认为是一个端口号,也可以由函数ftok 生成。 

       msgflg

         IPC_CREAT: 如果IPC 不存在,则创建一个IPC 资源,否则

        打开操作。                           

         IPC_EXCL :只有在共享内存不存在的时候,新的共享内存

        才建立,否则就产生错误。

    注意:如果单独使IPC_CREAT ,msgget() 函数要么返回一个已经        存在的共享内存的操作符,要么返回一个新建的共享内存的标识符。

        如果将IPC_CREATIPC_EXCL 标志一起使用,msgget() 将返   回一个新建的IPC 标识符;如果该IPC 资源已存在,或者返回-1 。 

        IPC_EXEL标志本并没有太大的意义,但是和IPC_CREAT 标志一

  起使用可以用来保证所得的对象是新建的,不是打开已有的对象。 

  2. 向队列读/写消息 

   原型: 

   msgrcv 从队列中取出消息:ssize_t msgrcv(int msqid, void 

          *msgp,size_t msgsz, long msgtyp, int msgflg); 

   msgsnd 将数据放到消息队列中:int msgsnd(int msqid, const 

          void *msgp, size_t msgsz, int msgflg); 

   参数: 

     msqid:消息队列的标识码 

     msgp:指向消息缓冲区的指针,此位置用来暂时存储发送和接收的消        息,是一个用户可定义的通用结构体,形态如下:  

   struct msgstru{ 

     long mtype; //大于0 

      char mtext[ 户指定定大小]; 

   }; 

      msgsz:消息的大小。 

     msgtyp:从消息队列内读取的消息形态。如果值为零,则表示消息队        列中的所有消息都会被读取。 

     msgflg:来指明核芯程序在队列没有数据的情况下所应采取的行动。        如果msgflg和常IPC_NOWAIT合,则在msgsnd()执行时若是消息        队列已满,则msgsnd()将不会阻塞,而会立即返回-1 ,如果执        的是msgrcv(),则在消息队列呈空时,不做等待马上返回-1 ,并        设定错误码为ENOMSG。当msgflg0时,msgsnd()msgrcv()在        队列呈满或呈空的情形时,采取阻塞等待的处理模式。 

  3. 设置消息队列属性 

   原型:int msgctl ( int msgqid, int cmd, 

           struct msqid_ds *buf  ); 

   参数:msgctl :系统调用对 msgqid 标识的消息队列执cmd 操作,      系统定义了 3 种 cmd 操作IPC_STAT , IPC_SET , IPC_RMID            IPC_STAT : 该命令用来获取消息队列对应的 msqid_ds数据结              构,并将其保存到 buf定的地址空间。

       IPC_SET : 该命令用来设置消息队列的属性,要设置的属性存储            在buf中。      

       IPC_RMID : 从内核中删除 msqid 标识的消息队列。 

五. key_t键 

  System V IPC 使用 key_t值作为它们的名字,在Redhat linux(后续验 证默认都在该平台下)下key_t被定义为int类型,追溯如下: 

  /usr/include/sys/ipc.h  

  #ifndef  __key_t_defined 

  typedef  __key_t key_t; 

  #define __key_t_defined 

  #endif  

  /usr/include/bits/types.h 

  typedef __DADDR_T_TYPE __daddr_t; /* The type of  a disk     address.  */ 

  typedef  __SWBLK_T_TYPE __swblk_t; /* Type of  a swap block maybe?   */ 

  typedef  __KEY_T_TYPE __key_t;  /*type of an ipc key*/

  /usr/include/bits/typesizes.h 

   #define __KEY_T_TYPE      __S32_TYPE 

  /usr/include/bits/types.h 

  #define   __S32_TYPE       int  

  ftok 函数 

   函数ftok 把一个已存在的路径名和一个整数标识得转换成一个key_t值,称为IPC 键: 

        # include <sys/types.h> 

        # include <sys/ipc.h>  

        key_t ftok(const char *pathname, int proj_id); 

DESCRIPTION  

        The ftok function uses the identity of  the  file        named by the given pathname (which  must  refer to an        existing, accessible file) and the least significant 8 

     bits  of  proj_id (which must be nonzero) to generate         a key_t  type System  V IPC   key 。 

 //该函数把从pathname导出的信息与id 的低序8位组合成一个整数IPC 键。


  ftok 的典型实现调用stat 函数,然后组合以下三个值:  

    1.pathname 所在的文件系统的信息(stat 结构的st_dev 成员)  

    2.该文件在本文件系统内的索引节点号(stat 结构的st_ino 成员)  

    3. proj_id 的低序8位(不能为0) 

   从程序运行的结果可以看出,ftok 调用返回的整数IPC 键由proj_id 的    低序8位,st_dev 成员的低序8位,st_info 的低序16位组合而成。 

  //不能保证两个不同的路径名与同一个proj_id 的组合产生不同的键,因为   上面所列三个条目(文件系统标识符、索引节点、proj_id)中的信息位数可   能用于一个整数的信息位数。 

思考:为何要有key_t?

  解释(个人见解):如果没有key,n个进程仅通过 FILEPATH 访问消息队列

,假设这些进程;两两通信,则每次写入数据和读取数据的位置是不确定的,即每次通过路径访问则很难定位到某一条消息,因此才引入key。下图是个人猜想的key的作用。

 wKiom1cOBu6jQQN0AAAa7TXet3c059.png

代码实例:

 comm.h 

#ifndef  __COMM__ 
#define __COMM__ 
#include <stdio.h> 
#include <stdlib.h> 
#include <sys/ipc.h> 
#include <sys/msg.h> 
#include <sys/types.h> 
#include <string.h> 
#include <time.h> 
#define __MSG_SIZE__ 1024 
#define FILEPATH "/tmp/.msg" 
#define ID       0 
extern const int g_ser_send_type;//server  
extern const int g_cli_send_type;//client 
typedef  struct _msginfo{ 
	 long mtype;           
	 char mtext[__MSG_SIZE__];           
}msginfo; 
void print_log(char *); 
#endif

   comm.c


#include "comm.h" 
const int g_ser_send_type=1;//server 
const int g_cli_send_type=2;//client 
void print_log(char *msg) 
{ 
	 printf("%s[%d] : %s\n", __FUNCTION__,__LINE__,msg);           
}

  msg_server.h

#ifndef  _MSG_SERVER_ 
#define _MSG_SERVER_ 
#include"comm.h" 
int msg_server_start(); 
int msg_server_end(int);  
#endif

  msg_server.c

#include "msg_server.h" 
int _msg_id = -1;  
int msg_server_start() 
{ 
        key_t _key =  ftok (FILEPATH,ID);// 创建键值 
        if( _key < 0 ){ 
                print_log("get key id error"); 
                return 1; 
        }    
        msginfo _ser_info; 
        _msg_id =  msgget(_key, IPC_CREAT); // 获取信号队列ID 
        if( _msg_id < 0 ){ 
          print_log("msg_server get key id failed\n"); 
                return 1; 
        }    
        while(1){ 
        //      sleep(10); 
                if( msgrcv(_msg_id, &_ser_info, sizeof(_ser_info) , g_cli_send_type, 0) == -1 ){ 
                        print_log("msg rcv error"); 
                        return 1; 
                }    
                printf("client :> %s\n",_ser_info.mtext); 
                printf("server :>"); 
                memset(_ser_info.mtext, '\0', sizeof(_ser_info.mtext)); 
                fgets(_ser_info.mtext, __MSG_SIZE__, stdin); 
                if(strncasecmp(_ser_info.mtext, "quit", 4) == 0){  
                        printf("server bye!\n"); 
                        break; 
                }    
                _ser_info.mtype = g_ser_send_type; 
                if( msgsnd(_msg_id, &_ser_info, __MSG_SIZE__, 0) == -1 ){ 
                        printf("server send msg error\n"); 
                        exit(0); 
                }  } 
        return 0; 
} 
int msg_server_end(int id) 
{ 
        if( msgctl(id, IPC_RMID, NULL) == -1){ 
                printf("delete msg kernel info error\n"); 
                return 1; 
        } 
        return 0; 
} 
static void delete_msg(void) 
{ 
        if( _msg_id != -1 ){ 
                msg_server_end(_msg_id); 
        } 
        printf("delete msg queue end\n"); 
} 
int main(int argc, char *argv[]) 
{ 
        atexit(delete_msg); 
        if(msg_server_start() == 0){ 
                print_log("msg_server start success\n"); 
        }else{ 
                print_log("msg_server start failed\n"); 
                       } 
       return 0; 
}

  msg_client.h

msg_client.h 
#ifndef  _MSG_CLIENT_ 
#define _MSG_CLIENT_ 
#include"comm.h" 
int msg_client_start(); 
int msg_client_end(int); 
#endif

   msg_client.c

#include "msg_client.h" 
int _msg_id = -1; 
int msg_client_start() 
{ 
        key_t _key = ftok(FILEPATH,ID);// 创建键值 
        if( _key < 0 ){ 
                print_log("client get key id error"); 
                return 1; 
        } 
        msginfo _cli_info; 
        _msg_id = msgget(_key, 0); //获取信号队列ID 
        if( _msg_id < 0 ){ 
                print_log("msg_server get key id failed\n"); 
                return 1; 
        } 
        while(1){ 
                printf("client :>"); 
                fgets(_cli_info.mtext, sizeof(_cli_info.mtext),stdin); 
                if(strncasecmp(_cli_info.mtext, "quit", 4) == 0){ 
                        printf("client bye!\n"); 
                        break; 
                } 
                _cli_info.mtype = g_cli_send_type; 
                if( msgsnd(_msg_id, &_cli_info,sizeof(_cli_info) , 0) == -1 ){ 
                        printf("client send msg error\n"); 
                        exit(0); 
                } 
                memset(_cli_info.mtext, '\0', sizeof(_cli_info.mtext)); 
                if( msgrcv(_msg_id, &_cli_info, __MSG_SIZE__, g_ser_send_type, 0) == -1 ){ 
                        print_log("client recive msg error"); 
                        return 1;
                           } 
                printf("server :>%s\n",_cli_info.mtext);        } 
        return 0; 
} 
int msg_client_end(int id) 
{ 
        if(msgctl(id, IPC_RMID, NULL) == -1){ 
               return 1; 
        } 
        return 0; 
} 
static void delete_msg(void) 
{ 
        if( _msg_id != -1 ){ 
                msg_client_end(_msg_id); 
        } 
        printf("delete msg queue end\n"); 
} 
int main(int argc, char *argv[]) 
{ 
        atexit(delete_msg); 
        if(msg_client_start() == 0){ 
                print_log("msg_server start success\n"); 
        }else{ 
                print_log("msg_server start failed\n"); 
        } 
        return 0; 
}

IPC 主题二:信号量

  信号量的本质是一种数据操作锁,它本身不具有数据交换的功能,而是通过控制其他的通信资源(文件,外部设备)来实现进程间通信,它本身只是一种外部资源的标识。信号量在此过程中负责数据操作的互斥、同步等功能。

   当请求一个使用信号量来表示的资源时,进程需要先读取信号量的值来判断资源是否可用大于0,资源可以请求,等于0,无资源可用,进程会进入睡眠状态直到资源可用。 

   当进程不再使用一个信号量控制的共享资源时信号量的值+1,对信号量的值进行的增减操作均为原子操作,这是由于信号量主要的作用是维护资源的互斥或多进程的同步访问。

 在信号量的创建及初始化上,不能保证操作均为原子性。 

一、为什么要使用信号量

  为了防止出现因多个程序同时访问一个共享资源而引发的一系列问题,我们需要一种方法,它可以通过生成并使用令牌来授权,在任一时刻只能有一个执线程访问代码的临界区域。

   临界区域是指执行数据更新的代码需要独占式地执行。而信号量就可以提供这样的一种访问机制,让一个临界区同一时间只有一个线程在访问它,也就是说信号量是用来调协进程对共享资源的访问的。其中共享内存的使用就要用到信号量。(临界资源:多个进程同时需要访问的资源 临界区:进程中需要访问临界资源的一段逻辑区)


二、信号量的工作原理 

   由于信号量只能进行两种操作等待和发送信号,即P(sv) 和V(sv),他们的行为是这样的: 

  P(sv) :如果sv 的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行 

  V(sv) :如果有其他进程因等待sv 而被挂起,就让它恢复运行,如果没有进程因等待sv 而挂起,就给它加1. 

  

  举个例子:就是两个进程共享信号量sv ,一旦其中一个进程执行了P(sv) 操作,它将得到信号量,并可以进入临界区,使sv 减1。第二个进程将被阻进入临界区,因为当它试图执P(sv) 时,sv 为0,它会被挂起以等待第一个进程离开临界区域并执行V(sv)释放信号量,这时第二个进程就可以恢复执行。


三 Linux 的信号量机制 

   Linux提供了一组精心设计的信号量接口来对信号进行操作,它们不只是针对二进制信号量,下面将会对这些函数进行介绍,但请注意,这些函数都是来对成组的信号量值进行操作的。它们声明在头件sys/sem.h中。 

   【信号量的意图在于进程间同步,互斥锁和条件变量的意图则在于线程间同步。但是信号量也可用于线程间,互斥锁和条件变量也可用于进程间。我们应该使用适合具体应用的那组原语。】

  描述: 

     semctl()semid 标识的信号量集上,或者该集合的第 semnum

   信号量上执行cmd 指定的控制命令。(信号量集合索引起始于零。)根据    cmd 不同,这个函数有三个或四个参数。当有四个参数时,第四个参数的    类型是 union

     调用程序必须按照下面行式定义这个联合体: 

     union semun  {  

         int val;         //   使用的值    

         struct semid_ds *buf;// IPC_STAT 、IPC_SET使用缓存区 

         unsigned short *array;// GETALL、SETALL 使用的数组  

         struct seminfo *__buf;//IPC_INFO(Linux 特有)使用缓存区      };  

   注意:该联合体没有定义在任何系统头文件中,因此得用户自己声明。          <Centos下确实是这样,但是UNIX下不同,不需要用户定义声明> 

 实例代码: 

    sem.h: 

#ifndef  __SEM__ 
#define __SEM__ 
#include  <stdio.h>  
#include  <unistd.h> 
#include  <stdlib.h> 
#include  <sys/sem.h> 
#include  <sys/ipc.h>  
#include  <string.h> 
#include  <errno.h> 
#define KEY_PATH  "." 
#define PROJECT_ID 88 
int create_sem( int _semset_num); 
int init_sem( int _sem_id, int _which); 
int sem_p(int _sem_id, int _which); 
int sem_v(int _sen_id,  int _which); 
int destroy_sem( int _sem_id); 
int show_sem_val( int _sem_id, int _sem_num); 
int get_sem();  
#endif

 sem.c

sem.c:  
#include  "sem.h" 
//if  success ,retrun : id of  a semaphore set. 
//else return -1.  
int create_sem( int semset_num) 
{ 
    int create_flag=IPC_CREAT | IPC_EXCL;//IPC_EXCL ,如果key已经存在,直接返回,防止key重复 
    key_t k = ftok(KEY_PATH, PROJECT_ID); 
    return  semget(k, semset_num, create_flag); 
} 
//if  success, retunr : sem id,else return -1;  
//semset_num is return sem_set num 
int get_sem() 
{ 
    key_t k = ftok(KEY_PATH, PROJECT_ID); 
    return  semget(k, 0,  0); 
} 
//return 0 if  success,else return -1  
static int op_sem( int sem_id,  int op,  int which) 
{ 
    struct sembuf  sem; 
    memset(&sem, '\0' ,  sizeof(sem)); 
    sem.sem_num = which; 
    sem.sem_op = okp; 
    sem.sem_flg = 0;
    return  semop(sem_id, &sem,  1/*sem num*/ ); 
}
int init_sem( int sem_id,  int which) 
{ 
    semun_t _semun; 
    _semun.val=1; 
    int ret =  semctl(sem_id, which, SETVAL, _semun); 
    if (ret == - 1){ 
        //printf("init sem error : errno code is : %d\n",errno);  
        return  ret; 
    } 
    //printf("init sem success : errno code is : %d\n",errno);  
    return  ret; 
} 
int sem_p(int sem_id,  int which) 
{ 
    int ret = op_sem(sem_id, - 1, which); 
    if (ret == 0){ 
        //printf("P operator is success!, errno code is : %d\n", errno);  
    } else { 
        //printf("P operator is failed!, errno code is : %d\n", errno); 
    } 
    return  ret; 
} 
int sem_v(int sem_id,  int which) 
{ 
    int ret = op_sem(sem_id, 1, which); 
    if (ret == 0){ 
        //printf("V operator is success!, errno code is : %d\n", errno); 
    } else { 
        //printf("V operator is failed!, errno code is : %d\n", errno); 
    } 
    return  ret; 
} 
int destroy_sem( int sem_id) 
{ 
    int ret = semctl(sem_id,  0, IPC_RMID,  NULL); 
    if (ret == - 1){ 
        //printf("destroy sem error\n");  
        return  ret; 
    } 
    //printf("destroy sem success, errno code is : %d\n", errno); 
    return  ret; 
} 
//for debug  
int show_sem_val( int sem_id,  int sem_num) 
{ 
    semun_t _semun; 
    unsigned  short *_sem_list=( unsigned  short *)malloc( sizeof(unsigned  short)*sem_num); 
	 if (  NULL == _sem_list ){ //printf("malloc error,errno code is : %d\n", errno); 
	 	//return 1;            
	 }           
	 memset(_sem_list, '\0' ,  sizeof(unsigned  short) * sem_num);           
	 _semun.array=_sem_list;           
    int ret = semctl(sem_id,  0, GETALL, _semun); 
    if (ret == - 1){ 
        //printf("get sem error : errno code is : %d\n",errno);  
    } 
	 else {           
		int i=0;           
		for(; i<sem_num; i++){           
	 	 	 printf("sem[%d] is : %d\n" ,i, _semun.array[i]);           
	 	 }           
	 }           
	 free(_sem_list);           
	 _semun.array=NULL;           
    return  ret; 
}

 test_nosem.c(使用信号量前)

  #include"sem.h"
#include<signal.h>
#include<sys/prctl.h>

int main()
{
    pid_t _pid=fork();//创建子进程
    if(_pid<0)//创建子进程失败
        printf("fork error!\n");
    else if(_pid ==0)//子进程中
    {
        int i=0;
        while(i++<20) 
        {
            sleep(1);
            printf("A");//打印 A 进程休眠 1秒
            fflush(stdout);//立马刷新到显示器
            sleep(1);
            printf("A");//打印 A 进程休眠 1秒
            fflush(stdout);
        }   
        return 0;
    }   
    else    
    {
        int i=0;
        while(i++<20)//父进程 打印 B
        {
            sleep(1);
            printf("B");
            fflush(stdout);
            sleep(1);
            printf("B");
            fflush(stdout);
        }
    }
    return 0;
}

  代码运行结果(A,B乱序输出)wKioL1cQ0L-QK1O7AAAeYWX6IOA406.png

 test_sem.c (使用信号量后)

#include"sem.h"
#include<signal.h>
#include<sys/prctl.h>

int main()
{
    pid_t _pid;
    int _sem_id,ret;
    _sem_id=create_sem(1);//创建信号量
    printf("%d\n",_sem_id);
    if(_sem_id<0)
    {   
        printf("create sem error!,error code%d\n",errno);
        return -1; 
    }   
    init_sem(_sem_id,0);//初始化信号量
    _pid=fork();//创建子进程
    if(_pid<0)
        printf("fork error!\n");
    else if(_pid ==0)
    {   
        int i=0;
        while(i++<20)
        {   
            sem_p(_sem_id,0);//在打印A之前 P 操作
             sleep(1);
            printf("A");
            fflush(stdout);
            sleep(1);
            printf("A");
            fflush(stdout);
            sem_v(_sem_id,0);//打印2次A 后 V 操作
        }
        return 0;
    }
    else
    {
        int i=0;
        while(i++<20)
        {
            sem_p(_sem_id,0);//在打印B之前 P 操作
            sleep(1);
            printf("B");
            fflush(stdout);
            sleep(1);
            printf("B");
            fflush(stdout);
            sem_v(_sem_id,0);//在打印2次B 后 P 操作
        }
         return 0;
}

代码执行结果(AA,BB成对出现 说明P、V 操作使父、子进程对输出终端互斥访问)wKioL1cQ0O7Ax0UEAAAf_0zIW0A983.png

注意:

semop操作中:sembuf结构的sem_flg成员可以为0、IPC_NOWAIT、SEM_UNDO 。为SEM_UNDO时,它将使操作系统跟踪当前进程对这个信号量的修改情况,如果这个进程在没有释放该信号量的情况下终止,操作系统将自动释放该进程持有的。若使用SEM_UNDO标志,则操作系统将自动释放该进程持有的信号量,从而使得另外一个进程可以继续工作。若没有这个标志,另外进程将P操作永远阻塞。




IPC 主题三:共享内存(最高效的通信方式)

以下转载自http://blog.csdn.net/ljianhui/article/details/10253345

一、什么是共享内存

   顾名思义,共享内存就是允许两个不相关的进程访问同一个逻辑内存。共享内存是在两个正在运行的进程之间共享和传递数据的一种非常有效的方式。不同进程之间共享的内存通常安排为同一段物理内存进程可以将同一段共享内存连接到它们自己的地址空间中,所有进程都可以访问共享内存中的地址,就好像它们是由用C语言函数malloc分配的内存一样而如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程

  特别提醒:共享内存并未提供同步机制,也就是说,在第一个进程结束对共享内存的写操作之前,并无自动机制可以阻止第二个进程开始对它进行读取。所以我们通常需要用其他的机制来同步对共享内存的访问,例如前面说到的信号量。


二、共享内存的使用

与信号量一样,在Linux中也提供了一组函数接口用于使用共享内存,而且使用共享共存的接口还与信号量的非常相似,而且比使用信号量的接口来得简单。它们声明在头文件 sys/shm.h中。

1、shmget函数

  该函数用来创建共享内存,它的原型为:

  int shmget(key_t key, size_t size, int shmflg);  

   第一个参数,与信号量的semget函数一样,程序需要提供一个参key(非0 整数),它有效地为共享内存段命名,shmget函数成功时返回一个与key相关的共享内存标识符(非负整数),用于后续的共享内存函数。调用失败返回-1.

不相关的进程可以通过该函数的返回值访问同一共享内存,它代表程序可能要使用的某个资源,程序对所有共享内存的访问都是间接的,程序先通过调用shmget函数并提供一个键,再由系统生成一个相应的共享内存标识符(shmget函数的返回值),只有shmget函数才直接使用信号量键,所有其他的信号量函数使用由semget函数返回的信号量标识符。

   第二个参数size以字节为单位指定需要共享的内存容量

   第三个参数shmflg是权限标志,它的作用与open函数的mode参数一样,如果要想在key标识的共享内存不存在时,创建它的话,可以与IPC_CREAT做或操作。共享内存的权限标志与文件的读写权限一样,举例来说,0644,它表示允许一个进程创建的共享内存被内存创建者所拥有的进程向共享内存读取和写入数据,同时其他用户创建的进程只能读取共享内存。

2、shmat函数

 第一次创建完共享内存时,它还不能被任何进程访问,shmat函数的作用就是用来启动对该共享内存的访问,并把共享内存连接到当前进程的地址空间。它的原型如下:

 void *shmat(int shm_id, const void *shm_addr, int shmflg);  

  第一个参数shm_id是由shmget函数返回的共享内存标识。

  第二个参数shm_addr指定共享内存连接到当前进程中的地址位置,通常为空,表示让系统来选择共享内存的地址。

  第三个参数shm_flg是一组标志位,通常为0

调用成功时返回一个指向共享内存第一个字节的指针,如果调用失败返回-1.

3、shmdt函数

 该函数用于将共享内存从当前进程中分离。注意,将共享内存分离并不是删除它,只是使该共享内存对当前进程不再可用。它的原型如下:

  int shmdt(const void *shmaddr);  

  参数shmaddrshmat函数返回的地址指针,调用成功时返回0,失败时返回-1.

4、shmctl函数

   与信号量的semctl函数一样,用来控制共享内存,它的原型如下:

 int shmctl(int shm_id, int command, struct shmid_ds *buf);  

  第一个参数shm_idshmget函数返回的共享内存标识符。

  第二个参数command是要采取的操作,它可以取下面的三个值 :

    IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值,即用共享内存的当前关联值覆盖shmid_ds的值。

    IPC_SET:如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值

    IPC_RMID:删除共享内存段

  第三个参数buf是一个结构指针,它指向共享内存模式和访问权限的结构。

shmid_ds结构至少包括以下成员:

  struct shmid_ds  

 {  

    uid_t shm_perm.uid;  

    uid_t shm_perm.gid;  

    mode_t shm_perm.mode;  

 };  


三、使用共享内存进行进程间通信

  说了这么多,又到了实战的时候了。下面就以两个不相关的进程来说明进程间如何通过共享内存来进行通信。其中一个文件shmread.c创建共享内存,并读取其中的信息,另一个文件shmwrite.c向共享内存中写入数据。为了方便操作和数据结构的统一,为这两个文件定义了相同的数据结构,定义在文shmdata.c中。结构shared_use_st中的written作为一个可读或可写的标志,非0:表示可读,0表示可写,text则是内存中的文件。

shmdata.h的源代码如下: 

  1. #ifndef _SHMDATA_H_HEADER

  2. #define _SHMDATA_H_HEADER  

  3.   

  4. #define TEXT_SZ 2048  

  5.   

  6. struct shared_use_st  

  7. {  

  8.     int written;//作为一个标志,非0:表示可读,0表示可写  

  9.     char text[TEXT_SZ];//记录写入和读取的文本  

  10. };  

  11.   

  12. #endif  

源文件shmread.c的源代码如下:

  1. #include <unistd.h> 

  2. #include <stdlib.h>  

  3. #include <stdio.h>  

  4. #include <sys/shm.h>  

  5. #include "shmdata.h"  

  6.   

  7. int main()  

  8. {  

  9.     int running = 1;//程序是否继续运行的标志  

  10.     void *shm = NULL;//分配的共享内存的原始首地址  

  11.     struct shared_use_st *shared;//指向shm  

  12.     int shmid;//共享内存标识符  

  13.     //创建共享内存  

  14.     shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666|IPC_CREAT);  

  15.     if(shmid == -1)  

  16.     {  

  17.         fprintf(stderr, "shmget failed\n");  

  18.         exit(EXIT_FAILURE);  

  19.     }  

  20.     //将共享内存连接到当前进程的地址空间  

  21.     shm = shmat(shmid, 0, 0);  

  22.     if(shm == (void*)-1)  

  23.     {  

  24.         fprintf(stderr, "shmat failed\n");  

  25.         exit(EXIT_FAILURE);  

  26.     }  

  27.     printf("\nMemory attached at %X\n", (int)shm);  

  28.     //设置共享内存  

  29.     shared = (struct shared_use_st*)shm;  

  30.     shared->written = 0;  

  31.     while(running)//读取共享内存中的数据  

  32.     {  

  33.         //没有进程向共享内存定数据有数据可读取  

  34.         if(shared->written != 0)  

  35.         {  

  36.             printf("You wrote: %s", shared->text);  

  37.             sleep(rand() % 3);  

  38.             //读取完数据,设置written使共享内存段可写  

  39.             shared->written = 0;  

  40.             //输入了end,退出循环(程序)  

  41.             if(strncmp(shared->text, "end", 3) == 0)  

  42.                 running = 0;  

  43.         }  

  44.         else//有其他进程在写数据,不能读取数据  

  45.             sleep(1);  

  46.     }  

  47.     //把共享内存从当前进程中分离  

  48.     if(shmdt(shm) == -1)  

  49.     {  

  50.         fprintf(stderr, "shmdt failed\n");  

  51.         exit(EXIT_FAILURE);  

  52.     }  

  53.     //删除共享内存  

  54.     if(shmctl(shmid, IPC_RMID, 0) == -1)  

  55.     {  

  56.         fprintf(stderr, "shmctl(IPC_RMID) failed\n");  

  57.         exit(EXIT_FAILURE);  

  58.     }  

  59.     exit(EXIT_SUCCESS);  

  60. }  

源文件shmwrite.c的源代码如下:  

  1. #include <unistd.h> 

  2. #include <stdlib.h>  

  3. #include <stdio.h>  

  4. #include <string.h>  

  5. #include <sys/shm.h>  

  6. #include "shmdata.h"  

  7.   

  8. int main()  

  9. {  

  10.     int running = 1;  

  11.     void *shm = NULL;  

  12.     struct shared_use_st *shared = NULL;  

  13.     char buffer[BUFSIZ + 1];//用于保存输入的文本  

  14.     int shmid;  

  15.     //创建共享内存  

  16.     shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666|IPC_CREAT);  

  17.     if(shmid == -1)  

  18.     {  

  19.         fprintf(stderr, "shmget failed\n");  

  20.         exit(EXIT_FAILURE);  

  21.     }  

  22.     //将共享内存连接到当前进程的地址空间  

  23.     shm = shmat(shmid, (void*)0, 0);  

  24.     if(shm == (void*)-1)  

  25.     {  

  26.         fprintf(stderr, "shmat failed\n");  

  27.         exit(EXIT_FAILURE);  

  28.     }  

  29.     printf("Memory attached at %X\n", (int)shm);  

  30.     //设置共享内存  

  31.     shared = (struct shared_use_st*)shm;  

  32.     while(running)//向共享内存中写数据  

  33.     {  

  34.         //数据还没有被读取,则等待数据被读取,不能向共享内存中写入文本  

  35.         while(shared->written == 1)  

  36.         {  

  37.             sleep(1);  

  38.             printf("Waiting...\n");  

  39.         }  

  40.         //向共享内存中写入数据  

  41.         printf("Enter some text: ");  

  42.         fgets(buffer, BUFSIZ, stdin);  

  43.         strncpy(shared->text, buffer, TEXT_SZ);  

  44.         //写完数据,设置written使共享内存段可读  

  45.         shared->written = 1;  

  46.         //输入了end,退出循环(程序)  

  47.         if(strncmp(buffer, "end", 3) == 0)  

  48.             running = 0;  

  49.     }  

  50.     //把共享内存从当前进程中分离  

  51.     if(shmdt(shm) == -1)  

  52.     {  

  53.         fprintf(stderr, "shmdt failed\n");  

  54.         exit(EXIT_FAILURE);  

  55.     }  

  56.     sleep(2);  

  57.     exit(EXIT_SUCCESS);  

  58. }  

 原理图:(网络资源 非原创)wKiom1cQ1-6C1edUAAGmOY2Xv4s922.jpg



备注: 

一、为什么进程间需要通信? 

1、数据传输 

一个进程需要将它的数据发送给另一个进程。 

2、资源共享 

 多个进程之间共享同样的资源。 

3、通知事件 

一个进程需要向另一个或一组进程发送消息,通知它们发生了某种事件。 

4、进程控制 

 有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有操作,并能够及时知道它的状态改变。 


Linux进程间通信(IPC )由以下几部分发展而来: 

1、UNIX进程间通信 

2、基于System V 进程间通信 

3POSIX进程间通信 


二. posix  和 system V 

  linux下的进程通信手段基本上是从Unix平台上的进程通信手段继承而来的。而对Unix发展做出重大贡献的两大主力 AT&T 的贝尔实验室及BSD (加州学伯克利分校的伯克利软件发布中)在进程间通信方面的侧重点有所不同。前者对Unix早期的进程间通信手段进行了系统的改进和扩充,形成了“system V IPC”,通信进程局限在单个计算机内;后者则跳过了该限制,形成了基于套接字(socket )的进程间通信机制。Linux则把两者继承了下来,如图: 

wKiom1cQ2i-AoC3uAABuRcp-K78069.png


其中,最初Unix IPC包括:管道、FIFO、信号;

    System V IPC包括:System V消息队列、System V信号灯、System V共享内存区;

    Posix IPC包括: Posix 消息队列、Posix信号灯、Posix共享内存区。     有两点需要简单说明下:1)由于Unix版本的多样性,电子电器工程协会(IEEE)开发了一个独立的Unix标准,这个新的ANSI Unix 标准被称为计算机环境的可移植性操作系统界面(PSOIX)。现有大部分Unix和流行版本都是遵循POSIX标准的,而Linux 从一开始就遵循POSIX标准;2)BSD 并不是没有涉单机内的进程间通信(socket 本来就可以用于单机内的进程间通信)。事实上,很多Unix版本的单机IPC 留有BSD 的痕迹,如4.4BSD持的匿名内存映射、4.3+BSD 对可靠信号语义的实现等等。 

   图一中给出了linux 所支持的各种IPC 手段,在本文接下来的讨论中,为了避免概念上的混淆,在尽可能少提及Unix的各个版本的情况下,所有问题的讨论最终都会归结到Linux 环境下的进程间通信上来。并且,对于Linux 所支持通信手段的不同实现版本(如对于共享内存来说,有Posix共享内存区以及System V共享内存区两个实现版本),将主要介绍Posix API 。 

   linux 下进程间通信的几种主要手段简介: 

1.管道(Pipe)及有名管道(named pipe):管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许非亲缘关系进程间的通信; 

 2.信号(Signal ):信号是比较复杂的通信方式,用于通知接受进程有某种事件发,除了用于进程间通信外,进程还可以发送信号给进程本身;  linux 除了支持Unix早期信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数sigaction(实际上,该函数是基于BSD 的,BSD 为了实现可靠信号机制,又能够统一对外接口,sigaction函数重新实现了signal 函数);

 3.报文(Message)队列(消息队列):消息队列是消息的链接表,包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读取队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。

       4.共享内存:使得多个进程可以访问同一块内存空间,是最快的可用IPC 形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,

来达到进程间的同步及互斥。 

 5.信号量(semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。            

 6.套接字(Socket):更为一般的进程间通信机制,可用于不同机器之间的进程间通信。起初是由Unix系统的BSD 分支开发出来的,但现在一般可以移植到其它类Unix系统上:Linux 和System V的变种都支持套接字。