Linux 进程间通信之共享内存

news2024/12/24 7:48:39

💓博主CSDN主页:麻辣韭菜💓

⏩专栏分类:Linux知识分享⏪

🚚代码仓库:Linux代码练习🚚

🌹关注我🫵带你学习更多Linux知识
  🔝 


目录

​编辑​

前言

共享内存直接原理 

共享内存数据结构 

 共享内存函数接口

 shmget

函数原型

参数说明:

返回值:

系统限制:

额外说明:

ftok

shmat

shmdt

函数原型

参数说明

返回值

附加说明

错误代码

shmctl

 代码实现

获得key 

创建共享内存 

 挂接

 取消挂接

 删除共享内存

 共享内存命令行操作


前言

前面讲的匿名和命名都是基于文件看到同一份资源,那有没有不通过文件可以看到同一份资源?有的系统 V

共享内存直接原理 

  • 每个进程都有属于自己的进程地址空间。
  • 操作系统在物理内存中开辟一段空间,一个进程创建一段虚拟内存,并将这段虚拟内存的起始与结束地址通过页表映射到物理内存的空间。
  • 如果另一个进程也通过页表映射到同一段物理内存,那么就可以实现了让多个进程看到同一段空间,当一个进程向这段物理空间写入数据,另一个进程就可以马上从这段空间读取数据,实现进程间的通信。

共享内存需不需要被管理?当然需要啊,又不是只有这一个共享内存,其他进程万一也需要通信。共享内存多了当然需要管理。那怎么管理?老规矩先描述再组织!!!

共享内存数据结构 

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 */
};

描述出来以后,对共享内存的管理,变成了对shmid_ds增删查改!!! 

 共享内存函数接口

 shmget

shmget 函数是 Linux 系统中用于创建或获取共享内存段标识符的函数。以下是 shmget 函数的详细解析:

函数原型

#include <sys/ipc.h>

#include <sys/shm.h>

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

参数说明:

  1. key:一个标识符,用于唯一确定系统中的共享内存段。它可以是:

    • IPC_PRIVATE:一个特殊的键值,用于创建新的共享内存段。
    • 由 ftok 函数生成的键值,通常基于文件名和项目 ID。
  2. size:要创建的共享内存段的大小,以字节为单位。实际创建的共享内存大小将是 size 向上取最近的 PAGE_SIZE(页面大小)的倍数。

  3. shmflg:共享内存的访问权限和控制标志,可以是以下选项的组合:

    • IPC_CREAT:如果共享内存不存在,则创建它。
    • IPC_EXCL:与 IPC_CREAT 一起使用,如果共享内存已存在,则 shmget 调用失败。
    • SHM_R 或 SHM_RDONLY:共享内存段为只读。
    • SHM_W:共享内存段为可写。

返回值:

  • 成功:返回共享内存段的标识符(shmid)。
  • 失败:返回 -1,并设置全局变量 errno 以指示错误类型。

错误代码:

  • EINVALsize 小于 SHMMIN 或大于 SHMMAX
  • EEXISTshmflg 包含 IPC_CREAT | IPC_EXCL,但已存在与 key 相匹配的共享内存。
  • EIDRM:尝试获取的共享内存已经被删除。
  • ENOENTkey 不存在,且 shmflg 没有设置 IPC_CREAT
  • EACCES:没有足够的权限访问指定的共享内存。

系统限制:

  • SHMALL:系统范围内共享内存段的总数限制。
  • SHMMAX:单个共享内存段的最大大小限制。
  • SHMMNI:系统范围内共享内存段标识符的最大数量限制。

额外说明:

  • 使用 shmget 创建共享内存时,通常会结合 shmctlshmat 和 shmdt 函数来完成共享内存的控制、映射、使用和脱离。
  • IPC_PRIVATE 作为 key 时,会忽略 shmflg 的低9位,并创建一个新的共享内存段。

ftok

ftok 是一个UNIX系统调用,用于创建一个唯一的键值,这个键值可以用于ftok函数创建的两个进程之间的通信。它通常用于初始化或识别一个共享内存段或信号量。

