进程间通信机制详解(1)——管道

管道

管道是内核管理的一个固定大小的缓冲区。在Linux 中,该缓冲区的大小为1 页,即4KB。

管道创建

首先,使用pipe函数创建一个匿名半双工管道。

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

fd[2]维护一个长度为2的文件描述符数组,fd[0]是读出端,fd[1]是写入端,函数值返回0表示成功,返回-1表示失败。当函数成功返回,自动建立了一个**从fd[1]到fd[0]**的数据通道。
最开始,两个文件描述符都连接在同一个进程上。

在这里插入图片描述
然后调用fork函数创建子进程,让两个进程连接到同一个PIPE上。调用fork会向父进程返回子进程pid(>0),向子进程返回父进程pid(=0)。当fork复制进程的时候,会将这两个连接也复制到新进程(具有相同的文件描述符)。随后,每个进程关闭自己不需要的一个连接 (例如,若要数据流从父进程流向子进程,则关闭父进程的读端(fd[0])与子进程的写端(fd[1]);反之,则可以使数据流从子进程流向父进程。)
在这里插入图片描述

管道通信

在Linux 中,管道实现借助了文件系统的file 结构和VFS(Virtual File System,虚拟文件系统)的索引节点inode(inode是文件的唯一标识),通过将两个 file 结构指向同一个临时VFS 索引节点,而这个 VFS 索引节点又指向一个物理页面来实现。
在这里插入图片描述
两个file数据结构定义的文件操作例程地址是不同的,一个例程地址向管道中写入数据,另一个例程地址从管道中读出数据。
当写进程向管道中写入时,首先利用标准库函数write(),根据传入write()函数的文件描述符找到该文件的 file 结构;file 结构中指定了写函数(pipe_wrtie())要写入的地址(数据页),然后内核调用写函数执行写操作,将字节复制到inode指向的物理内存。写入函数在向内存中写入数据之前,必须提前检查inode中的信息是否满足:
1)内存中有足够的空间可容纳所有要写入的数据;
2)内存没有被读程序锁定
如果同时满足上述条件,写入函数首先锁定内存,然后从写进程的地址空间中复制数据到内存。否则,写入进程就阻塞在VFS 索引节点的等待队列中,当内存中有足够的空间可以容纳写入数据,或内存被解锁时,读进程会唤醒写入进程,此时写进程接收到信号。写入的内容每次都添加在管道缓冲区的末尾,当数据写入内存之后,内存被解锁,而所有阻塞在索引节点的读进程会被唤醒。
管道的读取过程(将物理内存中的字节复制出来)和写入过程类似。每次从缓冲区的头部读出数据,且数据一旦被读,它就从管道中被抛弃,释放空间以便写更多的数据。读取程序的判断条件为:内存不为空且内存没有被写程序锁定。
当所有的进程完成了管道操作之后,管道的索引节点被丢弃,而共享数据页也被释放。

注:
(1)内核使用了锁、等待队列和信号等机制进行同步。
(2)read/write调用模式
阻塞模式(默认)(O_NONBLOCK disable)	阻塞,进程暂停执行,直到有进程写入数据	阻塞,进程暂停执行,直到有进程读走数据非阻塞模式(O_NONBLOCK enable)	返回 -1,errno 值被置为 EAGAIN	返回 -1,errno 值被置为EAGAIN
(3)文件描述符关闭
如果所有管道写端的文件描述符被关闭,那么管道中剩余的数据都被读取完后,再次 read 会返回 0
如果所有管道读端的文件描述符被关闭,则 write 操作会产生 SIGPIPE 信号,进而可能导致 write 进程退出
(4)原子性
当要写入的数据量n不大于PIPE_BUF时,将保证写入的原子性。
当要写入的数据量n大于PIPE_BUF时,将不再保证写入的原子性。

管道特点

(1)半双工(即数据只能在一个方向上流动),具有固定的读端和写端;双向通信需要建立两个管道;
(2)只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)。
(3)单独构成一种独立文件系统,只存在于内存中。

命名管道

命名管道也被称为FIFO文件,它提供一个路径名与管道关联,路径名以FIFO文件形式存在于文件系统中,内容存放于内存中。没有亲缘关系的进程可以通过文件路径名来识别管道,从而建立通信连接。当删除FIFO文件时,管道连接也随之消失。

示例
int main(void)
{
    int n;
    int fd[2];
    pid_t pid;
    char line[MAXLINE];

    if(pipe(fd)  0){                 /* 建立管道得到一对文件描述符 */
        exit(0);
    }

    if((pid = fork())  0)            /* 父进程把文件描述符复制给子进程 */
        exit(1);
    else if(pid > 0){                /* 父进程写 */
        close(fd[0]);                /* 关闭读描述符 */
        write(fd[1], "\nhello world\n", 14);
    }
    else{                            /* 子进程读 */
        close(fd[1]);                /* 关闭写端 */
        n = read(fd[0], line, MAXLINE);
        write(STDOUT_FILENO, line, n);
    }

    exit(0);
}
参考

进程间通信机制(IPC)
Linux进程间的通信方式和原理
操作系统中的进程间的通信机制

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值