Linux 进程通信1 - pipe & fifo

进程通信(IPC)是进程知识点中重要的一环,本篇意在介绍几种常用的通信技术及其基本用法。常用的IPC通信方式有:

  • 管道
  • FIFO
  • 信号
  • 消息队列
  • 共享内存
  • Socke

下面我们就从pipe 开始说起吧。

1. 管道(pipe)

管道是最古老也是最常用的IPC方式,具有实现简单,使用也简单的优点。但这种古老而经典的通信方式应用于现在的系统,就有了一定的局限性。pipe的基本思想可以如下:

父进程创建了一个文件,然后fork一个子进程,子进程会继承这个文件描述符,。这样父子进程就可以通过这个文件去通信,同样两个兄弟进程之间也可以利用这个文件去通信。如果这个文件没有名字而且只能单方向流通数据,那就是我们所说的管道了。下图就是一个常见的管道通信模型。

1.1  管道的创建

 #include<unistd.h>

 int pipe(int fd[2]);

pipe()函数通过fd数组返回两个文件描述符,fd[0]为读打开,相当于输入;fd[1]为写打开,相当于输出,fd[1]的输出是fd[0]的输入。这里的读写都是相对于pipe文件来说。

如果两个进程分别关闭读端和写端,则这两个进程就可以通过这个管道建立起一个共享信息的通道。

举个例子,父进程关闭 fd[0],子进程关闭fd[1],这样就建立了一个从父进程流向子进程的pipe通道。

1.2 管道读写

读写管道类似与读写文件,可以直接用write和 read 来读写。但是读数据时,每读一段数据,管道会自动清除已读走的数据。管道可以用于多个读写进程的通信,但是通常只有一个读进程和一个写进程,否则没有同步机制,不是很容易发生可重入问题吗?

  • 读管道时,如果管道为空,则读进程阻塞,直到写端往里面写入数据。如果写端已经关闭的话,则读端在读完数据后,下一个read会返回0,表示文件已经结束。
  • 写管道时,如果管道已满,写进程阻塞,直到读端读走数据。如果读端已经关闭,则会产生SIGPIPE信号,如果我们对该信号的处理方式是忽略,或者从其信号处理程序返回,则write 返回-1,errno设置为EPIPE。

问题是我们怎么知道读端或者写端已经关闭呢?

管道的读写两端都有一个计数器,当计数器为0时不就说明读端或者写端已经 关闭了吗?管道自己会判断的!

来吧,用事实说话,写个程序验证下上述说的第二点:

#include<stdio.h>  
#include<stdlib.h>  
#include<unistd.h>  
#include<signal.h>  
  
#define MAXLINE 2047  
  
static void sig_pipe(int signo)  
{  
    printf("signal sigpipe\n");  
}  
  
int main(int argc, char *argv[])  
{  
    int cout = 10;  
    int fd[2];  
    int n = 0;  
    pid_t pid;  
    char line[MAXLINE];  
    ssize_t m;  
      
    if(pipe(fd) < 0)  
    {  
        printf("creat pipe fail\n");      
    }  
  
    pid = fork();  
  
    if(pid < 0)  
    {  
        printf("fork fail\n");  
    }  
    else if(pid == 0)  
    {  
        close(fd[0]);  
        if(signal(SIGPIPE,sig_pipe) == SIG_ERR)  
        {  
            printf("signal error\n");  
        }   
        while(cout--)  
        {  
            printf("write data to pipe\n");  
            if(write(fd[1],"hello world\n",12) == -1)  
            {  
                printf("write fail\n");  
              
            }  
            sleep(1);  
        }  
    }  
    else  
    {  
        close(fd[1]); 
        while(1)  
        {  
            n = read(fd[0],line,MAXLINE);  
            write(STDOUT_FILENO,line,n);  
            close(fd[0]);  
        }  
    }  
  
    return 0;  
}

以上程序,在子进程每隔10s向管道写入数据,父进程则一直从管道读入数据。当父进程突然关闭读端时,子进程的写端会产生SIGPIPE 信号,然后从SIGPIPE处理程序返回后,write 返回-1,执行结果为:

写到这里,不妨先对pipe进行一个简单的总结:

  1. 半双工通信,管道只允许单方向流通数据;原因很简单:使用管道也没有其他的同步机制,全双工的话就会存在数据覆盖的情况;
  2. pipe只允许在具有血缘关系的进程之间通信;这一点也是显而易见的,通信的进程间需要共享文件描述符呢;
  3. 因pipe是内核直接建立在内存中的,所以它的容量大小是由限制的。而且pipe中传输的是无格式字节流,所以需要通信双方自己约定格式协议。

1.3  popen和pclose 函数

      上节中的例程是最用的pipe 用法,父进程先创建一个pipe,fork一个子进程,然后父进程关闭写端,子进程关闭读端。本节中将要介绍的是一组多功能pipe相关函数,popen函数。先来看下这组函数的定义:

#include<stdio.h>
  
FILE *popen(const char *cmdstring, const char *type);
//成功返回文件指针,失败返回NULL

int pclose*(FILE *fp);
//成功返回shell 的终止状态,出错返回-1

popen函数的执行过程如下:

  • 先创建一个管道
  • 随后fork一个子进程,子进程通过exec 函数调用shell执行 cmdstring命令,即execl(“/bin/sh”,”-c”,”cmdstring”,NULL)
  • popen函数返回一个指向输入输出流的文件指针fp。管道是单双工通信,这个的文件指针只能对应输入或者输出流。数据      流动的方向则取决于type参数:

