👹
Carlos's Tech Blog
  • 🧔ECUs
    • ZYNQ_Documents
      • [ZYNQ] 构建ZYNQ的BSP工程
      • [ZYNQ] 启动流程
      • [ZYNQ] Secure Boot Flow
      • [ZYNQ] Provisioning Guideline
      • [ZYNQ] Decrypting Partition by the Decrypt Agent Using PUF key
      • [ZYNQ] enabling the cryptsetup on ramdisk
      • [ZYNQ] Encrypt external files based on file system using PUF key
      • [ZYNQ] Loading an Encrypted Linux kernel at U-Boot with a KUP Key
      • [ZYNQ] cross-compile the cryptsetup on Xilinx ZYNQ aarch64 platform
      • [ZYNQ] Linux Linaro系统镜像制作SD卡启动
    • S32G_Documents
      • [S32G] Going through the s32g hard/soft platform
      • [S32G] S32g247's Secure Boot using HSE firmware
        • S32g2 HSE key config
        • How S32g verify secure boot image
        • S32g secure boot signature generation
        • How to download and build S32g Secure boot image
        • [S32G] OTA with Secure Boot
    • RT117x_Documents
      • [RT-117x]IMX RT1170 Provisioning Guideline
      • [RT-117x] Going through the MX-RT1170 hard/soft platform
      • [RT-117x] i.MX-RT1170's Secure Boot
        • [RT-117x]Signing image with the HSM (SignServer)
    • LS104x_Documents
      • [LS104x] bsp project
      • [LS104x] boot flow
      • [LS104x] secure boot
      • [LS104x] Application Note, Using the PKCS#11 in TCU platform
      • [LS104x] 使用ostree更新rootfs
      • [LS104x] ostree的移植
      • [LS104x] Starting with Yocto
      • [LS104x] 使用FIT的kernel格式和initramfs
    • IMX6/8_Documents
      • [IMX6] Defining A U-Boot Command
      • NXP IMX6 嵌入式板子一些笔记
      • NXP-imx6 initialization
    • Vehicle_Apps
      • [SecOC] Tree
        • [SecOC] SecOC Freshness and MAC Truncation
  • 😾TECH
    • Rust Arm OS
      • ARMv7m_Using_The_RUST_Cross_Compiler
    • ARM
      • ARM-v7-M
        • 01_ARMv7-M_处理器架构技术综述
        • 02_ARMv7-M_编程模型与模式
        • 03_ARMv7-M_存储系统结构
        • 04_ARMv7-M_异常处理及中断处理
      • ARM-v8-A
        • 02_ARMv8_基本概念
        • 03_ARMv8_指令集介绍_加载指令集和存储指令集
        • 04_ARMv8_指令集_运算指令集
        • 05_ARMv8_指令集_跳转_比较与返回指令
        • 06_ARMv8_指令集_一些重要的指令
        • 0X_ARMv8_指令集_基于汇编的UART驱动
        • 07_ARMv8_汇编器Using as
        • 08_ARMv8_链接器和链接脚本
        • 09_ARMv8_内嵌汇编(内联汇编)Inline assembly
        • 10_ARMv8_异常处理(一) - 入口与返回、栈选择、异常向量表
        • 11_ARMv8_异常处理(二)- Legacy 中断处理
        • 12_ARMv8_异常处理(三)- GICv1/v2中断处理
        • 13_ARMv8_内存管理(一)-内存管理要素
        • 14_ARMv8_内存管理(二)-ARM的MMU设计
        • 15_ARMv8_内存管理(三)-MMU恒等映射及Linux实现
        • 16_ARMv8_高速缓存(一)cache要素
        • 17_ARMv8_高速缓存(二)ARM cache设计
        • 18_ARMv8_高速缓存(三)多核与一致性要素
        • 19_ARMv8_TLB管理(Translation Lookaside buffer)
        • 20_ARMv8_barrier(一)流水线和一致性模型
        • 21_ARMv8_barrier(二)内存屏障案例
      • ARM Boot Flow
        • 01_Embedded_ARMv7/v8 non-secure Boot Flow
        • 02_Embedded_ARMv8 ATF Secure Boot Flow (BL1/BL2/BL31)
        • 03_Embedded_ARMv8 BL33 Uboot Booting Flow
      • ARM Compiler
        • Compiler optimization and the volatile keyword
      • ARM Development
        • 在MACBOOK上搭建ARMv8架构的ARM开发环境
        • Starting with JLink debugger or QEMU
    • Linux
      • Kernel
        • 0x01_LinuxKernel_内核的启动(一)之启动前准备
        • 0x02_LinuxKernel_内核的启动(二)SMP多核处理器启动过程分析
        • 0x21_LinuxKernel_内核活动(一)之系统调用
        • 0x22_LinuxKernel_内核活动(二)中断体系结构(中断上文)
        • 0x23_LinuxKernel_内核活动(三)中断体系结构(中断下文)
        • 0x24_LinuxKernel_进程(一)进程的管理(生命周期、进程表示)
        • 0x25_LinuxKernel_进程(二)进程的调度器的实现
        • 0x26_LinuxKernel_设备驱动(一)综述与文件系统关联
        • 0x27_LinuxKernel_设备驱动(二)字符设备操作
        • 0x28_LinuxKernel_设备驱动(三)块设备操作
        • 0x29_LinuxKernel_设备驱动(四)资源与总线系统
        • 0x30_LinuxKernel_设备驱动(五)模块
        • 0x31_LinuxKernel_内存管理(一)物理页面、伙伴系统和slab分配器
        • 0x32_LinuxKernel_内存管理(二)虚拟内存管理、缺页与调试工具
        • 0x33_LinuxKernel_同步管理_原子操作_内存屏障_锁机制等
        • 01_LinuxDebug_调试理论和基础综述
      • Userspace
        • Linux-用户空间-多线程与同步
        • Linux进程之间的通信-管道(上)
        • Linux进程之间的通信-管道(下)
        • Linux进程之间的通信-信号量(System V)
        • Linux进程之间的通信-内存共享(System V)
        • Linux进程之间的通信-消息队列(System V)
        • Linux应用调试(一)方法、技巧和工具 - 综述
        • Linux应用调试(二)工具之coredump
        • Linux应用调试(三)工具之Valgrind
        • Linux机制之内存池
        • Linux机制之对象管理和引用计数(kobject/ktype/kset)
        • Linux机制copy_{to, from}_user
        • Linux设备树 - DTS语法、节点、设备树解析等
        • Linux System : Managing Linux Services - inittab & init.d
        • Linux System : Managing Linux Services - initramfs
      • Kernel Examples
        • Linux Driver - GPIO键盘驱动开发记录_OMAPL138
        • 基于OMAPL138的Linux字符驱动_GPIO驱动AD9833(一)之miscdevice和ioctl
        • 基于OMAPL138的Linux字符驱动_GPIO驱动AD9833(二)之cdev与read、write
        • 基于OMAPL138的字符驱动_GPIO驱动AD9833(三)之中断申请IRQ
        • Linux内核调用SPI驱动_实现OLED显示功能
        • Linux内核调用I2C驱动_驱动嵌套驱动方法MPU6050
    • OPTEE
      • 01_OPTEE-OS_基础之(一)功能综述、简要介绍
      • 02_OPTEE-OS_基础之(二)TrustZone和ATF功能综述、简要介绍
      • 03_OPTEE-OS_系统集成之(一)编译、实例、在QEMU上执行
      • 05_OPTEE-OS_系统集成之(三)ATF启动过程
      • 06_OPTEE-OS_系统集成之(四)OPTEE镜像启动过程
      • 07_OPTEE-OS_系统集成之(五)REE侧上层软件
      • 08_OPTEE-OS_系统集成之(六)TEE的驱动
      • 09_OPTEE-OS_内核之(一)ARM核安全态和非安全态的切换
      • 10_OPTEE-OS_内核之(二)对安全监控模式的调用的处理
      • 11_OPTEE-OS_内核之(三)中断与异常的处理
      • 12_OPTEE-OS_内核之(四)对TA请求的处理
      • 13_OPTEE-OS_内核之(五)内存和cache管理
      • 14_OPTEE-OS_内核之(六)线程管理与并发
      • 15_OPTEE-OS_内核之(七)系统调用及IPC机制
      • 16_OPTEE-OS_应用之(一)TA镜像的签名和加载
      • 17_OPTEE-OS_应用之(二)密码学算法和安全存储
      • 18_OPTEE-OS_应用之(三)可信应用的开发
      • 19_OPTEE-OS_应用之(四)安全驱动开发
      • 20_OPTEE-OS_应用之(五)终端密钥在线下发系统
    • Binary
      • 01_ELF文件_目标文件格式
      • 02_ELF文件结构_浅析内部文件结构
      • 03_ELF文件_静态链接
      • 04_ELF文件_加载进程虚拟地址空间
      • 05_ELF文件_动态链接
      • 06_Linux的动态共享库
      • 07_ELF文件_堆和栈调用惯例以ARMv8为例
      • 08_ELF文件_运行库(入口、库、多线程)
      • 09_ELF文件_基于ARMv7的Linux系统调用原理
      • 10_ELF文件_ARM的镜像文件(.bin/.hex/.s19)
    • Build
      • 01_Script_makefile_summary
    • Rust
      • 02_SYS_RUST_文件IO
    • Security
      • Crypto
        • 1.0_Security_计算机安全概述及安全需求
        • 2.0_Security_随机数(伪随机数)
        • 3.0_Security_对称密钥算法加解密
        • 3.1_Security_对称密钥算法之AES
        • 3.2_Security_对称密钥算法之MAC(CMAC/HMAC)
        • 3.3_Security_对称密钥算法之AEAD
        • 8.0_Security_pkcs7(CMS)_embedded
        • 9.0_Security_pkcs11(HSM)_embedded
      • Tools
        • Openssl EVP to implement RSA and SM2 en/dec sign/verify
        • 基于Mac Silicon M1 的OpenSSL 编译
        • How to compile mbedtls library on Linux/Mac/Windows
    • Embedded
      • eMMC启动介质
  • 😃Design
    • Secure Boot
      • JY Secure Boot Desgin
    • FOTA
      • [FOTA] Module of ECUs' FOTA unit design
        • [FOTA] Tech key point: OSTree Deployment
        • [FOTA] Tech key point: repositories role for onboard
        • [FOTA] Tech key point: metadata management
        • [FOTA] Tech key point: ECU verifying and Decrpting
        • [FOTA] Tech key point: time server
      • [FOTA] Local-OTA for Embedded Linux System
    • Provisioning
      • [X-Shield] Module of the Embedded Boards initialization
    • Report
