Linux管道FIFO

管道FIFO

2.1 管道基本概念

​ 管道是针对于本地计算机的两个进程之间的通信而设计的通信方法,管道建立后,实际上是获得两个文件描述符:一个用与读取而另一个用于写入。任何从管道写入端写入的数据,可以从管道读取端读出。

​ 管道通信具有以下特点:

  • 管道是半双工的,数据只能向一个方向流动,需要双方通信时,要建立起两个管道。

  • 管道存放在内存中,是一种独立的文件系统。

2.2 无名管道的创建与读写

​ 系统调用pipe()用于创建一个管道,其函数原型如下:

#include <unistd.h>
int pipe(int pipefd[2]);

​ pipe()将建立一对文件描述符,放到参数pipefd中。pipefd[0]文件描述符用来从管道中读取数据,pipefd[1]用于写入数据到管道。

​ 单个进程中的管道几乎没有任何意义,通常,进程会先调用pipe,接着调用fork,从而创建从父进程到子进程的通道。

​ fork出子进程后,父进程的文件描述表也复制到子进程,于是如下图:

​ 关闭掉父进程的fd[0], 以及子进程的fd[1],则父进程可以发送消息给子进程,反之亦然。

源代码:pipe_test.c

#include <stdio.h>
#include <unistd.h>

void child_process(int pipefd[])
{
        int i = 0;
        char writebuf[128] = {0};
  
        /*子进程关闭读文件描述符*/
        close(pipefd[0]); 
        while(1)
        {
                sprintf(writebuf, "write pipe : %d", i);
          
                /*子进程往管道写入数据*/
                write(pipefd[1], writebuf, strlen(writebuf));
                printf("write pipe: %s\n", writebuf);
                i = (i + 1) % 10;
                sleep(1);
        }
}

void father_process(int pipefd[])
{
        char readbuf[128] = {0};
  
         /*父进程关闭写文件描述符*/
        close(pipefd[1]);
        while(1)
        {
                /*父进程从管道中读取数据*/
                read(pipefd[0], readbuf, sizeof(readbuf));
                printf("read pipe: %s\n", readbuf);
        }
}

int main(int argc, char** argv)
{
        int pipefd[2];
        pid_t pid;

        if( pipe(pipefd) == -1)
        {
                perror("pipe:");
                return -1;
        }

        pid = fork();
        if(pid == 0)
        {
                child_process(pipefd);
        }
        else if(pid != -1)
        {
                father_process(pipefd);
        }
        else
        {
                perror("fork:");
        }
        return 0;
}

运行结果:子进程写数据,父进程读出数据并且打印。

where@ubuntu:~$ ./pipe_test 
write pipe: write pipe : 0
read pipe: write pipe : 0
write pipe: write pipe : 1
read pipe: write pipe : 1
write pipe: write pipe : 2
read pipe: write pipe : 2
write pipe: write pipe : 3

​ 注意:匿名管道只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程)。

​ 如果管道读端被关闭,那么这个时候如果继续write()管道,会发出信号SIGPIPE。如果管道写端被关闭,

那么这个时候如果继续read()管道直接返回0。

2.3 命名管道FIFO

​ FIFO是first in first out(先进先出)的缩写,FIFO也称为“命名管道”。FIFO是一种特殊类型的管道,它在文件系统中有一个相应的文件,称为管道文件。

​ FIFO文件可以通过mkfifo()函数创建。在FIFO文件创建之后,任何一个具有适当权限的进程都可以打开FIFO文件。

  • mkfifo()函数原型为:

#include <unistd.h>
int mkfifo(const char* pathname, mode_t mode);

参数:

pathname一个FIFO文件的路径名。

mode与普通文件creat()函数中的mode参数相同。

返回值:

​ 如果要创建的文件已经存在,返回-1, errnoEEXIST错误, 成功返回0。

​ 一般文件的I/O函数都可以用于FIFO,如open()、read()、write()、close()等等。

  • 源代码fifo_write_test.c

#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <signal.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
void handle_sig(int sig)
{
    printf("signal pipe\n");
    exit(-1);
}

