进程通信之管道

        在谈管道之前我们先了解一下什么是进程间通信。

        每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷贝到内核,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信(IPC,InterProcess Communication)。如下图所示

                                

简单说进程间通信的本质是让不同进程看到一份共同的资源,这份共同资源一般由操作系统提供。

一、匿名管道(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。

开辟了管道之后的两个进程间的通信步骤如下

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

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

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

使用管道的一些限制:

1、两个进程通过一个管道只能实现单向通信,但也可以创建双管道实现双向通信。

2、管道只能实现有血缘关系的进程间通信。

3、管道依赖文件系统,它的生命周期随进程,即进程退出,管道不存在。

4、管道的数据更新基于字节流。

5、管道内部完成同步机制,保证数据的一致性。

使用管道需要注意以下4种特殊情况:

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

  1  #include<stdio.h>
  2  #include<unistd.h>
  3  #include<errno.h>
  4  #include<string.h>
  5  #include<sys/wait.h>
  6   int main()
  7   {
  8         int _pipe[2];
  9         int ret=pipe(_pipe);
 10         if(ret==-1)
 11         {
 12              printf("create pipe error!errno code is:%d\n",errno);
 13              return 1;
 14
 15         }
 16         pid_t id=fork();
 17         if(id<0)
 18         {
 19             printf("fork error!");
 20             return 2;
 21         }
 22         else if(id==0){//child
 23             close(_pipe[0]);
 24             int i=0;
 25             char* _mesg_c=NULL;
 26             while(i<10){
 27                 _mesg_c="i an child";
 28                 write(_pipe[1],_mesg_c,strlen(_mesg_c)+1);
 29                 sleep(1);

 30                 i++;
 31             }
 32             close(_pipe[1]);
 33         }else{//father
 34         close(_pipe[1]);
 35         char _mesg[100];
 36         int j=0;
 37         while(j<100)
 38         {
 39             memset(_mesg,'\0',sizeof(_mesg));

 40             int ret=read(_pipe[0],_mesg,sizeof(_mesg));
 41             printf("%s:code is%d\n",_mesg,ret);
 42            j++;
 43         }
 44         if(waitpid(id,NULL,0)<0)
 45         {  
 46             return 3;
 47         }
 48         }
 49         return 0;
 50         }
2、如果指向管道写端的文件描述符没关闭(管道写端的引用计数大于0),而持有管道写端的进程也没有向管道中写数据,这时进程从数据从管道读数据,那么管道中剩余的数据都被读取后,再次read会阻塞,直到管道中有数据可读才读取数据并返回。代码如下:

 1  #include<stdio.h>
  2  #include<unistd.h>
  3  #include<errno.h>
  4  #include<string.h>
  5  #include<sys/wait.h>
  6   int main()
  7   {
  8         int _pipe[2];
  9         int ret=pipe(_pipe);
 10         if(ret==-1)
 11         {
 12              printf("create pipe error!errno code is:%d\n",errno);
 13              return 1;
 14
 15         }
 16         pid_t id=fork();
 17         if(id<0)
 18         {
 19             printf("fork error!");
 20             return 2;
 21         }
 22         else if(id==0){//child
 23             close(_pipe[0]);
 24             int i=0;
 25             char* _mesg_c=NULL;
 26             while(i<20){
 27                 if(i<10){
 28                 _mesg_c="i an child";
 29                 write(_pipe[1],_mesg_c,strlen(_mesg_c)+1);
 30                 }
 31                 sleep(1);
 32                 i++;
 33             }
 34             close(_pipe[1]);
 35         }else{//father
 36         close(_pipe[1]);
 37         char _mesg[100];
 38         int j=0;
 39         while(j<20)
 40         {
 41             memset(_mesg,'\0',sizeof(_mesg));
 42             int ret=read(_pipe[0],_mesg,sizeof(_mesg));
 43             printf("%s:code is%d\n",_mesg,ret);
 44             j++;
 45         }
 46         if(waitpid(id,NULL,0)<0)
 47         {
 48             return 3;
 49         }
 50         }
 51         return 0;
 52         }
3、如果所有指向管道读端的文件描述符都关闭了(管道读端的引用计数等于0),这时有进程管道的写端write,那么进程会收到信号SIGPIPE,通常会导致进程异常终止。

 1  #include<stdio.h>
  2  #include<unistd.h>
  3  #include<errno.h>
  4  #include<string.h>
  5  #include<sys/wait.h>
  6   int main()
  7   {
  8         int _pipe[2];
  9         int ret=pipe(_pipe);
 10         if(ret==-1)
 11         {
 12              printf("create pipe error!errno code is:%d\n",errno);
 13              return 1;
 14         }
 15         pid_t id=fork();
 16         if(id<0)
 17         {
 18             printf("fork error!");
 19             return 2;
 20         }
 21         else if(id==0){//child
 22             close(_pipe[0]);
 23             int i=0;
 24             char* _mesg_c=NULL;
 25             while(i<20){
 26                 if(i<10){
 27                 _mesg_c="i an child";
 28                 write(_pipe[1],_mesg_c,strlen(_mesg_c)+1);
 29                 }
 30                 sleep(1);
 31                 i++;
 32             }
 33         }else{//father
 34         close(_pipe[1]);
 35         char _mesg[100];
 36         int j=0;
 37         while(j<3)
 38         {
 39             memset(_mesg,'\0',sizeof(_mesg));
 40             int ret=read(_pipe[0],_mesg,sizeof(_mesg));
 41             printf("%s:code is%d\n",_mesg,ret);
 42             j++;
 43         }
 44         close(_pipe[0]);
 45         sleep(10);
 46         if(waitpid(id,NULL,0)<0)
 47         {
 48             return 3;
 49         }
 50         }
 51         return 0;
 52         }
4、如果有指向管道读端的文件描述符没关闭(管道读端的引用计数大于0),而持有管道读端的进程也没有从管道中数据,这时有管道写端写数据,那么在管道被·写满时再次write会阻塞,直到管道中有空位置了才写入数据并返回。

 1  #include<stdio.h>
  2  #include<unistd.h>
  3  #include<errno.h>
  4  #include<string.h>
  5  #include<sys/wait.h>
  6   int main()
  7   {
  8         int _pipe[2];
  9         int ret=pipe(_pipe);
 10         if(ret==-1)
 11         {
 12              printf("create pipe error!errno code is:%d\n",errno);
 13              return 1;
 14         }
 15         pid_t id=fork();
 16         if(id<0)
 17         {
 18             printf("fork error!");
 19             return 2;
 20         }
 21         else if(id==0){//child
 22             close(_pipe[0]);
 23             int i=0;
 24             char* _mesg_c=NULL;
 25             while(1){
 26                 _mesg_c="i an child";
 27                 write(_pipe[1],_mesg_c,strlen(_mesg_c)+1);
 28                 i++;
 29                 printf("%d\n",i);
 30             }
 31         }else{//father
 32         close(_pipe[1]);
 33         sleep(1);
 34         if(waitpid(id,NULL,0)<0)
 35         {
 36             return 3;
 37         }
 38         }
 39         return 0;
 40         }
二、命名管道(FIFO)

概念

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

 命名管道的创建与读写

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

mknod namedpipe

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

       #include<sys/types.h>

       #include<sys/stat.h>

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

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

       函数mknod参数中path为创建的命名管道的全路径名:mod为创建的命名管道的模式u,指明其存取权限: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_IFIF0|0666)==-1)

