当前位置: 首页 > article >正文

IPC之管道

什么是管道?
管道的本质是操作系统在内核中创建出的一块缓冲区,也就是内存

管道的应用
$ ps aux | grep xxx
ps aux 的标准输出写到管道,grep 从管道这块内存中读取数据来作为它的一个标准输入,而且 ps 和 grep 之间是兄弟关系,因为二者的父进程都是 bash

一、匿名管道

功能:创建一个匿名管道

#include <unistd.h>
int pipe(int fd[2]);
输出型参数 fd:文件描述符数组,其中,fd[0] 是读端,fd[1] 是写端
返回值:成功返回 0
       失败返回 -1,并设置错误码

一个进程通过系统调用 pipe() 创建出一个匿名管道,操作系统就会在内核中创建一块没有明确标识的缓冲区,并返回给创建进程两个文件描述符作为管道的操作句柄供进程来操作管道,其中,一个文件描述符(fd[0])用于从管道中读,另一个(fd[1])用于往管道中写,返回两个文件描述符是为了让用户自己确定半双工的方向

由于匿名管道对应的这块缓冲区没有明确标识,这也就意味着其他进程无法找到该缓冲区,也就无法通信,因此匿名管道只能用于具有亲缘关系的进程间通信,因为子进程能复制父进程的文件描述符表

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

int main()
{
  int   fds[2];
  pid_t pid;
  char  buf[10] = {0};

  if(0 != pipe(fds))
  {
    perror("pipe error");
    exit(EXIT_FAILURE);
  }

  pid = fork();
  if (-1 == pid)
  {
    perror("fork error");
    exit(EXIT_FAILURE);
  }

  if (0 == pid)
  {
    close(fds[0]);  //关闭读端
    printf("child write data: hello\n");
    write(fds[1], "hello", 5);
    close(fds[1]);
    exit(0);
  }

  close(fds[1]);  //关闭写端
  read(fds[0], buf, sizeof(buf));
  printf("father read data: %s\n", buf);
  close(fds[0]);

  waitpid(pid, NULL, 0);

  return 0;
}
/*
 * child write data: hello
 * father read data: hello 
 */

通过上述示例,我们发现在操作匿名管道的时候完全是把它当作文件去使用的,抛开 Linux 一切皆文件的思想,主要还是因为这块内存是在内核中,用户态的代码没法直接操作,但是可以借助文件读写的系统函数来操作这块内存

特点
1、只能用于具有亲缘关系的进程,像 ps aux | grep xxx 这种兄弟进程等
2、提供流式服务,也就是面向字节流

  • 优点:读写灵活,一次性写 10 字节,分 10 次读,或 5 次读或……,也可以 1 字节/次分 10 次写
  • 缺点:存在粘包问题,原因是两条数据间没有明显的间隔

3、半双工通信(可以选择方向的单向传输,a 可以给 b 发,b 也可以给 a 发,但是确定好方向后就只能这么发了,此外还有全双工通信、单工通信(已经确定好方向的单向传输)),双方彼此都进行通信时,需要创建两个匿名管道
4、进程退出,匿名管道被释放,也就是匿名管道的生命周期随进程,这里的进程指持有匿名管道的最后一个进程,当然也可以主动关闭所有进程的有关匿名管道的那两个文件描述符
5、内核会对匿名管道操作进行同步与互斥

二、命名管道
内核中的一块有明确标识的缓冲区,该标识实际上是一个管道文件(p),可见于文件系统,这也就意味着同一主机上的任意进程都可以通过打开管道文件进而访问到内核中对应的缓冲区进行通信

注意,管道文件并不是命名管道的本体,仅是命名管道的入口,即便通过 mkfifo 命令/函数创建出管道文件,内核中也并没有与之对应的缓冲区

$ mkfifo myfifo
$ ll myfifo
prw-rw-r-- 1 mam mam 0 318 16:16 myfifo

功能:创建一个管道文件

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
返回值:成功返回 0
       失败返回 -1,并设置错误码

$ cat main.c
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>

#define MYFIFO  "./myfifo"

int main()
{
#if 0
  umask(0);  // prw-rw-rw-
#else
  /*
   * prw-rw-r--
   * because 0666 & ~022 = 0644
   */
#endif
  if (mkfifo(MYFIFO, 0666) < 0
    && EEXIST != errno)
  {
    perror("mkfifo perror");
    return EXIT_FAILURE;
  }

  printf("successfully create FIFO file '%s'\n", MYFIFO);

  return 0;
}

命名管道打开规则
1、当前为了读而打开 FIFO 时

  • O_NONBLOCK disable:open 调用阻塞,直到有进程为写而打开该 FIFO
  • O_NONBLOCK enable: open 调用返回文件描述符

