进程间通信(IPC)-----------------管道(pipe)和命名管道(fifo)

本文介绍了Linux中的进程间通信(IPC)方式,重点讲解了管道(pipe)和命名管道(FIFO)的区别与使用。管道是半双工的,仅在有亲缘关系的进程间通信,而命名管道可以实现任意进程间的通信,且具有文件系统中的位置。文中还给出了代码示例,展示如何创建和使用管道进行数据传输。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.二者的异同

管道只能在两个具有亲缘关系的进程之间通信,通过命名管道(Named PiPe)FIFO,不相关的进程也能 交换数据。FIFO不同于管道之处在于它提供一个路径与之关联,以FIFO的文件形式存在于系统中。它在磁盘上有对应的节点,但 没有数据块——换言之,只是拥有一个名字和相应的访问权限,通过mknode()系统调用或者mkfifo()函数来建立的。一旦建 立,任何进程都可以通过文件名将其打开和进行读写,而不局限于父子进程,当然前提是进程对FIFO有适当的访问权。当不再被 进程使用时,FIFO在内存中释放,但磁盘节点仍然存在。

2.管道(pipe)

管道是UNIX系统IPC的最古老的形式,所有的UNIX系统都提供此种通信机制。管道的实质是一个内核缓冲区,进程以先进 先出(FIFO, First In First Out)的方式从缓冲区存取数据:管道一端的进程顺序地将进程数据写入缓冲区,另一端的进程则顺 序地读取数据,该缓冲区可以看做一个循环队列,读和写的位置都是自动增加的,一个数据只能被读一次,读出以后再缓冲区都 不复存在了。当缓冲区读空或者写满时,有一定的规则控制相应的读进程或写进程是否进入等待队列,当空的缓冲区有新数据写 入或慢的缓冲区有数据读出时,就唤醒等待队列中的进程继续读写。

  1. 两个局限性:     
    (1)半双工,数据只能在一个方向流动,现在有些系统可以支持全双工管道,但是为了最佳的可移植性,应认为系统 不支持全双工管道;     
    (2)管道只能在具有公共祖先之间的两个进程之间使用;

  2. 管道的创建:    
    管道可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不 属于其他任何文件系统,并且只存在于内存中。管道是通过调用pipe函数创建的。

#include <unistd.h> 
int pipe(int fd[2]);      //返回值:若成功,返回0,若出错,返回-1.

经由参数fd返回的两个文件描述符:fd[0]为读而打开,fd[1]为写而打开,fd[1]的输出是fd[0]的输入。

通常,进程会先调用pipe,接着调用fork,从而创建了父进程与子进程的IPC通道。fork之后做什么取决于我们想要的数据流的方向,对于从父进程到子进程,父进程关闭管道的读端fd[0],子进程关闭写端 fd[1]。
如下图:
在这里插入图片描述
在这里插入图片描述
而子进程到父进程则是相反,父进程关闭写端,子进程关闭读端。

  1. 关闭管道的一端     
    (1)当读一个写端被关闭的管道时,在所有数据都被读取后,read返回0,表示文件结束;     
    (2)当写一个读端被关闭的管道时,则产生信号SIGPIPE,如果忽略该信号或者捕捉该信号并从其处理程序返回,则 wirte返回-1.

父子进程示例:
首先父进程创建管道之后fork(),这时子进程会继承父进程所有打开的 文件描述符(包括管道),这时对于一个管道就有4个读写端(父子进程各有一对管道读写端),如果需要父进程往子进程里写 数据,则需要在父进程中关闭读端,在子进程中关闭写端;而如果需要子进程往父进程中写数据,则可以在父进程关闭写端,然 后子进程中关闭读端。

