【C++高并发服务器WebServer】-7:共享内存

news2025/3/10 5:13:52

在这里插入图片描述

本文目录

  • 一、共享内存
    • 1.1 shmget函数
    • 1.2 shmat
    • 1.3 shmdt
    • 1.4 shmctl
    • 1.5 ftok
    • 1.6 共享内存和内存映射的关联
    • 1.7 小demo
  • 二、共享内存操作命令

一、共享内存

共享内存允许两个或者多个进程共享物理内存的同一块区域(通常被称为段)。由于一个共享内存段会称为一个进程用户空间的一部分,因此这种 IPC 机制无需内核介入。所有需要做的就是让一个进程将数据复制进共享内存中,并且这部分数据会对其他所有共享同一个段的进程可用。

共享内存是一种进程间通信(IPC)机制,允许多个进程共享同一块物理内存区域。这种共享内存区域通常被称为共享内存段。共享内存段可以被映射到多个进程的用户空间中,从而实现进程间的高效通信。虚拟内存映射:每个进程的虚拟地址空间中会有一部分被映射到这块共享的物理内存上。当进程访问这块映射的虚拟内存区域时,实际上是在访问共享的物理内存。由于这块内存被映射到进程的用户空间中,进程可以直接访问它,而不需要内核介入。

在这里插入图片描述

与管道等要求发送进程将数据从用户空间的缓冲区复制进内核内存和接收进程将数据从内核内存复制进用户空间的缓冲区的做法相比,这种 IPC 技术的速度更快。内存映射相比共享内存,效率会比较低。直接操作内存效率比较高。

  • 创建或获取共享内存段

调用shmget()函数创建一个新的共享内存段,或者获取一个已存在的共享内存段的标识符(该共享内存段可能由其他进程创建)。这个调用将返回一个共享内存标识符,该标识符将在后续的调用中被使用。

  • 将共享内存段附加到进程的虚拟内存

使用shmat()函数将共享内存段附加到调用进程的虚拟内存中,使其成为进程虚拟内存的一部分。从这一刻起,程序可以像对待其他普通内存一样使用这块共享内存。为了引用这块共享内存,程序需要使用shmat()调用返回的addr值,这是一个指向进程虚拟地址空间中共享内存段起点的指针。

  • 分离共享内存段(可选)

调用shmdt()函数来分离共享内存段。分离后,进程将无法再引用这块共享内存。这一步是可选的,因为在进程终止时,系统会自动完成这一步。

  • 删除共享内存段

调用shmctl()函数来删除共享内存段。只有在所有附加到该共享内存段的进程都与之分离之后,共享内存段才会被销毁。这一步通常只需要一个进程执行即可。

int shmget(key_t key, size_t size, int shmflg);
void *shmat(int shmid, const void *shmaddr, int shmflg);
int shmdt(const void *shmaddr);
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
key_t ftok(const char *pathname, int proj_id);

1.1 shmget函数

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

int shmget(key_t key, size_t size, int shmflg);
    - 功能:创建一个新的共享内存段,或者获取一个既有的共享内存段的标识。
        新创建的内存段中的数据都会被初始化为0
    - 参数:
        - key : key_t类型是一个整形,通过这个找到或者创建一个共享内存。
                一般使用16进制表示,非0- size: 共享内存的大小
        - shmflg: 属性
            - 访问权限
            - 附加属性:创建/判断共享内存是不是存在
                - 创建:IPC_CREAT
                - 判断共享内存是否存在: IPC_EXCL , 需要和IPC_CREAT一起使用
                    IPC_CREAT | IPC_EXCL | 0664
        - 返回值:
            失败:-1 并设置错误号
            成功:>0 返回共享内存的引用的ID,后面操作共享内存都是通过这个值。
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>

int main() {
    key_t key = 0x1234; // 定义一个共享内存的键值,通常使用16进制表示
    size_t size = 1024; // 定义共享内存的大小(以字节为单位)
    int shmflg = IPC_CREAT | 0664; // 设置共享内存的标志和访问权限

    // 调用 shmget 创建一个新的共享内存段或获取一个已存在的共享内存段的标识符
    int shmid = shmget(key, size, shmflg);

    if (shmid == -1) {
        perror("shmget failed"); // 如果失败,打印错误信息
        exit(EXIT_FAILURE);
    }

    printf("Shared memory created/obtained successfully. ID: %d\n", shmid);

    return 0;
}