由 GitBook 提供支持
在本页
  • Linux进程之间的通信-管道(上)
  • 1. stdin, stdout, stderr
  • 1.2 进程管道(Pipe)
  • Ref
  1. TECH
  2. Linux
  3. Userspace

Linux进程之间的通信-管道(上)

https://github.com/carloscn/blog/issues/13

Linux进程之间的通信-管道(上)

  • 标准输入、标准输出和标准错误

  • 进程管道

  • popen

  • pipe调用

  • 父进程和子进程

  • 命名管道:FIFO

  • 客户/服务器架构

1. stdin, stdout, stderr

作为Linux进程之间通信的基础,标准输入、输出和标准错误是必须了解的概念。我们在Linux的userspace层面shell上的机制每天都要和这三个东西打交道。我直接抄文献了:

  • stdin: Stands for standard input. It takes text as input.

  • stdout: Stands for standard output. The text output of a command is stored in the stdout stream.

  • stderr: Stands for standard error. Whenever a command faces an error, the error message is stored in this stream.

1.1 /dev/stdxxx和stdxxx的区别

基础的使用按照参考文献,这里需要补充的是,在Linux上/dev/stdin,/dev/stdout和/dev/stderr三个文件符号分别链接/proc/self/fd/0, /proc/self/fd/1,/proc/self/fd/2文件描述符(file descriptor),他们并不是stdin,stdout好stderr本身,他们只是一个特殊的文件标识用于指示stdxx的,Jim通过fopen打开函数获取文件句柄返回值的实验也可以看到,如果打开open("/dev/stdin")这类的文件,并不能获取真正的输入流,而是打开的链接文件本身。关于/dev/stdxxx特殊文件和stdxxx做了区分,就到这里了。下面对pipe在shell上面的使用做一个整理,方便更好理解pipe通信的内部原理和接口使用。