代码示例:

  1 #include<stdio.h>
  2 #include<string.h>
  3 #include<unistd.h>
  4 #include<errno.h>
  5 #include<sys/types.h>
  6 #include<sys/wait.h>
  7 
  8 #define WXJ "hello, child process!"
  9 
 10 int main(int argc, char **argv)
 11 {
 12         int             pipe_fd[2];
 13         int             rv;
 14         int             pid;
 15         char            buf[1024];
 16         int             wstatus;
 17 
 18         if(pipe(pipe_fd) < 0)
 19         {
 20                 printf("Create pipe failture:%s\n", strerror(errno));
 21                 return -1;
 22         }
 23 
 24         if((pid = fork()) < 0)
 25         {
 26                 printf("Create child process failture:%s\n", strerror(errno));
 27                 return -2;
 28         }
 29         else if(pid == 0)
 30         {
 31                 /*child process close write endpoint, then read data from parent process*/
 32                 close(pipe_fd[1]);
 33 
 34                 memset(buf, 0, sizeof(buf));
 35                 rv = read(pipe_fd[0], buf, sizeof(buf));
 36                 if(rv < 0)
 37                 {
 38                         printf("Child process read from pipe failture:%s\n", strerror(errno));
 39                         return -3;
 40                 }
 41 
 42                 printf("Child process read %d bytes data from pipe:\"%s\"\n", rv, buf);
 43                 return 0;
 44         }
 45 
 46         /*parent process close read endpoint, then write data to child process*/
 47         close(pipe_fd[0]);
 48         if(write(pipe_fd[1], WXJ, strlen(WXJ)) < 0)
 49         {
 50                 printf("Parent process write data to pipe failture:%s\n", strerror(errno));
 51                 return -3;
 52         }
 53 
 54         printf("Parent start wait child process exit...\n");
 55         wait(&wstatus);
 56 
 57         return 0;
 58 }

运行结果:
在这里插入图片描述

3.命名管道(fifo)

命名管道(FIFO)又称为先入先出队列,是一种特殊的管道,存在于文件系统中。命名管道一旦创建,就可以像普通文件一样进行读写操作。

特点:

  1. 命名管道可以用于任何两个进程间的通信,并不限制这两个进程同源;
  2. 数据被一个进程读出后,将被从管道中删除,其他读进程不能再读到这些数据;
  3. 命名管道作为一种特殊的文件存在于文件系统中,而不像管道一样存在于内核中。当进程对命名管道使用完毕,命名管道任然存在于文件系统中,除非对其进行删除操作,否则该命名管道不会消失;
  4. 命名管道只能用于单向数据传输,如果要用命名管道实现两个进程间数据的相互交换,需要使用两个命名管道。

命名管道的创建:

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);

解析:参数pathname是一个字符串指针,用于存放命名管道的文件名。参数mode用于指定所创建的文件的权限,其取值及含义与创建普通文件的creat函数中的mode相似。调用成功,返回值0;调用失败,返回值-1。

注意:

  1. 一旦创建了一个FIFO,就可以用open()打开它,一般的文件访问函数(read和write)进行读写,使用系统调用close关闭一个命名管道。若要删除一个命名管道,使用系统调用unlink;
  2. 使用命名管道时,需要在两个将进行通信的进程中分别打开命名管道,因此当一个进程读打开(或写打开)一个命名管道而没有其他进程写打开(或读打开)此命名管道,该进程就会进入阻塞状态,直到另一个进程写打开(或读打开)此命名管道;
  3. 打开FIFO,非阻塞标志(O_NONBLOCK)对以后的读写产生如下影响:
    没有使用O_NONBLOCK,访问要求无法满足时进程阻塞,如果试图读取空的FIFO,将导致进程阻塞;若使用O_NONBLOCK,访问时无法满足要求时阻塞。

