📝前言:
这篇文章我们来讲讲进程间通信的 System V 标准(共享内存、消息队列、信息量)
🎬个人简介:努力学习ing
📋个人专栏:Linux
🎀CSDN主页 愚润求学
🌄其他专栏:C++学习笔记,C语言入门基础,python入门基础,C++刷题专栏
这里写目录标题
一,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
大小的空间)
- 注意:申请的共享内存,内核开的物理内存大小是向上 4KB 取整的,但是给用户的大小是按用户实习要多少决定的(比如申请
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
) 将内核中的数据复制到用户空间的结构体中。
- 因为,在 Linux 系统中,用户空间程序无法直接访问内核内存,包括共享内存的管理结构体
- 返回值:返回共享内存的标识符(删除:成功返回
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
打印信息
要解决上面的不同步问题:
- 信号量(这个是重点,但是 System V 版本的信号量不是重点)
- 加中间层命名管道(这东西同步)来传“信号”实现暂停和环形另一个进程,达到同步效果
二,消息队列和信号量(非重点)
消息队列和信号量也是System V 标准中提供“共享资源”,实现通信的其他IPC方法,但是非重点(System V 标准的 设计不好 / 用的少了)
如果想了解,可以看这篇文章:【Linux】进程间通信4——system V消息队列,信号量【其中包括对同步与互斥 以及 系统对IPC资源的组织管理】,写的很好!
但是我对里面一些内容进行总结和补充:
- 原子性:行为是两态的(不做 / 做完,没有中间态)
- 临界资源:被保护起来的共享资源
- 同步:多个执行流,访问临界资源的时候,具有⼀定的顺序性(就比如要等待前一个完成)
- 互斥:任何时刻,只允许一个执行流访问共享资源
- 临界区:每个进程中访问临界资源的那段代码称为临界区(criticalsection),每次只允许一个进程进入临界区,进入后,不允许其他进程进入
- 所谓的对共享资源进行保护,本质是对访问共享资源的代码进行保护【防止多个进程同时对代码共享资源进行修改,限制代码行为】
保护方法:加锁
锁也是共享资源,所以锁本身也需要被保护。在设计锁时,会把申请锁的行为设置成原子性的。(为了保护锁)
信号量:
- 消息队列和信号量的生命周期也是随内核的,申请的IPC资源必须删除,否则不会自动清除,除非重启操作系统
- 我们将一大片共享内存分成多个不同的区域(资源)(按共性内存分),信号量就是记录其中可用资源的数量(可进入进程)的计数器。
- 左图:资源整体使用,信号量只有 0 / 1 :这种情况下就是互斥,每次只有一个进程能访问该资源
- 信号量本质是对资源的预订机制
- 申请资源,计数器–,P操作
- 释放资源,计数器++,V操作
- 如果当前信号量不够了,则申请资源的进程就会被加入到信号量结构体的等待队列中(即:进程先阻塞)
IPC资源的组织管理:
- 当共享内存、消息队列、信息量三者的
key
相同时,就会冲突 - 三种资源虽然都有各自的结构体,但是他们的第一个成员都是
kern_ipc_perm
(kern_ipc_perm
就存储了他们的key
值,同时会记录所处在的结构体的资源类型) - 而所有
kern_ipc_perm
会被组织到ipc_id_ary
这个数组里面(也就是这个数组里面存着指向kern_ipc_perm
的指针,即:指向对应的资源的结构体的头部) - 相当于
kern_ipc_perm
是基类,其余三种资源的结构体都是子类
🌈我的分享也就到此结束啦🌈
要是我的分享也能对你的学习起到帮助,那简直是太酷啦!
若有不足,还请大家多多指正,我们一起学习交流!
📢公主,王子:点赞👍→收藏⭐→关注🔍
感谢大家的观看和支持!祝大家都能得偿所愿,天天开心!!!