1.2 shmat

void *shmat(int shmid, const void *shmaddr, int shmflg);
    - 功能:和当前的进程进行关联
    - 参数:
        - shmid : 共享内存的标识(ID),由shmget返回值获取
        - shmaddr: 申请的共享内存的起始地址,指定NULL,内核指定
        - shmflg : 对共享内存的操作
            - 读 : SHM_RDONLY, 必须要有读权限
            - 读写: 0
    - 返回值:
        成功:返回共享内存的首(起始)地址。  失败(void *) -1
    // 调用 shmat 将共享内存段附加到当前进程的虚拟内存中
    // 参数:
    // - shmid: 共享内存的标识符
    // - shmaddr: 指定为NULL,让内核选择合适的地址
    // - shmflg: 0 表示读写权限
    void *shmaddr = shmat(shmid, NULL, 0);
    if (shmaddr == (void *)-1) {
        perror("shmat failed");
        exit(EXIT_FAILURE);
    }
    printf("Shared memory attached at address: %p\n", shmaddr);

1.3 shmdt

int shmdt(const void *shmaddr);
    - 功能:解除当前进程和共享内存的关联
    - 参数:
        shmaddr:共享内存的首地址
    - 返回值:成功 0, 失败 -1
    if (shmdt(shmaddr) == -1) {
        perror("shmdt failed");
        exit(EXIT_FAILURE);
    }
    printf("Shared memory detached successfully.\n");

1.4 shmctl

int shmctl(int shmid, int cmd, struct shmid_ds *buf);
    - 功能:对共享内存进行操作。删除共享内存,共享内存要删除才会消失,创建共享内存的进行被销毁了对共享内存是没有任何影响。
    - 参数:
        - shmid: 共享内存的ID
        - cmd : 要做的操作
            - IPC_STAT : 获取共享内存的当前的状态
            - IPC_SET : 设置共享内存的状态
            - IPC_RMID: 标记共享内存被销毁
        - buf:需要设置或者获取的共享内存的属性信息
            - IPC_STAT : buf存储数据
            - IPC_SET : buf中需要初始化数据,设置到内核中
            - IPC_RMID : 没有用,NULL
    // 删除共享内存段(可选,通常由最后一个使用它的进程完成)
    if (shmctl(shmid, IPC_RMID, 0) == -1) {
        perror("shmctl failed");
        exit(EXIT_FAILURE);
    }
    printf("Shared memory segment deleted.\n");

1.5 ftok

key_t ftok(const char *pathname, int proj_id);
    - 功能:根据指定的路径名,和int值,生成一个共享内存的key
    - 参数:
        - pathname:指定一个存在的路径
            /home/nowcoder/Linux/a.txt
            / 
        - proj_id: int类型的值,但是这系统调用只会使用其中的1个字节
                   范围 : 0-255  一般指定一个字符 'a'

    // 定义一个存在的路径名
    const char *pathname = "/home/nowcoder/Linux/a.txt";
    // 定义一个项目ID,通常使用一个字符的ASCII值
    int proj_id = 'a';

    // 调用 ftok 生成共享内存的键值
    key_t key = ftok(pathname, proj_id);
    if (key == -1) {
        perror("ftok failed");
        exit(EXIT_FAILURE);
    }
    printf("Generated key: %d\n", key);

    // 使用生成的键值创建或获取共享内存段
    size_t size = 1024; // 定义共享内存的大小
    int shmflg = IPC_CREAT | 0664; // 设置共享内存的标志和访问权限
    int shmid = shmget(key, size, shmflg);
    if (shmid == -1) {
        perror("shmget failed");
        exit(EXIT_FAILURE);
    }
    printf("Shared memory created/obtained successfully. ID: %d\n", shmid);
  • 问题1:操作系统如何知道一块共享内存被多少个进程关联?

操作系统通过维护一个结构体struct shmid_ds来跟踪共享内存段的状态。这个结构体中有一个成员shm_nattch,它记录了当前与该共享内存段关联的进程个数。每当一个进程通过shmat函数附加到共享内存段时,shm_nattch的值会增加;而当一个进程通过shmdt函数分离共享内存段时,shm_nattch的值会减少。通过这种方式,操作系统能够实时了解每个共享内存段的使用情况。

  • 问题2:可不可以对共享内存进行多次删除shmctl?