函数原型如下:

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

参数说明:

  • pathname:一个字符串,指定了一个文件的路径。这个文件的存在并不是必需的,但是它必须存在于文件系统中。
  • proj_id:一个字符,用于进一步区分同一个文件下生成的不同键值。

返回值:

  • 成功时返回一个key_t类型的键值,这个键值可以用于IPC(进程间通信)。
  • 失败时返回-1,并设置errno以指示错误类型。

ftok函数通过文件名和项目标识符生成一个键值,这个键值可以用来创建或访问共享内存段或信号量。ftok函数的实现通常依赖于文件的设备号和文件名的i节点号,以及项目标识符,来生成一个唯一的键值。

请注意,ftok函数是POSIX标准的,主要用于UNIX和类UNIX系统。在Windows系统中,该函数不可用。


shmat

shmat 是一个用于进程间通信(IPC)的系统调用函数,它是System V共享内存接口的一部分。shmat 函数用于将一个已经创建的共享内存段附加(attach)到调用进程的地址空间中。一旦附加,进程就可以像访问普通内存一样访问共享内存。

函数原型如下:

void *shmat(int shm_id, const void *shmaddr, int shmflg);

参数说明:

  1. shm_id:共享内存段的标识符,通常由 shmget 函数创建并返回。
  2. shmaddr:(可选)一个指针,指定了共享内存段在调用进程地址空间中附加的位置。如果设置为NULL,系统会找到一个合适的地址来附加共享内存。
  3. shmflg:控制共享内存附加行为的标志。常见的标志有:
    • SHM_RDONLY:以只读方式附加共享内存。
    • 0:以读写方式附加共享内存。

返回值:

  • 成功时返回一个指向共享内存段的指针,进程可以使用这个指针来访问共享内存。
  • 失败时返回-1,并设置全局变量 errno 以指示错误类型。

使用 shmat 时,需要确保已经包含了相应的头文件,如 <sys/ipc.h><sys/shm.h>


shmdt

shmdt 函数是用于断开与共享内存段的连接的Linux系统调用。当一个进程通过shmat函数将共享内存段映射到自己的地址空间后,如果不再需要访问该共享内存段,就可以使用shmdt函数来断开这种映射关系。以下是shmdt函数的一些关键信息:

函数原型

int shmdt(const void *shmaddr);

参数说明

  • shmaddr:共享内存段连接到当前进程地址空间的起始地址。

返回值

  • 成功:返回0。
  • 出错:返回-1,并且errno会被设置为相应的错误代码。

附加说明

  • 本函数调用并不删除所指定的共享内存段,而只是将先前用shmat函数连接(attach)好的共享内存脱离(detach)当前进程。
  • 如果shmaddr参数无效,将返回错误。

错误代码

  • EINVAL:无效的参数shmaddr

 


shmctl

shmctl 是一个用于进程间通信(IPC)的系统调用函数,它是System V共享内存接口的一部分。shmctl 函数用于控制共享内存段的各种操作,如获取共享内存段的信息、设置共享内存段的属性或删除共享内存段。

函数原型如下:

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

参数说明:

  1. shm_id:共享内存段的标识符,通常由 shmget 函数创建并返回。
  2. cmd:一个命令,指定了要执行的操作。常见的命令有:
    • IPC_STAT:获取共享内存段的状态信息,并将信息存储在 buf 指向的结构体中。
    • IPC_SET:设置共享内存段的属性,buf 指向的结构体包含了新的属性值。
    • IPC_RMID:删除共享内存段,释放相关资源。
  3. buf:一个指向 shmid_ds 结构体的指针,该结构体用于存储共享内存段的信息或新的属性设置。

返回值:

  • 成功时返回0。
  • 失败时返回-1,并设置全局变量 errno 以指示错误类型。

shmid_ds 结构体通常包含以下成员:

  • shm_perm:共享内存段的权限。
  • shm_segsz:共享内存段的大小。
  • shm_atime:最后访问时间。
  • shm_dtime:最后删除时间。
  • shm_ctime:最后修改时间。
  • shm_cprid:最后执行读操作的进程的进程ID。
  • shm_lprid:最后执行写操作的进程的进程ID。

