Linux入门——08 进程间通讯——管道

news2025/2/25 17:13:48

1.进程间通讯

1.1什么是通讯

  • 进程具有独立性(每个进程都有自己的PCB,独立地址空间,页表)
  • 但是要进行进程的通信,通信的成本一定不低,打破了独立性

进程间通信目的

  • 数据传输:一个进程需要将它的数据发送给另一个进程
  • 资源共享:多个进程之间共享同样的资源。
  • 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
  • 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

1.2为什么要有通讯

  • 有时候需要多进程协同,完成某种业务! cat file | grep 'hello' //管道

1.3如何进行进程间通讯

两种通信方式

  • System V进程间通信------聚焦在本地通信(共享内存,信号量,消息队列)(被主流排斥了)
  • POSIX进程间通信-------让通讯过程可以跨主机(消息队列,共享内存,信号量,互斥量,条件变量,读写锁)
  • 管道---------基于文件标准(匿名管道,命名管道)

1.3.1管道

管道是Unix中最古老的进程间通信的形式。我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”

该如何理解通讯的本质?

要把一个进程的数据保留到一个地方,让 另一个进程去读取,这个地方必须要第三方提供,不能是进程,进程是具有独立性的。

第三方就是操作系统,直接或间接为进程通讯提供”内存空间“

要通讯的进程,必须想看到一份公共的资源(OS直接或间接提供)

不同的通讯种类

本质就是:上面所说的资源,是OS中的哪一个模块提供的!

如果i这个资源是文件体统提供的就叫这种通讯为管道

通讯的成本:

  • 1.把必须让不同的进程看到同一份资源
  • 2.然后通讯

任何一个文件都有自己的操作方法和属于自己的内核缓冲区(struct Page{}缓冲区)

此时我们再看父子进程,他们两个是不是共同看到了同一份资源。是由文件管理系统提供的。

父进程 向文件缓冲区内写入,子进程向文件缓冲区内读取(这就是一个进程向文件中写数据,另一个进程向文件中读数据)

我们把这种通过文件的方式完成进程间的通讯的方式叫管道,操作系统提的内核级文件称为管道文件(本质就是文件)。

具有缓冲区就是为了不让文件再写入磁盘,再让另一个进程去磁盘里面读取,这样效率太慢了,需要在磁盘内存在该文件,

可以直接让OS创建struct file,只需要struct file的地址填入进程描述符表中就可以了,然后父进程创建子进程,拷贝文件描述符表,父子进程就可以直接在内存中进行通讯。

得到管道是内存级文件(不需要磁盘刷新)

如何让两个进程看到同一个管道文件呢?

fork创建子进程完成的。父子进程使用同一个文件描述符表

1.3.2匿名管道

前面我们说的管道文件,没有名称,所以称为匿名管道!

(pipe)管道函数

创建成功返回0,失败返回-1

int pipe(int pipefd[2]);

//pipefd[2] 为输出型参数
调用pipe的时候,操作系统内部,帮你打开对应的文件(读和写的方式,同一个文件打开两次,得到两个文件描述符),填充到你当前进程的文件描述符表中,
然后把文件描述符表中的对应数组下标传给pipefd[2],这样就以读和写的方式,分别打开了同一文件



#include <iostream>
#include <unistd.h>
#include <cassert>
#include <sys/types.h>
#include <sys/wait.h>
#include <string>
#include <cstdio>
#include <cstring>