int main(int argc, char** argv)
{
    int fd;
    int ret;
    char buf[128];
    int i =  0;

    /*处理管道信号*/
    signal(SIGPIPE, handle_sig);
    if(mkfifo("./fifo", 0640) == -1)
    {
        if(errno !=  EEXIST)
        {
            perror("mkfifo");
            return -1;
        }
    }
    /*只写方式打开管道*/
    fd = open("./fifo", O_WRONLY);  
    if(fd == -1)
    {
        perror("open");
        return -1;
    }

    while(1)    
    {
        sprintf(buf, "data %d", i++);
        /*往管道写数据*/
        ret = write(fd, buf, strlen(buf));

        printf("write fifo [%d] %s\n", ret, buf);
      
        sleep(1);
    }
    return 0;
}

​ 源码中,通过mkfifo来创建FIFO文件,并且以只写 的方式打开,只有当两边的管道都打开的时候才能写进去,否则阻塞在write()函数上,如果管道另一端打开后被关闭,那么这个时候如果继续write()FIFO管道,会发出信号SIGPIPE.

  • 源代码fifo_read_test.c

#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <signal.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>

int main(int argc, char** argv)
{
    int fd;
    int ret;
    char buf[128];
    if(mkfifo("./fifo", 0640) == -1)
    {
        if(errno !=  EEXIST) /*如果错误类型是fifo文件已经存在,则继续执行*/
        {
            perror("mkfifo");
            return -1;
        }
    }

    /*以只读方式打开管道*/
    fd = open("./fifo", O_RDONLY);  
    if(fd == -1)
    {
        perror("open");
        return -1;
    }

    while(1)    
    {
        memset(buf, 0, sizeof(buf));
        /*读管道*/
        ret = read(fd, buf, sizeof(buf) - 1);
    
        printf("read fifo [%d] : %s\n", ret, buf);
        
        sleep(1);
    }
    return 0;
}

​ 源代码中,通过mkfifo()函数创建FIFO管道,如果已经存在那么就直接只读方式打开,如果另一端没有被打开,则阻塞在read()函数上,如果另一端打开后关闭,则read()一直读到EOF也就是0个字节。

2.4 管道容量

​ 管道有它的容量大小,通过man 7 pipe来查看。默认情况下为64k字节,也可以通过fcntl函数来查看:

#define _GNU_SOURCE //在任何头文件包含之前添加这个宏定义
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
/*
cmd:F_GETPIPE_SZ 获取管道容量大小,设置管道容量大小F_SETPIPE_SZ
*/

​ 例如:

#define _GNU_SOURCE
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
int main()
{
    if(mkfifo("./fifo", 0640) == -1)
    {
        if(errno !=  EEXIST)
        {
            perror("mkfifo");
            return -1;
        }
    }
    /*只写方式打开管道*/
    int fd = open("./fifo", O_WRONLY | O_NONBLOCK);  
    if(fd == -1)
    {
        perror("open");
        return -1;
    }

    printf("pipe size %d\n", fcntl(fd, F_GETPIPE_SZ));
    fcntl(fd, F_SETPIPE_SZ, 4096 * 3); /*必须填写4096的整数倍*/
    printf("pipe size %d\n", fcntl(fd, F_GETPIPE_SZ));
    return 0;
}
    

2.5 注意事项

​ 使用管道需要注意以下4种特殊情况(假设都是阻塞I/O操作,没有设置O_NONBLOCK标志):

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

2.如果有指向管道写端的文件描述符没关闭(管道写端的引用计数大于0),而持有管道写端的进程也没有向管道中写数据,这时有进程从管道读端读数据,那么管道中剩余的数据都被读取后,再次read会阻塞,直到管道中有数据可读了才读取数据并返回。

3.如果所有指向管道读端的文件描述符都关闭了(管道读端的引用计数等于0),这时有进程向管道的写端write,那么该进程会收到信号SIGPIPE,通常会导致进程异常终止。讲信号时会讲到怎样使SIGPIPE信号不终止进程。

4.如果有指向管道读端的文件描述符没关闭(管道读端的引用计数大于0),而持有管道读端的进程也没有从管道中读数据,这时有进程向管道写端写数据,那么在管道被写满时再次write会阻塞,直到管道中有空位置了才写入数据并返回。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值