Linux学习笔记之八(进程间的共享内存)

news/2025/2/16 5:23:41/

Linux

  • 1、引言
  • 2、实现共享内存
    • 2.1、创建一个共享内存
    • 2.2、将共享内存链接到进程空间
    • 2.3、断开与共享内存的链接
    • 2.4、对共享内存进行后续操作
  • 3、应用实例

1、引言

在之前一篇文章Linux学习笔记之六(进程之间的管道通信和信号处理)中我讲了进程间可以通过管道通信。管道通信也是在内存中某个地方开辟一块内存,让不同进程可以访问它,进而实现通信的目的。
而本文所讲的共享内存也大概是这么一个逻辑,不过相较与管道通信,内存共享实现起来更加复杂,也没有读写阻塞功能,但执行起来的效率也更加高,似乎是最快的进程通信方式。

2、实现共享内存

共享内存其任务在于从物理内存中申请一块内存区域,然后将这个区域映射到不同进程的空间中。进而进程访问该共享区域,进行读写操作。下图截取于B站,该图比较清晰明了说明了共享内存的运行逻辑。从这张图可以看出,任何进程都有自己的空间,而这些空间本质是取之于物理内存,但取的顺序则是杂乱的。共享内存无非是一块大家都可以取走的内存块而已。
在这里插入图片描述
接下来说明实现共享内存的基本步骤:
第一步:创建一个共享内存
第二步:将共享内存链接到进程空间
第三步:断开与共享内存的链接
第四步:对共享内存进行后续操作(如销毁)

2.1、创建一个共享内存

创建一个共享内存用到的函数是shmget(share memory get),其函数原型如下:

int shmget(key_t key, size_t size, int shmflag);

参数概览:

key:设置该共享内容的关键字标识。
size:指定共享内存的空间大小。
shmflag:设置共享内存的访问权限。
返回值:创建成功则返回共享内存标识符,失败则返回-1.

参数详解:
1. key参数:key 参数是一个用于标识共享内存段的关键字。它允许多个进程通过使用相同的 key 参数来访问同一个共享内存段。这就是说,如果多个进程想要访问同一个共享内存段,它们必须提供相同的 key 参数值。
通常情况下,key 参数可以是一个由 ftok() 函数生成的键值(通过给定文件路径名和一个项目标识符来生成唯一的键值),也可以是手动指定的一个键值,也可以通过一些宏指定一些key值。
关于ftok函数,其函数原型如下:

key_t ftok(const char *pathname, int proj_id);

第一个参数是真实存在的文件路径,第二个参数是项目的id,可以自己设定。
计算机会根据这两个参数的组合,生成一个独一无二的key值,保证不同进程可以访问同一个内存空间。
2. size参数:一般而言我们会设置成4096的整数倍,因为计算机的最小内存单元就是4kB,但如果不设置成4096的整数倍也无所谓,计算机会根据自动帮你调整到合适的值。比如你设置2009,计算机也会帮你调整到4096.
2. shmflag参数:该参数用于设置共享内存的访问权限,一般由一些宏和四位整数组成,比如0777 | IPC_CREAT。权限的设置相信大家都耳熟能详了,第一位0表示后面的三位数字是八进制数字,777分别表示,用户、用户组、其他人的权限。7会被拆解位111,分别表示rwx(读写执行)三个权限。
常见的宏有:
IPC_CREAT:若共享内存存在则打开,否则创建一个。
IPC_EXCL:若共享内存不存在则创建,否则报错。
IPC_NOWAIT:若该操作需要等待,直接报错。

最后是调用该函数需要头文件(后文提到的所有函数也需要包含这几个头文件):

#include "sys/types.h"
#include "sys/ipc.h"
#include "sys/shm.h"

2.2、将共享内存链接到进程空间

在这一步用到函数是shmat,其函数原型如下:

void *shmat(int shmid, const void *addr, int shmflg);

参数概览:

shmid:这里输入的是共享内存的标识符,其实就是shmget函数的返回值。
void *addr:指定链接到进程中的哪个地址块,一般不需要指定,由计算机自动分配即可。
int shmflg:标志位,一般设置为0即可。
返回值:成功则返回共享内存在进程空间中的链接地址,失败则返回-1。

注:void *指的是无类型指针,无类型换一种理解方式就是,可以赋予它任意类型。
通过这个步骤,我们拿到了链接地址,再用这个地址来索引相应的内存空间之后,便可以进行读写操作,以实现进程通信。

2.3、断开与共享内存的链接

进程通信结束之后自然是要将进程与共享内存之间的链接断开。这一步用到的函数时shmdt,其函数原型如下:

int shmdt(const void *address);

void *address:该地址指的进程空间中的链接地址。一般就是shmat中的返回值了。
返回值:成功则返回0,失败则返回-1。

2.4、对共享内存进行后续操作

进程通信结束,链接也断开,下一步自然是要考虑如何回收或销毁这个共享内存了。用到的函数时shmctl,其函数原型如下:

int shmctl(int shm_id, int command, struct shmid_ds *buf);

shm_id:共享内存对象的id,即内存的标识符。一般时shmget的返回值。
command:指定对该内存的操作命令,主要有删除命令(IPC_RMID)。
shmid_ds *buf:该参数用于获取内核结构的结构值,一般也不用。故直接设置为0即可。

最后注意一点,如果使用调用shmctl(shmid, IPC_RMID, 0)这句话,最后删除的只是共享内存的标识符,使系统不再维护该内存标识符,而不是真正把共享内存释放掉。如果想要共享内存被释放掉,则需要等到该内存没有被任何进程调用时,系统才会自动被释放掉。