1). 若type = ‘r’, 于输入流文件指针;则fp相当此时fp与子进程执行cmd 的stdout直接相连,即stdout的数据会流向fp。换个角               度fd这里相当于管道的fd[0],子进程的stdout相当于fd[1]

2). 若type = ‘w’, 于输出流文件指针;则fp相当此时fp与子进程执行cmd 的stdin直接相连,即fp的数据会流向shell命令。换个               角度fd这里相当于管道的fd[1],子进程的stdout相当于fd[0]

关于popen函数还有以下几点需要说明下:

  • popen函数返回的文件指针fp同open函数获取的文件指针一样,可以用文件IO相关函数去操作,但不能用fclose去关闭
  • popen函数的输出流默认是全缓冲的

pclose 函数等待相关的进程结束并返回shell执行cmdstring命令的退出状态,随后会关闭popen创建的pipe以及文件指针。

 

下面这个不成熟的例子中,使用popen去执行“ps –ef”命令,并将结果显示出来:执行结果:

1.	#include<stdio.h>  
2.	#include<stdlib.h>  
3.	#include<unistd.h>  
4.	#define MAXLINE  1024  
5.	  
6.	int main(int argc, char *argv[])  
7.	{  
8.	    int n = 0;  
9.	    FILE *fp = NULL;      
10.	    char buf[MAXLINE];  
11.	    fp = popen("ps -ef","r");  
12.	    n = fread(buf,1,MAXLINE,fp); 
13.	    if(fputs(buf,stdout) == EOF)  
14.	    {  
15.	        printf("error\n");  
16.	    }  
17.	    pclose(fp);  
18.	    return 0;  
19.	}  

执行结果:

2. fifo

fifo类似于pipe,也是一种特殊管道文件,但不同于管道只能用于具有共同祖先的两个进程,fifo是有名字的,可以让不相关的进程进行通信,所以通常又被成为有名管道。创建fifo 的函数如下:

 

 #include<sys/stat.h>

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

 int mkfifoat(int fd, const char *path, mode_t mode )

创建成功后,会在指定的path下生成一个fifo文件,同时函数返回0;失败的话返回-1。

关于fifo,我们不妨大致的总结下吧:

  • 创建的fifo文件类似于creat函数,使用的话需要先open,文件IO函数也可以用于fifo文件(openwriteread、unlink等),也可以用S_ISFIFO对创建的fifo文件进行测试
  • Fifo遵循先入先出的原则,且其中的数据读取后就消失了
  • 没有指定O_NONBLCK标志时,只读open要阻塞到其它进程为写而打开这个fifo为止;只写write也会阻塞知道该fifo被其它进程以读的方式打开
  • 如果指定了O_NONBLCK标志,只读open或者只写open会立即返回
  • 读一个写端突然关闭的fifo,会返回文件结束标志;写一个读端突然关闭的fifo会产生一个SIGPIPE信号,这与pipe类似。

下面是一个简单的例子来看看fifo 是怎么用的,例程中,在子进程中创建了一个fifo,然后以只读的方式打开,并写入一些数据;在父进程中读取这个fifo中的数据并显示出来:

1.	#include<stdio.h>   
2.	#include<stdlib.h>   
3.	#include<sys/stat.h>  
4.	#include<sys/types.h>  
5.	#include<unistd.h>  
6.	#include<fcntl.h>  
7.	#include<string.h>  
8.	  
9.	# define FIFO_NAME  "/tmp/fifo2"  
10.	# define MAXLINE 1048  
11.	  
12.	int main(int argc, const char *argv[])   
13.	{  
14.	    int fd;  
15.	    pid_t pid;  
16.	  
17.	    pid = fork();  
18.	  
19.	    if(pid < 0)  
20.	    {  
21.	        printf("fork error\n");  
22.	    }  
23.	    else if(pid == 0)  
24.	    {  
25.	        if(mkfifo(FIFO_NAME,0777) == -1)  
26.	        {  
27.	            printf("fail\n");  
28.	        }  
29.	  
30.	        fd = open(FIFO_NAME,O_WRONLY);  
31.	        write(fd,"TEST\n",6);  
32.	        close(fd);  
33.	    }  
34.	    else  
35.	    {  
36.	        char ptr[MAXLINE];  
37.	        int n = 0;  
38.	        sleep(2);  
39.	        fd = open(FIFO_NAME,O_RDONLY);  
40.	        memset(ptr,'\0',sizeof(ptr));  
41.	        n = read(fd,ptr,MAXLINE);  
42.	        write(STDOUT_FILENO,ptr,n);  
43.	        close(fd);   
44.	    }  
45.	    return 0;  
46.	}  

fifo的两个经典应用场景:

  • Shell命令使用FIFO 将数据从一条管道传送到另一条时,而无需创建临时文件用来保存文件;
  • 客户进程-服务进程应用程序中,fifo用作汇聚点,在客户进程服务进程之间通信。

关于pipe和 fifo 的使用还有一个需要注意的点:

对fifo或者pipe进行写的时候,如果要求写入的字节数小于PIPE_BUF,则写操作不会与其他进程对同一管道的write操作交叉,但是如果多个进程同时去写,且要求写的字节数大于PIPE_BUF就会产生在竞争问题。

这里的PIPE_BUF是内核规定的管道缓冲区大小,我们可以通过pathconf函数或者fpathconfg函数获取。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值