1.2 Piping and Redirecting

Piping:

  • $ echo "hello world" | grep hello : echo的输出 -> stdout -> stdin -> grep

  • $ echo “hello world” |& cat : echo输出 -> stdout + stderr -> stdin -> grep

  • $ anything |& cat stderr -> stdin -> cat

Redirecting:

  • $ echo “hello world” >> hello.txt 追加新的一行字

  • $ echo "hello world" > hello.txt 清空后复写一行字

  • $ cat < hello.txt :把文件展开,喂给cat作为stdin

$ # pyin.py
$ name = input("Enter name\n")
$ email = input("Enter email\n")
$ print("Your name is %s and email is %s" % (name, email))

hello.txt

carlos
carlos.wei.hk@gmail.com

$ cat < hello.txt 可以得到结果为:

Enter name
Enter email
Your name is carlos and email is carlos.wei.hk@gmail.com

不要被迷惑:

$ cat < hello.txt >output.txt

这个情况就是上面结果被写到了output.txt文件里面了。

$ echo “hello world” 1>output.log 2>debug.log stdout被写入output.log, stderr被重定向到debug.log

感谢Sidratul 整了标准输出和标准输入在shell层级的使用,后面将会引出在Linux程序设计中C语言接口中的对pipe的处理,对于pipe进程之间的通信,我们也可以看到存在的局限性,需要其中一个进程运行完得到标准输出之后才能被第二个进程使用,并且从性能角度来考虑,透过stdin, stdout文件符号(需要起一个shell)再加上文件索引,传输吞吐的效率并不会太高。

