当前位置: 首页 > news >正文

【Linux】进程间通信(四):System V标准(共享内存、消息队列、信息量)

📝前言:

这篇文章我们来讲讲进程间通信的 System V 标准(共享内存、消息队列、信息量)

🎬个人简介:努力学习ing
📋个人专栏:Linux
🎀CSDN主页 愚润求学
🌄其他专栏:C++学习笔记,C语言入门基础,python入门基础,C++刷题专栏


这里写目录标题

  • 一,System V 标准
  • 一,共享内存
    • 1. 原理介绍
    • 2. 共享内存数据结构
    • 2. 接口介绍
      • 2.1 shmget
      • 2.2 shmat
      • 2.4 shmdt
      • 2.3 shmctl
      • 2.4 命令行级操作
    • 3. 使用示例
  • 二,消息队列和信号量(非重点)

一,System V 标准

Linux中支持了System V标准的进程管理与通信,使得该标准下的通信模块接口的设计、原理、和使用方式相似。下面要介绍的三个进程间方式就属于System V 标准。

一,共享内存

1. 原理介绍

原理:

  • 在物理内存上申请一块空间,然后将这块空间映射到不同进程的虚拟地址中。【在共享内存的结构体shmid_ds中会有引用计数记录这块内存被多少进程使用】
  • 于是,各个进程就可以通过页表的映射访问到这块共享内存

在这里插入图片描述
共享内存的特点:

  • 最快的IPC形式
    • 因为:⼀旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递就不再涉及到内核(即:映射成功后,进程不再通过执行内核的系统调⽤来传递彼此的数据,就像malloc一样,申请完就是一块空间)
  • 不具有同步机制
    • 因为,一旦映射成功以后,就不依赖物理内存和内核,每个进程各干各的,不像管道一样会等待另一端。【就会导致读写混乱】
  • 生命周期随内核,不用指令删就一直存在,除非操作系统重启

2. 共享内存数据结构

struct shmid_ds {struct ipc_perm shm_perm; /* operation perms */int shm_segsz; /* size of segment (bytes) */__kernel_time_t shm_atime; /* last attach time */__kernel_time_t shm_dtime; /* last detach time */__kernel_time_t shm_ctime; /* last change time */__kernel_ipc_pid_t shm_cpid; /* pid of creator */__kernel_ipc_pid_t shm_lpid; /* pid of last operator */unsigned short shm_nattch; /* no. of current attaches */unsigned short shm_unused; /* compatibility */void shm_unused2; /* ditto - used by DIPC */void shm_unused3; /* unused */
};

2. 接口介绍

2.1 shmget

  • 用于获取共享内存

  • 函数原型:int shmget(key_t key, size_t size, int shmflg)

  • key:用户提供,仅用于获取共享内存时,内核通过key来找对应的共享内存(这个key具有唯一性)

    • key的生成用ftok函数:
    • 函数原型:key_t ftok(const char *pathname, int proj_id)
    • 传入一个字符串和一个整数,生成一个key(可以随便给,但是为了代码可读性,一般字符串传入路径)
  • size:要开辟的共享内存的大小

    • 注意:申请的共享内存,内核开的物理内存大小是向上 4KB 取整的,但是给用户的大小是按用户实习要多少决定的(比如申请 4097,内核物理内存开 4096 * 2,但是给用户还是4097,即:虚拟地址只映射4097大小的空间)
  • shmflg:标记位。两种传参方法:

    • IPC_CREAT:获取共享内存,不存在就创建
    • IPC_EXCL | IPC_CREAT:获取全新的共享内存。如果已经存在就报错(系统就是通过传入的key来判断共享内存是不是全新)
    • 0xxx:还要传创建共享内存时的权限【共享内存也类似文件】
  • 返回值:返回共享内存标识符shmid【这才是后续用户用来标识共享内存的】

