Linux——进程间通信一(共享内存、管道、systrem V)

一、进程间通信介绍

1.1、进程间通信的概念和意义

进程间通信(IPC interprocess communication)是一组编程接口,让不同进程之间相互传递、交换信息(让不同的进程看到同一份资源)

数据传输:一个进程需要将它的数据发送给另外一个进程

资源共享:多个进程之间共享同样的资源

通知事件:一个进程向另一个或一组发送消息

进程控制:有些进程希望完全控制另一个进程的执行

为什么要进行进程间通信?

以上的行为往往需要多个进程协同、共同完成一些事情

两个进程之间是不能进行”数据”的直接传递的(进程具有独立性)

不要以为,进程独立了就是彻底独立,有时我们需要双方能够进行一定程序的信息交互。

1.2、如何进行进程间通讯及其本质

怎么办?

一般规律

1、交换数据的空间(内存)

2、不能由通信双方任何一个提供(那由谁提供,OS提供) 

具体做法

OS提供的"空间"有不同的样式,就决定了有不同的通信方式

1、管道(匿名、命名)

2、共享内存

3、消息队列
4、信号量

进程间通信的本质:让不同的进程看到同一份资源(一般由OS提供)

为了进程在通信的时候,既能满足进程之间的独立性,又能够到达通信的目的,那么进程之间通信的地点就不能在两个进程中。 一个进程将自己的数据交给另一个进程,并且还要等待另一个进程的应答,这样一来,这个进程将不独立了,受到了另一个进程的影响,这就与进程的独立性矛盾。所以,两个进程进行通信的地点必须是由第三方提供的,第三方只能是操作系统。操作系统提供的这个地点被我们称为:公共资源。公共资源有了,还必须让要通信的进程都看到这一份公共资源,此时要通信的进程将有了通信的前提。之后就是进程通信,也就是访问这块公共资源的数据。

之所以有不同的通信方式,是因为公共资源的种类不一,如果公共资源是一块内存,那么通信方式就叫做共享内存,如果公共资源是一个文件,也就是struct file结构体,那么就叫做管道。

二、管道 

2.1管道介绍

什么是管道?

open("log.txt",w);
open("log.txt",r);

一个文件打开两次,那么在操作系统中会有2个struct file 但是这两个struct file指向同一个缓冲区 

 若父进程3为读端,4为写端,子进程也一样。那么子进程写入,父进程读取缓冲区内容,这是父子进程看到了同一块资源。

这种基于文件的,让不同进程看到同一份资源的通信方式叫做管道

管道只能被设计成单向通信 

如:子进程为写(writer,关掉读端)                                                父进程为读(reader,关掉写端)    当子进程关掉读端/父进程关掉写端对应的struct file没有释放掉,说明 struct file有引用计数(记录多少指针指向我) 当引用计数为0才释放。struct file是允许多个进程通过指针指向的。

为什么父进程最开始用rw方式打开同一个文件呢?                                                                            如果只以r方式打开的话,子进程拷贝完后就也是r;父进程只以w打开,子进程拷贝完也只是w

3.2匿名管道

匿名管道:就是没有名字的文件

如何让不同的进程看到同一份资源?匿名管道的解决办法是:创建子进程,继承父进程的属性信息,也就是说匿名管道可以(只能)进行具有血缘关系的进程进行进程间通信(常用于父子)

为了支持我们进行管道通信,OS提供系统调用pipe()

原型:int pipe(int fd[2]);

头文件unistd.h
功能:创建一无名管道
参数          fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端
返回值:成功返回0,失败返回错误代码 

3.3匿名管道代码

通过系统调用接口创建一个匿名管道

#include <iostream>
#include <cerrno>
#include <cstring>
#include <unistd.h>
 
using namespace std;
 