1.2 进程管道(Pipe)

1.2.1 API定义

#include <stdio.h>

FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);

Parameters:

Params
I/O
Details

const char *command

Input

The command argument is a pointer to a null-terminated string containing a shell command line. This command is passed to /bin/sh using the -c flag; interpretation, if any, is performed by the shell.

const char *type

Output

Return:

The return value from popen() is a normal standard I/O stream in all respects save that it must be closed with pclose() rather than fclose(3). Writing to such a stream writes to the standard input of the command; the command's standard output is the same as that of the process that called popen(), unless this is altered by the command itself. Conversely, reading from the stream reads the command's standard output, and the command's standard input is the same as that of the process that called popen().

1.2.2 读取

使用popen,返回一个文件句柄,然后通过fread函数就可以得到另一个进程里面输出到标准输出的返回值。

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

#define debug_log printf("%s:%s:%d--",__FILE__, __FUNCTION__, __LINE__);printf

#define P_CMD "./helper_process_long_loop.elf 10"

int main(void)
{
    FILE *read_fp = NULL;
    char buffer[BUFSIZ + 1];
    int chars_read;
    memset(buffer, '\0', sizeof buffer);
    read_fp = popen(P_CMD, "r");
    debug_log("popen start ...\n");
    if (NULL == read_fp) {
        debug_log("popen failed\n");
        return -1;
    }
    // read one time.
#if 1
    //chars_read = fread(buffer, sizeof(char), BUFSIZ, read_fp);
    if (chars_read > 0) {
        debug_log("Output was:  \n\n %s \n\n", buffer);
    }
#else
    // read multi times.
    do {
        chars_read = fread(buffer, sizeof(char), 32, read_fp);
        buffer[chars_read - 1] = '\0';
        debug_log("Output was:  \n\n %s \n\n", buffer);
    } while (chars_read > 0);
#endif
    pclose(read_fp);
    debug_log("popen end ...\n");
    return 0;
}

1.2.3 写入

int test_pipe_write()
{
    FILE *write_fp = NULL;
    char buffer[BUFSIZ + 1];
    int chars_read;
    sprintf(buffer, "hello my name is Carlos\n");
    write_fp = popen("od -c", "w");
    debug_log("popen start ...\n");
    if (NULL == write_fp) {
        debug_log("popen failed\n");
        return -1;
    }
    chars_read = fwrite(buffer, sizeof(char), strlen(buffer), write_fp);
    debug_log("call pclose, wait ...\n");
    pclose(write_fp);
    debug_log("pclose end ...\n");

    return 0;
}

1.2.4 system与popen

我觉得这个话题还是蛮重要的,我也查了不少的资料,网上有很多人做了system和popen的使用对比,也有人做了system和popen的性能对比。我们研究popen的过程,来确定linux对管道创建的进程调度这是非常重要的。system()函数是一个阻塞式的调用API,里面的过程大体是: fork() + execl() + waitpid(),如果我们不想要创建的子进程阻塞式影响到我们自己进程的程序,我们也可以单独的使用fork execl及waitpid,或许还可以和各种信号配合。然而,popen创建进程只是使用了fork()并没有waitpid()的操作,从popen的源代码里面也可以看到,因此,popen是一个非阻塞式的调用,system是一个阻塞式的调用。

