《Linux系统编程篇》共享内存(Linux 进程间通信(IPC))——基础篇

发布于:2025-02-27 ⋅ 阅读:(17) ⋅ 点赞:(0)

今天的你有没有前进一小步呢

——家驹(StrangeHead)

引言

那么共享内存,我们如何去使用他呢,先来听笔者啰嗦一段话吧!有个整体的概念。

共享内存,听这个名字就像已经知道他是什么东东了,既然我们现在学习的的Linux进程之间的通讯,所以这个共享内存肯定也离不开通讯的功能,是的,他是IPC中的重要一员,成为高手,也是必须要掌握的。

对于共享内存,Linux中共享内存的两种主要实现方式:System V和POSIX。没错又是两套API,关于这两套API,笔者就教大家如何使用System V吧,因为POSIX感觉并不常用,读者朋友们有兴趣可以自己学一学。

那么进入正题。

什么是共享内存

他总体的作用就是,在Linux当中开辟一段内存空间,使得两个进程可以同时访问,是的就是这么简单

具体操作可以具象为操作一个文件(毕竟Linux中一切皆是文件呢)

open一个文件(没有就创建他)
我可以使用read读取他,也可以使用write去修改他。
当然这个文件,也可以让所有进程看到,并操作。
这样说,我们理所当然的就学会了共享内存,我们对于一个文件的操作,其实就是对共享内存进行操作!

那么他有什么API呢,总不能和文件的open等等的API冲突吧,肯定也有自己的优势,方便之处吧,来看一下吧。

特性 共享内存 文件操作
速度 直接在内存中操作,速度快 涉及磁盘 I/O,速度较慢
内存共享 多个进程可以直接共享数据 每个进程通常创建自己的文件副本
资源利用 内存使用更加高效,避免冗余 每个进程可能会重复打开和关闭文件
数据一致性 直接访问最新数据,避免不一致性 可能需要额外同步机制保证一致性
适用场景 高频率、大量数据交换 持久化存储、日志记录等
复杂性 需要管理访问权限和同步机制 操作相对简单,易于理解
系统调用数量 包含 shmget, shmat, shmdt, shmctl 包含 open, read, write, close
  • 共享内存 更适合需要快速、高效的数据交换的场景,尤其是涉及到大量数据的实时处理。
  • 文件操作 更适合需要持久化存储和简单文件操作的场景。

查看和删除共享内存(拓展)
你可以使用 ipcs -m 查看共享内存段,并通过 ipcrm 删除它

System V 共享内存 API 引入

总之就是下面这一坨了

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); // 控制操作

我们依次介绍他。大致看看即可,通过实例学习,不了解可以回来继续复习。🎯


1. shmget

功能:创建一个新的共享内存段或者获取一个已经存在的共享内存段的标识符。

原型

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

参数

  • key:用于标识共享内存段的键。可以通过 ftok 函数生成一个唯一的键。
  • size:共享内存段的大小(以字节为单位)。如果要创建新的共享内存段,此值必须大于 0。
  • shmflg:控制标志,可以包括以下选项:
    • IPC_CREAT:如果指定的共享内存段不存在,则创建它。
    • IPC_EXCL:与 IPC_CREAT 一起使用,如果共享内存段已经存在,则调用失败。
    • 权限标志(如 0666)指定谁可以读/写共享内存段。

返回值:成功时返回共享内存段的标识符(非负整数),失败时返回 -1,并设置 errno

示例

int shm_id = shmget(IPC_PRIVATE, 1024, IPC_CREAT | 0666);

2. shmat

功能:将共享内存段附加到调用进程的地址空间。

原型

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

参数

  • shm_id:通过 shmget 返回的共享内存段标识符。
  • shmaddr:希望共享内存段附加到的地址。通常可以设置为 NULL,由系统自动选择。
  • shmflg:控制标志,通常设置为 0。

返回值:成功时返回指向共享内存段的指针,失败时返回 (void*) -1,并设置 errno

示例

char* shm_ptr = (char*) shmat(shm_id, NULL, 0);

3. shmdt

功能:将共享内存段从调用进程的地址空间分离。

原型

int shmdt(const void* shmaddr);

参数

  • shmaddr:指向之前通过 shmat 返回的共享内存指针。

返回值:成功时返回 0,失败时返回 -1,并设置 errno