int main()
{
    int pipefd[2];
    int ret = pipe(pipefd); // 一.创建管道
    if(ret < 0)
    {
        cerr << errno << ": " << strerror(errno) << endl;
    }
    cout << "pipefd[0]: " << pipefd[0] << endl; // 3
    cout << "pipefd[1]: " << pipefd[1] << endl; // 4
 
    return 0;
}

然后就可以创建子进程,关闭不需要的读端或写端

#include <iostream>
#include <cerrno>
#include <cstring>
#include <unistd.h>
 
using namespace std;

int main()
{
    int pipefd[2];
    int ret = pipe(pipefd); // 一.创建管道
    if(ret < 0)
    {
        cerr << errno << ": " << strerror(errno) << endl;
    }
    pid_t id = fork(); // 二.创建子进程
    assert(id != -1);
    if(id == 0)
    {
        //子进程  关掉读端,只写
        close(pipefd[0]);
        
        exit(1);
    }

    //父进程
    //关掉写端,只读
    close(pipefd[1]);
    
    close(pipefd[0]); // 父进程,只写,关闭读
 
    return 0;
}

这时父子进程已经可以看到同一份资源,可以开始通信了

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<assert.h>

void writer(int wfd)
{
    const char* str = "我是子进程,o.O,我在给你发消息";
    char buffer[128];
    int cnt = 0;
    pid_t pid = getpid();
    while(1)
    {
        snprintf(buffer,sizeof(buffer),"message:%s,pid:%d,count:%d\n",str,pid,cnt);
        write(wfd, buffer, strlen(buffer));
        cnt++;
        sleep(1);
    }
    close(wfd);
}

void reader(int rfd)
{
    char buffer[1024];
    int cnt = 10;
    while(1)
    {
        size_t n = read(rfd,buffer,sizeof(buffer)-1);
        if(n>0)
            printf("父亲获得信息是: %s\n", buffer);
        else
        {
            printf("缓冲区读完了,文件也读完了\n");
            break;
        }
        cnt--;
        if(cnt==0)
            break;
    }
    close(rfd);
}


int main()
{
    //创建管道
    int pipefd[2];
    int n = pipe(pipefd);
    if(n<0)
        return 1;
    pid_t id = fork();

    if(id == 0)
    {
        //子进程  关掉读端,只写
        close(pipefd[0]);
        writer(pipefd[1]);
        exit(1);
    }

    //父进程
    //关掉写端,只读
    close(pipefd[1]);
    reader(pipefd[0]);
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if(rid == id)
        printf("退出码为:%d,信号为:%d\n",WEXITSTATUS(status), status & 0x7f);

    return 0;
}

匿名管道的一些读写现象以及对应的特性

按上面代码将子进程休眠上5s,那么在子进程休眠这段时间,父进程在等待子进程退出休眠(可以理解为管道内无数据)

写端一直写,读端一直不读或者很久读一次:若一次写入一个字符"A",每次写入时cnt++,执行后会发现当cnt=65536时不在写入(也就是写入65536个字节时)65536÷1024=64

在Ubuntu20.04操作系统下默认建立的管道大小为64KB;

管道内部被写满,父进程还没有读取的时候,那子进程要等到父进程来读它

对以上两种情况的总结:

1.管道内部没有数据且子进程不关闭自己的写端文件fd,读端就要阻塞等待直到pipe有数据

2.管道内部被写满且读端不关闭自己的fd,写端写满后就要阻塞等待

由此推断出管道的两种特性:

特性一:自带同步机制

特性二:血缘关系进程进行通信,常见父子

若把父进程休眠时间改短一点,每次父进程读完后,子进程又能继续写入,在此过程中我们不难发现:无论写端写多少个,读端都能一次读完,由此我们发现管道的另一个特性:

特性三:管道是面向字节流的(写多少次和读多少次没有直接关系,称为面向字节流)

当子进程写入10s后退出,而父进程一直读,且打印了返回值,10s后子进程关掉写文件描述符,此时返回值为0;若父进程退出,子进程会僵尸 

