Linux进程间通信之管道

管道



管道,你可以把它想成一根数据线,连接了两个进程,使他们可以互相通信。更严谨来说,它是一个文件或者一块共享区,一个进程往里面写数据,另一个进程从里面拿数据,以此种方式完成进程间通信。
管道是UNIX系统IPC最古老的形式,所有的UNIX系统都提供此种通信机制(UNIX系统IPC是各种进程通信方式的统称)。
管道在进行通信时,基于字节流

管道可分为两类:

  • 匿名管道,只能完成具有血缘关系间进程(或者说具有公共祖先的进程)的通信。如父子进程,兄弟进程。
  • 命名管道,任何两个进程间都可以通信。

匿名管道PIPE



创建函数pipe


匿名管道是通过调用函数pipe创建的:

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

返回值:成功返回0,失败返回-1。
该函数会经过参数fd返回两个文件描述符fd[0]和fd[1]。我们可以理解为fd[0]是为了从管道中读数据而打开的;fd[1]是为了向管道中写数据而打开的。所以管道在用户程序看了像是打开了一个文件。向这个文件读写数据其实就是在读写内核缓冲区。

原理


调用完函数后,系统会将当前进程的输入输出(即返回的两个文件描述符fd[0]和fd[1])分别连接到管道的读端与写段。如图:
这里写图片描述
文件描述符信息是记录在PCB当中的,当我们想让两个进程通信时就可以令当前进程fork子进程,让子进程继承父进程的PCB信息。这样子进程的fd[0]和fd[1]也是连接到这个管道的。

这里写图片描述
这也是为什么匿名管道限制了只有具有血缘关系的进程才能通信。没有血缘关系的是不可能连接到一个管道的。

这里再说明匿名管道的一个特性,它只支持单向通信(半双工通信),也就是说同一时刻只能有一个人在发一个人在收。所以父进程与子进程额读端与写端不能全部打开。同时只能一个进程打开读端另一个进程打开写端,另外的端口全部关闭。
我们下面用父进程打开写段,子进程打开读段继续讲解。(因为所有端口默认都是打开的,所以我们需要调用close函数将其关闭)
这里写图片描述

这样我们就构成了一个由父进程到子进程的管道。父进程负责写,子进程负责读。

示例代码


向管道读写数据类似于向文件读写数据,调用write和read函数就可实现。

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
int main()
{
    int fd[2];
    int ret = pipe(fd);//创建管道
    if(ret < 0)
    {
        perror("pipe");
        return 0;
    }
    pid_t id = fork();//创建子进程
    if(id < 0)
    {
        perror("fork\n");
    }
    else if(id == 0)
    {
        close(fd[1]);//子进程关闭写端
        char read_buf[100];
        int i = 0;
        while(i < 100)
        {
            memset(read_buf,'\0',sizeof(read_buf));
            read(fd[0],read_buf,sizeof(read_buf));//子进程从管道读数据
            printf("%s\n",read_buf);
            i++;
        }
    }
    else
    {
        close(fd[0]);//父进程关闭读端
        char *write_buf = NULL;
        int j = 0;
        while(j < 100)
        {
            write_buf = "helloworld!!!";
            int ret = write(fd[1],write_buf,strlen(write_buf)+1);//父进程向管道写数据
            sleep(1);
            j++;
        }
        wait();
    }
    return 0;
}

匿名管道有以下四种情况需要注意:

  • 当管道的写端已被关闭,这时有一个进程去读这个管道时,在管道内的剩余数据都被读取完之后就,read函数就会返回0,相当于读到文件末尾。
    这里写图片描述
  • 当管道写端没有关闭,但在进程的读速度大于另一个进程的写速度时,在管道内的剩余数据都被读取后,read会阻塞,直到管道内有新的数据。
  • 当管道的读端已被关闭,这时有一个进程向管道写数据时,这时进程就会收到SIGPIPE信号,导致进程异常终止。
    这里写图片描述
  • 当管道额读段没有关闭,但在进程的写速度大于另一个进程的读速度时,在管道被写满之后,write被阻塞,直到管道有空位置。

命名管道FIFO



