基于POSIX标准的Linux进程间通信

news2024/11/19 1:36:46

文章目录

  • 1 管道(匿名管道)
    • 1.1 管道抽象
    • 1.2 接口——`pipe`
    • 1.3 管道的特征
    • 1.4 管道的四种情况
    • 1.5 匿名管道用例
  • 2 命名管道
    • 2.1 创建一个命名管道——`mkfifo`
    • 2.2 关闭一个管道文件——unlink
    • 2.3 管道和命名管道的补充
    • 2.4 命名管道用例
  • 3 共享内存
    • 3.1 原理
    • 3.2 系统调用接口——`shmget`
      • 3.2.1 `key`
      • 3.2.2 `ftok`——返回key
      • 3.2.3 `size`
      • 3.2.4 `shmflg`
      • 3.2.5 返回值——shmid
    • 3.3 主动释放共享内存
      • 3.3.1 使用命令——`ipcrm`
      • 3.3.2 系统调用函数——`shmctl`
    • 3.4 将进程与共享内存挂接
      • 3.4.1 关联——`shmat`
      • 3.4.2 去关联——`shmdt`
    • 3.5 共享内存的特点
    • 3.6 共享内存属性 —— `chmctl`
    • 3.5 共享内存用例
  • 4 消息队列
  • 5 IPC在内核中的数据结构

进程间的通信标准

  • system V IPC
    • 消息队列、共享内存、信号量
  • POSIX IPC
  • 消息队列、共享内存、信号量、互斥量、条件变量、读写锁
  • 基于文件的通信方式——管道

1 管道(匿名管道)

当创建子进程时,进程中的files_struct同样被赋值了一份,如果此时内存中打开了一份内存级文件(该文件同样包含:cnt、inode、file_operators、缓冲区,但是该内存级文件不需要向磁盘刷新缓冲区),并且在父进程的文件描述符表中,那么子进程也会赋值也会同样打开这份文件,此时父子进程可以通过该文件进行通信

1.1 管道抽象

image-20231113233932235

image-20231113235339531

  • 管道只能进行单向通信

1.2 接口——pipe

#include <unistd.h>

int pipe(int pipefd[2]);
//pipefd为输出型参数,函数通过该参数将两个文件描述符输出出来,给用户使用
//pipefd[0] : 读下标
//pipefd[1] : 写下标

1.3 管道的特征

  • 具有血缘关系的进程进行进程间通信
  • 管道只能单向通信
  • 父子进程之间是会协同的,同步与互斥 —— 保护管道文件的数据安全
  • 管道是面向字节流的
  • 管道是基于文件的,而文件的生命周期是随进程的,当进程退出时系统自动释放文件内存

查看一些操作系统的限制,例如管道的大小

ulimit -a

1.4 管道的四种情况

  1. 读写端正常,管道为空,读端阻塞
  2. 读写端正常,管道被写满,写端阻塞
  3. 读写端正常,写端关闭,读端read返回0,表示读到文件(pipe)结尾,不会被阻塞
  4. 写端正常,读端关闭,操作系统会通过信号(13: SIGPIPE)杀掉正在写入的进程

1.5 匿名管道用例

//匿名管道用例
#include <iostream>
#include <cstdlib>
#include <unistd.h>
#include <cstdio>
#include <cstring>
#include <sys/wait.h>

using namespace std;

void Writer(int write_fd) {
    char buffer[1023] = {'\0'};
    snprintf(buffer, sizeof(buffer), "%s-%d", "message, pid: ", getpid());
    write(write_fd, buffer, strlen(buffer));
}

void Reader(int read_fd) {
    char buffer[1023] = {'\0'};
    ssize_t n = read(read_fd, buffer, sizeof(buffer));
    buffer[n] = '\0';
    cout << "child output: " <<  buffer << endl;
}

int main() {
    int pipefd[2];
    int ret = pipe(pipefd);

    pid_t id = fork();
    if (id == 0) {
        close(pipefd[1]);
        Reader(pipefd[0]);
    }
    close(pipefd[0]);
    Writer(pipefd[1]);
    
    int wait_ret = waitpid(id, nullptr, 0);
    return 0;
}

2 命名管道

  • 如果两个不同的进程,打开同一个文件的时候,在内核中,操作系统只会打开一个文件
  • 管道文件
    • 内存级文件,只用文件缓冲区,不用写入到磁盘中
    • 两个进程如何知道打开的是同一份文件:使用同一个文件名

