LinuxC编程——进程间通信(一)(管道)

news2025/1/16 20:17:55

目录

  • 一、Linux平台通信方式发展史
  • 二、进程间通信方式⭐⭐⭐
  • 三、无名管道
    • 3.1 特点⭐⭐⭐
    • 3.2 函数pipe
    • 3.3 注意事项⭐⭐⭐
    • 3.4 练习
  • 四、有名管道
    • 4.1 特点⭐⭐⭐
    • 4.2 函数 mkfifo
    • 4.3 注意事项⭐⭐
    • 4.4 练习
  • 五、无名管道与有名管道对比⭐⭐

复杂的编程环境通常使用多个相关的进程来执行有关操作。进程之间必须进行通信,来共享资源和信息。因此,要求内核提供必要的机制,这些机制通常称为进程间通信(InterProcess Communication, IPC)。

一、Linux平台通信方式发展史

  1. 早期通信方式:早期的Unix IPC包括管道、FIFO和信号
  2. AT&T的贝尔实验室,对Unix早期的进程间通信进行了改进和扩充,形成了“system V IPC”,其通信进程主要局限在单个计算机内。
  3. BSD(加州大学伯克利分校的伯克利软件发布中心),跳过了只能在同一计算机通信的限制,形成了基于套接字(socket)的进程间通信机制。

二、进程间通信方式⭐⭐⭐

  1. 早期通信:无名管道(pipe),有名管道(fifo)、信号(sem)
  2. system V IPC:共享内存(share memory) 、信号灯集(semaphore)、消息队列(message queue)
  3. BSD:套接字(socket)

三、无名管道

3.1 特点⭐⭐⭐

  1. 只能用于具有亲缘关系的进程间进行通信
  2. 半双工通信,具有固定的读端与写端
    (单工:只能单方面传输信息->广播
    半双工:可以双向,但是同一时间只能一个方向传输信息
    全双工:可以双向同时传输信息)
  3. 无名管道可以看作一种特殊的文件,对它的读写采用文件IO:read、write
  4. 管道是基于文件描述符通信方式。当一个无名管道创建会自动创建两个文件描述符,分别的fd[0]、fd[1],其中fd[0]固定的读端,fd[1]固定的写端

3.2 函数pipe

int pipe(int fd[2])

  • 功能:创建无名管道
  • 参数:文件描述符(fd[0]:读端 fd[1]:写端)
  • 返回值:成功 0;失败 -1
    注📢:管道要用文件I/O进行操作(read,write,close)且管道创建后,fd[0]=3,fd[1]=4
    例:
    在这里插入图片描述
    在这里插入图片描述

3.3 注意事项⭐⭐⭐

  1. 当管道中无数据时,读操作会阻塞;管道中无数据,将写端关闭,读操作会立即返回
  2. 管道中装满(管道大小64K)数据写阻塞,一旦有4k空间,写继续,直到写满为止
  3. 只有在管道的读端存在时,向管道中写入数据才有意义。否则,会导致管道破裂,向管道中写入数据的进程将收到内核传来的SIGPIPE信号 (通常Broken pipe错误)。(GDB调试可以查看到)

代码示例:

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

int main(int argc, char const *argv[])
{
    int fd[2] = {0};
    if (pipe(fd) < 0)
    {
        perror("pipe error");
        return -1;
    }
    printf("fd[0]:%d  fd[1]:%d\n", fd[0], fd[1]);

    char buf1[32] = {"hello world!"};
    char buf2[32] = {0};
    // write(fd[1],buf1,strlen(buf1)); //往管道写入buf1
    // ssize_t s = read(fd[0],buf2,32);  //从管道读取数据到buf2
    // printf("%s %d\n",buf2,s);
    // close(fd[0]);
    // close(fd[1]);
#if 0
    // 1.管道中无数据,读阻塞
    read(fd[0], buf2, 32);
#endif
#if 0
    // 2.将写端关闭,读操作会立即返回
    close(fd[1]);
    read(fd[0],buf2,32);
#endif
#if 1
    //3.1 当无名管道中写满数据64k,写阻塞
    char buf[65536] = {0};
    write(fd[1], buf, 65536);
    printf("full\n");
    write(fd[1], "a", 1);
    printf("write a ok\n");
    //至少读出4k的空间,才能继续写
    read(fd[0], buf, 4095);
    write(fd[1], 'a', 1);
    printf("write 'a' ok\n");
#endif
#if 1
    // 3.1 将读端关闭,继续写
    close(fd[0]);
    write(fd[1], "a", 1);
    printf("ok...\n");
#endif
    // close(fd[0]);
    // close(fd[1]);
    return 0;
}

