Linux共享内存 linux共享内存如何实现
liebian365 2024-11-03 15:47 19 浏览 0 评论
基本概念
共享内存就是将内存进行共享,它允许多个不相关的进程访问同一块逻辑内存。因此,共享内存是效率最高的一种进程间(IPC)通信机制,它可以在多个进程之间共享和传递数据,进程间需要共享的数据被放在共享内存区域,所有需要访问该共享区域的进程都要把该共享区域映射到本进程的地址空间中去,因此所有进程都可以访问共享内存中的地址,就好像访问 malloc 分配的内存一样。
但是,这种共享的内存需要进程自己去维护好,如同步、互斥等工作,比如当进程1在读取共享内存的数据时, 进程2却修改了共享内存中的数据,那么必然会造成数据的混乱,进程1读取到的数据就是错误的,因此,共享内存是属于临界资源,在某一时刻最多只能有一个进程对其操作(读/写数据), 共享内存一般不能单独使用,而要配合信号量、互斥锁等协调机制。
共享内存的思想非常简单,进程与进程之间虚拟内存空间本来相互独立,不能互相访问的,但是可以通过某些方式, 使得相同的一块物理内存多次映射到不同的进程虚拟空间之中,这样的效果就相当于多个进程的虚拟内存空间部分重叠在一起, 如下图所示
IPC Shared Memory
当进程1向共享内存写入数据后,共享内存的数据就变化了,那么进程2就能立即读取到变化了的数据, 而这中间并未经过内核的拷贝,因此效率极高。
优缺点
先来说优点,使用共享内存进行进程间的通信非常方便,而且函数的接口也简单,数据的共享使进程间的数据不用传送, 而是直接访问内存,加快了程序的效率。同时,它也不像匿名管道那样要求通信的进程有一定的 血缘 关系,系统中的任意进程都可以对共享内存进行读写操作。
再来说缺点,共享内存没有提供同步的机制。我们在使用共享内存进行进程间通信时,需要借助其他的手段(如信号量、互斥量等)来进行进程间的同步工作。
相关函数
创建共享内存函数
内核提供了 shmget() 函数的创建或获取一个共享内存对象,并返回共享内存标识符。函数原型如下:
int shmget(key_t key, size_t size, int shmflg);
参数说明:
- key:标识共享内存的键值,可以有以下取值:
- 0 或 IPC_PRIVATE。当 key 的取值为 IPC_PRIVATE,则函数 shmget() 创建一块新的共享内存;如果 key 的取值为0,而参数 shmflg 中设置了 IPC_PRIVATE 这个标志,则同样将创建一块新的共享内存。
- 大于0的32位整数:视参数 shmflg 来确定操作。
- size:要创建共享内存的大小,所有的内存分配操作都是以页为单位的,所以即使只申请只有一个字节的内存, 内存也会分配整整一页。
- shmflg:表示创建的共享内存的模式标志参数,在真正使用时需要与 IPC 对象存取权限(如0600)进行“|”运算来确定共享内存的存取权限。msgflg 有多种情况:
- IPC_CREAT:如果内核中不存在关键字与 key 相等的共享内存,则新建一个共享内存;如果存在这样的共享内存,返回此共享内存的标识符。
- IPC_EXCL:如果内核中不存在键值与 key 相等的共享内存,则新建一个共享内存;如果存在这样的共享内存则报错。
- SHM_HUGETLB:使用大页面来分配共享内存,所谓的大页面指的是内核为了提高程序性能,对内存实行分页管理时,采用比默认尺寸(4KB)更大的分页,以减少缺页中断。Linux 内核支持以2MB作为物理页面分页的基本单位。
- SHM_NORESERVE:不在交换分区中为这块共享内存保留空间。
返回值:shmget() 函数的返回值是共享内存的 ID。
映射函数
shmat() 函数就是把共享内存区对象映射到调用进程的地址空间。函数原型如下:
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数说明:
- shmid:共享内存 ID,通常是由 shmget() 函数返回的。
- shmaddr:如果不为 NULL,则系统会根据 shmaddr 来选择一个合适的内存区域, 如果为 NULL,则系统会自动选择一个合适的虚拟内存空间地址去映射共享内存。
- shmflg:操作共享内存的方式:
- SHM_RDONLY:以只读方式映射共享内存。
- SHM_REMAP:重新映射,此时 shmaddr 不能为 NULL。
- NULLSHM:自动选择比 shmaddr 小的最大页对齐地址。
返回值: 调用成功后返回共享内存的起始地址。
共享内存的映射有以下注意的要点:
共享内存只能以只读或者可读写方式映射,无法以只写方式映射。
shmat() 第二个参数 shmaddr 一般都设为 NULL,让系统自动找寻合适的地址。但当其确实不为空时,那么要求 SHM_RND 在 shmflg 必须被设置,这样的话系统将会选择比 shmaddr 小而又最大的页对齐地址(即为 SHMLBA 的整数倍)作为共享内存区域的起始地址。如果没有设置 SHM_RND,那么 shmaddr 必须是严格的页对齐地址。
解除映射函数
shmdt() 函数与 shmat() 函数相反,是用来解除进程与共享内存之间的映射的,在解除映射后, 该进程不能再访问这个共享内存。函数原型:
int shmdt(const void *shmaddr);
参数说明:
- shmaddr:映射的共享内存的起始地址。
shmdt() 函数调用成功返回0,如果出错则返回-1,并且将错误原因存于 error 中。
获取或设置属性函数
shmctl() 用于获取或者设置共享内存的相关属性。函数原型
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数说明:
- shmid:共享内存标识符。
- cmd:函数功能的控制命令,其取值如下:
- IPC_STAT:获取属性信息,放置到 buf 中。
IPC_SET:设置属性信息为 buf 指向的内容。
IPC_RMID:删除这该共享内存。
IPC_INFO:获得关于共享内存的系统限制值信息。
SHM_INFO:获得系统为共享内存消耗的资源信息。
SHM_STAT:与 IPC_STAT 具有相同的功能,但 shmid 为该 SHM 在内核中记录所有 SHM 信息的数组的下标, 因此通过迭代所有的下标可以获得系统中所有 SHM 的相关信息。
SHM_LOCK:禁止系统将该 SHM 交换至 swap 分区。
SHM_UNLOCK:允许系统将该 SHM 交换至 swap 分。 - buf:共享内存属性信息结构体指针,设置或者获取信息都通过该结构体,shmid_ds 结构如下:
struct shmid_ds {
struct ipc_perm shm_perm; /* 所有权和权限 */
size_t shm_segsz; /* 共享内存尺寸(字节) */
time_t shm_atime; /* 最后一次映射时间 */
time_t shm_dtime; /* 最后一个解除映射时间 */
time_t shm_ctime; /* 最后一次状态修改时间 */
pid_t shm_cpid; /* 创建者PID */
pid_t shm_lpid; /* 后一次映射或解除映射者PID */
shmatt_t shm_nattch; /* 映射该SHM的进程个数 */
...
};
其中权限信息结构体如下
struct ipc_perm {
key_t __key; /* 该共享内存的键值key */
uid_t uid; /* 所有者的有效UID */
gid_t gid; /* 所有者的有效GID */
uid_t cuid; /* 创建者的有效UID */
gid_t cgid; /* 创建者的有效GID */
unsigned short mode; /* 读写权限 + SHM_DEST + SHM_LOCKED 标记 */
unsigned short __seq; /* 序列号 */
};
使用示例
先来说说,使用共享内存的一般步骤
- 创建或获取共享内存ID
- 将共享内存映射至本进程虚拟内存空间的某个区域
- 当不再使用时,解除映射关系
- 当没有进程再需要这块共享内存时,删除它
下面来看个具体的示例,实现两个进程,一个是共享内存写进程,另一个是共享内存读进程,使用信号量来控制临界区。
共享内存写进程 write.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#define SHM_SIZE 1024
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};
void P(int sem_id) {
struct sembuf sb;
sb.sem_num = 0;
sb.sem_op = -1;
sb.sem_flg = SEM_UNDO;
semop(sem_id, &sb, 1);
}
void V(int sem_id) {
struct sembuf sb;
sb.sem_num = 0;
sb.sem_op = 1;
sb.sem_flg = SEM_UNDO;
semop(sem_id, &sb, 1);
}
int main() {
int shmid, semid;
key_t key;
char *shm, *msg;
union semun sem_union;
// 创建共享内存段
key = ftok(".", 'R');
shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0666);
if (shmid == -1) {
perror("shmget");
exit(1);
}
// 连接共享内存
shm = shmat(shmid, NULL, 0);
if (shm == (char *) -1) {
perror("shmat");
exit(1);
}
// 创建信号量
semid = semget(key, 1, IPC_CREAT | 0666);
if (semid == -1) {
perror("semget");
exit(1);
}
// 初始化信号量
sem_union.val = 1;
if (semctl(semid, 0, SETVAL, sem_union) == -1) {
perror("semctl");
exit(1);
}
int counter = 1;
while (1) {
// 等待读进程准备好
P(semid);
// 写入消息到共享内存
printf("Message %d", counter);
sprintf(shm, "Message %d", counter);
counter++;
// 释放信号量
V(semid);
sleep(1); // 等待1秒再写入下一条消息
}
// 断开共享内存连接
shmdt(shm);
return 0;
}
共享内存读进程 read.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#define SHM_SIZE 1024
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};
void P(int sem_id) {
struct sembuf sb;
sb.sem_num = 0;
sb.sem_op = -1;
sb.sem_flg = SEM_UNDO;
semop(sem_id, &sb, 1);
}
void V(int sem_id) {
struct sembuf sb;
sb.sem_num = 0;
sb.sem_op = 1;
sb.sem_flg = SEM_UNDO;
semop(sem_id, &sb, 1);
}
int main() {
int shmid, semid;
key_t key;
char *shm;
union semun sem_union;
// 获取共享内存段
key = ftok(".", 'R');
shmid = shmget(key, SHM_SIZE, 0666);
if (shmid == -1) {
perror("shmget");
exit(1);
}
// 连接共享内存
shm = shmat(shmid, NULL, 0);
if (shm == (char *) -1) {
perror("shmat");
exit(1);
}
// 获取信号量
semid = semget(key, 1, 0666);
if (semid == -1) {
perror("semget");
exit(1);
}
while (1) {
// 等待写进程准备好
P(semid);
// 读取共享内存中的消息并输出
printf("Received: %s\n", shm);
// 释放信号量
V(semid);
sleep(1); // 等待1秒再读取下一条消息
}
// 断开共享内存连接
shmdt(shm);
return 0;
}
接着,编译下2个进程
gcc -o write write.c
gcc -o read read.c
完成后,在两个终端中相继启动2个可执行文件
./write
./read
可以得到结果
IPC Shared Memory
写进程将会不断地向共享内存中写入不同的消息,而读进程将会从共享内存中读取并输出这些消息。两个进程之间通过信号量进行同步,使得写和读操作交替进行。值得注意的是,共享内存和信号量是一种较底层的进程间通信机制,需要开发者自行管理同步和互斥。在实际应用中,还需要考虑异常处理、错误处理以及进程退出等情况,以确保程序的稳定性和正确性。
相关推荐
- 4万多吨豪华游轮遇险 竟是因为这个原因……
-
(观察者网讯)4.7万吨豪华游轮搁浅,竟是因为油量太低?据观察者网此前报道,挪威游轮“维京天空”号上周六(23日)在挪威近海发生引擎故障搁浅。船上载有1300多人,其中28人受伤住院。经过数天的调...
- “菜鸟黑客”必用兵器之“渗透测试篇二”
-
"菜鸟黑客"必用兵器之"渗透测试篇二"上篇文章主要针对伙伴们对"渗透测试"应该如何学习?"渗透测试"的基本流程?本篇文章继续上次的分享,接着介绍一下黑客们常用的渗透测试工具有哪些?以及用实验环境让大家...
- 科幻春晚丨《震动羽翼说“Hello”》两万年星间飞行,探测器对地球的最终告白
-
作者|藤井太洋译者|祝力新【编者按】2021年科幻春晚的最后一篇小说,来自大家喜爱的日本科幻作家藤井太洋。小说将视角放在一颗太空探测器上,延续了他一贯的浪漫风格。...
- 麦子陪你做作业(二):KEGG通路数据库的正确打开姿势
-
作者:麦子KEGG是通路数据库中最庞大的,涵盖基因组网络信息,主要注释基因的功能和调控关系。当我们选到了合适的候选分子,单变量研究也已做完,接着研究机制的时便可使用到它。你需要了解你的分子目前已有哪些...
- 知存科技王绍迪:突破存储墙瓶颈,详解存算一体架构优势
-
智东西(公众号:zhidxcom)编辑|韦世玮智东西6月5日消息,近日,在落幕不久的GTIC2021嵌入式AI创新峰会上,知存科技CEO王绍迪博士以《存算一体AI芯片:AIoT设备的算力新选择》...
- 每日新闻播报(September 14)_每日新闻播报英文
-
AnOscarstatuestandscoveredwithplasticduringpreparationsleadinguptothe87thAcademyAward...
- 香港新巴城巴开放实时到站数据 供科技界研发使用
-
中新网3月22日电据香港《明报》报道,香港特区政府致力推动智慧城市,鼓励公私营机构开放数据,以便科技界研发使用。香港运输署21日与新巴及城巴(两巴)公司签署谅解备忘录,两巴将于2019年第3季度,开...
- 5款不容错过的APP: Red Bull Alert,Flipagram,WifiMapper
-
本周有不少非常出色的app推出,鸵鸟电台做了一个小合集。亮相本周榜单的有WifiMapper's安卓版的app,其中包含了RedBull的一款新型闹钟,还有一款可爱的怪物主题益智游戏。一起来看看我...
- Qt动画效果展示_qt显示图片
-
今天在这篇博文中,主要实践Qt动画,做一个实例来讲解Qt动画使用,其界面如下图所示(由于没有录制为gif动画图片,所以请各位下载查看效果):该程序使用应用程序单窗口,主窗口继承于QMainWindow...
- 如何从0到1设计实现一门自己的脚本语言
-
作者:dong...
- 三年级语文上册 仿写句子 需要的直接下载打印吧
-
描写秋天的好句好段1.秋天来了,山野变成了美丽的图画。苹果露出红红的脸庞,梨树挂起金黄的灯笼,高粱举起了燃烧的火把。大雁在天空一会儿写“人”字,一会儿写“一”字。2.花园里,菊花争奇斗艳,红的似火,粉...
- C++|那些一看就很简洁、优雅、经典的小代码段
-
目录0等概率随机洗牌:1大小写转换2字符串复制...
- 二年级上册语文必考句子仿写,家长打印,孩子照着练
-
二年级上册语文必考句子仿写,家长打印,孩子照着练。具体如下:...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- wireshark怎么抓包 (75)
- qt sleep (64)
- cs1.6指令代码大全 (55)
- factory-method (60)
- sqlite3_bind_blob (52)
- hibernate update (63)
- c++ base64 (70)
- nc 命令 (52)
- wm_close (51)
- epollin (51)
- sqlca.sqlcode (57)
- lua ipairs (60)
- tv_usec (64)
- 命令行进入文件夹 (53)
- postgresql array (57)
- statfs函数 (57)
- .project文件 (54)
- lua require (56)
- for_each (67)
- c#工厂模式 (57)
- wxsqlite3 (66)
- dmesg -c (58)
- fopen参数 (53)
- tar -zxvf -c (55)
- 速递查询 (52)