system和popen的调用控制流也是不一样的,popen创建的进程,虽然不在popen进行阻塞,但是是在fread()函数,或者是pclose()的时候才进行阻塞的。换句话说,如果我们希望从popen创建的子进程的标准输出里面拿到数据,就一定要等子进程执行完毕之后才可以拿到。我也做了相关的实验,得到的结论两种情况:

  • 如果使用fread函数读取子进程的标准输出,那么会在fread阻塞。

  • 如果没有使用fread函数,主进程会等待子进程结束之后,在pclose位置阻塞。

我这里设计一个例子,helper进程,会循环方式sleep 10秒的时间,主进程会调用这个helper进程,主进程最终会等待子进程结束之后完成。

// helper_process_long_loop.c

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

#define debug_log printf("%s:%s:%d--",__FILE__, __FUNCTION__, __LINE__);printf

int main(int argc, char *argv[])
{
    int i = atoi(argv[1]); // 10 seconds
    debug_log(">test new sleep process: will sleep %d s\n", i);
    while(i --) {
        debug_log(">sleep at %d s\n", i);
        sleep(1);
    }
    debug_log(">test new sleep process: wakeup done.\n");
    return 0;
}

如果使用了fread()会阻塞在fread()函数

如果去掉了fread()函数,则会在调用pclose之前,等待子进程的调用。

因此,可以将上述过程绘制出来, 调用fread的控制流如图所示:

没有调用fread的控制流如图所示:

我们这里对popen进程创建进行对比,并不是说popen是一个创建进程的方法,popen的确有创建进程的功能,但是我们应该把关注点放在建立管道通信上面。

1.2.5 popen与SIGCHLD信号

system()接口一直让人诟病,因为其返回值太多,而且依赖于Linux内核做一个初始条件的初始化,例如,一些防止子进程成为僵尸进程的案例中,有人粗暴的喜欢把 signal(SIGCHLD, SIG_IGN)注册为IGN,这样的话,我们不需要在自己的程序里面对子进程的结束进行处理,而依赖于系统进程周期性的回收,而这对system()的使用是致命的,system的使用依赖于Linux内核将SIGCHLD注册为SIG_DFL,否则在wait_pid的时候将找不到子进程而报错ECHILD,而且使用system还需要注意SIGINT和SIGQUIT这两个信号。而对于popen对SIGCHLD并没有依赖于Linux内核对信号做一些初始化,但是有种场景也是又waitpid引起的——错误的pclose的错误的error状态:

  • 注册信号signal(SIGCHLD, handler)

  • 主线程popen一个长时间的进程,使其阻塞在pclose()

  • fork一个线程,执行时间比主线程短。

1.2.5.1 注册SIGCHLD->handler

此时,可以看到一个错误,在fork()线程退出后,pclose()的阻塞结束了,换句话说,fork进程的退出结束了pclose对线程的阻塞。

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

#define debug_log printf("%s:%s:%d--",__FILE__, __FUNCTION__, __LINE__);printf

#define P_CMD "./helper_process_long_loop.elf "
#define CFG_PARENT_PROCESS_EXEC_TIME  12
#define CFG_CHILD_PROCESS_EXEC_TIME 2

void handler(int sig)
{
    (void)sig;
	pid_t id;
	while((id = waitpid(-1, NULL, WNOHANG)) > 0)
		debug_log("wait child success : %d\n", id);
}

int main(void)
{
    FILE *read_fp = NULL;
    char buffer[BUFSIZ + 1];
    char cmd[BUFSIZ];
    int chars_read;
    pid_t pid = 1;
    int ret = 0;
    memset(buffer, '\0', sizeof buffer);
    sprintf(cmd, "%s%d", P_CMD, CFG_PARENT_PROCESS_EXEC_TIME);
    read_fp = popen(cmd, "r");
    debug_log("popen start: father process %d s...\n", CFG_PARENT_PROCESS_EXEC_TIME);
    if (NULL == read_fp) {
        debug_log("popen failed\n");
        return -1;
    }
    signal(SIGCHLD, handler);
    //signal(SIGCHLD, SIG_IGN);
    pid = fork();
    if (pid != 0) {
        debug_log("parent process: call pclose, wait ...\n");
        ret = pclose(read_fp);
        debug_log("parent process: popen end with ret = %d...\n", ret);
    } else {
        debug_log("child process: sleep %d s !\n ",CFG_CHILD_PROCESS_EXEC_TIME);
        sleep(CFG_CHILD_PROCESS_EXEC_TIME);
        debug_log("child process: wakeup !\n ");
    }
    return 0;
}