int main()
{

    // 1.创建管道通讯
    int fds[2];
    int n = pipe(fds);
    assert(n == 0);


    std::cout << "fds[0]:" << fds[0] << std::endl;
    std::cout << "fds[1]:" << fds[1] << std::endl;

    // 父进程读取,子进程写入
    // 2.fork()
    pid_t id = fork();
    assert(id >= 0);
    if(id == 0) //子进程
    {
        //子进程的通讯
        //子进程关闭读的描述符[0]
        close(fds[0]);
        //string msg = "hello,i am child!";
        const char *s = "我是子进程,我正在给你发消息";
        int cnt = 0;
        while(true)
        {
            char buffer[1024];//只有子进程能看到!
            snprintf(buffer,sizeof(buffer),"child->parent say:%s[%d][%d]",s,cnt,getpid());
            // snprintf(buffer,sizeof(buffer),"child->parent say:[%d][%d]",cnt,getpid());
            write(fds[1],buffer,strlen(buffer));  //不用+1,\0只是在C语言中,这是系统
            sleep(1);//细节,我每隔1s写一次
            cnt ++;
        }
        exit(0);
    }
    //父进程的通讯代码
    //父进程关闭写的描述符[1]
    close(fds[1]);
    while(true)
    {
        char buffer[1024]; //只有父进程能看到
        //ssize_t s = read(fds[0],buffer,sizeof(buffer)-1); //把读到的数据当作字符串来处理
        //这里用sizeof(buffer)
        //read 函数返回的是读取的字节数,如果读取成功,则返回值大于 0。
        //在读取前,需要清空 buffer 数组,以避免读取到之前的残留数据。
        //在判断是否有数据可读时,你使用了 strlen(buffer)-1,这可能导致读取的数据长度为负数,
        //因为 buffer 数组未初始化。应该使用 sizeof(buffer)。
        if(s > 0) buffer[s] = 0; //主动添加反斜杠0
        std::cout << "Get Message#" << buffer <<" | My Pid:"<< getpid() <<std::endl;
       
        //细节:父进程没有sleep
    }

    n = waitpid(id,nullptr,0);
    assert(n == id);
    


    return 0;
    
}
//谁读谁写
fds[0]:3   ---》下标0对应读取0对应嘴(读)
fds[1]:4   ---》下标1对应写入1对应笔(写)

管道的四种情况:
  • 读慢写快

在进程通讯的时候,故意让子进程写的慢,sleep了一下,但当sleep的时间变长的时候,父进程读取的时间也变长,

那么在父进程读取前的时间,他在干什么?他在读取,read是一种阻塞,如果管道中没有了数据,读端在读,默认会直接阻塞当前正在读取的进程

  • 读快写慢

相反 ,如果让子进程不sleep,父进程sleep(500);让子进程不断向管道中写,管道的总容量是有大小的,固定大小,

当写端向缓冲区写满的时候,写端会进入阻塞,等读端进行读取。缓冲区的数据不会被覆盖。

缓冲区的读写特点

当缓冲区有很多数据的时候,看似是一行一行写入,一行一行读取,其实是以二进制的形式指定大小进行读取

  • 写关闭,读端读到0也会关闭。

当子进程写一条消息,直接break,这时写端的文件描述符已经关闭,只有读端还在读。总会过一段时间,将管道内的数据读完。

当读到0个字符的时候,将读的管道也关闭,读是为写作服务的,没有了写,读也没有意义。

  • 读关闭,写就没有意义了,浪费资源,所以OS会给写进程发信号,终止写端!

1.3.3总结管道的特征

1.管道的生命周期进程就是进程的周期

2.管道可以用来进行具有血缘关系的进程之间的进程通信,常用于父子通讯。

3.管道是面向字节流的(网络部分)

4.半双工 ------ 单向通讯(特殊情况)

5.互斥与同步机制--------对共享资源进行保护的方案

1.4命名管道 两个没有血缘关系进程之间的通讯。

  • 命令行命令 mkfifo

mkfifo name_pipe

出现以p开头的文件管道,

这个文件的特点,可以一个进程进行写入,另一个进程从管道中读取

ls > named_pipe    //一个终端写

cat < named_pipe  //另一个终端读

这是命令行式的两进程之间的管道通讯.但是管道文件的内容大小为0

  • 当打开同一个文件两次,操作系统不会给我们创建两个struct file对象,而是公用一个对象。
  • 这时候向这个文件写,并不会保存到磁盘,而是在内存让另一个线程去读取,这不就是一个管道吗。

请问命名管道,让把不同的进程看到同一文件呢?

  • 让不同的进程,打开指定名称(路径+文件名(唯一性))的同一个文件。
  • 这就是命名管道的规则,可以通过名字来标志唯一性的。
  • 匿名管道是通过继承,继承地址,来保证唯一性的
  • mkfifo()函数创建有名管道

#include

#include

int mkfifo(constchar *filename, mode_t mode);

open(constchar *path, O_RDONLY);//1

open(constchar *path, O_RDONLY | O_NONBLOCK);//2

open(constchar *path, O_WRONLY);//3

open(constchar *path, O_WRONLY | O_NONBLOCK);//4