2.2 shmat

  • 用于将共享内存段映射到进程地址空间
  • 函数原型:void *shmat(int shmid, const void *shmaddr, int shmflg)
  • shmid:共享内存标识符
  • shmaddr:指定映射的虚拟地址,一般设置为NULL 表示由系统自动分配地址。(因为我们用户也不知道虚拟地址的使用情况)
  • shmflg:设置映射地址的权限(一般也用 默认设置 0
    • 0:默认读写权限。进程可以读取并修改共享内存中的数据。
    • SHM_RDONLY:只读模式。进程只能读取共享内存
  • 返回值:返回指向共享内存段在当前进程中的起始虚拟地址

2.4 shmdt

  • 用于取消映射,仅减少引用计数
  • 原型:int shmdt(const void *shmaddr)
  • shmaddr:指向共享内存段在当前进程中映射地址的指针(由 shmat 返回)。

2.3 shmctl

  • 用于控制共享内存(我们删除共享内存也用这个)
  • 函数原型:int shmctl(int shmid, int cmd, struct shmid_ds *buf)
  • shmid:共享内存标识符。
  • cmd:操作命令,有以下几种取值。
    • IPC_RMID:删除这片共享内存。(此时buf参数设置成 NULL
    • IPC_STAT:得到共享内存的状态,把共享内存的 shmid_ds 结构复制到 buf 中。
    • IPC_SET:改变内核共享内存的状态,把 buf 所指的 shmid_ds 结构中的 uid、gid、mode 复制到共享内存的 shmid_ds 结构内。
  • buf:用户层的共享内存管理结构体。需要我们自己创建struct shmid_ds结构体,用来存储内核结构体的信息。
    • 因为,在 Linux 系统中,用户空间程序无法直接访问内核内存,包括共享内存的管理结构体 struct shmid_ds。必须通过 系统调用(如 shmctl(),用IPC_STAT) 将内核中的数据复制到用户空间的结构体中。
  • 返回值:返回共享内存的标识符(删除:成功返回 0,失败:-1

2.4 命令行级操作

  • 查看ipcs -m
  • 删除ipcrm -m <shmid>
  • 创建ipcmk -m<size>

3. 使用示例

  • 当共享内存创建并且映射好以后,这篇区域就已经属于用户了,用户访问这篇区域的时候就不需要再调用系统调用。
// comm.h
#ifndef _COMM_
#define _COMM_# include <stdio.h>
# include <sys/types.h>
# include <sys/ipc.h>
# include <sys/shm.h>#define SIZE 1024// ftok 参数
# define PATHNAME "."
# define PROJ_ID 0x6666// 功能上:用户端:写,服务端:读。
// 服务端创建,用户端获取,服务端删除
// 对系统调用封装实现
int CreateShm(int size);
int DestroyShm(int shmid);
int GetShm(int size);
int CloseShm(void* shmaddr);#endif // 条件编译,确保头文件只被包含一次// comm.cpp
#include "comm.h"#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <iostream>
using namespace std;#define ERR_EXIT(m)         \do                      \{                       \perror(m);          \exit(EXIT_FAILURE); \} while (0)int CreateShm(int size)
{key_t key = ftok(PATHNAME, PROJ_ID);int shmid = shmget(key, size, IPC_CREAT | IPC_EXCL | 0666);if (shmid < 0)ERR_EXIT("shmget");cout << "创建共享内存成功" << endl;return shmid;
}int DestroyShm(int shmid)
{int ret = shmctl(shmid, IPC_RMID, NULL);if(ret < 0)perror("shmctl");cout << "删除成功"  << endl;return ret;
}int CloseShm(void* shmaddr)
{int ret = shmdt(shmaddr);if(ret < 0)perror("shmdt");cout << "关闭成功"  << endl;return ret;
}int GetShm(int size)
{key_t key = ftok(PATHNAME, PROJ_ID);int shmid = shmget(key, size, IPC_CREAT);if (shmid < 0)ERR_EXIT("shmget");cout << "获取共享内存成功" << endl;return shmid;
}// client.cpp
#include "comm.h"
#include <unistd.h>
int main()
{int shmid = GetShm(SIZE);char * ptr = (char*)shmat(shmid, nullptr, 0); // 我们把返回的地址当一个字符串指针for(int i = 0; i < 10; i+=2){*(ptr + i) = 'A' + i;*(ptr + i + 1) = 'A' + i;sleep(1);}CloseShm(ptr); // 关闭共享内存(引用计数 - 1)return 0;
}// server.cpp
#include "comm.h"
#include <iostream>
#include <unistd.h>
using namespace std;int main()
{int shmid = CreateShm(SIZE);char* ptr = (char*)shmat(shmid, nullptr, 0);for(int i = 0; i < 26; i++){cout << ptr << endl; // 直接当字符串打印sleep(1);}CloseShm(ptr);DestroyShm(shmid);return 0;
}

运行:

  • 两个进程是不具有同步性的,我们可以感受一下

先启动server.exe
在这里插入图片描述
server.exe自己干自己的,即使没有内容写入
在这里插入图片描述
共享内存被创建,nattch引用计数为 1

再运行client.exe
在这里插入图片描述
nattch变为2
在这里插入图片描述
并且开始server.exe打印信息

要解决上面的不同步问题:

  1. 信号量(这个是重点,但是 System V 版本的信号量不是重点)
  2. 加中间层命名管道(这东西同步)来传“信号”实现暂停和环形另一个进程,达到同步效果

二,消息队列和信号量(非重点)

消息队列和信号量也是System V 标准中提供“共享资源”,实现通信的其他IPC方法,但是非重点(System V 标准的 设计不好 / 用的少了)
如果想了解,可以看这篇文章:【Linux】进程间通信4——system V消息队列,信号量【其中包括对同步与互斥 以及 系统对IPC资源的组织管理】,写的很好!

但是我对里面一些内容进行总结和补充:

  • 原子性:行为是两态的(不做 / 做完,没有中间态)
  • 临界资源:被保护起来的共享资源
  • 同步:多个执行流,访问临界资源的时候,具有⼀定的顺序性(就比如要等待前一个完成)
  • 互斥:任何时刻,只允许一个执行流访问共享资源
  • 临界区:每个进程中访问临界资源的那段代码称为临界区(criticalsection),每次只允许一个进程进入临界区,进入后,不允许其他进程进入
  • 所谓的对共享资源进行保护,本质是对访问共享资源的代码进行保护【防止多个进程同时对代码共享资源进行修改,限制代码行为】

保护方法:加锁
在这里插入图片描述
锁也是共享资源,所以锁本身也需要被保护。在设计锁时,会把申请锁的行为设置成原子性的。(为了保护锁)

信号量

  • 消息队列和信号量的生命周期也是随内核的,申请的IPC资源必须删除,否则不会自动清除,除非重启操作系统
  • 我们将一大片共享内存分成多个不同的区域(资源)(按共性内存分),信号量就是记录其中可用资源的数量(可进入进程)的计数器。
    在这里插入图片描述
  • 左图:资源整体使用,信号量只有 0 / 1 :这种情况下就是互斥,每次只有一个进程能访问该资源
  • 信号量本质是对资源的预订机制
  • 申请资源,计数器–,P操作
  • 释放资源,计数器++,V操作
  • 如果当前信号量不够了,则申请资源的进程就会被加入到信号量结构体的等待队列中(即:进程先阻塞)

IPC资源的组织管理

  • 当共享内存、消息队列、信息量三者的key相同时,就会冲突
  • 三种资源虽然都有各自的结构体,但是他们的第一个成员都是kern_ipc_permkern_ipc_perm就存储了他们的key值,同时会记录所处在的结构体的资源类型)
  • 而所有kern_ipc_perm会被组织到ipc_id_ary这个数组里面(也就是这个数组里面存着指向kern_ipc_perm的指针,即:指向对应的资源的结构体的头部)
  • 相当于kern_ipc_perm是基类,其余三种资源的结构体都是子类

在这里插入图片描述


🌈我的分享也就到此结束啦🌈
要是我的分享也能对你的学习起到帮助,那简直是太酷啦!
若有不足,还请大家多多指正,我们一起学习交流!
📢公主,王子:点赞👍→收藏⭐→关注🔍
感谢大家的观看和支持!祝大家都能得偿所愿,天天开心!!!

http://www.xdnf.cn/news/580033.html

相关文章:

  • [Git] 认识 Git 的三大区域 文件的修改和提交
  • linux杀死进程自身
  • Docker实战
  • docker network 自定义网络配置与管理指南
  • 数字孪生技术如何重塑能源产业?
  • 生成树协议(STP)配置详解:避免网络环路的最佳实践
  • java基础(api)
  • 第八天的尝试
  • 印度语言指令驱动的无人机导航!UAV-VLN:端到端视觉语言导航助力无人机自主飞行
  • AllToAll通信为什么用于EP并行?
  • Linux性能监控工具nmon
  • 【开源解析】基于深度学习的双色球预测系统:从数据获取到可视化分析
  • Axure系统原型设计首页模版方案
  • InetAddress 类详解
  • AI大模型技术全景解析:核心原理与关键技术拆解
  • 【C++ 真题】P5736 【深基7.例2】质数筛
  • HJ23 删除字符串中出现次数最少的字符【牛客网】
  • 《Effective Java(第三版)》笔记
  • ESP32-S3 (ESP IDF 5.4.1 - LVGL 9.2.0)九宫格拼音输入法
  • 工业控制解决方案三段论
  • Java 实现四种单例(都是线程安全)
  • 【Linux】了解 消息队列 system V信号量 IPC原理
  • 常见字符串相似度算法详解
  • 使用Pandoc实现Markdown和Word文档的双向转换
  • 基于LiveData和ViewModel的路线管理实现(带PopupWindow删除功能)
  • 人工智能价值:技术革命下的职业新坐标
  • 【java】Java注解
  • 通信协议详解(分层技术解析)
  • 4-码蹄集600题基础python篇
  • 16、Python运算符全解析:位运算实战、字符串拼接与列表合并技巧