示例

if (shmdt(shm_ptr) == -1) {
    perror("shmdt failed");
}

4. shmctl

功能:控制共享内存段的操作,包括查询、修改和删除。

原型

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

参数

  • shm_id:共享内存段的标识符。
  • cmd:控制命令,可以是以下之一:
    • IPC_STAT:填充 shmid_ds 结构体(用于获取共享内存信息)。
    • IPC_RMID:标记共享内存段为待删除。
    • IPC_SET:修改共享内存段的权限和其他信息(需要提供相应的 shmid_ds 结构体)。
    • IPC_INFO:获取系统共享内存的信息(通常是系统级的)。
  • buf:指向 shmid_ds 结构体的指针,用于输入/输出的控制信息。

返回值:成功时返回 0,失败时返回 -1,并设置 errno

示例(删除共享内存段):

if (shmctl(shm_id, IPC_RMID, NULL) == -1) {
    perror("shmctl IPC_RMID failed");
}

5. 结构体 shmid_ds

用于描述共享内存段的状态,包括以下字段:

struct shmid_ds {
    struct ipc_perm shm_perm; // 共享内存的权限
    size_t shm_segsz;         // 共享内存段的大小
    time_t shm_atime;         // 最后附加时间
    time_t shm_dtime;         // 最后分离时间
    time_t shm_ctime;         // 最后控制时间
    unsigned short shm_cpid;  // 创建该共享内存段的进程 ID
    unsigned short shm_lpid;  // 最后操作该共享内存段的进程 ID
    short shm_nattch;         // 当前附加到该共享内存段的进程数
};

开始实操

通过上述 API,可以有效地创建和管理共享内存段,从而实现进程间的高效通信。在使用共享内存时,务必注意同步问题,以避免数据竞争和不一致性。对于更复杂的应用,可能需要结合其他 IPC 机制(如信号量)来实现更可靠的进程间通信。

我们先简单的利用API来实现两个程序,一个写入,一个读取。

写入进程:

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

#define SHM_SIZE 1024

int main() {
    key_t key = ftok("shmfile", 65);
    int shmid = shmget(key, SHM_SIZE, 0666|IPC_CREAT);
    
    char *str = (char*) shmat(shmid, NULL, 0);
    sprintf(str, "Hello from PID %d", getpid());
    
    shmdt(str);
    return 0;
}

读取进程:

// ...(头文件同上)
int main() {
    key_t key = ftok("shmfile", 65);
    int shmid = shmget(key, SHM_SIZE, 0666);
    
    char *str = (char*) shmat(shmid, NULL, 0);
    printf("Received: %s\n", str);
    
    shmdt(str);
    shmctl(shmid, IPC_RMID, NULL); // 删除共享段
    return 0;
}

这里仅仅做了这样一个小实验,随着日后,技术的提升,不断迭代,希望读者可以灵活应用,这里没有加入其它IPC同步机制,实际使用,对于共享内存的访问是要做保护机制的!!!仅仅让读者快速掌握他如何使用。

监控与调试技巧 使用ipcs -m查看System V共享段

通过lsof /dev/shm检查POSIX对象

分析/proc/sysvipc/shm获取详细信息

使用strace跟踪shm相关系统调用

注意

同步机制(必须实现以下之一):

使用共享内存必须必须使用同步机制,避免同时访问或者操作,造成数据错乱或者死机

  • 信号量(semaphore)

  • 互斥锁(pthread_mutex,需配置进程共享属性)

  • 文件锁(fcntl)

生命周期管理:

  • System V:需主动调用shmctl(IPC_RMID)

  • POSIX:引用计数归零后自动删除

安全配置:

  • 严格设置权限位(如0600)

  • 避免使用可预测的IPC键值

  • 及时清理未使用的共享段

性能调优:

  • 使用大页内存(Hugepages)

  • 对齐内存访问边界

  • 避免频繁attach/detach操作

结束

浅浅的总结一下吧,这节我们学到了System V共享内存,以及他的一些核心API的使用,知道了他是什么东西。虽然操作文件的大致流程上没有俩样。但是使用上还是更具实际情况选择哦,并且给了两段小程序来帮助理解,再次提醒,实际情况,要做同步机制保护。
好了,本文就到这里了,让我们每天进步一小步吧!