尽量使用阻塞方式打开

特点:

1有名管道可以使非亲缘的两个进程互相通信

2通过路径名来操作,在文件系统中可见,但内容存放在内存中

3 文件IO来操作有名管道

4 遵循先进先出规则

5 不支持leek操作

6 单工读写

注意事项:

1 程序不能以O_RDWR(读写)模式打开FIFO文件进行读写操作,而其行为也未明确定义,因为如一个管道以读/写方式打开,进程可以读回自己的输出,同时我们通常使用FIFO只是为了单向的数据传递

2 第二个参数中的选项O_NONBLOCK,选项O_NONBLOCK表示非阻塞,加上这个选项后,表示open调用是非阻塞的,如果没有这个选项,则表示open调用是阻塞的

3  对于以只读方式(O_RDONLY)打开的FIFO文件,如果open调用是阻塞的(即第二个参数为O_RDONLY),除非有一个进程以写方式打开同一个FIFO,否则它不会返回;如果open调用是非阻塞的的(即第二个参数为O_RDONLY | O_NONBLOCK),则即使没有其他进程以写方式打开同一个FIFO文件,open调用将成功并立即返回。

对于以只写方式(O_WRONLY)打开的FIFO文件,如果open调用是阻塞的(即第二个参数为O_WRONLY),open调用将被阻塞,直到有一个进程以只读方式打开同一个FIFO文件为止;如果open调用是非阻塞的(即第二个参数为O_WRONLY | O_NONBLOCK),open总会立即返回,但如果没有其他进程以只读方式打开同一个FIFO文件,open调用将返回-1,并且FIFO也不会被打开。

4.数据完整性,如果有多个进程写同一个管道,使用O_WRONLY方式打开管道,如果写入的数据长度小于等于PIPE_BUF(4K),那么或者写入全部字节,或者一个字节都不写入,系统就可以确保数据决不会交错在一起。

1.5实现命名管道的通讯

1.5.1comm.hpp

#ifndef COMM_HPP
#define COMM_HPP
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <cassert>
#include <cerrno>
#include <unistd.h>
#include <fcntl.h>

#define NAMED_PIPE "/home/lin/Desktop/Linux_learn/named_pipe/mypipe.lin"

bool creatFifo(const std::string &path)
{
    umask(0);
    int n = mkfifo(path.c_str(),0666);
    if(n == 0) return true;
    else{
        std::cout<< "errno:"<< errno << "err string:" << strerror(errno) << std::endl;
        return false;
    }
}

void removeFifo(const std::string &path)
{
    int n = unlink(path.c_str());
    //assert不要乱用,意料之中用assert,意料之外用if判断
    assert(n == 0);  //debbug时是有效的,release的时候就没有了。
    //如果在release的时候,这个n就没有被使用,会walling ,加一个强制类型转换,会避免报错
    (void)n;  
}

#endif

1.5.2client.cc

#include "comm.hpp"

int main()
{
    std::cout << "client begin"<< std::endl;
    int wfd = open(NAMED_PIPE,O_WRONLY);
    std::cout << "client end"<< std::endl;
    if(wfd <0 )
    {
       exit(1); 
    }
    //写
    char buffer[1024];
    while (true)
    {
        std::cout << "Please Saying#";
        fgets(buffer,sizeof(buffer),stdin);
        //if(strlen(buffer)>0) buffer[strlen(buffer) - 1] = 0; 
        ssize_t n = write(wfd,buffer,strlen(buffer) );
        assert(n == strlen(buffer));
        (void)n;

    }
    

    close(wfd);

    removeFifo(NAMED_PIPE);
    return 0;
}

1.5.3server.cc

#include "comm.hpp"

int main()
{
    bool r = creatFifo(NAMED_PIPE);
    assert(r);
    (void)r;

    std::cout << "server begin"<< std::endl;
    int rfd = open(NAMED_PIPE,O_RDONLY);
    std::cout << "server end"<< std::endl;
    if(rfd <0 )
    {
       exit(1); 
    }
    //读取

    char buffer[1024];
    while(true)
    {
        ssize_t s = read(rfd,buffer,sizeof(buffer));
        if(s > 0)
        {
            buffer[s] = 0;
            std::cout << "client->server#"<< buffer;
        }
        else if(s == 0)
        {
            std::cout << "client quit,me too!" << std::endl;
        }
        else
        {
            std::cout << "err string" << strerror(errno) << std::endl; 
        }
    }

    close(rfd);

    removeFifo(NAMED_PIPE);
    return 0;
}

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

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

