当前位置:首页 > 技术 > 正文内容

System V共享内存机制详解与实践

访客 技术 2026年6月23日 1

共享内存概述

共享内存的基本概念

共享内存是进程间通信(IPC)方式中效率最高的一种。当共享内存区域被映射到参与通信的进程地址空间后,这些进程之间的数据交换将不再需要内核介入。换句话说,进程无需通过系统调用进入内核态即可直接读写数据。 可以从另一个角度理解:操作系统为通信双方分配了一块公共内存区域,双方可以直接对该区域进行读取和写入操作,整个过程完全在用户态完成。

共享内存工作原理

共享内存工作原理图

共享内存的使用流程

使用共享内存进行进程间通信需要经历以下步骤:
  1. 创建或获取共享内存块
  2. 将共享内存挂载到进程地址空间
  3. 通过映射的地址进行数据交换
  4. 卸载共享内存
  5. 删除共享内存资源

关键函数接口详解

1. 创建或获取共享内存:shmget函数

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

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

参数说明:

  • key:用于标识共享内存块的唯一键值,通过ftok函数生成
  • size:需要创建的共享内存大小(字节)
  • shmflg:权限标志位,常用组合如下:
    • IPC_CREAT:当共享内存不存在时创建,存在时返回已存在的内存块
    • IPC_EXCL:与IPC_CREAT配合使用,不存在则创建,存在则返回错误
    • 0660:权限掩码,表示所有者可读写,组用户可读写

2. 生成唯一键值:ftok函数

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

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

参数说明:

  • pathname:指定一个已存在的文件路径,通常使用当前目录"."
  • proj_id:用户自定义的项目标识符(通常只使用低8位),如0x66

返回值是生成的key_t类型键值,用于后续shmget调用。

3. 挂载共享内存:shmat函数

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

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

参数说明:

  • shmid:shmget返回的共享内存标识符
  • shmaddr:指定映射地址,传入NULL由系统自动选择合适地址
  • shmflg:操作权限,0表示读写,SHM_RDONLY表示只读

4. 卸载共享内存:shmdt函数

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

int shmdt(const void *shmaddr);

参数为shmat返回的地址指针。成功返回0,失败返回-1。

5. 控制共享内存:shmctl函数

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

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

常用命令(cmd参数):

  • IPC_RMID:删除共享内存块
  • IPC_STAT:获取共享内存状态信息
  • IPC_SET:设置共享内存属性

第三个参数buf是输出型参数,用于获取共享内存的相关信息(如创建者PID、权限、大小等)。

共享内存数据结构

每个共享内存块都对应一个shmid_ds结构体,该结构体记录了共享内存的各种属性信息。Linux内核通过统一的ipc_perm结构体来管理不同IPC资源的权限,这使得共享内存、消息队列和信号量可以使用相同的管理机制。

实战案例:进程间消息传递

公共头文件定义

#pragma once
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <cstring>
#include <string>
#include <iostream>
#include <cstdlib>
#include <cerrno>

using namespace std;

const char* kPathName = ".";
const int kProjId = 0x66;
const size_t kShmSize = 4096;

class SharedMemoryHelper {
public:
    static int generateKey() {
        int key = ftok(kPathName, kProjId);
        if (key == -1) {
            cerr << "ftok failed: " << errno << " " << strerror(errno) << endl;
            exit(1);
        }
        return key;
    }

    static int createOrGetShm(key_t key, size_t size, int flag) {
        int shmid = shmget(key, size, flag);
        if (shmid == -1) {
            cerr << "shmget failed: " << errno << " " << strerror(errno) << endl;
            exit(2);
        }
        return shmid;
    }

    static int getShm(key_t key) {
        return createOrGetShm(key, kShmSize, IPC_CREAT | 0660);
    }

    static int createShm(key_t key) {
        return createOrGetShm(key, kShmSize, IPC_CREAT | IPC_EXCL | 0660);
    }

    static void* attachShm(int shmid) {
        void* addr = shmat(shmid, nullptr, 0);
        if (addr == (void*)-1) {
            cerr << "shmat failed: " << errno << " " << strerror(errno) << endl;
            exit(3);
        }
        return addr;
    }

    static void detachShm(void* addr) {
        if (shmdt(addr) == -1) {
            cerr << "shmdt failed: " << errno << " " << strerror(errno) << endl;
            exit(4);
        }
    }

    static void removeShm(int shmid) {
        if (shmctl(shmid, IPC_RMID, nullptr) == -1) {
            cerr << "shmctl failed: " << errno << " " << strerror(errno) << endl;
            exit(5);
        }
    }
};

服务端进程实现

#include "shm_helper.hpp"
#include <unistd.h>
#include <cstdio>

int main() {
    int key = SharedMemoryHelper::generateKey();
    cout << "Generated key: " << key << endl;
    
    int shmid = SharedMemoryHelper::createShm(key);
    cout << "Shared memory ID: " << shmid <&endl;
    
    sleep(3);
    
    char* shmAddr = static_cast<char*>(SharedMemoryHelper::attachShm(shmid));
    printf("Mapped address: %p\n", static_cast<void*>(shmAddr));
    
    while (true) {
        printf("Received: %s\n", shmAddr);
        sleep(1);
    }
    
    SharedMemoryHelper::detachShm(shmAddr);
    sleep(3);
    SharedMemoryHelper::removeShm(shmid);
    
    return 0;
}

客户端进程实现

#include "shm_helper.hpp"
#include <unistd.h>
#include <cstdio>

int main() {
    int key = SharedMemoryHelper::generateKey();
    cout << "Generated key: " << key <&endl;
    
    int shmid = SharedMemoryHelper::getShm(key);
    cout << "Shared memory ID: " << shmid <&endl;
    
    sleep(3);
    
    char* shmAddr = static_cast<char*>(SharedMemoryHelper::attachShm(shmid));
    printf("Mapped address: %p\n", static_cast<void*>(shmAddr));
    
    const char* msgTemplate = "Client says hello";
    int counter = 1;
    
    while (true) {
        snprintf(shmAddr, kShmSize, "%s [#%d] PID=%d", 
                 msgTemplate, counter++, getpid());
        sleep(5);
    }
    
    SharedMemoryHelper::detachShm(shmAddr);
    return 0;
}

运行效果与问题分析

通过上述代码可以观察到服务端持续读取共享内存中的数据,客户端定时更新数据。然而,这种实现存在一个明显的缺陷:由于缺乏同步机制,当写入速度较慢而读取速度较快时,会出现重复读取相同数据的情况。

解决这个问题的一种方式是结合管道机制。当写端写入速度慢、读端读取速度快时,管道的read操作会被阻塞,从而避免无效的重复读取。

消息队列与信号量简介

消息队列和信号量的使用方法与共享内存类似,都需要通过ftok函数生成key值,然后使用对应的get函数创建资源。

值得注意的是,这三种IPC资源在Linux内核中共享相同的管理结构。系统通过ipc_perm结构体来统一管理这些资源的权限信息,无论资源类型如何,其第一个成员都是ipc_perm结构。这意味着内核可以通过统一的接口来处理不同类型IPC资源的权限验证和资源管理。

查看系统中IPC资源可以使用以下命令:

  • 查看共享内存:ipcs -m
  • 查看消息队列:ipcs -q
  • 查看信号量:ipcs -s
  • 删除指定资源:ipcrm -m/-q/-s [id]

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。