从实验结果看,pclose已经拿到了一个错误的返回值。

1.2.5.1 注册SIGCHLD->ignore

此时,可以看到一个错误,在fork()线程退出后,pclose()的阻塞结束了,换句话说,fork进程的退出结束了pclose对线程的阻塞。

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

#define debug_log printf("%s:%s:%d--",__FILE__, __FUNCTION__, __LINE__);printf

#define P_CMD "./helper_process_long_loop.elf "
#define CFG_PARENT_PROCESS_EXEC_TIME  12
#define CFG_CHILD_PROCESS_EXEC_TIME 2

void handler(int sig)
{
    (void)sig;
	pid_t id;
	while((id = waitpid(-1, NULL, WNOHANG)) > 0)
		debug_log("wait child success : %d\n", id);
}

int main(void)
{
    FILE *read_fp = NULL;
    char buffer[BUFSIZ + 1];
    char cmd[BUFSIZ];
    int chars_read;
    pid_t pid = 1;
    int ret = 0;
    memset(buffer, '\0', sizeof buffer);
    sprintf(cmd, "%s%d", P_CMD, CFG_PARENT_PROCESS_EXEC_TIME);
    read_fp = popen(cmd, "r");
    debug_log("popen start: father process %d s...\n", CFG_PARENT_PROCESS_EXEC_TIME);
    if (NULL == read_fp) {
        debug_log("popen failed\n");
        return -1;
    }
    //signal(SIGCHLD, handler);
    signal(SIGCHLD, SIG_IGN);
    pid = fork();
    if (pid != 0) {
        debug_log("parent process: call pclose, wait ...\n");
        ret = pclose(read_fp);
        debug_log("parent process: popen end with ret = %d...\n", ret);
    } else {
        debug_log("child process: sleep %d s !\n ",CFG_CHILD_PROCESS_EXEC_TIME);
        sleep(CFG_CHILD_PROCESS_EXEC_TIME);
        debug_log("child process: wakeup !\n ");
    }
    return 0;
}

从实验结果看,pclose还是拿到了一个错误的返回值。

因此,建议popen不要和其他进程同步使用,否则会拿到一个错误的状态。除了状态的错误,运行的时序调度都是正确的。

此外,如果我们没有屏蔽掉SIG_CHLD信号,此时没有调用pclose(),那么就->可能<-会出现僵尸进程。

1.2.6 底层pipe调用

上层的popen和pclose内部实际上是调用pipe,我们来看一下pipe的使用,popen对应的文件读写fread,fwrite,而pipe这个层级相应的需要用底层的read和write函数来读写操作。

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

#define _GNU_SOURCE             /* See feature_test_macros(7) */
#include <fcntl.h>              /* Definition of O_* constants */
#include <unistd.h>

int pipe2(int pipefd[2], int flags);
/* On Alpha, IA-64, MIPS, SuperH, and SPARC/SPARC64, pipe() has the
   following prototype; see NOTES */
#include <unistd.h>
struct fd_pair {
    long fd[2];
};
struct fd_pair pipe(void);

Parameters:

Params
I/O
Details

int pipefd[2]

Input

pipefd[0] refers to the read end of the pipe. pipefd[1] refers to the write end of the pipe. Data written to the write end of the pipe is buffered by the kernel until it is read from the read end of the pipe.

Return:

On success, zero is returned. On error, -1 is returned, errno is set to indicate the error, and pipefd is left unchanged.

On Linux (and other systems), pipe() does not modify pipefd on failure. A requirement standardizing this behavior was added in POSIX.1-2008 TC2. The Linux-specific pipe2() system call likewise does not modify pipefd on failure.

**Example 1 **: 管道建立在同一个进程上面

int test_pipe_rw()
{
    int ret = 0;
    int data_process = 0;
    int file_pipes[2];
    const char some_data[] = "hello world";
    char buffer[BUFSIZ + 1];
    memset(buffer, '\0', sizeof buffer);

    ret = pipe(file_pipes);
    if (ret != 0) {
        debug_log("pipe failed, ret = %d\n", ret);
        goto exit;
    }
		// write some_data to pipe
    data_process = write(file_pipes[1], some_data, strlen(some_data));
    debug_log("Wrote the %d bytes\n", data_process);
  	// read buffer from pipe
    data_process = read(file_pipes[0], buffer, BUFSIZ);
    debug_log("read %d bytes: %s\n", data_process, buffer);
exit:
    return ret;
}