代码示例:
下面这个例程创建了两个掩藏的命名管道文件(.fifo_chat1和.fifo_chat2)在不同的进程间进行双向通信。该程序需要运行两 次(即两个进程),其中进程0(mode=0)从标准输入里读入数据后通过命名管道2(.fifo_chat2)写入数据给进程 1(mode=1);而进程1(mode=1)则从标准输入里读入数据后通过命名管道1(.fifo_chat1)写给进程0,这样使用命名管 道实现了一个进程间聊天的程序。

  1 #include<stdio.h>
  2 #include<string.h>
  3 #include<unistd.h>
  4 #include<errno.h>
  5 #include<fcntl.h>
  6 #include<sys/types.h>
  7 #include<sys/wait.h>
  8 #include<sys/stat.h>
  9 #include<libgen.h>
 10 #include<stdlib.h>
 11 
 12 #define FIFO_FILE1    ".fifo_chat1"
 13 #define FIFO_FILE2    ".fifo_chat2"
 14 
 15 int g_stop = 0;
 16 
 17 void sig_pipe(int signum)
 18 {
 19         if(SIGPIPE == signum)
 20         {
 21                 printf("get pipe broken signal and let progname exit\n");
 22                 g_stop = 1;
 23         }
 24 }
 25 
 26 int main(int argc, char **argv)
 27 {
 28         int             fdr_fifo;
 29         int             fdw_fifo;
 30         int             rv;
 31         fd_set          rdset;
 32         char            buf[1024];
 33         int             mode = 0;
 34 
 35         if(argc!=2)
 36         {
 37                 printf("Usage:%s [0/1]\n", basename(argv[0]));
 38         printf("This chat progname need run twice, 1st time run with [0] and 2nd time with [1]\n");
 39                return -1;
 40         }
 41 
 42         mode = atoi(argv[1]);
 43 
 44         if(access(FIFO_FILE1, F_OK))
 45         {
 46                 printf("FIFO file \"%s\" not exist and create it now\n", FIFO_FILE1);
 47                 mkfifo(FIFO_FILE1, 0666);
 48         }
 49 
 50         if(access(FIFO_FILE2, F_OK))
 51         {
 52                 printf("FIFO file \"%s\" not exist and create it now\n", FIFO_FILE2);
 53                 mkfifo(FIFO_FILE2, 0666);
 54         }
 55 
 56         signal(SIGPIPE, sig_pipe);
 57 
 58         if( 0 == mode)
 59         {
 60                 printf("start open '%s' for and it will blocked untill write endpoint opened...\n", FIFO_FILE1);
 61                 if((fdr_fifo=open(FIFO_FILE1, O_RDONLY)) < 0)
 62                 {
 63                         printf("Open fifo[%s] for chat read endpoint failture:%s\n",FIFO_FILE1,strerror(errno));
 64                         return -1;
 65                 }
 66 
 67                 printf("start open '%s' for write...\n", FIFO_FILE2);
 68                 if((fdw_fifo=open(FIFO_FILE2, O_WRONLY)) < 0)
 69                 {
 70                         printf("Open fifo[%s] for chat write endpoint failture:%s\n",FIFO_FILE2,strerror(errno));
 71                         return -1;
 72                 }
 73         }
 74         else
 75         {
 76        printf("start open '%s' for write and it will blocked untill read endpoint opened...\n",FIFO_FILE1);
 77         if((fdw_fifo=open(FIFO_FILE1, O_WRONLY)) < 0)
 78         {
 79                 printf("Open fifo[%s] for chat write endpoint failture:%s\n",FIFO_FILE1,strerror(errno));
 80                 return -1;
 81         }
 82 
 83         printf("start open '%s' for read...\n",FIFO_FILE2);
 84         if((fdr_fifo=open(FIFO_FILE2, O_RDONLY)) < 0)
 85         {
 86                 printf("Open fifo[%s] for chat read endpoint failture:%s\n",FIFO_FILE2,strerror(errno));
 87                 return -1;
 88         }
 89     }
 90         printf("start chating with another program now,please input message now:\n");
 91         while(!g_stop)
 92         {
 93                 FD_ZERO(&rdset);
 94                 FD_SET(STDIN_FILENO,&rdset);
 95                 FD_SET(fdr_fifo, &rdset);
 96 
 97                 rv=select(fdr_fifo+1,&rdset, NULL,NULL,NULL);
 98                 if(rv <= 0)
 99                 {
100                         printf("Select get timeout or error:%s\n",strerror(errno));
101                         continue;
102                 }
103 
104                 if(FD_ISSET(fdr_fifo, &rdset))
105                 {
106                         memset(buf, 0, sizeof(buf));
107                         rv=read(fdr_fifo, buf, sizeof(buf));
108                         if(rv<0)
109                         {
110                                 printf("Read data from FIFO get error:%s\n",strerror(errno));
111                                 break;
112                         }
113                         else if( 0==rv)
114                         {
115                                 printf("Another side of FIFO get closed and programwillexitnow\n");
116                                 break;
117                         }
118                         printf("<--%s", buf);
119 
120                         if(FD_ISSET(STDIN_FILENO, &rdset))
121                         {
122                                 memset(buf, 0, sizeof(buf));
123                                 fgets(buf, sizeof(buf), stdin);
124                                 write(fdw_fifo, buf, strlen(buf));
125                         }
126                 }
127         }
128 }
129 

运行结果:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值