第三种情况管道破裂,通过GDB调试查看到的结果如下:
在这里插入图片描述

3.4 练习

父子进程实现通信,父进程循环从终端输入数据,子进程循环打印数据,输入一次打印一次,当输入quit结束,使用无名管道

/*
  练习:父子进程实现通信,父进程循环从终端输入数据,
  子进程循环打印数据,当输入quit结束,使用无名管道
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

int main(int argc, char const *argv[])
{
    char buf[32] = {0};
    int fd[2] = {0};
    if(pipe(fd)<0) //创建无名管道
    {
        perror("pipe err");
        return -1;
    }
    pid_t pid = fork();
    if(pid < 0)
    {
        perror("fork err");
        return -1;
    }
    else if(pid == 0)
    {
        while (1) //子进程循环从管道读取数据,管道为空阻塞
        {
            read(fd[0],buf,32);
            if(strcmp(buf,"quit")==0)
                exit(0);
            printf("%s\n",buf);
        }  
    }
    else
    {
        while(1)//循环从终端输入数据,循环往管道写入数据
        {
            //scanf("%s",buf);
            fgets(buf,32,stdin);
            if(buf[strlen(buf)-1] == '\n')
                buf[strlen(buf)-1] = '\0';
            write(fd[1],buf,strlen(buf)+1);
            if(strcmp(buf,"quit")==0)
                exit(0);
        }
        wait(NULL);
    }
    close(fd[0]);
    close(fd[1]);
    return 0;
}

四、有名管道

4.1 特点⭐⭐⭐

  1. 可以用于两个不相关的进程之间通信
  2. 有名管道可以通过路径名指出,在文件系统中可见,但内容存放在内存里
  3. 通过文件IO操作
  4. 遵循先进先出,故不支持lseek操作
  5. 半双工通信

4.2 函数 mkfifo

int mkfifo(const char *filename,mode_t mode);

  • 功能:创健有名管道
  • 参数:
    • filename:有名管道文件名
    • mode:权限
  • 返回值:成功:0;失败:-1,并设置errno号
    注意对错误的处理方式:📢📢
    如果错误是file exist时,注意加判断,如:if(errno == EEXIST)
    执行如下代码:
    在这里插入图片描述
    第一次运行:
    在这里插入图片描述
    再次运行:
    在这里插入图片描述
    处理方式:捕捉错误码,进行过滤即可👇
    在这里插入图片描述
  1. 由上面的有名管道特点的第二条可以知道,写入有名管道的内容并非存放在文件中,而是存在内存,也就是说有名管道文件的大小为0,下面进行验证:
    在这里插入图片描述
    在写后面加一个while死循环,运行后会等写完阻塞,不读出数据。然后在终端可以查看当前管道文件的大小,结果是大小为0👇
    在这里插入图片描述

4.3 注意事项⭐⭐

  1. 只写方式,写阻塞,直到另一个进程将读打开
  2. 只读方式,读阻塞,直到另一个进程将写打开
  3. 可读可写,管道中无数据,读阻塞。

验证1,2:
创建两个c文件,一个以只读方式打开有名管道,从中读数据;另一个以只写方式打开同一个有名管道,从中写数据。
只读
在这里插入图片描述
只写
在这里插入图片描述
通过运行可以看到,运行了其中任意一个,会发生阻塞,只有当再运行另一个才可以解除阻塞,两个程序得以顺利执行下去。验证了只写方式,写阻塞,直到另一个进程将读打开只读方式,读阻塞,直到另一个进程将写打开。
还可以得知,上面程序并不是在read或iwrite发生阻塞,而是在open函数处发生了阻塞

补充:如果所有写进程都关闭命名管道,则只读进程的读操作会认为到达文件末尾,读操作解除阻塞并返回
验证:
以只读方式打开有名管道的程序代码1.c👇
在这里插入图片描述
以只写方式打开有名管道程序代码2.c👇
在这里插入图片描述
先执行1.c,会发生阻塞;再执行2.c,2.c不会往管道写入数据(保证1.c不会因为管道中有数据而解除阻塞),2.c间隔1秒关闭管道,可以看到原先阻塞的1.c也会在2.c执行1秒后解除阻塞,且read返回值为0。

4.4 练习

通过有名管道实现cp文件复制
方法一:两个c文件,一个只读管道,一个只写管道
3cp_MkfifoToDest.c

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

int main(int argc, char const *argv[])
{
    if(argc != 2)
    {
        printf("Please input %s <des>\n",argv[0]);
        return -1;
    }
    if(mkfifo("./fifo",0666) < 0)//创建有名管道
    {
        //处理文件已存在的情况
        if(errno == EEXIST)//EEXTST=17
        {
            printf("file eexist\n");
        }
        else
        {
            perror("mkfifo err");
            return -1;
        }
    }
    //打开管道和目标文件
    int fd = open("./fifo",O_RDONLY);
    //此处一定不要用可读可写的方式打开
    //若以可读可写的方式打开,管道中无数据则读阻塞
    if(fd<0)
    {
        perror("open fifo err");
        return -1;
    }
    int dest = open(argv[1],O_WRONLY|O_CREAT|O_TRUNC,0666);
    if(fd<0)
    {
        perror("open dest err");
        return -1;
    }
    //循环读管道,写目标文件
    ssize_t s;
    char buf[32] = {0};
    while ((s=read(fd,buf,32)) != 0)
        write(dest,buf,s);
    close(fd);
    close(dest);
    return 0;
}

3cp_SrcToMkfifo.c

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

int main(int argc, char const *argv[])
{
    if(argc != 2)
    {
        printf("Please input %s <src>\n",argv[0]);
        return -1;
    }
    if(mkfifo("./fifo",0666) < 0)//创建有名管道
    {
        //处理文件已存在的情况
        if(errno == EEXIST)//EEXTST=17
        {
            printf("file eexist\n");
        }
        else
        {
            perror("mkfifo err");
            return -1;
        }
    }
    //打开管道和源文件
    int fd = open("./fifo",O_WRONLY);
    if(fd<0)
    {
        perror("open fifo err");
        return -1;
    }
    int src = open(argv[1],O_RDONLY);
    if(fd<0)
    {
        perror("open src err");
        return -1;
    }

    ssize_t s;
    char buf[32] = {0};
    while ((s=read(src,buf,32)) != 0)
    {
        write(fd,buf,s);
    }
    close(fd);
    close(src);
    return 0;
}

运行结果:
在这里插入图片描述

方法二:单个c文件,用父子进程实现,父进程只写有名管道,子进程只读有名管道

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

int main(int argc, char const *argv[])
{
    if (argc != 3)
    {
        printf("Please iniput %s <src> <dest>\n", argv[0]);
        return -1;
    }
    if (mkfifo("./fifo", 0666) < 0) //创建有名管道
    {
        //处理文件已存在的情况
        if (errno == EEXIST) //EEXTST=17
        {
            printf("file eexist\n");
        }
        else
        {
            perror("mkfifo err");
            return -1;
        }
    }
    //打开管道、源文件、目标文件
    
    int src = open(argv[1], O_RDONLY);
    if (src < 0)
    {
        perror("open src err");
        return -1;
    }
    int dest = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0666);
    if (dest < 0)
    {
        perror("open dest err");
        return -1;
    }

    ssize_t s;
    char buf[32] = {0};

    pid_t pid = fork(); // 创建子进程
    if (pid < 0)
    {
        perror("fork err");
        return -1;
    }
    else if (pid == 0) //子进程从有名管道中读出数据,写到目标文件中
    {
        int fd = open("./fifo", O_RDONLY); //管道设置为只读
        if (fd < 0)
        {
            perror("open fifo err");
            return -1;
        }
        while ((s = read(fd, buf, 32)) != 0)
            write(dest, buf, s);
        printf("child end...\n");
        close(fd);
        exit(0);
    }
    else //父进程从源文件读出数据,写到有名管道中
    {
        int fd = open("./fifo", O_WRONLY); //管道设置为只写
        if (fd < 0)
        {
            perror("open fifo err");
            return -1;
        }
        while ((s = read(src, buf, 32)) != 0)
            write(fd, buf, s);
        printf("parent end...\n");
        close(fd);
        wait(NULL);
    }
    close(src);
    close(dest);
    return 0;
}

注:该程序容易被怀疑最后在子进程的read(fd, buf, 32)会发生阻塞,其实不然,这里用到了上面的一个要点:如果所有写进程都关闭命名管道,则只读进程的读操作会认为到达文件末尾,读操作解除阻塞并返回

五、无名管道与有名管道对比⭐⭐

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/869125.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【周末闲谈】人工智能热潮下的AIGC到底指的是什么?

生成式人工智能AIGC&#xff08;Artificial Intelligence Generated Content&#xff09;是人工智能1.0时代进入2.0时代的重要标志。 个人主页&#xff1a;【&#x1f60a;个人主页】 系列专栏&#xff1a;【❤️周末闲谈】 系列目录 ✨第一周 二进制VS三进制 ✨第二周 文心一…

HTML5 基础标签

目录 前言 标题标签 段落标签 换行标签和水平线标签 文本格式化标签 图像标签 超链接标签 多媒体标签 列表标签 无序列表 有序列表 表格 合并单元格 表单 无语义的布局标签 字符实体 前言 当今互联网时代&#xff0c;网页是我们获取信息、交流和展示自己的重要渠…

【SpringBoot学习笔记】04. Thymeleaf模板引擎

模板引擎 所有的html元素都可以被thymeleaf替换接管 th:元素名 templates下的只能通过Controller来跳转&#xff0c;templates前后端分离&#xff0c;需要模板引擎thymeleaf支持 模板引擎的作用就是我们来写一个页面模板&#xff0c;比如有些值呢&#xff0c;是动态的&#x…

Leetcode-每日一题【剑指 Offer 27. 二叉树的镜像】

题目 请完成一个函数&#xff0c;输入一个二叉树&#xff0c;该函数输出它的镜像。 例如输入&#xff1a; 4 / \ 2 7 / \ / \ 1 3 6 9 镜像输出&#xff1a; 4 / \ 7 2 / \ / \ 9 6 3 1 示例 1&#xff1a; 输入&#xff1a;root [4,2,…

实验二十六、RC桥式正弦波振荡电路参数选择

一、题目 电路如图1所示。利用 Multisim 分析下列问题&#xff1a; &#xff08;1&#xff09;选择合适的 R f R_f Rf​ 和稳压管&#xff0c;使电路产生正弦波振荡&#xff0c;并观察起振过程&#xff1b; &#xff08;2&#xff09;调整电路参数&#xff0c;使输出电压峰值…

2. 获取自己CSDN文章列表并按质量分由小到大排序(文章质量分、博客质量分、博文质量分)(阿里云API认证)

文章目录 写在前面步骤打开CSDN质量分页面粘贴查询文章url按F12打开调试工具&#xff0c;点击Network&#xff0c;点击清空按钮点击查询是调了这个接口https://bizapi.csdn.net/trends/api/v1/get-article-score用postman测试调用这个接口&#xff08;不行&#xff0c;认证不通…

Linux 基础篇(六)sudo和添加信任用户

一、sudo 1.是什么&#xff1f; 给被信任的普通用户授权&#xff0c;让被信任的普通用户能执行root用户才能执行的命令的一个命令。 2.为什么&#xff1f; 很多时候我们要在被信任的普通用户下执行一些root用户才能执行的命令&#xff0c;如 yum… 所以需要有一个命令能给普通用…

阿里云预装LAMP应用导致MySQL不显示访问密码如何解决

&#x1f600;前言 本篇博文是关于阿里云云服务器ECS部署MySQL过程中出现的一下坑&#xff0c;希望能够帮助到您&#x1f60a; &#x1f3e0;个人主页&#xff1a;晨犀主页 &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是晨犀&#xff0c;希望我的文章可以帮助到大家…

[静态时序分析简明教程(九)]多周期路径set_multicycle_path

静态时序分析简明教程-多周期路径 一、写在前面1.1 快速导航链接 二、多周期路径2.1 多周期路径的SDC命令2.2 路径常规约束2.3 建立/保持规格2.4 位移量2.5 多时钟周期案例 三、总结 一、写在前面 一个数字芯片工程师的核心竞争力是什么&#xff1f;不同的工程师可能给出不同的…

C语言 字符指针

1、介绍 概念&#xff1a; 字符指针&#xff0c;就是字符类型的指针&#xff0c;同整型指针&#xff0c;指针指向的元素表示整型一样&#xff0c;字符指针指向的元素表示的是字符。 假设&#xff1a; char ch a;char * pc &ch; pc 就是字符指针变量&#xff0c;字符指…

群晖安装wireguard(群晖7.1)

前言 上篇文章介绍了乌班图如何安装wireguard&#xff0c;但是感觉虚拟机安装有损优雅性。 本期视频我们介绍使用群晖安装wireguard。 由于原来黑群晖内核版本太低了。 我这里升级到群晖dns918&#xff08;7.1版本&#xff09; 内核版本为4.4 实际上这仍然不满足wireguar…

知识图谱基本工具Neo4j使用笔记 四 :使用csv文件批量导入图谱数据

文章目录 一、系统说明二、说明三、简单介绍1. 相关代码以及参数2. 简单示例 四、实际数据实践1. 前期准备&#xff08;1&#xff09; 创建一个用于测试的neo4j数据库&#xff08;2&#xff09;启动neo4j 查看数据库 2. 实践&#xff08;1&#xff09; OK 上面完成后&#xff0…

【多模态】25、ViLT | 轻量级多模态预训练模型(ICML2021)

文章目录 一、背景二、ViLT 方法三、效果3.1 数据集3.2 分类任务 VQA 和 NLVR23.3 Image Retrieval 论文&#xff1a;ViLT: Vision-and-Language Transformer Without Convolution or Region Supervision 代码&#xff1a;https://github.com/dandelin/vilt 出处&#xff1a;…

【项目管理】PMP备考宝典-第二章《环境》

第一节&#xff1a;概述 1.项目所处的组织环境 &#xff08;1&#xff09;事业环境因素&#xff08;EEFs&#xff09; 组织内部的事业环境因素&#xff1a; 企业都会有愿景、使命、价值观&#xff0c;这些决定了企业的发展方向。不忘初心&#xff0c;坚定地走自己的路&#…

「已解决」iframe 本地生效 但是在测试环境不生效问题

背景 我有一个表格中一列是个详情&#xff0c;这个详情可被点击&#xff0c;点击后弹出抽屉&#xff0c;抽屉里是后端传给我详情字段的值对应的 url 的 iframe 展示。 问题是&#xff0c;在本地 localhost 下运行&#xff0c;ifame 运行正常&#xff0c;但是部署到测试环境就看…

搭建 Python 环境 | Python、PyCharm

计算机 计算机能完成的工作&#xff1a; 算术运算逻辑判断数据存储网络通信…更多的更复杂的任务 以下这些都可以称为 “计算机”&#xff1a; 一台计算机主要由以下这几个重要的组件构成 CPU 中央处理器&#xff1a;大脑&#xff0c;算术运算&#xff0c;逻辑判断 存储器&…

传输层协议——udp

文章目录 1. 再谈端口号1.1 认识知名端口号 2. netstat3. pidof4. UDP协议4.1 UDP协议端格式4.2 UDP的特点4.3 面向数据报4.4 UDP的缓冲区 1. 再谈端口号 传输层是负责数据能够从发送端传输接收端&#xff0c;那么我们就需要再一次了解端口号。端口号(Port)标识了一个主机上进…

【Bert101】变压器模型背后的复杂数学【01/4】

一、说明 众所周知&#xff0c;变压器架构是自然语言处理&#xff08;NLP&#xff09;领域的突破。它克服了 seq-to-seq 模型&#xff08;如 RNN 等&#xff09;无法捕获文本中的长期依赖性的局限性。变压器架构被证明是革命性架构&#xff08;如 BERT、GPT 和 T5 及其变体&…

ARTS 挑战打卡的第8天 ---volatile 关键字在MCU中的作用,四个实例讲解(Tips)

前言 &#xff08;1&#xff09;volatile 关键字作为嵌入式面试的常考点&#xff0c;很多人都不是很了解&#xff0c;或者说一知半解。 &#xff08;2&#xff09;可能有些人会说了&#xff0c;volatile 关键字不就是防止编译器优化的吗&#xff1f;有啥好详细讲解的&#xff1…

清除pip安装库时的缓存

目录 1、命令清除缓存 2、路径手动清除 在使用pip安装Python库时&#xff0c;如果之前已经下载过该库&#xff0c;pip会默认使用缓存来安装库&#xff0c;而不是重新从网络上下载。缓存文件通常存储在用户目录下的缓存文件夹中&#xff0c;具体位置因操作系统和Python版本而异…