可以对共享内存进行多次调用shmctl进行删除操作。这是因为shmctl标记共享内存为删除状态,并不是立即删除它。共享内存段的实际删除发生在与之关联的进程数为0时。当shm_nattch的值降为0,表示没有进程再使用该共享内存段,此时操作系统才会真正删除它。此外,当共享内存的key值被设置为0时,表示该共享内存段已被标记为删除。如果一个进程与共享内存取消关联,那么该进程将无法继续操作该共享内存,也不能再次进行关联。

1.6 共享内存和内存映射的关联

共享内存可以直接通过系统调用(如shmget)创建,而内存映射通常需要一个磁盘文件作为后端支持(匿名映射除外)。共享内存的创建更为直接和简单,而内存映射则需要额外的文件支持。

共享内存通常具有更高的效率。由于共享内存允许多个进程直接访问同一块物理内存,因此在进程间通信时,数据传输的开销较小。相比之下,内存映射虽然也可以实现高效的内存访问,但在某些情况下可能需要额外的文件操作,这会增加一定的开销。

共享内存允许多个进程操作同一块物理内存,这意味着所有进程看到的是同一个数据副本。而内存映射则为每个进程在自己的虚拟地址空间中提供了一个独立的内存映射。尽管这些映射可能指向同一块物理内存,但每个进程的操作是独立的,不会直接影响其他进程的内存映射。

即使一个进程突然退出,共享内存仍然存在,其他进程仍然可以继续访问和操作共享内存。但如果一个进程突然退出,该进程的内存映射区会被销毁,其他进程无法再访问该映射区。

如果运行共享内存的电脑死机或宕机,共享内存中的数据会丢失,因为共享内存依赖于系统的内存管理。如果电脑死机或宕机,内存映射区的数据仍然存在,因为这些数据是基于磁盘文件的。只要磁盘文件未被损坏,数据仍然可以恢复。

当进程退出时,该进程的内存映射区会被销毁。这意味着其他进程无法再访问该映射区。
当一个进程退出时,它会自动与共享内存取消关联,但共享内存本身仍然存在。共享内存只有在所有关联的进程都退出后才会被标记为删除。如果系统关机,共享内存中的数据也会丢失。

1.7 小demo

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

int main() {    

    // 1.获取一个共享内存
    int shmid = shmget(100, 0, IPC_CREAT);
    printf("shmid : %d\n", shmid);

    // 2.和当前进程进行关联
    void * ptr = shmat(shmid, NULL, 0);

    // 3.读数据
    printf("%s\n", (char *)ptr);
    
    printf("按任意键继续\n");
    getchar();

    // 4.解除关联
    shmdt(ptr);

    // 5.删除共享内存
    shmctl(shmid, IPC_RMID, NULL);

    return 0;
}
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>

int main() {    

    // 1.创建一个共享内存
    int shmid = shmget(100, 4096, IPC_CREAT|0664);
    printf("shmid : %d\n", shmid);
    
    // 2.和当前进程进行关联
    void * ptr = shmat(shmid, NULL, 0);

    char * str = "helloworld";

    // 3.写数据
    memcpy(ptr, str, strlen(str) + 1);

	//设置一个等待操作,不然会进程直接结束,那么共享内存也没了。
    printf("按任意键继续\n");
    getchar();

    // 4.解除关联
    shmdt(ptr);

    // 5.删除共享内存
    shmctl(shmid, IPC_RMID, NULL);

    return 0;
}

二、共享内存操作命令

ipcs -a // 打印当前系统中所有的进程间通信方式的信息
ipcs -m // 打印出使用共享内存进行进程间通信的信息
ipcs -q // 打印出使用消息队列进行进程间通信的信息
ipcs -s // 打印出使用信号进行进程间通信的信息

图中的共享内存段的键是0x0000,已经被标记删除了,但是因为连接数还存在,所以没有被删除。
在这里插入图片描述

ipcrm -M shmkey // 移除用shmkey创建的共享内存段
ipcrm -m shmid // 移除用shmid标识的共享内存段
ipcrm -Q msgkey // 移除用msqkey创建的消息队列
ipcrm -q msqid // 移除用msqid标识的消息队列
ipcrm -S semkey // 移除用semkey创建的信号
ipcrm -s semid // 移除用semid标识的信号

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

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