2.1 创建一个命名管道——mkfifo

  • 与文件相同,不能重复创建文件名相同的管道文件!!
  1. 命令

image-20231120170010308

image-20231120170102231

  1. 系统调用函数

image-20231120171040698

2.2 关闭一个管道文件——unlink

image-20231120171212163

2.3 管道和命名管道的补充

  1. 多个进程在通过管道通信时,删除管道文件则无法继续通信? —— 错误

    管道的生命周期随进程,本质是内核中的缓冲区,命名管道文件只是标识,用于让多个进程找到同一块缓冲区,删除后,之前已经打开管道的进程依然可以通信

  2. 命名管道的本质和匿名管道的本质相同都是内核中的一块缓冲区

2.4 命名管道用例

//命名管道用例

//comm.hpp
#pragma once
#include <sys/types.h>
#include <iostream>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>

using namespace std;
const string pathname = "./myfifo";

class Init {
public:
    Init() {
        mkfifo(pathname.c_str(), 0666);
    }
    ~Init() {
        unlink(pathname.c_str());
    }
};

//server.cc
#include "comm.hpp"
//读数据
int main() {
    Init init;    //创建管道文件,进程结束调用unlink销毁管道文件
    int fd = open(pathname.c_str(), O_RDONLY);
    
    while (true) {
        char buffer[1024] = {0};
        ssize_t n = read(fd, buffer, sizeof(buffer));
        if (n == 0) {
            cout << "write close -> read close" << endl;
            break;
        }
        buffer[n] = 0;
        cout << "[server read]$ " << buffer << endl;
    }
    close(fd);
    return 0;
}

//client.cc
#include "comm.hpp"
//写数据
int main() {
    //读端关闭,发送SIGPIPE信号
    signal(SIGPIPE, [](int){
        cout << "read close -> write close" << endl;
        // unlink(pathname.c_str());
        exit(0);
    });
    int fd = open(pathname.c_str(), O_WRONLY);
    string input;
    while (true) {
        cout << "[client write]$ ";
        getline(cin, input);
        write(fd, input.c_str(), input.size());
    }
    close(fd);
    return 0;
}

3 共享内存

3.1 原理

  1. 申请内存(通过系统调用, 通过操作系统管理所有共享内存)
  2. 挂接到进程地址空间
  3. 返回首地址(虚拟地址)

image-20231120191422225

3.2 系统调用接口——shmget

//创建共享内存用例
const string pathname = "/home/dusong";
const int proj_id = 0x12345;
const int size = 4096;
int main() {
    key_t k = ftok(pathname.c_str(), proj_id);
    int shmid = shmget(k, size, IPC_CREAT|IPC_EXCL|0X666);
}

image-20231120192625794

3.2.1 key

  • 通过key标识系统中的一块共享内存
  • 对于一个已经创建好的共享内存,key在哪里? key在共享内存的描述对象中

3.2.2 ftok——返回key

image-20231120194416466

通过pathnameproj_id生成一个哈希值,作为key

  • 若返回值<0,则key获取失败

3.2.3 size

共享内存大小,单位字节

  • 共享内存的大小一般建议是4096(4Kb)的整数倍
  • 如果size为4097,操作系统会给4096*2的空间

3.2.4 shmflg

  • IPC_CREAT:单独使用时,如果申请的共享内存不存在,则创建,存在则获取并返回
  • IPC_CREAT|IPC_EXCL:如果申请的共享内存不存在,则创建,存在则出错返回,确保每次申请都是一块新的共享内存
  • IPC_EXCL不单独使用
  • 权限(八进制)

3.2.5 返回值——shmid

共享内存标识符shmid

keyshmid的区别

key:用于操作系统内标定唯一性;

shmid(shmget的返回值):只在进程内,用来表示资源的唯一性

  • 查看所有shmid:
ipcs -m 

3.3 主动释放共享内存

共享内存被删除后,则其它进程直接无法实现通信?——错误

共享内存的删除操作并非直接删除,而是拒绝后续映射,只有在当前映射链接数为0时,表示没有进程访问了,才会真正被删除

image-20231123000221166

当进程还在通过共享内存通信时,通过ipcrm删除共享内存,此时任然可以通信

3.3.1 使用命令——ipcrm

  • 使用shmid关闭共享内存
ipcrm -m [shmid]
  • 删除所有进程间通信资源
ipcrm -a

3.3.2 系统调用函数——shmctl