{

perror("mkfifo error!");

exit(1);

}

        "S_IFIFO|0666"指明创建一个命名管道且存取权限为0666,即创建者、与创建者同组的用户、其他用户对命名管道的访问权限是可读可写的。

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

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

总结

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

        在Linux下一切皆文件,所以对命名管道的使用可以向平常的文件名一样在命令中使用。

         创建命名管道的函数原型如下:

          #include<sys/types.h>

          #include<sys/stat.h>

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

          int mknod(const char* filename,mode_t mode|S_IFIFO,(dev_t)0);

         这两个函数都能创建一个FIFO文件,mkfifo函数的作用是在文件系统中创建一个文件,该文件用于提供FIFO功能,即命名管道。前面讲的那些管道没有名字,因此它们被称为(匿名)管道。对文件系统来说,匿名管道只能实现有血缘关系的进程间通信。而命名管道是一个可见文件,因此,他可以用于任何两个进程间的通信。

fifo read端:

#include<stdio.h>

#include<sys/types.h>

#include<sys/stat.h>

#include<unistd.h>

#include<fcntl.h>

#include<string.h>

#define _PATH_ "/tmp/file.tmp"

#define _SIZE_ 100

int main()

{

     int fd=open(_PATH_,O_PDONLY);

     if(fd<0){

             printf("open file error!\n");

             return 1;

           }

           char but[_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;

}

fifo write端

#include<stdio.h>

#include<sys/types.h>

#include<sys/stst.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(if<0){

              printf("open error\n");

          }

          char buf[_SIZE_];

           memset(but,'\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(but,"quit",4)==0){

                                    break;

                         }

                  }

             close(fd);

                   return 0;

}

发布了53 篇原创文章 · 获赞 3 · 访问量 2万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章