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. 实现element-ui对话框可拖拽功能

    element-ui对话框可拖拽及边界处理 应业务需求,需要实现对话框可拖拽问题,应element-ui没有提供官方支持,于是便参考大神的文章,得出了适合业务需要的解决方案.很多大神给出的代码是没有解 ...

  2. @Autowired,@Resource,@Qualifier,@Primary,@Inject的作用和区别

    @Autowired注解的用法:可以用于构造器,方法,参数,字段进行属性注入,有一个required属性,默认是true,当改成false时,如果注入的属性在容器中不存在也不会报错@Resource该 ...

  3. 阿里云oss对象存储配置CDN

    阿里云oss对象存储配置CDN 1.打开阿里云CDN 2.填写信息,这个地方要注意,我的备案域名是www.ljwXXX.work,我们可以自定义一个域名,test.ljwXXX.work作为加速域名. ...

  4. PADS Layout VX.2.3 将PCB中的元器件封装保存到库

    工具1:PADS Layout VX.2.3 菜单File > Library...,打开Library Manager,点击Create New Lib...新建一个库. 使用快捷键Ctrl ...

  5. 记录编译JDK11源码时遇到的两个问题

    执行make all报错信息: 错误一 /src/hotspot/share/runtime/arguments.cpp:1461:35: error: result of comparison ag ...

  6. vue拼图动画Demo

    这是一个基于vue的Demo,可以实现拼图动画,但是具体的没有写拼图成功的逻辑,鼠标悬停移动.周期刷新 我把它放到的我的博客园界面上了.刷新界面可以看到. 演示地址 :https://liruilon ...

  7. dgraph 使用简介

    dgraph 简介 dgraph 使用示例(基于 golang) golang client 安装 创建 schema 数据的 CURD 事务 总结 dgraph 简介 dgraph 是基于 gola ...

  8. Redis 的完整安装过程

    Windos 版本安装 Redis 官方并不支持 Window 版本,但是微软公司在 Github 上维护了一个 Windows 版本的 Redis 项目,供 Windows 用户下载使用. 下载地址 ...

  9. 动画演示Sunday字符串匹配算法——比KMP算法快七倍!极易理解!

    前言 上一篇我用动画的方式向大家详细说明了KMP算法(没看过的同学可以回去看看). 这次我依旧采用动画的方式向大家介绍另一个你用一次就会爱上的字符串匹配算法:Sunday算法,希望能收获你的点赞关注收 ...

  10. excel 无效引用 所引用的单元格不能位于256列

    无效引用 该文件版本所包含的公式中,所引用的单元格不能位于256列(列IW 或更远)或 65536 行以外的区域. 原因及解决方法: 1.版本问题,把两文件都另存为一致的版本,改为后缀为xlsx. 2 ...