使用 shmctl 时,需要确保已经包含了相应的头文件,如 <sys/ipc.h><sys/shm.h>

 代码实现

我们要创建共享内存,首先要有key而key是调用ftok来获得,我们先创建一个comm.hpp

创建共享内存的需要的用到的系统调,我们直接用这个头文件进行封装,然后prosessa 和 prosessb直接进行调用

获得key 

 

#include <iostream>
#include <string>
#include <sys/ipc.h>
#include <sys/shm.h>

using namespace std;
const string pathname = "/home/gx/linux-exercise";
const int proj_id = 0x666; // 这个你可以自己随便定一个。

key_t get_key()
{
    key_t key = ftok(pathname.c_str(), proj_id);
    if (key == -1)
    {
        cerr << "ftok error" << endl;
        exit(1);
    }
    return key;

}

创建共享内存 

 

int create_shm(int flag)
{
    key_t key = get_key();
    int n = shmget(key, SHM_SIZE, flag);
    if(n < 0)
    {
        cerr << "shmget error" << endl;
        exit(1);        
    } 
    return n;                    
}

这里我用a进程进行创建共享内存,b获得共享内存,这样的话对create_shm函数进行封装,a调用createShm这个函数。

int createShm()
{
    return create_shm(IPC_CREAT | IPC_EXCL | 0666);
}

b就调用getShm这个函数。 

int getShm()
{
    return create_shm(IPC_CREAT);
}

关于shmget的shmflg这个参数,前面文档已经有详细的说明。这里不在细说。

共享内存创建好了,就意味着我们能通信了吗?并没有,因为shmget是系统调用,是OS划分出来的一块区域。 是真实的物理内存,而进程用的是虚拟地址,我们需要用shmat进行挂接。通过挂接,挂接到进程地址空间。        

 挂接

 char* shmaddr = (char*)shmat(shmid,nullptr,0);

 取消挂接

 shmdt(shmaddr);

 删除共享内存

 shmctl(shmid, IPC_RMID, nullptr);

 

完整代码  

头文件

#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>


using namespace std;
const int SHM_SIZE = 4096; // 共享内存的大小 也就是4KB,我们申请的大小一般是4096的整数倍
const string pathname = "/home/gx/linux-exercise";
const int proj_id = 0x666; // 这个你可以自己随便定一个,只要保证唯一就行

key_t get_key()
{
    key_t key = ftok(pathname.c_str(), proj_id);
    if (key == -1)
    {
        cerr << "ftok error" << endl;
        exit(1);
    }
    return key;
}

int create_shm(int flag)
{
    key_t key = get_key();
    int n = shmget(key, SHM_SIZE, flag);
    if (n < 0)
    {
        cerr << "shmget error" << endl;
        exit(1);
    }
    return n;
}

int createShm()
{
    return create_shm(IPC_CREAT | IPC_EXCL | 0666);
}
int getShm()
{
    return create_shm(IPC_CREAT);
}

进程a 

 

int main()
{
    int shmid = createShm();
    char *shmaddr = (char *)shmat(shmid, nullptr, 0);

    while (true)
    {

        cout << "Enter a message: " ;
        fgets(shmaddr, 4096, stdin);
        
    }

    shmdt(shmaddr);
    shmctl(shmid, IPC_RMID, nullptr);

    return 0;
}

 进程b

 

int main()
{
    int shmid = getShm();
    char* shmaddr = (char*)shmat(shmid,nullptr,0);
    while(true)
    {
        cout << "client say@:" << shmaddr << endl;
        sleep(1);
    }
    shmdt(shmaddr);

    return 0;   
}

代码运行起来,我们可以发现共享内存是不会同步的,一方不写,一方也不会阻塞。

有没有什么方法可以让它们同步互斥?用管道。 

 共享内存命令行操作

查看共享内存

指令:ipcs -m 

 