相关文章

稀土抗菌剂:提升产品质量,保障公共健康

随着全球对抗菌技术需求的不断增长&#xff0c;传统的抗菌剂逐渐暴露出其局限性&#xff0c;包括耐药性、环境污染及副作用等问题。在此背景下&#xff0c;稀土抗菌剂作为一种新兴的抗菌材料&#xff0c;凭借其卓越的抗菌性能、环保特性以及应用多样性&#xff0c;正在成为各行…

机器学习11-学习路径推荐

机器学习11-学习路径推荐 本文希望摒除AI学习商业宣传要素&#xff0c;推荐一条极简的AI学习路线&#xff01;推荐内容均为在线免费内容&#xff0c;如果有条件可以咨询专业的培训机构&#xff01; 文章目录 机器学习11-学习路径推荐[toc] 1-AI培训路线第一阶段 Python-人工智能…

《边界感知的分而治之方法:基于扩散模型的无监督阴影去除解决方案》学习笔记

paper&#xff1a;Boundary-Aware Divide and Conquer: A Diffusion-Based Solution for Unsupervised Shadow Removal 目录 摘要 1、介绍 2、相关工作 2.1 阴影去除 2.2 去噪扩散概率模型&#xff08;Denoising Diffusion Probabilistic Models, DDPM&#xff09; 3、方…

java后端之事务管理

Transactional注解&#xff1a;作用于业务层的方法、类、接口上&#xff0c;将当前方法交给spring进行事务管理&#xff0c;执行前开启事务&#xff0c;成功执行则提交事务&#xff0c;执行异常回滚事务 spring事务管理日志&#xff1a; 默认情况下&#xff0c;只有出现Runti…

数据结构——概念与时间空间复杂度

目录 前言 一相关概念 1什么是数据结构 2什么是算法 二算法效率 1如何衡量算法效率的好坏 2算法的复杂度 三时间复杂度 1时间复杂度表示 2计算时间复杂度 2.1题一 2.2题二 2.3题三 2.4题四 2.5题五 2.6题六 2.7题七 2.8题八 四空间复杂度 1题一 2题二 3…

牛客周赛 Round 78 A-C

A.时间表查询&#xff01; 链接&#xff1a;https://ac.nowcoder.com/acm/contest/100671/A 来源&#xff1a;牛客网 题目描述 今天是2025年1月25日&#xff0c;今年的六场牛客寒假算法基础集训营中&#xff0c;前两场比赛已经依次于 20250121、20250123 举行&#xff1b;而…

HTML-新浪新闻-实现标题-样式1

用css进行样式控制 css引入方式&#xff1a; --行内样式&#xff1a;写在标签的style属性中&#xff08;不推荐&#xff09; --内嵌样式&#xff1a;写在style标签中&#xff08;可以写在页面任何位置&#xff0c;但通常约定写在head标签中&#xff09; --外联样式&#xf…

能说说MyBatis的工作原理吗?

大家好&#xff0c;我是锋哥。今天分享关于【Redis为什么这么快?】面试题。希望对大家有帮助&#xff1b; 能说说MyBatis的工作原理吗&#xff1f; MyBatis 是一款流行的持久层框架&#xff0c;它通过简化数据库操作&#xff0c;帮助开发者更高效地与数据库进行交互。MyBatis…

MFC程序设计(四)窗口创建机制

钩子函数 钩子属于win32技术&#xff0c;具有优先勾取消息的权利&#xff1a;当一个消息产生时&#xff0c;钩子勾取消息进行处理&#xff0c;然后消息才送回程序 接下来以一个勾取窗口创建消息的钩子为例进行讲解 钩子类型有键盘钩子&#xff0c;鼠标钩子&#xff0c;WH_CBT…

【JavaEE进阶】Spring留言板实现

目录 &#x1f38d;预期结果 &#x1f340;前端代码 &#x1f384;约定前后端交互接口 &#x1f6a9;需求分析 &#x1f6a9;接口定义 &#x1f333;实现服务器端代码 &#x1f6a9;lombok介绍 &#x1f6a9;代码实现 &#x1f334;运行测试 &#x1f384;前端代码实…

