Linux 系统编程 学习:02-进程间通信1:Unix IPC(1)管道

背景

上一讲我们介绍了创建子进程的方式。我们都知道,创建子进程是为了与父进程协作(或者是为了执行新的程序,参考 Linux exec族函数解析

我们也知道,进程之间的资源在默认情况下是无法共享的,所以我们需要借助系统提供的 进程间通信(IPC, InterProcess Communication) 有关的接口。

进程间通信

由于进程间的地址空间相对独立。进程与进程间不能像线程间通过全局变量通信,所以进程之间要交换数据必须通过内核。

在内核中开辟一块缓冲区,进程A把数据从用户空间拷到内核缓冲区,进程B再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信。

IPC的方式通常有:

  • Unix IPC包括:管道(pipe)、命名管道(FIFO)与信号(Signal)
  • System V IPC:消息队列、信号量、共享内存
  • Socket(支持不同主机上的两个进程IPC)

我们在这一讲介绍Unix IPC,包括:管道(pipe)、命名管道(FIFO)与信号(Signal)。

注意:对于管道来说,只有读端存在,写端才有意义;如果读端不在,写端向FIFO或者PIPE写数据,内核将向对应的进程发送SIGPIPE信号(默认终止进程)。

无名管道(pipe)

无名管道,是 UNIX 系统IPC最古老的形式,内部是用环形队列实现的。

在 Linux 中,管道的实现并没有使用专门的数据结构,而是借助了文件系统的file结构和VFS的索引节点inode。通过将两个 file 结构指向同一个临时的 VFS 索引节点,而这个 VFS 索引节点又指向一个物理页面而实现的。

特点:

1)无名,在文件系统中找不到它的存在,只存在于内存中。

2)必须得是亲缘关系的进程(父子,兄弟,祖孙等)

3)操作是不具备原子性(即,不具备完整性)

4)不能用lseek来定位

5)半双工(读写只能一端进行,另一端等待),具有固定的读端和写端。

6)具备阻塞(有读者有写者,当读没有数据或者是写满了的时候会卡在那里等待)

使用下面的函数创建 pipe管道。用于亲缘进程通信,其他操作与普通文件相同。

#include <unistd.h>

int pipe(int pipefd[2]);

#define _GNU_SOURCE             /* See feature_test_macros(7) */
#include <fcntl.h> /* Obtain O_* constant definitions */
#include <unistd.h> int pipe2(int pipefd[2], int flags); //
/*
flags 包括O_CLOEXEC、O_DIRECT、O_NONBLOCK。
- CLOEXEC:close-on-exec即当调用exec()函数成功后,文件描述符会自动关闭
- O_DIRECT:任何读写操作都只在用户态地址空间和磁盘之间传送而不经过page cache
- O_NONBLOCK:非阻塞模式,在读取不到数据或是写入缓冲区已满会马上return,而不会阻塞等待。
*/

成功:返回0,同时为 pipefd 赋值。

  • pipefd[0]:读端
  • pipefd[1]:写端

失败:返回-1

读写情况:

  • 有数据时,无论有无写者(持有文件可写权限的描述符的进程,无的意思是指:进程退出,不再持有对应的fd;下同)都能够正常读;
  • 无数据时:如果 有 写者,则阻塞等待;如果 无 写着,则立即返回。

  • 如果 有 读者(读者,持有文件可读权限的描述符的进程):缓冲未满,正常写入;缓冲已满,阻塞等待。
  • 如果 无 读者,无论缓冲区如何,立即收到 SIGPIPE 信号(可以通过先注册的信号捕获 SIGPIPE 进行处理,默认是终止进程)

匿名管道 原理

管道是由内核管理的一个缓冲区,相当于我们放入内存中的一个纸条。管道的一端连接一个进程的输出。这个进程会向管道中放入信息。管道的另一端连接一个进程的输入,这个进程取出被放入管道的信息。