删除共享内存

指令:ipcrm -m shmid 

 

 最后说说 key 和shmid的区别 key是操作系统创建出来的,确定这个共享内存的唯一性。

而shmid是给用户用的。

 

 

 

 

 

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

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

相关文章

标准IO学习

思维导图&#xff1a; 有如下结构体 struct Student{ char name[16]; int age; double math_score; double chinese_score; double english_score; double physics_score; double chemistry_score; double bio_score; }; 申请该结构体数组&#xff0c;容量为5&#xff0c;初始…

dstat 与系统 I/O

知道系统运行了哪些服务也许并不能告诉你是谁拖慢了系统。 top 命令可以报告CPU占用情况以及I/O等待时间&#xff0c;但这可能也不足以找出导致系统过载的任务。 跟踪I/O以及上下文切换有助于揪出问题的源头。 dstat 实用工具可以为你指出系统潜在的瓶颈。 …

【Qt 学习笔记】Qt常用控件 | 输入类控件 | Date/Time Edit的使用及说明

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Qt 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ Qt常用控件 | 输入类控件 | Spin Box的使用及说明 文章编号&#xff1…

1-2 ARM单片机GPIO

def&#xff1a;通用输入输出口 GPIO输出模式原理讲解 1&#xff1a;推挽输出 2&#xff1a;复用推挽输出 电流最大是20mA&#xff0c;对于单片机来说总体的输出是由范围的 开漏/复用开漏输出 外部接上拉电阻的开漏输出 线与的概念 注&#xff1a; 与的概念&#xff1a;全1为1&…

Django调用MTP服务器给指定邮箱发送邮件

Django调用MTP服务器发送邮箱 邮箱的激活链接含有用户数据不能直接发送需要对其进行加密 发送邮箱是借助SMTP服务器进行中转 一. 配置SMTP服务中的邮箱信息以及激活链接 1. 配置邮箱权限 打开网易邮箱设置点击POP3 开启选项 注 : 在打开的过程中会弹出授权密码一点要保存 …

【ARM Cortex-M3指南】8:中断行为

文章目录 八、中断行为8.1 中断/异常流程8.1.1 压栈8.1.2 取向量8.1.3 寄存器更新 8.2 异常退出8.3 嵌套中断8.4 末尾连锁中断8.5 延迟到达8.6 进一步了解异常返回值8.7 中断等待8.8 中断相关的错误8.8.1 压栈8.8.2 出栈8.8.3 取向量8.8.4 非法返回 八、中断行为 8.1 中断/异常…

Hive内部表、外部表

Hive内部表、外部表 1. 内部表&#xff08;Managed Table&#xff09;&#xff1a; 内部表是由Hive完全管理的表&#xff0c;包括数据和元数据。当你删除内部表时&#xff0c;Hive会同时删除表的数据和元数据。内部表的数据存储在Hive指定的默认位置&#xff08;通常是HDFS上…

在做题中学习(51): x的平方根

69. x 的平方根 - 力扣&#xff08;LeetCode&#xff09;​​​​​​ 解法&#xff1a;二分查找 思路&#xff1a;看示例2&#xff1a; 可以看到8的平方根是2.82&#xff0c;在2^2和3^2之间&#xff0c;所以可以把数组分为两部分&#xff0c;(具有二段性) 而2.82去掉小数部…

JavaScript异步编程——02-Ajax入门和发送http请求

同步和异步回顾 同步和异步的简单理解 同步&#xff1a;必须等待前面的任务完成&#xff0c;才能继续后面的任务。 异步&#xff1a;不受当前任务的影响。 拿排队举例&#xff1a; 同步&#xff1a;在银行排队时&#xff0c;只有等到你了&#xff0c;才能够去处理业务。 异…

代码审计之某高校通用系统getRequsetURI函数的三次鉴权绕过

前言&#xff1a; 写个随笔&#xff0c;写个代码审计的漏洞案例&#xff0c;该系统为大量高校使用的一个系统。 三次绕过。 正文&#xff1a; 如图所见&#xff0c;系统厂商采用的是利用过滤器的写法进行鉴权 第一次的校验代码&#xff1a; 即requestURI转为小写后&#x…

