Linux进程间通信之——管道(整理)

来源:互联网 发布:网络传输技术种类 编辑:程序博客网 时间:2024/06/13 03:36

进程间通信 fork pipe pie_t 等用法(管道机制 通信)

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

图 30.6. 进程间通信

http://img.ph.126.net/5YVr6gmhJ-newCLdPuMJzg==/6597841623820643769.jpg

4.1. 管道

管道创建的原型函数为:
int pipe(int fd[2]);
其中fd[2]为用于创建的管道的两端,其中fd[0]为读端,fd[1]为写端,这两端任务是固定的,不能混乱,如果混乱,将会导致错误出现

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

int pipe(int filedes[2]) 中的两个文件描述符被强制规定filedes[0]只能指向管道的读端,如果进行写操作就会出现错误;同理filedes[1]只能指向管道的写端,如果进行读操作就会出现错误
开辟了管道之后如何实现两个进程间的通信呢?比如可以按下面的步骤通信。

图 30.7. 管道

 

 

  1. 父进程调用pipe开辟管道,得到两个文件描述符指向管道的两端。 
2. 父进程调用fork创建子进程,那么子进程也有两个文件描述符指向同一管道。 
3. 父进程关闭管道读端,子进程关闭管道写端。父进程可以往管道里写,子进程可以从管道里读,管道是用环形队列实现的,数据从写端流入从读端流出,这样就实现了进程间通信。

注:之前一直没明白为什么在这里父进程要关闭管道读端,并且子进程要关闭管道写端,想了很久终于想通了...,原因如下:因为上面的这个程序是要模拟父进程和子进程的管道读写操作,其中父进程用于向管道中写入数据,子进程用于向管道中读取数据,因此开始要关闭父进程的读文件描述符filedes[0], 以及关闭子进程的写文件描述符filedes[1],这是为了模拟这个过程。

        然后至于为什么父进程关闭管道的读文件描述符filedes[0]后子进程还能读取管道的数据,是因为系统维护的是一个文件的文件描述符表的计数,父子进程都各自有指向相同文件的文件描述符,当关闭一个文件描述符时,相应计数减一,当这个计数减到0时,文件就被关闭,因此虽然父进程关闭了其文件描述符filedes[0],但是这个文件的文件描述符计数还没等于0,所以子进程还可以读取。也可以这么理解,父进程和子进程都有各自的文件描述符,因此虽然父进程中关闭了filedes[0],但是对子进程中的filedes[0]没有影响


文件表中的每一项都会维护一个引用计数,标识该表项被多少个文件描述符(fd)引用,在引用计数为0的时候,表项才会被删除。所以调用close(fd)关闭子进程的文件描述符,只会减少引用计数,但是不会使文件表项被清除,所以父进程依旧可以访问


        最后需要注意,在linux的pipe管道下,在写端进行写数据时,不需要关闭读端的缓冲文件(即不需要读端的文件描述符计数为0),但是在读端进行读数据时必须先关闭写端的缓冲文件(即写端的文件描述符计数为0)然后才能读取数据。


使用管道有一些限制: 
? 两个进程通过一个管道只能实现单向通信,比如上面的例子,父进程写子进程读,如果有时候也需要子进程写父进程读,就必须另开一个管道。请读者思考,如果只开一个管道,但是父进程不关闭读端,子进程也不关闭写端,双方都有读端和写端,为什么不能实现双向通信? 
? 管道的读写端通过打开的文件描述符来传递,因此要通信的两个进程必须从它们的公共祖先那里继承管道文件描述符。上面的例子是父进程把文件描述符传给子进程 之后父子进程之间通信,也可以父进程fork两次,把文件描述符传给两个子进程,然后两个子进程之间通信,总之需要通过fork传递文件描述符使两个进程 都能访问同一管道,它们才能通信。 
使用管道需要注意以下4种特殊情况(假设都是阻塞I/O操作,没有设置O_NONBLOCK标志): 
1. 如果所有指向管道写端的文件描述符都关闭了(管道写端的引用计数等于0),而仍然有进程从管道的读端读数据,那么管道中剩余的数据都被读取后,再次read会返回0,就像读到文件末尾一样。 
2. 如果有指向管道写端的文件描述符没关闭(管道写端的引用计数大于0),而持有管道写端的进程也没有向管道中写数据,这时有进程从管道读端读数据,那么管道中剩余的数据都被读取后,再次read会阻塞,直到管道中有数据可读了才读取数据并返回。 
3. 如果所有指向管道读端的文件描述符都关闭了(管道读端的引用计数等于0),这时有进程向管道的写端write,那么该进程会收到信号SIGPIPE,通常会导致进程异常终止。在第 33 章 信号会讲到怎样使SIGPIPE信号不终止进程。 
4. 如果有指向管道读端的文件描述符没关闭(管道读端的引用计数大于0),而持有管道读端的进程也没有从管道中读数据,这时有进程向管道写端写数据,那么在管道被写满时再次write会阻塞,直到管道中有空位置了才写入数据并返回。 
管道的这四种特殊情况具有普遍意义。在第 37 章 socket编程要讲的TCP socket也具有管道的这些特性。



