当前位置: 首页 > article >正文

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

在这里插入图片描述

本文目录

  • 一、共享内存
    • 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.kler.cn/a/520109.html

相关文章:

  • Vue.js组件开发-实现多个文件附件压缩下载
  • 【橘子Kibana】Kibana的分析能力Analytics简易分析
  • 【阅读笔记】基于整数+分数微分的清晰度评价算子
  • 83,【7】BUUCTF WEB [MRCTF2020]你传你[特殊字符]呢
  • 在小红书挖掘信息的实践之旅(第一部分)
  • 【Python】导入类
  • 【Kubernetes】Pod生命周期、初始化容器、主容器
  • 第18章 走进xUnit:测试驱动开发的关键工具
  • Docker 从零开始掌握容器化技术
  • 数据融合的经典模型:早期融合、中期融合与后期融合的对比
  • appium自动化环境搭建
  • 12.Shader开发概述
  • 高并发压力测试
  • 【go语言】map 和 list
  • Verilog边沿检测
  • 16.好数python解法——2024年省赛蓝桥杯真题
  • 谈谈对JavaScript 中的事件冒泡(Event Bubbling)和事件捕获(Event Capturing)的理解
  • 从63 秒到 0.482 秒:深入剖析 MySQL 分页查询优化
  • pipeline快速将数据存入redis
  • 【含代码】逆向获取 webpack chunk 下的__webpack_require__ 函数,获悉所有的模块以及模块下的函数
  • wordpress调用指定ID页面的链接
  • Maven下载与配置
  • SYN Flooding的攻击原理
  • 微服务网关鉴权之sa-token
  • 问题修复记录:Linux docker 部署 dify,无法调用宿主机本地服务
  • 计算机网络 (59)无线个人区域网WPAN