image-20231120234004628

  • 参数2:cmd

    • IPC_RMID:删除共享内存
    • IPC_STAT:查看属性
  • 参数3:buf

    • 删除共享内存时,置为nullptr
    • 查看属性时作为输出型参数,传入一个shmid_ds结构体(描述管理共享内存的属性的结构体)

3.4 将进程与共享内存挂接

3.4.1 关联——shmat

image-20231120232143685

参数2:shmaddr: 指定将共享内存挂接到地址空间的哪个部分,一般设为nullptr

参数3:shmflg: 权限设置, 通常设置为0

返回值:连接到虚拟地址的首地址(作为shmaddr)

  • 查看一个共享内存被进程挂接的数量:
ipcs -m
  • shmat之后nattch+1

image-202312081252594731

3.4.2 去关联——shmdt

  • 进程退出时会自动去关联

image-20231120233329871

3.5 共享内存的特点

  1. 共享内存没有同步互斥之类的保护机制,没有内容时不阻塞(管道要阻塞)
  2. 共享内存是所有进程间通信中速度最快的,因为拷贝少,对比管道,管道需要先将内容写到用户级缓冲区中,再通过write写到文件缓冲区中
  3. 共享内存内部的数据由用户自己维护

3.6 共享内存属性 —— chmctl

image-20231121130404235

const string pathname = "/home/dusong";
const int proj_id = 0x12345;
const int size = 4096;

int main() {
    key_t k = ftok(pathname.c_str(), proj_id);
    int shmid = shmget(k, size, IPC_CREAT|IPC_EXCL|0X666);
    
    struct shmid_ds shmds;
    shmctl(shmid, IPC_STAT, &shmds);
    cout << shmds.shm_stgsz << << endl;   //该共享内存的大小
    cout << shmds.shm_prem.__key << << endl;   //key
    cout << shmds.shm_prem.mode << << endl;    //权限
}

3.5 共享内存用例

//comm.hpp
#pragma once
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <cstring>
#include <unistd.h>
#include <iostream>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
using namespace std;

const string pathname = "./home/for_pipe/sharedmemory";
const int proj_id = 1234;
const size_t size = 4096;
const string pipepath = "./myfifo";

class Shm {
public:
    Shm(){
        int ret = mkfifo(pipepath.c_str(), 0666);
        if (ret < 0) {
            cout << "make namedfifo fail"<< endl;
            exit(1);
        }
        if (shmid < 0) {
        cout << "shared_memory have created" << endl;
        exit(1);
    }
    }
    ~Shm() {
        shmctl(shmid, IPC_RMID, nullptr);
        unlink(pipepath.c_str());
    }
    static int key;
    static int shmid;
};
int Shm::key = ftok(pathname.c_str(), proj_id);
int Shm::shmid = shmget(Shm::key, size, IPC_CREAT|IPC_EXCL|0666);
//server.cc
#include "comm.hpp"

//读端
int main() {
    Shm shm;

    int fd = open(pipepath.c_str(), O_RDONLY);   //通过管道保证互斥与同步

    char* st = (char*)shmat(shm.shmid, nullptr, 0);
    char buffer[2] = {0};
    while(true)  {
        int n = read(fd, buffer, sizeof(buffer));
        if (n == 0) {
            cout << "write close -> read close" << endl;
            break;
        }
        cout << "[sever read]$ " << st << endl; 
        sleep(1);
    }
    shmdt(st);
}


//client.cc
#include "comm.hpp"

//写端
int main() {
    //默认信号处理为忽略
    signal(SIGPIPE, [](int){
        shmctl(Shm::shmid, IPC_RMID, nullptr);
        unlink(pipepath.c_str());
        cout << "read close -> write close" << endl;
        exit(0);
    });
    int key = ftok(pathname.c_str(), proj_id);
    int shmid = shmget(key, size, IPC_CREAT|0666);

    char* st = (char*)shmat(shmid, nullptr, 0);  //得到共享内存在虚拟内存空间中的起始地址

    int fd = open(pipepath.c_str(), O_WRONLY);


    string input = "";
    while (true) {
        cout << "[client write]$ ";
        getline(cin, input);
        write(fd, "c", 1);
        memcpy(st, input.c_str(), input.size());   //写数据
    }
    shmdt(st);
}

4 消息队列

  • 创建——msgget <-----ftok

image-20231121193308813

  • 删除——msgctl

    image-20231121193220744

  • 发送/接收数据块——msgsndmsgrcv

image-20231204105114938

  • 查看消息队列
ipcs -q
  • 删除消息队列
