Linux 下 IPC 之 System V 共享内存的使用和原理初探

本文深入探讨了共享内存作为进程间通信(IPC)机制的工作原理,包括其创建过程、API使用及实例演示。共享内存允许多个进程访问同一块内存区域,实现高效数据交换,但需自行处理同步问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


前言

总结一下最近对于共享内存的学习, 可能比较浅显或者有疏漏, 欢迎指正!

原理初探

我们知道, 进程空间相互隔离, 互相对立, 但是共享内存允许多个进程可以访问同一块内存来达到进程间通信的目的.

共享内存是最高效的 IPC 机制, 它不涉及任何进程间的数据传输, 而且他和进程同处于用户空间, 不像消息队列, 信号量是内核空间的系统对象, 不需要花费额外的数据拷贝, 但是同时他并没有预防竞态条件, 也就是说在多进程利用共享内存进行通信的情况下, 我们需要自己去利用锁等操作来进行同步

共享内存的创建过程

当我们创建了一块共享内存, 其实是在 tmpfs 中创建了一个文件 (这个文件是存储于内存的), 也就意味着在 tmpfs 中创建了一个 iNode 节点

然后我们需要将这个创建好的文件映射到进程中 (如下图, 此图来自网络, 应该是哪个博客或者知乎吧…已经记不清了)

在这里插入图片描述

我们创建的这块共享内存不会随着进程的结束而被释放, 他会在关机前一直存在于内存中, 除非被进程明确的删除

也可以使用 `ipcrm + 共享内存 id ` 命令删除

在这里插入图片描述

比如这里我在刚开始练习的时候没有删除创建的共享内存

在这里插入图片描述

系列 API 的使用

ftok

#include <sys/shm.h>
#include <sys/types.h>
key_t ftok ( const char* fname, int fd);

成功返回一个key_t值, 失败返回-1

fname 参数是一个必须存在且能访问到的目录
id 子序号, 自己约定, 只有8个比特位被指用

注意, 当文件路径和子序号都相同时返回的并不一定永远返回一样的key值, 如果该路径指向的文件或者目录被删除而又重新创建, 就算名字还是一样, 但是文件系统会赋予它不同的 iNode 信息, 所以返回的 key 值就不同了

shmget

#include <sys/shm.h>
int shmget (key_t key, size_t size, int shmflag);

用来创建一块共享内存并返回其 id 
或者获得一块已经被创建的共享内存的 id

成功返回一个正整数值, 这个整数是共享内存的标识符, 失败时返回 -1, 错误存储于 errno

key_t key 参数用来唯一标识一段全局共享内存, 通常通过 ftok 函数获得,
size_t size 参数是创建的共享内存的大小, 单位为字节, 如果是要创建一块共享内存, 此参数必须被指定, 如果是要获取一块创建好的共享内存的 id, 可以将其设置为 0
int shmglag 参数为 0 为获取共享内存 id, 为 IPC_CREAT 时是创建一个新的共享内存, 通常要同时指定权限 (和权限进行 | 运算)
同时还能取IPC_EXCL , 只有在共享内存不存在的时候,新的共享内存才建立,否则就产生错误。

使用 shmget 创建的共享内存段会全部被初始化为 0, 同时和他关联的内核数据结构 shmid_ds 将被创建和初始化

但是我尝试发现并没有被初始化为0, 请谨慎使用

struct shmid_ds { 
    struct ipc_perm    shm_perm;      /* 操作权限 */ 
    size_t             shm_segsz;     /* 大小,单位是字节 */ 
    __kernel_time_t    shm_atime;     /* 对这段内存最后一次调用shmat的时间 */ 
    __kernel_time_t    shm_dtime;     /* 最后一次调用shmdt的时间*/ 
    __kernel_time_t    shm_ctime;     /* 最后一次调用shmctl的时间*/ 
    __kernel_ipc_pid_t shm_cpid;      /* 创建者的pid*/ 
    __kernel_ipc_pid_t shm_lpid;      /* 最后一次执行shmat或shmdt的进程的pid*/ 
    unsigned short     shm_nattch;    /*目前关联到次共享内存的进程的数量*/ 
    unsigned short     shm_unused;    /* 以下为填充 */ 
    void               *shm_unused2; /* ditto - used by DIPC */ 
    void               *shm_unused3; /* unused */ 
};