相关文章

探索风扇产品模型的3D可视化魅力

在这个科技日新月异的时代&#xff0c;每一个细微的创新都能为我们的生活带来前所未有的便捷与享受。今天&#xff0c;就让我们一起踏入一场视觉与科技的盛宴&#xff0c;探索风扇产品模型如何通过3D可视化技术&#xff0c;重新定义家居生活的舒适与美学。 想象一下&#xff0c…

Redis—缓存机制

Redis 缓存机制 1. 缓存三兄弟1.1 缓存击穿1.2 缓存穿透1.3 缓存雪崩 2. 布隆过滤器3. 缓存和数据库数据一致性3.1 缓存更新策略3.2 缓存不一致处理 4. 热点 key4.1 热点 key 处理4.2 热点 key 重建 5. 缓存预热 Redis&#xff0c;一个轻量级的开源内存数据结构存储系统&#x…

Java:循环练习

目录 1. 回文判断 2. 减法求商余 3. 求平方根 4.求质数 5. 猜数字 1. 回文判断 输入一个数字&#xff0c;判断是否为回文&#xff0c;回文就是正着读和反着读都一样&#xff0c;如121是回文&#xff0c;123则不是。 import java.util.Scanner;public class DemoNew {publ…

白卡无法注册TDSCDMA问题分析

1、问题描述 MTK平台 实验室白卡测试TDSCDMA&#xff0c;默认无法注册。使用移动卡测试&#xff0c;无此问题。 2、问题分析 查看Radio log&#xff0c;Radio中反复下发EFUN去开关飞行模式。 39191: 08-14 22:45:57.159210 1469 1512 D RmcWp : [0] ECSRA info E…

CART决策树-基尼指数(全网最详解)

文章目录 一、基尼指数的定义二、基尼指数在CART决策树中的应用三、基尼指数与CART决策树的构建1.计算每个子集的基尼系数&#xff1a;2.计算基尼指数3.选择最优特征4.其余基尼指数5.构建决策树 四、总结 CART决策树基尼指数是CART&#xff08;Classification And Regression T…

稳石机器人 | 工业级AMR S1200L,专为多样化需求设计,柔性拓展更易用

近日&#xff0c;稳石机器人重磅推出基于新品控制器ROC1000的全新移动机器人AMR S1200L&#xff0c;专为满足生产制造和仓储物流的多样化需求而设计&#xff0c;无需改造现场&#xff0c;最快可在1周内完成部署。 重载型AMR-S1200L设计注重实用性和灵活性&#xff0c;可在室内…

Excel 中找出每列第一个和最后一个非空格对应的行--Excel难题#87

Excel表格的第2-6列有空格。 ABCDEF1StartDateQID1QID2QID3QID4QID5210/03/2024 10:561yes32310/03/2024 03:102no423409/03/2024 19:253yes22509/03/2024 11:404no1yes609/03/2024 03:555yes5no708/03/2024 20:106808/03/2024 12:257no908/03/2024 04:408yes1007/03/2024 20:…

Linux云计算 |【第二阶段】SECURITY-DAY4

主要内容&#xff1a; Kali系统、扫描与抓包、Nginx安全加固、Linux基本防护 补充&#xff1a;使用Curl命令查看网页头部信息和页面内容 不加选项&#xff0c;默认查看网页的内容&#xff1b; [ -I ] 选项&#xff1a;访问服务器页面时&#xff0c;显示HTTP的头部信息&#xf…

用阿里云“无影”搭建《黑神话:悟空》电脑环境

目录 《黑神话&#xff1a;悟空》 阿里云无影试用版概述 阿里云无影云电脑试用版情况 具体详细过程&#xff08;搭建环境&#xff09; 《黑神话&#xff1a;悟空》 《黑神话&#xff1a;悟空》作为一款高品质的国产游戏&#xff0c;对硬件配置有一定的要求。根据公开发布的…

【鸿蒙学习】HarmonyOS应用开发者高级认证 - 应用性能优化一(界面层面)