一个缓冲区不需要很大,它被设计成为环形的数据结构,以便管道可以被循环利用。当管道中没有信息的话,从管道中读取的进程会等待,直到另一端的进程放入信息。

当管道被放满信息的时候,尝试放入信息的进程会等待,直到另一端的进程取出信息。当两个进程都终结的时候,管道也自动消失。

匿名管道如何实现亲缘进程间的通信

1)父进程创建管道,得到两个文件描述符指向管道的两端。

2)父进程fork出子进程,子进程也有两个文件描述符指向同一管道。

3)写进程关闭 pipefd[0],读进程关闭 pipefd[1](因为管道只支持单向通信,关闭会比较好)。

数据从写端流入从读端流出,这样就实现了进程间通信。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h> int main(int argc, char *argv[])
{
pid_t pid = -1;
int i;
int ret;
int status;
char buffer[20]={'a', 0};
int pipefd[2] = {0}; ret = pipe(pipefd);
if(!ret)
{
printf("Reader fd is : %d\n",pipefd[0]);
printf("Writer fd is : %d\n",pipefd[1]);
} pid = fork();
if(pid == 0)
{
printf("Son as reader\n");
close(pipefd[1]);// 关闭写端
for (i = 0; i < 5; ++i) {
read(pipefd[0], buffer, sizeof(buffer));
printf("%s\n", buffer);
}
exit(0xaa);
}else if(pid > 0)
{
sleep(1);
printf("Father as Writer\n");
close(pipefd[0]);// 关闭读端
for (i = 0; i < 5; ++i) {
write(pipefd[1], buffer, sizeof(buffer));
buffer[i] = 'a' + i;
} ret = wait(&status);
printf("%x\n", WEXITSTATUS(status));
} return 0;
}

命名管道(fifo)

FIFO (First in, First out)为一种特殊的文件类型(通过 stat 结构的 st_mode 成员可以知道文件是否是 FIFO 类型,可以用 S_ISFIFO 宏对此进行测试),它在文件系统中有对应的路径。

当一个进程以读(r)的方式打开该文件,而另一个进程以写(w)的方式打开该文件,那么内核就会在这两个进程之间建立管道,所以FIFO实际上也由内核管理,不与硬盘打交道

FIFO 存在于文件系统中,内容存放在内存中,如果使用 ls -l 观察时,它的文件大小永远是0。

实际上,Linux中的命令的 " | " 就是 fifo。

之所以叫FIFO,是因为管道本质上是一个先进先出的队列数据结构,最早放入的数据被最先读出来,从而保证信息交流的顺序。FIFO只是借用了文件系统来为管道命名。

写模式的进程向FIFO文件中写入,而读模式的进程从FIFO文件中读出。当删除FIFO文件时,管道连接也随之消失。

FIFO的好处在于我们可以通过文件的路径来识别管道,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信;

创建 FIFO 类似于创建文件,它名字对应于一个磁盘索引节点,有了这个文件名,任何进程有相应的权限都可以对它进行访问。它。()

FIFO严格遵循先进先出(first in first out), 对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾。它们不支持诸如lseek()等文件定位操作。

Linux中通过系统调用mknod()或makefifo()来创建一个命名管道。最简单的方式是通过直接使用shell:mkfifo myfifo(等价于mknod myfifo p)

特点

1)有名

2)任一个进程都可以交互

3)会诞生一个类型p的管道文件

4)操作具备原子性

5)全双工

6)不能lseek来定位

使用下面的函数创建 fifo。读写之前必须先open。

mkfifoat 中的 dirfd 请参考: dirfd参数 有关解析

#include <sys/types.h>
#include <sys/stat.h> int mkfifo(const char *pathname, mode_t mode); #include <fcntl.h> /* Definition of AT_* constants */
#include <sys/stat.h> int mkfifoat(int dirfd, const char *pathname, mode_t mode);

参数解析

mode :参数的说明同 open 的 mode 相同。如果open时没有使用O_NONBLOCK参数,不论读端还是写端先打开,先打开者都会阻塞,一直阻塞到另一端打开。