【运维】如何安装ubuntu-24.04? 如何分区?

如何安装ubuntu-24.04&#xff1f;如何分区 经过一系列折腾&#xff0c;我总结了这几点&#xff1a; &#xff08;1&#xff09;在BIOS启动设置里&#xff0c;如果是GPT的硬盘格式&#xff0c;那么对应的就是UEFI的启动方式&#xff1b;如果是MBR的硬盘格式&#xff0c;那么对…

优雅的实现接口统一调用!

有些时候我们在进行接口调用的时候&#xff0c;比如说一个 push 推送接口&#xff0c;有可能会涉及到不同渠道的推送。 比如做结算后端服务的&#xff0c;会与金蝶财务系统进行交互&#xff0c;那么我结算后端会涉及到多个结算单类型&#xff0c;如果每一个种类型的结算单都去…

VBA技术资料MF151:单元格注释标识数字化

我给VBA的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#xff0c;可以大大提高自己的工作效率&#xff0c;而且可以提高数据的准确度。“VBA语言専攻”提供的教程一共九套&#xff0c;分为初级、中级、高级三大部分&#xff0c;教程是对VBA的系统讲解&#…

SpringBoot 打包所有依赖

SpringBoot 项目打包的时候可以通过插件 spring-boot-maven-plugin 来 repackage 项目&#xff0c;使得打的包中包含所有依赖&#xff0c;可以直接运行。例如&#xff1a; <plugins><plugin><groupId>org.springframework.boot</groupId><artifact…

信创 | 信创产业数字化转型与升级:路径规划与实践!

信创产业的数字化转型与升级路径&#xff0c;主要围绕着构建国产化信息技术软硬件底层架构体系和全周期生态体系&#xff0c;解决核心技术关键环节“卡脖子”的问题&#xff0c;以推动中国经济数字化转型的平稳健康发展。 一、信创产业的发展趋势包括&#xff1a; 加强国产信息…

红海云OA存在任意文件上传漏洞【附poc】

漏洞复现 1、fofa poc见文末 body"RedseaPlatform" 打开burp进行抓包发送到repeater&#xff0c;如下图所示&#xff1a; 打入poc&#xff08;文末获取&#xff09;&#xff0c;成功上传。 「你即将失去如下所有学习变强机会」 学习效率低&#xff0c;学不到实战内…

新火种AI|挑战谷歌,OpenAI要推出搜索引擎?

作者&#xff1a;一号 编辑&#xff1a;美美 在AI革新的浪潮下&#xff0c;谷歌搜索迎来了越来越多的“挑战者”。 最近&#xff0c;据多家外媒的消息&#xff0c;有知情人士透露&#xff0c;OpenAI正计划上线一款基于ChatGPT的大型产品&#xff0c;将提供一个新的搜索引擎&…

Unet简单结构概述

总体结构代码 class UNet(nn.Module):def __init__(self, n_channels, n_classes, bilinearFalse):super(UNet, self).__init__()self.n_channels n_channelsself.n_classes n_classesself.bilinear bilinearself.inc (DoubleConv(n_channels, 64))self.down1 (Down(64, …

【计算机科学速成课】笔记三——操作系统

文章目录 18.操作系统问题引出——批处理设备驱动程序多任务处理虚拟内存内存保护Unix 18.操作系统 问题引出—— Computers in the 1940s and early 50s ran one program at a time. 1940,1950 年代的电脑&#xff0c;每次只能运行一个程序 A programmer would write one at…

硬件设计细节1-缓冲驱动器使用注意事项

目录 一、缓冲驱动器二、实例分析1.硬件结构2.问题描述3.原因分析4.原因定位 三、结论 一、缓冲驱动器 缓冲驱动器通常用于隔离、电平转换等应用场景。在使用时&#xff0c;需要关注的点较多&#xff0c;如电平范围、频率范围、延时、控制方式、方向以及输入输出状态。通常&am…