学完时间&#xff1a;2024年8月22日 学完排名&#xff1a;第1801名 一、介绍 在开发HarmonyOS应用时,优化应用性能是至关重要的。通过/ArkTS高性能编程、减少丢帧卡顿、提升应用启动和响应速度 可以有效提升用户体验。本文将介绍一些优化HarmonyOS应用性能的方法。 一、Ark…

Go开发桌面客户端软件小试:网站Sitemap生成

在前一篇【手把手教你用Go开发客户端软件&#xff08;使用Go HTML&#xff09;】中&#xff0c;我们详细介绍了如何通过Go语言开发一个简单的桌面客户端软件。本次&#xff0c;我们将继续这个系列&#xff0c;使用Go语言结合Sciter的Go绑定库——go-sciter&#xff0c;实战开发…

14.C基础_结构体

定义与使用 1、定义 定义结构体&#xff1a; 定义结构体时&#xff0c;需要注意最后的分号必须加上。 定义结构体时&#xff0c;成员只去声明类型&#xff0c;不进行赋值。赋值在定义结构体变量时进行。 struct 结构体名{结构体成员列表 }; //注意这里的分…

Qt入门学什么?

Qt是一个跨平台的C图形用户界面应用程序框架&#xff0c;它为应用程序开发者提供建立图形界面所需的所有功能。Qt框架以其面向对象、易于扩展的特性而受到广泛欢迎&#xff0c;并且支持多种平台&#xff0c;包括桌面、嵌入式和移动平台 。 对于Qt的入门学习&#xff0c;可以通过…

uniapp+vue3的defineProps传递

//index.vue <view class"topic"><!-- 磨砂背景 --><view class"content"><matte v-for"(item,index) in 8" :key"index"></matte><matte isMore"false"></matte></view>&…

0成本学习Liunx系统【只需要一台笔记本电脑,无需购买云服务器】

【准备工作&#xff0c;需要软件】&#xff1a; 1&#xff1a;MobaXterm 【服务器连接工具&#xff08;免费开源&#xff09;】 2&#xff1a;CentOS-7-x86_64-DVD-2009.iso 【CentOS-7 镜像】 3&#xff1a;VirtualBox-7.0.20-163906-Win.exe 【虚拟机壳子】 4&…

朴素贝叶斯与决策树分类

朴素贝叶斯分类 1贝叶斯分类理论 选择高概率对应的类别 2条件概率 事件B发生的情况下&#xff0c;事件A发生的概率 &#x1d443;(&#x1d434;|&#x1d435;)&#x1d443;(&#x1d434;∩&#x1d435;)/&#x1d443;(&#x1d435;) > &#x1d443;(&#x1d43…

【前端面试】浏览器原理解读

前端进阶——浏览器篇-CSDN博客 浏览器工作原理与Javascript高级&#xff08;前后端异步&#xff09;-CSDN博客 DOM树的建立过程 前端DOM&#xff08;文档对象模型&#xff09;数的建立过程&#xff0c;实际上是浏览器解析HTML文档并构建DOM树的过程。这一过程大致可以分为以…

声音克隆GPT-SoVITS 2.0软件和详细的使用教程!

天命人&#xff0c;请允许我先蹭个热点&#xff01; 原始声音&#xff1a; 播放 克隆声音&#xff1a; 播放 文章写了一半&#xff0c;被《黑神话悟空》刷屏了。突发奇想&#xff0c;用里面的声音来做个素材试试看。 B站捞了一点声音素材&#xff0c;随便剪一剪&#xff0c…

IOS半越狱工具nathanlr越狱教程

简介 nathanlr 是一款半越狱工具&#xff0c;不是完整越狱。 半越狱只能使用一些系统范围的插件。 无法做到完整越狱 Dopamine 越狱一样插件兼容性。 nathanlr支持 iOS 16.5.1 – 16.6.1 系统。 支持 A12 及以上设备。 肯定有人问&#xff0c;为什么仅仅支持这些系统&#xff…

关于全球影像下载你需要知道这些参数

经常会有客户问我们&#xff0c;如果想要下载全球的影像应该怎么下载&#xff0c;这里我们用数字说话&#xff0c;为你介绍一下全球影像下载的那些关键参数。 TIF文件大小 在开始之前说明一下&#xff0c;以下表格中所有出现的级别均为标准级别&#xff0c;如果想对应水经微图…