ipcrm -q [msqid]

5 IPC在内核中的数据结构

系统中维护一个struct ipc_prem*数组,通过shmidmsqidsemid作为下标访问,因为每一个shmid_dsmsqid_dssemid_ds结构体的第一个字段均为ipc_prem,所以系统可以通过将ipc_prem*强制类型转换为对应消息队列、共享内存或者信号量的结构体,从而进行管理

image-20231121200212372

image-20231121200321590

image-20231121200538938

image-20231121200557501


  1. 图片中两个进程通过共享内存建立通信,此时共享内存的连接数为2 ↩︎

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

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

相关文章

中国特供阉割版 RTX 4090 曝光,老黄这操作绝了

到了现在大伙儿应该发现&#xff1a;国内禁售 NVIDIA RTX 4090 显卡这事儿基本实锤了。 实际上根据老美规定&#xff0c;从上个月 17 号开始&#xff0c;凡是公司主体在中国的显卡品牌&#xff0c;就已经不能生产和销售 RTX 4090。 以后厂商想要卖 4090 只能以整机形式出售&am…

第一启富金:新添澳大利亚(ASIC)牌照

第一启富金&#xff1a;澳大利亞證券及投資委員會&#xff08;ASIC&#xff09; GOLDWELL GLOBAL PTY LTD 是 WHOLESALE INVESTOR SERVICES PTY LTD&#xff08;CAR 編號 001304943&#xff09;的企業授權代表開發澳大利亞客戶&#xff0c;WHOLESALE INVESTOR SERVICES PTY LT…

二维码智慧门牌管理系统升级:提升社会管理和公共服务水平

文章目录 前言一、升级的意义二、升级方案三、升级后的好处 前言 随着科技不断进步&#xff0c;二维码智慧门牌管理系统在公共管理和服务领域扮演关键角色。随着需求的增长&#xff0c;现有系统已难以满足各方需求。因此&#xff0c;系统升级成为紧迫任务。 一、升级的意义 升…

静态HTTP和动态HTTP的混合使用:最佳实践

在当今的互联网环境中&#xff0c;静态HTTP和动态HTTP各有其优势和局限。静态HTTP具有速度快、安全性高和易于维护的特点&#xff0c;而动态HTTP则能够实现动态交互和处理大量动态数据。为了充分利用两者的优势&#xff0c;越来越多的网站开始采用静态HTTP和动态HTTP混合使用的…

存储成本降71%,怪兽充电历史库迁移OceanBase

怪兽充电作为共享充电宝第一股&#xff0c;业务增长迅速&#xff0c;以至于业务架构不停地增加组件。在验证 OceanBase 可以简化架构并带来更大的业务价值后&#xff0c;首次尝试在历史库中使用 OceanBase 替代 MySQL&#xff0c;存储成本降低 71%。本文为怪兽充电运维架构部王…

性能优化,单台4核8G机器支撑5万QPS

前言 这篇文章的主题是记录一次Python程序的性能优化&#xff0c;在优化的过程中遇到的问题&#xff0c;以及如何去解决的。为大家提供一个优化的思路&#xff0c;首先要声明的一点是&#xff0c;我的方式不是唯一的&#xff0c;大家在性能优化之路上遇到的问题都绝对不止一个…

Facebook广告报告指标CPC

在Facebook广告中&#xff0c;CPC可以作为一个关键指标来评估广告效果和投资回报。较低的CPC意味着广告主能以更低的价格获得更多的点击量&#xff0c;从而降低广告投放成本。而较高的CPC可能暗示着广告主需要更大的预算才能获得相同数量的点击。本文小编将讲讲Facebook广告报告…

剧本杀小程序搭建:打造线上剧本杀新体验

剧本杀是一款以角色扮演为主的游戏&#xff0c;一度成为了年轻人的最喜爱的社交游戏。在剧本杀市场需求下&#xff0c;剧本杀规模也迅速上升。今年第一季度&#xff0c;剧本杀市场规模环比增长47%&#xff0c;市场整体消费水平逐渐呈上升趋势。 随着剧本杀的不断发展&#xff…

富士通LPK240标签打印机维修案例

故障描述: 一台送修的富士通LPK240标签打印机,故障为通电不开机,打开机器后面的电源开关后电源灯不亮,按机器上面的测试按钮后红灯闪烁,无法正常工作; 速印机(理想、荣大等)、复印机(夏普、东芝、理光、佳能、震旦等全系列)、打印机、扫描仪、传真机、多媒体教学一体…