3.对于写端而言,不写且关闭pipe,读端会将管道中的数据读完,返回值为0,表示读结束,类是读到了文件的结尾

若写端一直在写,而读端读一会就结束,关闭读文件描述符

4.读端不读且关闭,写端在写,OS会直接终止写入的进程(通过信号13SIGPIPE杀死进程)

由此可以得出管道另外的特性

特性四:父子进程退出,管道自动释放,文件的生命周期是随进程的

特性五:管道只能单向通信,半双工的一种特殊情况(一方传信息时,另一方不能传,如:对讲机)

 5.当要写入的数据量不大于PIPE_BUF(4KB)时,linux将保证写入的原子性。
6.当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。

就是写入数据小于4kb,则次操作为安全的 

有时候公共资源有可能被两个执行流共同访问,访问时会出现信息交叉、数据混乱等问题;由此我们要有一种特性:一段数据、一块空间或一种资源我们要么不访问、要访问就把它改完了,这种特性叫原子性。

3.3进程池

processpool.cc

#include <iostream>
#include <string>
#include <cstdlib>
#include <vector>
#include <unistd.h>
#include <ctime>
#include "task.hpp"

using namespace std;


enum
{
    UsageError= 1,
    ArgError,
    PipeError
};
void Usage(const std::string &proc)
{
    cout<<"Usage:"<<proc<<"sub_process_num"<<endl;
}
//用一个类封装管道
class Channel
{
public:
    Channel(int wfd,pid_t sub_id,const std::string &name)//构造
        :_wfd(wfd)
        ,_sub_process_id(sub_id)
        ,_name(name)
        {}
    void PrintDebug()
    {
        cout << "_wfd: " << _wfd;
        cout << ",_sub_process_id: " << _sub_process_id;
        cout << ", _name: " << _name << endl;
    }
    string name() {return _name;}
    int wfd() {return _wfd;}
    pid_t pid() { return _sub_process_id; }
    ~Channel()//析构
    {
    }

private:
    int _wfd;//父进程通过此向channel写东西
    pid_t _sub_process_id;//记录子进程
    string _name;//channel名字
};

//将冗长的创建子进程封装一下
class ProcessPool
{
public:
    ProcessPool(int sub_process_num) //构造
        : _sub_process_num(sub_process_num)
    {}
    int CreateProcess(work_t work) // 回调函数
    {
        for (int number = 0; number < _sub_process_num; number++)
        {
            int pipefd[2]{0};
            int n = pipe(pipefd);
            if (n < 0)
                return PipeError;

            pid_t id = fork();
            if (id == 0)
            {
                // child -> r
                close(pipefd[1]);
                // 执行任务
                dup2(pipefd[0], 0);
                work();
                exit(0);
            }
            string cname = "channel-" + to_string(number);
            // father
            close(pipefd[0]);
            channels.push_back(Channel(pipefd[1], id, cname));
        }
        return 0;
    }
    int NextChannel()
    {
        static int next = 0;
        int c = next;
        next++;
        next %= channels.size();
        return c;
    }
    void SendTaskCode(int index, uint32_t code)
    {
        cout << "send code: " << code << " to " << channels[index].name() << " sub prorcess id: " <<  channels[index].pid() << endl;
        write(channels[index].wfd(), &code, sizeof(code));
    }
    void Debug()
    {
        for (auto &channel : channels)
        {
            channel.PrintDebug();
        }
    }
    ~ProcessPool()
    {
    }

private:
    int _sub_process_num;
    vector<Channel> channels;
};