FIFO被成为匿名管道,上面说的匿名管道只能完成有血缘关系的两个进程间的通信,这两个进程要有公共祖先。而命名管道FIFO可以让不相关的两个进程也通信。
命名管道不同于PIPE之处在于命名管道是一个设备文件,它真真在在的存在于硬盘之上,存在于文件系统中。而匿名管道存在于内存或者内核中,它对文件系统不可见。也正因为如此,命名管道可以完成任意两个进程间的通信。
命名管道的创建有一个路径名path与之关联,以FIFO文件的形式存储在文件系统中,所以只要可以访问该路径,就能够通过FIFO互相通信。
注意:FIFO是按照先入先出的规则进行数据读写,第一个被写入的也将最先被读出。

创建


命名管道的创建方式有两种,是

  • 在shell命令行下使用命令mknod或者mkfifo创建。
    这里写图片描述
  • 在程序内通过调用函数创建。同样,者两个函数的名字也叫mknod和mkfifo。
#include<sys/types.h>
#include<sys/stat.h>
int mknod(const char* path,mode_t mod,dev_t dev)

函数mknod的参数path即为创建的命名管道的全路径名(包括管道文件的文件名),如”/home/tmp/fifo”。第二个参数mod为创建命名管道的模式(一般为S_IFIFO|0666,0666表示管道IPC的默认权限)。dev为设备值,取决于文件创建的种类,它只在创建设备文件时才会用到。
返回值:成功返回0,失败返回-1。

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

此函的的两个参数与上相同,返回值也相同,成功返回0,失败返回-1。
mknod是比较老的函数,mkfifo函数更加的简单规范,所以建议尽量使用mkfifo。

open

当我们创建好管道之后并不能直接使用它,使用之前必须调用open函数将其打开,并获取文件描述符。

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);

参数pathname即为我们要打开的管道文件,参数flags为打开方式,一般有以只读方式打开O_RDONLY 或以只写方式打开O_WRONLY。
open函数的返回值为一个文件描述符,我们通过这个文件描述符对管道进行读写操作。

代码实例:


发送端代码:

#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
#define _PATH_ "/home/lzk/test/11_class_2_14/fifo/namefifo"
int main()
{
    int ret = mkfifo(_PATH_,S_IFIFO|0666);//创建管道
    if(ret < 0 )
    {
        perror("mkfifo");
        return 0;
    }
    int fd = open(_PATH_,O_WRONLY);//打开管道并获取文件描述符
    if(fd < 0 )
    {
        perror("open");
        return 0;
    }
    char send_buf[100];
    memset(send_buf,'\0',sizeof(send_buf));
    while(1)
    {
        scanf("%s",send_buf);
        write(fd,send_buf,strlen(send_buf)+1);//向管道中写数据
    }
    close(fd);//关闭文件描述符
    printf("hello geter\n");
    return 0;
}

接受端代码:

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#define _PATH_ "/home/lzk/test/11_class_2_14/fifo/namefifo"
int main()
{
//接受端就不需要创建新的管道了,只需打开使用。
    int fd = open(_PATH_,O_RDONLY);//以只读方式打开管道,获取文件描述符
    if(fd < 0 )
    {
        perror("open");
        return 0;
    }
    char* get_buf[100];
    while(1)
    {
        read(fd,get_buf,sizeof(get_buf));//从管道中读取数据
        printf("%s\n",get_buf);
    }
    close(fd);//关闭文件描述符
    return 0;
}

总结

  1. 文件系统中的路径名是全局的,各个进程都可以访问,因此可以用文件系统的路径名来标识一个IPC通道。
  2. 命名管道FIFO是一个特殊的设备文件,它在文件系统中真实存在。
  3. 命名管道与匿名管道的使用方法是一致的,只是命名管道在使用前要调用open函数打开。因为命名管道是存在于硬盘上的真实文件,而匿名管道是存在与内存中的特殊文件。
  4. 命名管道打开方式的不同也可能引起阻塞。如果同时以读写方式打开,则一定不会阻塞;若单单以读方式打开,则调用open函数的进程会一直阻塞到有写方式打开;同样,单单以写方式打开的进程会一直阻塞到有读方式打开。当然在阻塞时我们可以异常终止程序。
  • 3
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值