制作一个RISC-V的操作系统三-编译与链接

文章目录 GCCGCC简介GCC的命令格式gcc -Egcc -cgcc -Sgcc -ggcc -vGCC的主要执行步骤GCC涉及的文件类型针对多个源文件的处理 ELFELF介绍ELF文件格式ELF文件处理相关工具&#xff1a;Binutils&#xff08;binary utility&#xff09;readlelf -hreadelf -S或readelf -SW&#x…

MIT_线性代数笔记: 复习一

目录 问题一问题二问题三问题四 本讲为考前复习课&#xff0c;考试范围就是 Axb 这个单元&#xff0c;重点是长方形矩阵&#xff0c;与此相关的概念包括零空间、左零空间、秩、向量空间、子空间&#xff0c;特别是四个基本子空间。当矩阵为可逆的方阵时&#xff0c;很多性质是一…

【开源】基于Vue.js的停车场收费系统

文末获取源码&#xff0c;项目编号&#xff1a; S 076 。 \color{red}{文末获取源码&#xff0c;项目编号&#xff1a;S076。} 文末获取源码&#xff0c;项目编号&#xff1a;S076。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 停车位模块2.2 车辆模块2.3 停车收费…

谷达冠楠:抖音新手开店在哪里进货

随着抖音平台的日益火爆&#xff0c;越来越多的新手商家选择在抖音上开设自己的店铺。然而&#xff0c;开店的第一步就是货源问题&#xff0c;那么抖音新手开店应该在哪里进货呢? 首先&#xff0c;我们可以选择线上批发市场。例如阿里巴巴、拼多多等大型电商平台&#xff0c;这…

2023亚太五岳杯量子计算挑战赛数学建模思路代码模型论文

2023五岳杯数学建模思路&#xff1a;比赛开始后第一时间更新&#xff0c;获取见文末名片 今年&#xff0c;APMCM亚太地区大学生数学建模竞赛组委会正式和玻色量子、中国移动云能力中心等多家单位达成合作。 开展APMCM校企合作高校巡回学术讲座活动&#xff0c;为企业、高校搭…

虚幻学习笔记10—C++函数与蓝图的通信

一、前言 除了上一章C变量与蓝图通信讲的变量能与蓝图通信外&#xff0c;还有函数和枚举也可以和蓝图通信。函数的关键字为”UFUNCTION“、枚举的关键字为”UENUM“。 二、实现 2.1、BlueprintCallable蓝图中调用 该函数时带执行的&#xff0c;带入如下。编译成功后在蓝图中输…

排序-插入排序与希尔排序

文章目录 一、插入排序二、希尔排序 一、插入排序 思路&#xff1a; 当插入第i(i>1)个元素时&#xff0c;前面的array[0],array[1],…,array[i-1]已经排好序&#xff0c;此时用array[i]的排序码与array[i-1],array[i-2],…的排序码顺序进行比较&#xff0c;找到插入位置即将…

VIT总结

关于transformer、VIT和Swin T的总结 1.transformer 1.1.注意力机制 An attention function can be described as mapping a query and a set of key-value pairs to an output, where the query, keys, values, and output are all vectors. The output is computed as a wei…

Redis基础系列-持久化

Redis基础系列-持久化 文章目录 Redis基础系列-持久化1. 什么是持久化2. 为什么要持久化3. 持久化的两种方式3.1 持久化方式1&#xff1a;RDB(redis默认持久化方式)3.11 配置步骤-自动触发3.12 配置步骤-手动触发3.12 优点3.13 缺点3.14 检查和修复RDB快照文件3.15 哪些情况会触…

【华为数据之道学习笔记】3-2 基础数据治理

基础数据用于对其他数据进行分类&#xff0c;在业界也称作参考数据。基础数据通常是静态的&#xff08;如国家、币种&#xff09;&#xff0c;一般在业务事件发生之前就已经预先定义。它的可选值数量有限&#xff0c;可以用作业务或IT的开关和判断条件。当基础数据的取值发生变…

小航助学2023年6月GESP_Scratch四级真题(含题库答题软件账号)

需要在线模拟训练的题库账号请点击 小航助学编程在线模拟试卷系统&#xff08;含题库答题软件账号 单选题2.00分 删除编辑附件图文 答案:D 第1题高级语言编写的程序需要经过以下&#xff08; &#xff09;操作&#xff0c;可以生成在计算机上运行的可执行代码。 A、编辑B、…