当 open 一个 FIFO,非阻塞标志 O_NONBLOCK 会产生下列影响:

  • 在没有指定该标志的情况下,只读 open 要阻塞到某个其他进程为写而打开这个 FIFO 为止,只写 open 要阻塞到某个其他进程为读而打开它为止。
  • 指定了该标志时,则只读 open 立即返回。但如果没有进程为读而打开一个 FIFO,那么只写 open 将返回 -1,并将 errno 设置成 ENXIO。

    类似于管道,
  • 若 write 一个尚无进程为读而打开的 FIFO,则产生信号 SIGPIPE。
  • 若某个 FIFO 的最后一个写进程关闭了该 FIFO,则将为该 FIFO 的读进程产生一个文件结束标志。
  • 一个给定的 FIFO 可能有多个写进程,如果不希望多个进程所写的数据交叉,则必须考虑原子写操作,可被原子地写到 FIFO 的最大数据量也是通过常量 PIPE_BUF 来指定的。

如果mkfifo的路径已经存在时,会返回EEXIST错误,所以一般典型的调用代码首先会检查是否返回该错误,如果确实返回该错误,那么只要调用打开FIFO的函数就可以了。

#include <stdio.h>

#include <stdlib.h>
#include <unistd.h> #include <sys/stat.h>
#include <fcntl.h> #include <sys/types.h>
#include <sys/wait.h> #include <errno.h> #define FIFO_FILE "fifo_test" int main(int argc, char *argv[])
{
pid_t pid = -1;
int i;
int ret;
int status;
char buffer[20]={'a', 0};
int fd = 0; ret = mkfifo(FIFO_FILE, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
if(ret == -1)
{
if(errno == EEXIST)
{
printf("FIFO FILE existed.\n");
}else
{
perror("fifo");
return -1;
}
} pid = fork();
if(pid == 0)
{
printf("Son as reader\n");
printf("[Son] Waiting Writer\n");
fd = open(FIFO_FILE, O_RDONLY);
printf("Reader Pass\n"); for (i = 0; i < 5; ++i) {
read(fd, buffer, sizeof(buffer));
printf("%s\n", buffer);
}
exit(0xaa);
}else if(pid > 0)
{
sleep(1);
printf("Father as Writer\n");
printf("[Dad] Waiting Reader\n");
fd = open(FIFO_FILE, O_WRONLY);
printf("Writer pass\n");
for (i = 0; i < 5; ++i) {
write(fd, buffer, sizeof(buffer));
buffer[i] = 'a' + i;
} ret = wait(&status);
printf("%x\n", WEXITSTATUS(status));
} return 0;
}

Linux 系统编程 学习:02-进程间通信1:Unix IPC(1)管道的更多相关文章

  1. Linux 系统编程 学习:03-进程间通信1:Unix IPC(2)信号

    Linux 系统编程 学习:03-进程间通信1:Unix IPC(2)信号 背景 上一讲我们介绍了Unix IPC中的2种管道. 回顾一下上一讲的介绍,IPC的方式通常有: Unix IPC包括:管道 ...

  2. Linux 系统编程 学习 总结

    背景 整理了Liunx 关于 进程间通信的 很常见的知识. 目录 与 说明 Linux 系统编程 学习:000-有关概念 介绍了有关的基础概念,为以后的学习打下基础. Linux 系统编程 学习:00 ...

  3. Linux 系统编程 学习:00-有关概念

    Linux 系统编程 学习:00-有关概念 背景 系统编程其实就是利用系统中被支持的调度API进行开发的一个过程. 从这一讲开始,我们来介绍有关Linux 系统编程的学习. 知识 在进行Linux系统 ...

  4. Linux 系统编程 学习:04-进程间通信2:System V IPC(1)

    Linux 系统编程 学习:04-进程间通信2:System V IPC(1) 背景 上一讲 进程间通信:Unix IPC-信号中,我们介绍了Unix IPC中有关信号的概念,以及如何使用. IPC的 ...

  5. Linux 系统编程 学习:05-进程间通信2:System V IPC(2)

    Linux 系统编程 学习:05-进程间通信2:System V IPC(2) 背景 上一讲 进程间通信:System V IPC(1)中,我们介绍了System IPC中有关消息队列.共享内存的概念 ...

  6. Linux 系统编程 学习:06-基于socket的网络编程1:有关概念

    Linux 系统编程 学习:006-基于socket的网络编程1:有关概念 背景 上一讲 进程间通信:System V IPC(2)中,我们介绍了System IPC中关于信号量的概念,以及如何使用. ...

  7. Linux 系统编程 学习:07-基于socket的网络编程2:基于 UDP 的通信

    Linux 系统编程 学习:07-基于socket的网络编程2:基于 UDP 的通信 背景 上一讲我们介绍了网络编程的一些概念.socket的网络编程的有关概念 这一讲我们来看UDP 通信. 知识 U ...

  8. Linux 系统编程 学习:01-进程的有关概念 与 创建、回收

    Linux 系统编程 学习:01-进程的有关概念 与 创建.回收 背景 上一讲介绍了有关系统编程的概念.这一讲,我们针对 进程 开展学习. 概念 进程的身份证(PID) 每一个进程都有一个唯一的身份证 ...

  9. Linux 系统编程 学习:09-线程:线程的创建、回收与取消

    Linux 系统编程 学习:09-线程:线程的创建.回收与取消 背景 我们在此之前完成了 有关进程的学习.从这一讲开始我们学习线程. 完全的开发可以参考:<多线程编程指南> 在Linux ...

随机推荐

  1. 秋天的第一份“干货” I Referer 防盗链,为什么少了个字母 R?

    Referer 为什么叫 Referer?它代表什么意思?在诸多防盗链竞争中它有什么优势? 今天,在聊 Referer 防盗链之前,先来聊聊我们在现实生活中常常碰到的推荐人(Referrer)信息. ...

  2. Spring 配置文件AOP

    1 <!-- 配置切面bean --> 2 <bean id="permAspect" class="com.tx.spring.aspect.Perm ...

  3. Spring与Junit测试整合

    一.引入spring测试包:text包 二.@RunWith:指定spring对junit提供的一个运行器 @ContextConfiguration:  locations指定spring配置文件位 ...

  4. Pyinstaller打包通用流程

    Pyinstaller打包通用流程 前言 什么是Pyinstaller Pyinstaller是用于打包python项目的一个工具, 可以将项目代码打包成可执行文件, 在其他机器上使用. 通俗的说, ...

  5. matlab中set设置图形属性

    来源:https://ww2.mathworks.cn/help/matlab/ref/set.html?searchHighlight=set&s_tid=doc_srchtitle set ...

  6. 【题解】[APIO2010]特别行动队

    Link 题目大意:一段区间的贡献是\(ax^2+bx+c,x=\sum v\),求一个划分让总区间的价值最大.分段必须连续. \(\text{Solution:}\) 设计\(dp[i]\)表示前\ ...

  7. JavaScript DOM三种创建元素的方式

    三种创建元素的方式: document.write() element.innerHTML document.createElement() 初始HTML内容: <button>btn&l ...

  8. ASP。NET MVC的部分视图和部分模型

    下载source - 1.7 MB 介绍 本文解决了返回视图内容包含表单元素的部分视图的问题. 代码重用是一种非常有用的节省时间的特性,任何优秀的工程师都会在他们的工作过程中构建许多有用的函数.对于W ...

  9. How to install the NVIDIA drivers on Fedora 32

    https://linuxconfig.org/how-to-install-the-nvidia-drivers-on-fedora-32 The NVIDIA Driver is a progra ...

  10. macvlan几种模式

    vepa模式:各个子设备直接无法直接通信(可以通过支持端口聚合的交换机通信),可以和外部通信. private模式:和vepa模式类似,各个子设备之间无法通信,即使通过支持端口聚合的交换机也不能. b ...