2、当前为了写而打开 FIFO 时

  • O_NONBLOCK disable:open 调用阻塞,直到有进程为读而打开该 FIFO
  • O_NONBLOCK enable: open 调用返回 -1,errno 值为 ENXIO

特点
1、可用于同一主机上的任意进程间通信,这是命名管道和匿名管道的最大区别
2、面向字节流
3、半双工通信
4、进程退出,命名管道被释放,但命名管道文件还在
5、内核会对命名管道操作进行同步与互斥

验证如下:
1、O_NONBLOCK disable:open 调用阻塞,直到有进程为写而打开该 FIFO

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define MYFIFO  "./myfifo"

int main()
{
  int fd = -1;

  if (-1 == access(MYFIFO, F_OK)
    && -1 == mkfifo(MYFIFO, 0664))
  {
    perror("mkfifo error");
    return EXIT_FAILURE;
  }

  fd = open(MYFIFO, O_RDONLY);
  printf("open %s for reading, fd: %d\n", MYFIFO, fd);

  if (fd >= 0)
    close(fd);

  return 0;
}

2、O_NONBLOCK enable: open 调用返回文件描述符

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define MYFIFO  "./myfifo"

int main()
{
  int fd = -1;

  if (-1 == access(MYFIFO, F_OK)
    && -1 == mkfifo(MYFIFO, 0664))
  {
    perror("mkfifo error");
    return EXIT_FAILURE;
  }

  fd = open(MYFIFO, O_RDONLY | O_NONBLOCK);
  printf("open %s for reading, fd: %d\n", MYFIFO, fd);

  if (fd >= 0)
    close(fd);

  return 0;
}
/*
 * open ./myfifo for reading, fd: 3
 */

3、O_NONBLOCK disable:open 调用阻塞,直到有进程为读而打开该 FIFO

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define MYFIFO  "./myfifo"

int main()
{
  int fd = -1;

  if (-1 == access(MYFIFO, F_OK)
    && -1 == mkfifo(MYFIFO, 0664))
  {
    perror("mkfifo error");
    return EXIT_FAILURE;
  }

  fd = open(MYFIFO, O_WRONLY);
  printf("open %s for writing, fd: %d\n", MYFIFO, fd);

  if (fd >= 0)
    close(fd);

  return 0;
}

4、O_NONBLOCK enable: open 调用返回 -1,errno 值为 ENXIO(6)

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

#define MYFIFO  "./myfifo"

int main()
{
  int fd = -1;

  if (-1 == access(MYFIFO, F_OK)
    && -1 == mkfifo(MYFIFO, 0664))
  {
    perror("mkfifo error");
    return EXIT_FAILURE;
  }

  fd = open(MYFIFO, O_WRONLY | O_NONBLOCK);
  if (fd < 0)
  {
    perror("open error");
    printf("errno: %d\n", errno);
    return EXIT_FAILURE;
  }

  printf("open %s for writing, fd: %d\n", MYFIFO, fd);

  if (fd >= 0)
    close(fd);

  return 0;
}
/*
 * open error: No such device or address
 * errno: 6
 */

三、管道读写规则


http://www.kler.cn/a/273561.html

相关文章:

  • IDC报告解读:实用型靶场将成为下一代网络靶场的必然方向
  • SSH详解
  • Armv8的安全启动
  • 软件对象粒度控制与设计模式在其中作用的例子
  • HTML5+css3(伪类,动态伪类,结构伪类,否定伪类,UI伪类,语言伪类,link,hover,active,visited,focus)
  • PHP JSON 教程
  • Android14之HIDL报错:Invalid sparse file format at header magic(一百九十六)
  • RHCE——三:Web服务器(内网穿透实验)
  • transferto转换文件类型报错
  • Auto-DataProcessing:一组让制作数据集变轻松的脚本
  • 显示android设备所以已安装App 可点击启动、搜索
  • Halcon 点云处理流程(点云分割、连通筛选、模型位姿变换、三角化)
  • Linux命令-dhclient命令(动态获取或释放IP地址)
  • 前后端分离项目部署服务器教程--实践成功
  • 【简历篇】如何写简历(二)简历元素、技巧、规范、三省
  • Git——GitHub远端协作详解
  • Apache Doris 如何基于自增列满足高效字典编码等典型场景需求
  • 在Hive中使用Python编写的UDF函数
  • 全量知识系统 微服务及特征复数空间和立体逻辑方阵的设想及百度AI回复
  • MySql安装与卸载—我耀学IT
  • 小程序云开发(十六):JavaScript基础
  • 浅谈Java 编程语言
  • 全量知识系统“全基因序列”程序构想及SmartChat的回复
  • 2025张宇考研数学基础36讲,视频百度网盘+PDF
  • 【Pandas】(1)安装与Series
  • 研究人员发现 OpenAI ChatGPT、Google Gemini 的漏洞