3、应用实例

本例子的目的:创建两个进程A和B,A进程往共享内存写入数据,B进程将数据读出来。
故,首先创建一个进程A,去往一块共享内存写入数据。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "sys/types.h"
#include "sys/ipc.h"
#include "sys/shm.h"int main()
{key_t key  = ftok("$HOME/shared_memory/program_A.c", 9);        //generate a unique keyint shmid = shmget(key, 4096, 0751|IPC_CREAT);                  //create a shared memorychar *addr = shmat(shmid, 0, 0);        //link the shared memory with this program               char buffer[100];           //create a buffer for writing datawhile(1){printf("\nPlease input a character: ");fgets(buffer, sizeof(buffer), stdin);if(strcmp(buffer, "q\n") == 0)              //judge whether what I input is 'q' or notbreak;                                  strncpy(addr, buffer, sizeof(buffer));          //writing data into the shared memory}if(shmdt(addr) == 0){printf("disconnect shared memory successfully!\n");}else{printf("fail to disconnect!\n");}if(shmctl(shmid, IPC_RMID, 0) == 0){printf("delete the identifer successfully!\n");}else{printf("the identifer already be deleted!\n");}return 0;
}

其次,创建一个进程B,从共享内存中读取数据。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "sys/types.h"
#include "sys/ipc.h"
#include "sys/shm.h"int main()
{key_t key  = ftok("$HOME/shared_memory/program_A.c", 9);      int shmid = shmget(key, 4096, 0751|IPC_CREAT);                  char *addr = shmat(shmid, 0, 0);                     while(1){char input;printf("please input a character: ");scanf("%c", &input);if(input == 'q')break;int c;while ((c = getchar()) != '\n' && c != EOF) {}printf("what i receive is: %s\n", addr);}if(shmdt(addr) == 0){printf("disconnect successfully!\n");}else{printf("fail to disconnect!\n");}if(shmctl(shmid, IPC_RMID, 0) == 0){printf("delete successfully!\n");}else{printf("the identifer already be deleted!\n");}return 0;
}

最后,我打开两个终端,同时运行进程A和B,运行结果如下:
在这里插入图片描述


http://www.ppmy.cn/news/1266905.html

相关文章

658. 找到 K 个最接近的元素

658. 找到 K 个最接近的元素 Java代码&#xff1a;滑窗 class Solution {public List<Integer> findClosestElements(int[] arr, int k, int x) {List<Integer> list new ArrayList<>();for (int i 0; i < arr.length; i) {arr[i] arr[i] - x;}for(i…

IDEA版SSM入门到实战(Maven+MyBatis+Spring+SpringMVC) -Spring依赖注入数值问题

第一章 Spring依赖注入数值问题 1.1 字面量数值 数据类型&#xff1a;基本数据类型及包装类、String语法&#xff1a;value属性或value标签 1.2 CDATA区 语法&#xff1a;<![CDATA[]]>作用&#xff1a;在xml中定义特殊字符时&#xff0c;使用CDATA区 1.3 外部已声明…

【IC验证】perl脚本——分析前/后仿用例回归情况

目录 1 脚本名称 2 脚本使用说明 3 nocare_list文件示例 4 脚本执行方法 5 postsim_result.log文件示例 6 脚本代码 1 脚本名称 post_analysis 2 脚本使用说明 help&#xff1a;打印脚本说明信息 命令&#xff1a;post_analysis help 前/后仿结束后&#xff0c;首先填…

【Canvas】记录一次从0到1绘制风场空间分布图的过程

前言 &#x1f4eb; 大家好&#xff0c;我是南木元元&#xff0c;热衷分享有趣实用的文章&#xff0c;希望大家多多支持&#xff0c;一起进步&#xff01; &#x1f345; 个人主页&#xff1a;南木元元 目录 背景 前置知识 风场数据 绘制风场 准备工作 生成二维网格 获取…

iOS——UIPickerView选择器

UIPickerView UIPickerView是 iOS 开发中常用的用户界面组件之一&#xff0c;用于在垂直方向上显示一个滚动的列表&#xff0c;用户可以通过滚动选择其中的一项。 UIPickerView的协议方法 UIPickerView和UItableView差不多&#xff0c;UIPickerView也要设置代理和数据源。UI…

数据库范式(详细介绍)

目录 第一范式&#xff08;原子性&#xff09; 第二范式&#xff08;主键唯一性&#xff09; 第三范式&#xff08;原子性主键唯一性&#xff09; BC范式(3NFplus) 第一范式&#xff08;原子性&#xff09; 确保每列保证原子性&#xff0c;保证这个属性&#xff08;字段&am…

【智能家居】九、停车场车牌识别功能点(回调、解耦)

一、翔云 人工智能开放平台&#xff08;车牌识别&#xff09; 二、cJSON 库 三、实现代码 四、回调函数 五、人脸识别和车牌识别获取数据的区别 六、异步网络请求和同步网络请求的区别 七、解耦 一、翔云 人工智能开放平台&#xff08;车牌识别&#xff09; 翔云 人工智能开放…

Kimichat使用案例:将一大片无序文本内容整理成有序的Excel表格

Kimichat是一个国产的AI大模型应用。2024年10月9日&#xff0c;专注于通用人工智能领域的公司月之暗面&#xff08;Moonshot Al&#xff09;宣布在“长文本”领域实现了突破&#xff0c;推出了首个支持输入20万汉字的大模型moonshot&#xff0c;以及搭载该模型的智能助手产品Ki…