进程间通信 ---- System V 共享内存

目录

[System V IPC 整体框架](#System V IPC 整体框架)

[System V 共享内存的定义](#System V 共享内存的定义)

[System V 共享内存的特点](#System V 共享内存的特点)

[System V 共享内存原理](#System V 共享内存原理)

[System V 共享内存函数](#System V 共享内存函数)

shmget

[size 参数的隐藏规则](#size 参数的隐藏规则)

ftok

[ftok 的意义](#ftok 的意义)

shmat

shmdt

shmctl

[System V 共享内存的命令行操作](#System V 共享内存的命令行操作)

[System V 共享内存的内核数据结构](#System V 共享内存的内核数据结构)

[基于System V 共享内存的代码示例](#基于System V 共享内存的代码示例)


System V IPC 整体框架

System V 是一种进程间通信的标准,诞生于早期的 UNIX System V 系统,它为不同进程提供了一套统一的通信机制,主要包含三类核心组件:

System V 共享内存 System V 消息队列 System V 信号量

这三类机制常被结合使用,构成了一套完整的基于System V 标准进程间通信方案。


System V 共享内存的定义

System V 共享内存:允许多个进程可以将同一块物理内存映射到自己的虚拟地址空间,进程之间可以通过访问自己的虚拟地址,直接读写共享的物理内存,从而实现高效的进程间通信。


System V 共享内存的特点

System V 共享内存的生命周期随内核,不随进程,需要手动删除。

优点:System V 共享内存是System V IPC 中效率最高的通信方式。

原因:进程无需通过内核中转,直接通过自身的虚拟地址读写共享内存,避免了用户态与内核态之间的数据拷贝。

例如:对于管道,进程要实现进程间通信,需要先将用户态数据拷贝到内核态缓冲区,再把内核态缓冲区的数据,读取到用户态,多次数据拷贝与系统调用会带来较大开销,降低通信效率。

缺点:System V 共享内存不提供读写保护或同步通知机制。(解决方案:共享内存必须配合信号量、互斥锁等同步机制使用,才能保证多进程访问时的数据一致性)

造成的影响:缺乏保护机制会直接导致数据一致性问题。

例如:进程 A 正在向共享内存写入数据时,进程 B 无法感知当前共享内存的读写状态。若此时进程 B 也执行写入操作,就可能覆盖进程 A 正在写入的数据;若进程 B 执行读取操作,则可能读到进程 A 未写完的、不完整的数据,最终导致数据不一致。


System V 共享内存原理

分配共享内存之前

分配共享内存之后


System V 共享内存函数

shmget

参数

key:共享内存的系统级标示符

size:共享内存的大小

shmflg:常见的两种用法

IPC_CREAT | IPC_EXCL | mode:mode 八进制数字,通常为0666,代表共享内存被创建出来的权限。若共享内存不存在,则创建共享内存并返回;若共享内存已经存在,出错返回。

0:若共享内存不存在,则出错返回;若共享内存已经存在,获取共享内存,并返回,获取的权限为共享内存创建的权限。

注意:IPC_EXCL 单独使用没有任何意义。

返回值

成功:返回一个非负整数,代表共享内存的用户级标示符

失败:返回 -1

注意:key值是内核层面使用的,shmid是用户层面使用的。

size 参数的隐藏规则

共享内存的实际分配大小,会被内核向上对齐到通常以4KB为单位的大小。例如你申请4097字节,内核会分配4096 * 2 个字节,虽然OS给你多分配了4095字节,但是你只能使用这4097字节,不能越界访问。

当shmflg 包含 IPC_CREAT 时,size 才会生效;如果共享内存已经存在,size 必须小于等于已有共享内存的大小,但此时 size 的值并不代表获取共享内存的进程能访问的共享内存的大小,即使设置为 0,依旧可以使用整个共享内存,如果size大于共享内存的大小,则出错返回。

ftok

ftok 接受 pathname 和 proj_id 两个参数,内部通过某种算法运算,专门生成一个唯一的key,让不同进程能找到同一个共享内存。

参数

pathname:一个真实存在的普通文件路径,不推荐使用目录路径

proj_id:一个非 0 的数字,只用低8位(1 ~ 255)

返回值:

成功:返回key值

失败:返回-1

ftok 的意义

如果两个进程想用同一个共享内存,它们必须约定用同一个key。ftok 就是让不同进程能稳定生成相同且唯一 key 的标准方法。

shmat

功能:将共享内存映射到进程的虚拟地址空间的共享区

参数

shmid:共享内存的标示符

shmaddr:指定映射到进程的虚拟地址空间的共享区起始地址

NULL :表示让内核自动选择一个合适的虚拟地址。 ---- 推荐、新手用

某个具体地址:强制把共享内存映射到指定的虚拟地址 ---- 不推荐、高手才用

shmflg:shmaddr 为 NULL 时,shmflg的作用:控制本次映射的读写权限。

0:表示共享内存创建的权限。---- 常用

SHM_RDONLY:只读权限。 ---- 不常用

返回值:

成功:返回共享内存的起始虚拟地址。

失败:返回(void*) -1

shmdt

功能:将共享内存与当前进程脱离

参数

shmaddr:共享内存的起始虚拟地址,由shmat函数得到

返回值:

成功:返回0

失败:返回-1

注意:将共享内存与当前进程脱离不等于删除共享内存

shmctl

功能:控制共享内存,查看、修改、删除内核的共享内存。

参数

shmid:共享内存的标示符

cmd:将要采取的动作

buf:一个指针,指向保存着共享内存的内核数据结构

cmd:

  1. IPC_STAT:第三个参数需要传递一个struct shmid_ds 类型的地址,将指定共享内存的内核数据结构拷贝到传递的参数中,获取共享内存的信息。

  2. IPC_RMID:第三个参数无效,通常设为 NULL,删除共享内存。

  3. IPC_SET:在进程有足够权限的前提下,将buf中的数据拷贝到指定共享内存的内核数据结构中,修改共享内存的信息。

共享内存删除规则:调用 IPC_RMID 并不会立刻删除,只有当使用该共享内存的所有进程都断开映射后,才真正删除。

注意:调用删除共享内存的进程不会阻塞,而是正常运行结束,因为共享内存为内核资源,操作系统会对该共享内存打上标签,只要该共享内存被打上标签,那么其他进程调用shmat对该共享内存进行映射,会直接失败返回。所以直到该共享内存的计数器为0时,OS自动释放共享资源。

返回值:

成功:返回0

失败:返回-1


System V 共享内存的命令行操作

ipcs -m ---- 查看共享内存

------ Shared Memory Segments --------

key shmid owner perms bytes nattch status

ipcrm -m shmid ---- 删除指定共享内存


System V 共享内存的内核数据结构

cpp 复制代码
/* Obsolete, used only for backwards compatibility and libc5 compiles */
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 */
};
cpp 复制代码
/* Obsolete, used only for backwards compatibility and libc5 compiles */
struct ipc_perm
{
	__kernel_key_t	key;
	__kernel_uid_t	uid;
	__kernel_gid_t	gid;
	__kernel_uid_t	cuid;
	__kernel_gid_t	cgid;
	__kernel_mode_t	mode; 
	unsigned short	seq;
};

基于System V 共享内存的代码示例

comm.hpp ---- 封装实现了共享内存的创建,建立映射,取消映射,删除。

cpp 复制代码
#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string>
#include <cstdio>

#define PATHNAME "./comm.hpp"
#define PROJ_ID 0
#define SIZE 4096
#define CREATER "creater"
#define USER "user"

#define ERR_EXIT(ERROR_MSG) \
    do                      \
    {                       \
        perror(ERROR_MSG);  \
        exit(EXIT_FAILURE); \
    } while (0)

class Shm
{
private:
    void CreatOrGet()
    {
        _k = ftok(PATHNAME, PROJ_ID);
        if (_k < 0)
        {
            ERR_EXIT("ftok error");
        }
        printf("ftok sucess,key:0x%x\n", _k);
        umask(0);
        if (_name == CREATER)
            _shmid = shmget(_k, _size, IPC_CREAT | IPC_EXCL | 0666);
        else if (_name == USER)
            _shmid = shmget(_k, _size, 0);
            
        if (_shmid < 0)
        {
            ERR_EXIT("shmget error");
        }
        printf("shmget sucess,shmid:%d\n", _shmid);
    }
    void Attach()
    {
        _start_address = shmat(_shmid, NULL, 0);
        if (_start_address == (void *)-1)
        {
            ERR_EXIT("shmat error");
        }
        printf("shmat sucess, Virtul Address:%p\n", _start_address);
    }
    void Detach()
    {
        int n = shmdt(_start_address);
        if (n < 0)
        {
            ERR_EXIT("shmdt error");
        }
        printf("shmdt sucess\n");
    }
    void Destory()
    {
        Detach();
        if (_name == CREATER)
        {
            int n = shmctl(_shmid, IPC_RMID, NULL);
            if (n < 0)
            {
                ERR_EXIT("delete error");
            }
            printf("delete sucess\n");
        }
    }

public:
    Shm(std::string &name)
        : _size(SIZE), _name(name), _start_address(nullptr)
    {
        CreatOrGet();
        Attach();
    }

    int Shmid()
    {
        return _shmid;
    }
    int Key()
    {
        return _k;
    }
    void *VirAddress()
    {
        return _start_address;
    }
    size_t Size()
    {
        return _size;
    }

    ~Shm()
    {
        Destory();
    }

private:
    int _shmid;
    key_t _k;
    size_t _size;
    void *_start_address;
    std::string _name;
};

进程可以通过下列方式进行访问共享内存 ---- 访问共享内存的方式与访问堆上申请的空间一摸一样,这里不再做演示,因为共享内存没有进行同步与互斥,没有保护共享内存,会带来并发问题,也就是上述共享内存特点中的缺点。

cpp 复制代码
#include "comm.hpp"

int main()
{
    std::string name = "creater";
    Shm shm(name);

    int* p = (int*)shm.VirAddress();
    // 进行读写数据

    return 0;
}
相关推荐
云计算磊哥@15 分钟前
运维开发宝典026-MySQL02数据库表操作
运维·数据库·运维开发
weixin_5231853220 分钟前
Collections.unmodifiableMap详解:真的不可修改吗?
java·linux·前端
黄同学real29 分钟前
解决 Visual Studio Web Deploy 远程发布报 401 未授权 (ERROR\_USER\_UNAUTHORIZED)
服务器
天天进步20151 小时前
Tunnelto 源码解析 #9:控制服务器设计:Warp、WebSocket、Ping/Pong 与连接保活
运维·服务器·websocket
凡人叶枫1 小时前
Effective C++ 条款04:确定对象被使用前已先被初始化
java·linux·开发语言·c++·嵌入式开发
云栖梦泽1 小时前
玩转RK3506SDK
linux·嵌入式硬件
极客先躯1 小时前
高级java每日一道面试题-2026年02月01日-实战篇[Docker]-Docker Volume 的生命周期管理是怎样的?
java·运维·docker·容器·持久化·架构图·容器卷
Java面试题总结2 小时前
Linux-Ubantu-贴士-apt的地盘
linux·运维·服务器
●VON2 小时前
AtomGit Flutter鸿蒙客户端:数据模型
android·服务器·安全·flutter·harmonyos·鸿蒙
志栋智能2 小时前
超自动化巡检:提升MTTR,缩短业务影响时间
运维·自动化