有名管道


下面我们看一下有名管道:上面的管道我们很容易看得出它的局限性,那么有名管道就会解决那种问题,即他可以使任意两个进程之间通信。有名管道是一个存在于硬盘上的文件。
有两个函数可以创建有名管道:
int mknod(const char *path,mode_t mod,dev_t dev);
int mkfifo(const char *path,mode_t mode);
有名管道和管道的使用方法相同,只是使用有名管道时必须使用open()函数将其打开。
值得注意的是:调用open()打开有名管道的进程可能会被阻塞,但如果同时用读写方式(O_RDWR)打开,则一定不会阻塞,如果以只读方式打开,则一定会阻塞直到有写的进程打开管道,同样以写的方式打开的时候也会被阻塞,知道有读的进程打开管道,下面我们通过一个具体的例子更深一步的了解有名管道的使用方法:

点击(此处)折叠或打开

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. #include<string.h>
  4. #include<fcntl.h>
  5. #include<sys/types.h>
  6. #include<sys/stat.h>
  7. #define FIFO_NAME "myfifo"
  8. #define BUF_SIZE 1024
  9. int main()
  10. {
  11.     int fd;
  12.     char buf[BUF_SIZE]="hello procwrite,i come frome process named procread";
  13. //    umask(0);


  14.     if(mkfifo(FIFO_NAME,S_IFIFO|0666)==-1){
  15.     
  16.         perror("mkfifo error!");
  17.         exit(0);
  18.     }
  19.     if((fd=open(FIFO_NAME,O_WRONLY))==-1){

  20.         perror("open fifo error!");
  21.         exit(0);
  22.     }
  23.     write(fd,buf,strlen(buf)+1);
  24.     close(fd);
  25.     exit(0);
  26. }

点击(此处)折叠或打开

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. #include<unistd.h>
  4. #include<sys/stat.h>
  5. #include<fcntl.h>
  6. #include<sys/types.h>
  7. #define FIFO_NAME "myfifo"
  8. #define BUF_SIZE 1024
  9. int main(void)
  10. {
  11.     int fd;
  12.     char buf[BUF_SIZE];
  13. //    umask(0);
  14.     fd=open(FIFO_NAME,O_RDONLY);
  15.     read(fd,buf,BUF_SIZE);
  16.     printf("read content:%s\n",buf);
  17.     exit(0);
  18. }

刚开始由于好奇,当进程退出之后我就看见目录下面生成了一个文件夹myfifo,奇怪的是与其他文件夹的颜色是不同的,我就试图察看它的内容,结果里面是空的,我当时就纳了闷了,明明是一个文件夹,明明写进去东西的,而且另一个进程也从中读出了内容阿,难道是内容被读走了以后就不存在在有名管道中了,但是它不也是一个文件么,写进去的内容怎么会不见呢,难道不是一般的文件?于是上网查了查?结果如下:
有名管道是有名有形的,为了使用这种管道Linux中设立了一个专门的特殊文件系统--管道文件,它存在于文件系统中,任何进程可以在任何时候通过有名管道的路径和文件来访问管道,但是在磁盘上的只是一个节点,而文件的数据则只存在于内存缓冲页面中与普通管道一样。哦,原来是这样,看来它还确实跟普通的文件不一样呢!
0 0
原创粉丝点击