linux下管道问题

来源:互联网 发布:数据库和数据源的关系 编辑:程序博客网 时间:2024/06/05 07:34

  一、管道含义

              对于管道,它是一种半双工的通信机制,也就是说,他只能一端用来读,另一端用来写; 管道通信遵循先进先出的原理,并且数据只能被读取一次,当此段数据被读取后,马上会从数据中消失。(写入的数据每次都添加到管道缓冲区的末尾,读数据的时候都是从缓冲区的头部读出数据的。

     按照管道的类别分有两种管道,匿名的和命名的;按照管道的传输方向分也可以分成两种,单向和双向的。根据管道的特点,命名管道通常用在网络环境下不同计算机上运行的进程之间的通信(当然也可以用在同一台机的不同进程中)它可以是单向或双向的匿名管道只能用在同一台计算机中,它只能是单向的。

     管道是一种最基本的IPC机制,是由pipe函数创建,当其执行后,会产生两个文件描述符,分别为读短和写端,单个进程中的管道几乎没有作用,通常先调用pipe函数,然后调fork函数,从而创建从父进程到子进程的IPC管道。管道的生命周期随进程,管道给不同进程提供同步与互斥的功能 。

  


二、管道的通信

         管道两端的任务是固定的,一端只能用于读,有描述符fd[0]表示,称其为管道读端;另一端只能用于写,由描述符fd[1]来表示,称其为管道写端;  

  

          通信方式:1、父进程调用pipe开辟管道,得到两个文件描述符指向管道的两端;

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

                            3、父进程关闭管道读端,子进程关闭管道写端。父进程向管道里写数据,子进程可以从管道内读数据。数据从写端流入从读端流出,这样就实现了父子进程之间的通信。

三、(匿名)管道的简单实现   

    从本质上说,管道也是一种文件,但它又和一般的文件有所不同,管道可以克服使用文件进行通信的两个问题,具体表现为:

       1、 限制管道的大小。实际上,管道是一个固定大小的缓冲区在Linux中,该缓冲区的大小为1页,即4K字节,使得它的大小不像文件那样不加检验地增长总缓冲区大小:64K。使用单个固定缓冲区也会带来问题,比如在写管道时可能变满,当这种情况发生时,随后对管道的write()调用将默认地被阻塞,那么只有等待某些数据被读取,以便腾出足够的空间供write()调用写。

     2、读取进程也可能工作得比写进程快。当所有当前进程数据已被读取时,管道变空。当这种情况发生时,一个随后的read()调用将默认地被阻塞,只有等待某些数据被写入。

#include <unistd.h>  #include <stdio.h>  int main()  {      int fd[2];                              //管道入口fd[0]为读,fd[1]为写入口      char r_buf[100];                                  char w_buf[20]= "hello word!";      pid_t pid;      if(pipe(fd) < 0)                               {          printf("pipe error!\n");          exit(1);      }        if((pid = fork()) < 0)      {          printf("fork error!\n");      }else if(pid == 0)                  //child          {              printf("child\n");              close(fd[1]);               //关闭写端口              sleep(2);                   //程序休眠2秒              read(fd[0], r_buf, 100);//读阻塞,当管道内写入东西后才读取,所以当主程序运行完成后,子程序才运行完成。子程序由init管理              printf("%s",r_buf);          }else                                   //主进程              {                  printf("father\n");                  close(fd[0]);                  sleep(2);                  write(fd[1], w_buf, 20);              }            return 0;  }  
   使用管道需要注意以下4种特殊情况(假设都是阻塞I/O操作,没有设置 O_NONBLOCK 标志):
   1. 如果所有指向管道写端的文件描述符都关闭了(管道写端的引用计数等于0),而仍然有进程从管道的读端读数据,那么管道中剩余的数据都被读取后,再次 read 会返回0,就像读到文件末尾一样。
   2. 如果有指向管道写端的文件描述符没关闭(管道写端的引用计数大于0),而持有管道写端的进程也没有向管道中写数据,这时有进程从管道读端读数据,那么管道中剩余的数据都被读取后,再次 read 会阻塞,直到管道中有数据可读了才读取数据并返回。
   3. 如果所有指向管道读端的文件描述符都关闭了(管道读端的引用计数等于0),这时有进程向管道的写端write ,那么该进程会收到信号 SIGPIPE ,通常会导致进程异常终止。
   4. 如果有指向管道读端的文件描述符没关闭(管道读端的引用计数大于0),而持有管道读端的进程也没有从管道中读数据,这时有进程向管道写端写数据,那么在管道被写满时再次 write 会阻塞,直到管道中有空位置了才写入数据并返回。

四、命名管道

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

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

mknod创建了一个命名管道:
mknod namedpipe
   创建命名管道的系统函数有两个:mknod和mkfifo。



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

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

     函数mknod参数中pathname为创建有名管道的全路径名,mode为创建命名管道的模式,指明其存取权限;dev为设备值,该值取决于文件创建的种类,它只在创建设备文件是才会用到。这两个函数调用成功都返回0,否则返回-1.
   读写命名管道:

ssize_t read (int fd , void * buf , size_t nbytes)

ssize_t write (int fd , void * buf , size_t nbytes)

五、管道的环形缓冲区

   每个管道只有一个页面作为缓冲区,该页面是按照环形缓冲区的方式来使用的。

    这种方式就像生产者和消费者问题:

  



0 0
原创粉丝点击