内核篇第十五讲:system-V共享内存

大家好,接下来的一段时间我将开始学习野火的Linux 系统课程并将学习到的干货逐步更新到我的CSDN博客中。没时间刷课的同学可以把我的博客喂给AI 突击一下。

System‑V IPC 三大组件:消息队列、信号量、共享内存;

共享内存是速度最快的 IPC 方式。

一、简介

高效率传输大量数据原理 :多个进程把同一块内核内存映射到各自进程虚拟地址空间,进程直接读写这块内存,没有内核缓冲区拷贝。

⚠️共享内存本身不带同步互斥机制!多进程并发读写必须搭配 System‑V 信号量来保护临界区。

二、使用流程

  1. ftok():生成唯一key,用于跨进程定位同一个共享内存对象
  2. shmget():创建 / 获取共享内存内核对象,得到shmid
  3. shmat():映射,把内核共享内存挂载到当前进程虚拟地址,得到可用内存指针
  4. 读写共享内存(直接指针操作)
  5. shmdt():解除映射,断开进程和这块内存的关联(进程退出也会自动 detach,但最好手动写)
  6. shmctl(IPC_RMID):删除内核中的共享内存对象

注意:shmdt 只是脱离映射,不是删除共享内存;IPC_RMID 才是销毁内核对象。


三、函数详解

1. shmget --- 创建 / 获取共享内存 ID

功能:创建共享内存段,或者打开已存在的共享内存,返回 shmid

复制代码
int shmget(key_t key, size_t size, int shmflg);
  • 参数
    • key:ftok 生成的键值
    • size:共享内存字节大小;内核分配时会向上对齐到内存页大小
    • shmflg:标志
      • IPC_CREAT:不存在则创建;存在直接打开
      • mode:权限,如0664
  • 返回
    • 成功:共享内存 ID**(shmid)**
    • 失败:-1

2. shmat --- 映射共享内存

功能:将内核共享内存映射到当前进程虚拟地址空间

复制代码
void *shmat(int shmid, const void *shmaddr, int shmflg);
  • 参数
    • shmid:共享内存 ID
    • shmaddr:指定映射地址;
    • shmflg
      • SHM_RDONLY:只读映射
      • 0:可读可写
  • 返回
    • 成功:共享内存起始虚拟地址
    • 失败:-1

重点:拿到返回指针后,直接像普通数组一样读写内存。

3. shmdt --- 解除映射

功能:断开当前进程与共享内存段的映射关系

复制代码
int shmdt(const void *shmaddr);
  • 参数:shmaddr:shmat 返回的映射首地址
  • 返回
    • 成功:0
    • 失败:-1

仅解除当前进程的映射,不会删除内核里的共享内存对象;只要还有进程 attach,内存就不会销毁。

4. shmctl --- 控制共享内存

功能:获取 / 修改共享内存属性,删除共享内存对象

复制代码
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
  • 参数
    • shmid:共享内存 ID
    • cmd 命令
      • IPC_STAT:读取共享内存属性,存入 buf
      • IPC_SET:修改共享内存属性
      • IPC_RMID:标记删除共享内存;不会立刻销毁,等所有进程都 shmdt 之后才真正释放
    • buf:属性结构体指针,IPC_RMID 时填 NULL 即可
  • 返回
    • 成功:视 cmd 而定
    • 失败:-1

四、实战演练

cpp 复制代码
#include <sys/types.h>
#include "sem.h"

#define DELAY_TIME 3

int main(void)
{
    pid_t result;
    int sem_id;
    int shm_id;
    char *addr;

    sem_id = semget((key_t)6666, 1, 0666 | IPC_CREAT); /* 创建一个信号量 */
    shm_id = shmget((key_t)7777, 1024, 0666 | IPC_CREAT); /* 创建一个共享内存对象 */

    /* 初始化信号量 */
    init_sem(sem_id, 0);

    /* 调用 fork() 函数 */
    result = fork();
    if (result == -1) {
        perror("Fork\n");
    } else if (result == 0) { /* 返回值为 0 代表子进程 */
        printf("Child process will wait for some seconds...\n");
        sleep(DELAY_TIME);

        /* 映射共享内存 */
        addr = shmat(shm_id, NULL, 0);
        if (addr == (void *)-1) {
            printf("shmat111 error!");
            exit(-1);
        }

        /* 设置共享内存中的内容 */
        memcpy(addr, "helloworld", 11);

        printf("the child process is running...\r\n");
        sem_v(sem_id);
    } else { /* 返回值大于 0 代表父进程 */
        sem_p(sem_id);
        printf("the father process is running...\r\n");

        /* 映射共享内存地址 */
        addr = shmat(shm_id, NULL, 0);
        if (addr == (void *)-1) {
            printf("shmat222 error!");
            exit(-1);
        }

        printf("shared memory string:%s\r\n", addr);

        /* 接触共享内存映射 */
        shmdt(addr);
    }

    exit(0);
}

五、关键考点总结

  1. 速度优势:无内存拷贝,进程直接读写物理内存,三大 IPC 里最快。

  2. 同步问题:共享内存本身没有互斥保护,多进程读写必须配合信号量。

  3. 生命周期:内核对象,进程退出不会自动删除,必须shmctl(IPC_RMID)。

  4. 删除机制:IPC_RMID 只是打删除标记;所有进程 detach 之后,内核才真正释放内存。

    ipcs -m # 查看系统所有共享内存
    ipcrm -m shmid # 删除指定共享内存

IPC 核心 API 本质 特点
消息队列 msgget/msgsnd/msgrcv/msgctl 内核消息链表 有数据拷贝;自带消息边界;自带阻塞
信号量 semget/semctl/semop 内核计数器 不传输数据;用于互斥、同步
共享内存 shmget/shmat/shmdt/shmctl 内核内存段 无拷贝速度最快;无同步,必须搭配信号量
相关推荐
一条破秋裤1 小时前
Linux 线程创建:pthread_create 与基本回收
java·linux·运维
s6516654963 小时前
IDT和GDT是什么
linux
Android系统攻城狮3 小时前
Linux Gstreamer深度解析之gst_audio_decoder_set_estimate_rate调用流程与实战(四十五)
linux·gstreamer音视频·音视频进阶
FED_AF3 小时前
Linux运维“邪修”功法之通配符
linux·运维
箓维3 小时前
线程的概念和理解
linux
云杂项4 小时前
tmux指南:安装、配置与高效使用(Ubuntu上)
linux·服务器·ubuntu
j7~4 小时前
【Linux网络编程】四十七.《Linux IO 模型详解:阻塞 IO、非阻塞 IO 与 IO 多路转接(select)》
linux·运维·网络·select·非阻塞io·阻塞io·i/o多路连接
GeW5 小时前
智能工厂背后:Linux在跑,数据库在扛,高端制造在加速
linux
qq_394562005 小时前
【在Linux上升级nginx】
linux·服务器·nginx