int main(int argc ,char* argv[])
{
    if(argc!=2)
    {
        Usage(argv[0]);
        return UsageError;
    }

    int sub_process_num = std::stoi(argv[1]);//把进程数转整型
    if(sub_process_num == 0)
        return ArgError;

    //vector<Channel> channels;
    //把所有的channel(管道)push到vector中,那么对管道的管理就会变成对vector的增删查改
    //create process
    // for(int num=0;num<sub_process_num;num++)
    // {
    //     int pipefd[2]{0};
    //     int n = pipe(pipefd);
    //     if(n<0)
    //         return PipeError;
    //     pid_t id = fork();
    //     if(id == 0)//子进程
    //     {
    //         close(pipefd[1]);
    //         sleep(1);
    //         exit(0);
    //     }
    //     string cname = "channel-"+to_string(num);
    //     //父进程
    //     close(pipefd[0]);
    //     channels.push_back(Channel(pipefd[1],id,cname));
    // }
    ProcessPool *proc_ptr = new ProcessPool(sub_process_num);
    proc_ptr->CreateProcess(worker);
    //控制子进程
    // for(auto& e:channels)
    // {
    //     e.PrintDebug();
    // }
    while(1)
    {
        // a. 选择一个进程和通道
        int channel = proc_ptr->NextChannel();
        // cout << channel.name() << endl;

        // b. 你要选择一个任务
        uint32_t code = NextTask();

        // c. 发送任务
        proc_ptr->SendTaskCode(channel, code);

        sleep(1);
    }

    //回收、等待子进程
    delete proc_ptr;
    return 0;
}

task.hpp

#include <iostream>
#include <unistd.h>

using namespace std;

typedef void(*work_t)();  //函数指针类型
typedef void(*task_t)();  //函数指针类型

void PrintLog()
{
    cout << "printf log task" << endl;
}

void ReloadConf()
{
    cout << "reload conf task" << endl;
}

void ConnectMysql()
{
    cout << "connect mysql task" << endl;
}

task_t tasks[3] = {PrintLog, ReloadConf, ConnectMysql};

uint32_t NextTask()
{
    return rand() % 3;
}

void worker()
{
    // 从0中读取任务即可!
    while(true)
    {
        uint32_t command_code = 0;
        ssize_t n = read(0, &command_code, sizeof(command_code));
        if(n == sizeof(command_code))
        {
            if(command_code >= 3) continue;
            tasks[command_code]();
        }
        cout << "I am worker: " << getpid() << endl;
        sleep(1);
    }
}

makefile

processpool:processpool.cc
	g++ -o $@ $^ -std=c++11 -g
.PHONY:clean
clean:
	rm -f processpool

三、命名管道

管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。
如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。
命名管道是一种特殊类型的文件

man mkfifo:

指令:mkfifo 文件名

功能:创建命名管道文件

命名管道可以从命令行上创建,命令行方法是使用下面这个命令

mkfifo filename

此时就成功地建立了一个命名管道,可以发现它的(文件类型)权限前面的字母是p(pipe),而目录的文件类型是d(directory)。命名管道文件类型是p,而且该文件还有inode,说明在磁盘上是真实存在的。 

 当磁盘中有了命名管道文件以后,两个进程将可以通过这个管道文件进行通信了,步骤和匿名管道非常相似。一个进程以写方式打开管道文件,另一个进程以读端方式打开管道文件。

直接写入的话可以发现会阻塞在这里

它需要被另一个进程读取 

 可以通过unlink或者rm删掉命名管道

系统调用mkfifo以及unlink

第一个形参:管道文件的名字

第二个形参:创建管道文件的权限

返回值:0表示创建成功,-1表示创建失败。

man 2 unlink 

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Hadoop-16-Hive HiveServer2 HS2 允许客户端远程执行HiveHQL HCatalog 集群规划 实机配置运行

章节内容 上一节我们完成了&#xff1a; Metastore的基础概念配置模式&#xff1a;内嵌模式、本地模式、远程模式实机配置远程模式 并测试 背景介绍 这里是三台公网云服务器&#xff0c;每台 2C4G&#xff0c;搭建一个Hadoop的学习环境&#xff0c;供我学习。 之前已经在 V…