shmat

前面我们也说道, 我们创建共享内存其实是创建了一个文件, 然后要把它映射到当前进程, 即, 我们在使用 shmget 创建了一块共享内存之后, 要使用 shmat 将他关联到当前进程, 同样的使用完不再需要之后, 也需要使用 shmdt 将其分离, 让我们先看 shmat

#include <sys/shm.h>
void* shmat ( int shm_id, const void* shm_addr, int shmflag );

成功返回映射到进程的地址空间 失败返回 (void *)-1并将错误存储于 errno

shm_id 参数是 shmget 返回的共享内存 id,
shm_addr 参数是指定共享内存在进程内存地址的映射位置, 推荐使用 NULL, 由内核自己决定
shmflag一般为0

shmat 调用成功后, 会修改 shmid_ds 的部分字段:
将 shm_nattach 加一
将 shm_lpid 设置为调用进程 pid
将 shm_atime 设置为当前时间

shmdt

在共享内存使用完之后使用此函数将其从进程地址空间分离

#include <sys/shm.h>
int shmdt ( const void* shm_addr );

成功返回 0, 失败返回 -1

shm_addr 参数是共享内存在进程的映射地址, 即 shmat 返回的值

调用 shmdt 并不会删除共享内存
调用成功时修改内核数据结构 shmid_ds 部分字段:
将 shm_nattach 减一
将 shm_lpid 设置为调用进程的pid
将 shm_dtime 设置为当前时间

shmctl

管理共享内存

#include 
int shmctl ( int shm_id, int command, struct shmid_ds* buf );

失败返回-1, 并存储错误于 errno, 成功时的返回值取决于 command

shm_id 是共享内存标识符
command 指定要执行的命令

常用命令为 IPC_RMID, 即, 删除共享内存

如果共享内存已经与所有访问它的进程断开了连接,则调用IPC_RMID子命令后,系统将立即删除共享内存的标识符,并删除该共享内存区,以及所有相关的数据结构;

如果仍有别的进程与该共享内存保持连接,则调用IPC_RMID子命令后,该共享内存并不会被立即从系统中删除,而是被设置为IPC_PRIVATE状态,并被标记为"已被删除";直到已有连接全部断开,该共享内存才会最终从系统中消失。

共享内存实例

通过共享内存进行同机间的进程间通信

创建一个长度为10的 Stu 数组, 向其中写入数据, 另一个进程读取
代码很简单, 就不做注释了
/*
write.cpp
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>

typedef struct Stu 
{
    int age;
    char name[10];
} Stu;

int main() 
{
 
    int id = shmget((key_t)1234, sizeof(Stu) * 10, IPC_CREAT | 0644);
    if (id == -1) 
    {
        perror("shmget "), exit(1);
    }
    
    Stu* t = (Stu *)shmat(id, NULL, 0);
    for (int i = 0; i < 5; i++) 		//初始化前五个
    {
        (t + i)->age = i;
        strcpy((t + i)->name, "lvbai");
    }

    shmdt(t);
 
    return 0;
}

/*
read.cpp
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>

typedef struct Stu 
{
    int age;
    char name[10];
} Stu;

int main() 
{
    int id = shmget((key_t)1234, 0, IPC_CREAT);
    if (id == -1) 
    {
        perror("shmget "), exit(1);
    }

    void* p = NULL;
    p = shmat(id, NULL, 0);
    if (p == (void *)-1) 
    {
        perror("****** : ");
    }


    Stu* ptr = (Stu *)p;

	printf("here is message : \n");

    for (int i = 0; i < 10; i++) 
    {
        printf("age = %d, name = %s\n", (ptr + i)->age, (ptr + i)->name);
    }

    shmdt(p);

    shmctl(id, IPC_RMID, 0);
   
   
    
    return 0;
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值