Unity开发一个单人FPS游戏的教程总结

这个系列的前几篇文章介绍了如何从头开始用Unity开发一个FPS游戏&#xff0c;感兴趣的朋友可以回顾一下。这个系列的文章如下&#xff1a; Unity开发一个FPS游戏_unity 模仿开发fps 游戏-CSDN博客 Unity开发一个FPS游戏之二_unity 模仿开发fps 游戏-CSDN博客 Unity开发一个F…

论文速读|Is Cosine-Similarity of Embeddings Really About Similarity?WWW24

论文地址&#xff1a; https://arxiv.org/abs/2403.05440 https://dl.acm.org/doi/abs/10.1145/3589335.3651526 bib引用&#xff1a; inproceedings{Steck_2024, series{WWW ’24},title{Is Cosine-Similarity of Embeddings Really About Similarity?},url{http://dx.doi.o…

71.在 Vue 3 中使用 OpenLayers 实现按住 Shift 拖拽、旋转和缩放效果

前言 在前端开发中&#xff0c;地图功能是一个常见的需求。OpenLayers 是一个强大的开源地图库&#xff0c;支持多种地图源和交互操作。本文将介绍如何在 Vue 3 中集成 OpenLayers&#xff0c;并实现按住 Shift 键拖拽、旋转和缩放地图的效果。 实现效果 按住 Shift 键&#…

PyQt6医疗多模态大语言模型(MLLM)实用系统框架构建初探(上.文章部分)

一、引言 1.1 研究背景与意义 在数字化时代,医疗行业正经历着深刻的变革,智能化技术的应用为其带来了前所未有的发展机遇。随着医疗数据的指数级增长,传统的医疗诊断和治疗方式逐渐难以满足现代医疗的需求。据统计,全球医疗数据量预计每年以 48% 的速度增长,到 2025 年将…

250125-package

1. 定义 包就是文件夹&#xff0c;作用是在大型项目中&#xff0c;避免不同人的编写的java文件出现同名进而导致报错&#xff1b;想象一个场景&#xff0c;在一个根目录中&#xff0c;每一个人都有自己的一个java文件夹&#xff0c;他可以将自己编写的文件放在该文件夹里&…

FastExcel的使用

前言 FastExcel 是一款基于 Java 的开源库&#xff0c;旨在提供快速、简洁且能解决大文件内存溢出问题的 Excel 处理工具。它兼容 EasyExcel&#xff0c;提供性能优化、bug 修复&#xff0c;并新增了如读取指定行数和将 Excel 转换为 PDF 的功能。 FastExcel 的主要功能 高性…

Redis实战(黑马点评)——关于缓存(缓存更新策略、缓存穿透、缓存雪崩、缓存击穿、Redis工具)

redis实现查询缓存的业务逻辑 service层实现 Overridepublic Result queryById(Long id) {String key CACHE_SHOP_KEY id;// 现查询redis内有没有数据String shopJson (String) redisTemplate.opsForValue().get(key);if(StrUtil.isNotBlank(shopJson)){ // 如果redis的数…

python3+TensorFlow 2.x(三)手写数字识别

目录 代码实现 模型解析&#xff1a; 1、加载 MNIST 数据集&#xff1a; 2、数据预处理&#xff1a; 3、构建神经网络模型&#xff1a; 4、编译模型&#xff1a; 5、训练模型&#xff1a; 6、评估模型&#xff1a; 7、预测和可视化结果&#xff1a; 输出结果&#xff…

基础项目——扫雷(c++)

目录 前言一、环境配置二、基础框架三、关闭事件四、资源加载五、初始地图六、常量定义七、地图随机八、点击排雷九、格子类化十、 地图类化十一、 接口优化十二、 文件拆分十三、游戏重开 前言 各位小伙伴们&#xff0c;这期我们一起学习出贪吃蛇以外另一个基础的项目——扫雷…

[操作系统] 深入进程地址空间

程序地址空间回顾 在C语言学习的时&#xff0c;对程序的函数、变量、代码等数据的存储有一个大致的轮廓。在语言层面上存储的地方叫做程序地址空间&#xff0c;不同类型的数据有着不同的存储地址。 下图为程序地址空间的存储分布和和特性&#xff1a; 使用以下代码来验证一下…