**Example 2 **: 父进程和fork的子进程共用pipe,子进程写,父进程读。

这里要注意的是:

  • 子进程故意延迟了5s的时间再写,会发现父进程会阻塞在read函数。

int test_pipe_rw_fork()
{
    int ret = 0;
    int data_process = 0;
    int file_pipes[2];
    const char some_data[] = "hello world";
    char buffer[BUFSIZ + 1];
    pid_t pid = 0;
    memset(buffer, '\0', sizeof buffer);

    ret = pipe(file_pipes);
    if (ret != 0) {
        debug_log("pipe failed, ret = %d\n", ret);
        goto exit;
    }

    pid = fork();
    if (pid == -1) {
        debug_log("fork failed, ret = %d\n", ret);
        goto exit;
    }
    // child process
    else if (pid == 0) {
        sleep(5);
        data_process = write(file_pipes[1], some_data, strlen(some_data));
        debug_log("child wrote the %d bytes\n", data_process);
    }
    // parent process
    else {
        data_process = read(file_pipes[0], buffer, BUFSIZ);
        debug_log("parent read %d bytes: %s\n", data_process, buffer);
    }

exit:
    return ret;
}

**Example 3 **: 并行进程共用pipe,A进程写,B进程读。

  • fd的顺序,为读写顺序, 这里需要先操作file_description[1],再操作file_descirptin[0]。先操作[1]写入,再去用[0]读,是可以读到东西的。而先写0,从1读,是读不到东西的,这是FIFO结构。

  • 栈是LIFO结构,

A进程:

int test_pipe_rw_exec()
{
    int ret = 0;
    int data_process = 0;
    int file_pipes[2];
    char buffer[BUFSIZ + 1];
    char cmd[BUFSIZ];
    pid_t pid = 0;
    memset(buffer, '\0', sizeof buffer);

    ret = pipe(file_pipes);
    if (ret != 0) {
        debug_log("pipe failed, ret = %d\n", ret);
        goto exit;
    }

    pid = fork();
    if (pid == -1) {
        debug_log("fork failed, ret = %d\n", ret);
        goto exit;
    }
    // child process
    else if (pid == 0) {
        debug_log("run the helper_pipe_write.elf send fd = %d\n", file_pipes[1]);
        cmd[0] = file_pipes[1];
        (void)execl("helper_pipe_write.elf", "helper_pipe_write.elf", cmd, (char *)0);
        debug_log("call the helper_pipe_write.elf wrote the bytes\n");
    }
    // parent process
    else {
        debug_log("go on the main process,.....\n");
        data_process = read(file_pipes[0], buffer, BUFSIZ);
        debug_log("parent read %d bytes: %s\n", data_process, buffer);
    }

exit:
    return ret;
}

B进程:

int main(int argc, char **argv)
{
    int ret = 0;
    int data_process = 0;
    int file_pipes = (int) *(char *)argv[1];
    const char some_data[] = "hello world";
    debug_log("helper_pipe_write.elf start, recv the fd = %d ....\n",file_pipes);
    data_process = write(file_pipes, some_data, strlen(some_data));
    debug_log("helper_pipe_write.elf wrote the %d bytes\n", data_process);
    return 0;
}

Ref

  1. echo or print /dev/stdin /dev/stdout /dev/stderr

  2. man3-popen

  3. Popen/system, understand the difference between these two functions and fork.

  4. Linux下system()函数引发的错误

上一页Linux-用户空间-多线程与同步下一页Linux进程之间的通信-管道(下)

最后更新于1年前

image-20220324211237904

The type argument is a pointer to a null-terminated string which must contain either the letter 'r' for reading or the letter 'w' for writing. Since glibc 2.9, this argument can additionally include the letter 'e', which causes the close-on-exec flag (FD_CLOEXEC) to be set on the underlying file descriptor; see the description of the O_CLOEXEC flag in for reasons why this may be useful.

image-20220325120026930
image-20220325120120180
image-20220325142910498
image-20220325143327664
image-20220325144923705
😾
open(2)