Hadoop-YARN-Tutorial

Hadoop-YARN-Tutorial 1 What is YARN? Yarn is the acronym for yet another resource negotiator. Yarn是yet another resource negotiator的缩写。 Yarn is a resource manager created by separating the processing engine and the management function of mapreduce. …

YOLOv8_obb数据集可视化[旋转目标检测实践篇]

先贴代码,周末再补充解析。 这个篇章主要是对标注好的标签进行可视化,虽然比较简单,但是可以从可视化代码中学习到YOLOv8是如何对标签进行解析的。 import cv2 import numpy as np import os import randomdef read_obb_labels(label_file_path):with open(label_file_path,…

ViewController 生命周期

ViewController 生命周期 ViewController 生命周期测试程序&#xff1a;ViewControllerLifeCircle ViewController 生命周期 ViewController 是 iOS 开发中 MVC 框架中的 C&#xff0c;ViewColllecter 是 View&#xff08;视图&#xff09;的 Collecter&#xff08;控制器&…

Vim编辑器与Shell命令脚本

前言&#xff1a;本博客仅作记录学习使用&#xff0c;部分图片出自网络&#xff0c;如有侵犯您的权益&#xff0c;请联系删除 目录 一、Vim文本编辑器 二、编写Shell脚本 三、流程控制语句 四、计划任务服务程序 致谢 一、Vim文本编辑器 “在Linux系统中一切都是文件&am…

TQ15EG开发板教程:MPSOC创建fmcomms8工程

链接&#xff1a;https://pan.baidu.com/s/1jbuYs9alP2SaqnV5fpNgyg 提取码&#xff1a;r00c 本例程需要实现在hdl加no-OS系统中&#xff0c;通过修改fmcomms8/zcu102项目&#xff0c;实现在MPSOC两个fmc口上运行fmcomms8项目。 目录 1 下载文件与切换版本 2 编译fmcomms8项…

【SpringCloud】概述 -- 微服务入门

在Java的整个学习过程中&#xff0c;大家势必会听见一些什么分布式-微服务、高并发、高可用这些专业术语&#xff0c;给人的感觉很高级&#xff0c;有一种高深莫测的感觉。可以看一下这篇博客对这些技术架构的演变有一个初步的认识: 服务端⾼并发分布式结构演进之路-CSDN博客文…

Java开源ERP系统Axelor汉化方法初探

Axelor简介 汉化过程介绍 定义语言和本地化 导出多语言记录 导入翻译 验证翻译 调整翻译 Axelor简介 2024年6月份Axelor ERP发布了8.1版本&#xff0c;适配JDK11及PostgreSQL12及以上版本&#xff08;7及以前版本适配JDK8及PostgreSQL10&#xff09;数据库。v8版本较之前…

kubernetes集群部署:node节点部署和cri-docker运行时安装(四)

安装前准备 同《kubernetes集群部署&#xff1a;环境准备及master节点部署&#xff08;二&#xff09;》 安装cri-docker 在 Kubernetes 1.20 版本之前&#xff0c;Docker 是 Kubernetes 默认的容器运行时。然而&#xff0c;Kubernetes 社区决定在 Kubernetes 1.20 及以后的…

昇思MindSpore学习入门-评价指标

当训练任务结束&#xff0c;常常需要评价函数&#xff08;Metrics&#xff09;来评估模型的好坏。不同的训练任务往往需要不同的Metrics函数。例如&#xff0c;对于二分类问题&#xff0c;常用的评价指标有precision&#xff08;准确率&#xff09;、recall&#xff08;召回率&…

代码随想录算法训练Day58|LeetCode417-太平洋大西洋水流问题、LeetCode827-最大人工岛

太平洋大西洋水流问题 力扣417-太平洋大西洋水流问题 有一个 m n 的矩形岛屿&#xff0c;与 太平洋 和 大西洋 相邻。 “太平洋” 处于大陆的左边界和上边界&#xff0c;而 “大西洋” 处于大陆的右边界和下边界。 这个岛被分割成一个由若干方形单元格组成的网格。给定一个…

调度系统揭秘(下):调度算法与架构设计

文章目录 一、调度算法1.1、广度优先:1.2、深度优先1.3、总结广度优先搜索&#xff08;BFS&#xff09;深度优先搜索&#xff08;DFS&#xff09; 二、架构设计2.1、Master/Slave架构优劣分析 2.2、Leader架构优劣分析 2.3、总结 一、调度算法 在调度系统中&#xff0c;调度算…

【】AI八股-神经网络相关

Deep-Learning-Interview-Book/docs/深度学习.md at master amusi/Deep-Learning-Interview-Book GitHub 网上相关总结&#xff1a; 小菜鸡写一写基础深度学习的问题&#xff08;复制大佬的&#xff0c;自己复习用&#xff09; - 知乎 (zhihu.com) CV面试问题准备持续更新贴 …

本安防爆手机:危险环境下的安全通信解决方案

在石油化工、煤矿、天然气等危险环境中&#xff0c;通信安全是保障工作人员生命安全和生产顺利进行的关键。防爆智能手机作为专为这些环境设计的通信工具&#xff0c;提供了全方位的安全通信解决方案。 防爆设计与材料&#xff1a; 防爆智能手机采用特殊的防爆结构和材料&…

机械硬盘故障分析及损坏处理(坏道屏蔽)

机械硬盘故障分析&#xff1a; 1、加电后没有声音就是电机不转&#xff0c;是电路问题&#xff0c;更换电路板解决。 2、加电后电机转&#xff0c;有连续敲击声音&#xff0c;或有异响&#xff0c;磁头损坏或机械故障。 3、加电后电机转&#xff0c;运行正常&#xff0c;BIOS无…

建立数据通路(一)

指令周期(Instruction Cycle) 指令种类 Fetch(取得指令) 也就是从PC寄存器里找到对应的指令地址&#xff0c;根据指令地址从内存里把具体的指令&#xff0c;加载到指令寄存器中然后把PC寄存器自增&#xff0c;好在未来执行下一条指令 Decode(指令译码) 也就是根据指令寄存器里…

Apache Seata新特性支持 -- undo_log压缩

本文来自 Apache Seata官方文档&#xff0c;欢迎访问官网&#xff0c;查看更多深度文章。 本文来自 Apache Seata官方文档&#xff0c;欢迎访问官网&#xff0c;查看更多深度文章。 Apache Seata新特性支持 – undo_log压缩 Seata新特性支持 – undo_log压缩 现状 & 痛点…

类与对像(1)

好几个月没有写了&#xff0c;差了好多&#xff0c;这些天补回来吧。 接下来&#xff0c;让我们正式步入C与C语言开始不同的地方。 我对类的理解&#xff1a;类是对于具有相同或相似属性的数据集合。 类的关键词&#xff1a;class&#xff0c;public&#xff0c;protected&a…

2024年加密货币市场展望:L1、L2、LSD、Web3 和 GameFi 板块的全面分析与预测

随着区块链技术的快速发展&#xff0c;加密货币市场在2024年继续展现出蓬勃的生机和创新的潜力。本文将深入分析L1、L2、LSD、Web3和GameFi这五大板块的发展趋势和预测&#xff0c;帮助投资者和爱好者更好地理解和把握市场机遇。 一、L1&#xff1a;基础层协议的持续进化 L1&a…

python自动化办公之cryptography加密解密

目录 用到的库 实现效果 代码部分 1、加密2024.txt文件 2、解密2024.txt文件 用到的库 cryptography 实现效果 加密文件和解密文件 代码部分 1、加密2024.txt文件 # 加密 from cryptography.fernet import Fernet # 生成加密密钥 keyFernet.generate_key() cipher_s…