文章目录

System V 消息队列(Message Queue)

消息队列概念

消息队列(Message Queue) 是操作系统在内核中提供的一种 进程间通信(IPC, Inter-Process Communication)机制。
它在 内核空间维护一个队列结构,用于在不同进程之间传递数据。
消息队列具有以下特点

  • 由 操作系统内核维护
  • 数据以 消息(message)为单位 进行传递
  • 消息按照 队列(FIFO)顺序存储
  • 支持 多个进程读写
  • 消息具有 类型(type),可按类型选择性读取
    因此,消息队列可以理解为:

由操作系统提供的内核级 FIFO 队列,用于进程之间传递结构化消息。


消息队列通信模型

通信过程如下:

进程A (Sender)
      │
      │ msgsnd()
      ▼
   ┌───────────┐
   │ Message   │
   │   Queue   │  ← 内核维护
   └───────────┘
      ▲
      │ msgrcv()
      │
进程B (Receiver)
  • 发送进程 将消息写入消息队列
  • 接收进程 从消息队列读取消息
    不同进程通过 同一个消息队列 ID 访问同一队列,从而实现通信。

消息结构

消息队列中的数据以 消息块(message block) 的形式存储。
在用户空间中通常定义如下结构:

struct msgbuf
{
    long mtype;      // 消息类型
    char mtext[SIZE]; // 消息数据
};

字段说明:

字段含义
mtype消息类型(必须 > 0)
mtext实际数据内容

消息类型的作用

消息队列支持 按类型读取消息。
例如:

类型含义
1Client 发送
2Server 发送
这样:
  • Server 可以只读取 type = 1
  • Client 可以只读取 type = 2
    从而实现 双向通信。

创建或获取消息队列

使用 msgget() 系统调用:

#include <sys/msg.h>

int msgget(key_t key, int msgflg);

参数说明:

参数说明
key消息队列的唯一标识
msgflg创建标志
常见标志:
IPC_CREAT
IPC_EXCL
0666

例如:

key_t key = ftok("./", 1);

int msgid = msgget(key, IPC_CREAT | 0666);

返回值:

msgid(消息队列标识符)

发送消息

使用 msgsnd():

int msgsnd(int msgid,
           const void *msgp,
           size_t msgsz,
           int msgflg);

参数说明:

参数含义
msgid消息队列ID
msgp消息结构体
msgsz消息数据大小
msgflg标志
示例:
struct msgbuf msg;

msg.mtype = 1;
strcpy(msg.mtext, "hello");

msgsnd(msgid, &msg, sizeof(msg.mtext), 0);

接收消息

使用 msgrcv():

ssize_t msgrcv(int msgid,
               void *msgp,
               size_t msgsz,
               long msgtyp,
               int msgflg);

参数说明:

参数含义
msgid消息队列ID
msgp接收消息结构
msgsz数据大小
msgtyp读取指定类型
msgflg标志
示例:
struct msgbuf msg;

msgrcv(msgid, &msg, sizeof(msg.mtext), 1, 0);

表示:

只读取 type = 1 的消息

控制消息队列

使用 msgctl():

int msgctl(int msgid, int cmd, struct msqid_ds *buf);

常见操作:

删除消息队列

msgctl(msgid, IPC_RMID, NULL);

获取消息队列属性

msgctl(msgid, IPC_STAT, &buf);

内核中的消息队列结构

操作系统需要管理所有消息队列,因此在内核中会维护一个数据结构

struct msqid_ds

其主要内容包括:

字段含义
msg_perm权限信息
msg_stime最后发送时间
msg_rtime最后接收时间
msg_ctime最后修改时间
msg_qnum队列中消息数量
msg_qbytes队列最大字节数
其中:
msg_perm

内部包含:

key
uid
gid
mode

用于描述 IPC 权限信息。


System V IPC 统一设计思想

System V IPC(共享内存 / 信号量 / 消息队列)具有相同的设计模式:
1 描述资源

ipc_perm

2 使用 key 标识资源

ftok()

3 获取资源

xxxget()

4 控制资源

xxxctl()
IPC 类型创建控制
共享内存shmgetshmctl
消息队列msggetmsgctl
信号量semgetsemctl
消息队列的核心特征:
  1. 内核维护的 FIFO 队列
  2. 以 消息块 为单位传输数据
  3. 支持 按类型读取消息
  4. 支持 多进程通信
  5. 属于 *System V IPC 机制

信号量与并发控制

信号量(Semaphore)的本质

信号量(Semaphore)是一种用于进程或线程之间进行同步与互斥控制的同步原语。
信号量通过原子操作(P/V 操作)实现对临界资源的访问控制,从而保证多个执行流在访问共享资源时满足互斥性、同步性以及原子性要求。
信号量(Semaphore)本质上是一种计数器,用于描述系统中某类资源的可用数量。也就是公共资源中资源数量多少的问,
其核心作用是:

用于实现进程或线程之间的同步与互斥控制。

信号量通常表示:

  • 当前资源总量
  • 当前可用资源数量
  • 对共享资源访问的许可数量
    例如:
sem = 3

表示:
系统中当前有 3 个可用资源。


为什么需要信号量

在多进程或多线程环境中,多个执行单元可能会 同时访问同一资源。
如果没有任何保护机制,就可能出现 数据不一致问题(Data Inconsistency)。
例如:
进程 A 正在向共享内存写入:

ABCD1234

如果写入过程尚未完成:

ABCD

此时进程 B 读取该数据,就会得到 不完整的数据。
这种情况称为:

并发访问导致的数据不一致问题


引入通信机制的原因

进程具有 地址空间独立性,默认情况下无法直接访问彼此的数据。
因此,为了实现进程之间的数据交换与协作,需要引入 进程间通信(IPC)机制。
通信机制的核心思想是:

让多个进程能够访问同一份资源,从而实现数据交换。

常见 IPC 机制包括:

  • 管道(Pipe)
  • 共享内存(Shared Memory)
  • 消息队列(Message Queue)
  • 信号量(Semaphore)

并发访问带来的新问题

虽然 IPC 机制解决了 进程通信问题,但同时也带来了新的问题:
并发访问冲突。
多个进程如果 同时访问共享资源,可能会导致:

  • 数据不一致
  • 数据覆盖
  • 状态错误
    因此需要引入 同步与互斥机制。

公共资源(Shared Resource)

在并发系统中:
可以被多个进程或线程同时访问的资源称为公共资源(Shared Resource)。
典型的公共资源包括:

  • 共享内存
  • 文件
  • 网络连接
  • 管道
  • 消息队列
    当多个执行单元同时访问这些资源时,就会产生 并发问题。

临界资源(Critical Resource)

当共享资源需要通过同步机制进行保护时,该资源称为:

临界资源(Critical Resource)

即:
一次只允许一个执行单元访问的共享资源。


临界区(Critical Section)

访问临界资源的代码区域称为临界区(Critical Section)。
例如:

shared_memory = data;

这段代码如果访问共享资源,就属于 临界区代码。
系统必须保证:

同一时刻只有一个执行单元进入临界区。


非临界区(Non-Critical Section)

程序中 不访问临界资源的代码区域 称为:
非临界区(Non-Critical Section)
例如:

printf("hello");
sleep(1);
return 0;

这些代码不涉及共享资源,因此不需要同步控制。


并发程序的结构

一个典型的并发程序通常包含两类代码:

程序代码
 ├─ 非临界区代码(Non-Critical Section)
 ├─ 临界区代码(Critical Section)
 └─ 非临界区代码

其中:

  • 临界区代码需要同步保护
  • 非临界区代码可以并发执行

并发控制思想总结

并发系统中的设计逻辑通常如下:

  1. 进程具有独立性,无法直接通信
  2. 为实现通信,引入 IPC 机制
  3. IPC 使多个进程能够访问共享资源
  4. 共享资源带来并发访问冲突
  5. 为解决冲突,引入同步机制
  6. 信号量用于实现资源访问控制

互斥(Mutual Exclusion)

当多个执行单元需要访问同一临界资源时,需要保证:

在任意时刻仅允许一个执行单元进入临界区。

这种访问控制策略称为:
互斥(Mutual Exclusion)
互斥通常通过 锁(Lock)机制 实现,例如:

  • 互斥锁(Mutex)
  • 信号量(Semaphore)
  • 自旋锁(Spinlock)
    现实生活中的类比:
    一个自习室一次只允许一个人进入学习,进入后将门锁上,其余人员必须等待当前使用者离开。

原子性(Atomicity)

原子性概念

原子性(Atomicity) 指的是:

一个操作要么完全执行成功,要么完全不执行,不会出现中间状态。

即:

Only two states:
完成 / 未执行

不存在“部分完成”的情况。


原子性示例:银行转账

银行转账通常包含两个操作:

  1. 从转出账户扣除金额
  2. 向转入账户增加金额
    例如:
A账户:1000 → 800
B账户:1000 → 1200

如果第一步完成而第二步失败,就会造成数据错误。
因此转账操作必须保证:

要么全部成功
要么全部回滚

这就是 原子性保证。


原子性与并发控制的关系

在并发系统中:

  • 临界区操作通常需要 原子性保证
  • 原子性通常依赖 互斥机制
    因此三者之间存在密切关系:
共享资源
      ↓
临界资源
      ↓
临界区访问
      ↓
互斥控制
      ↓
保证原子性

管道与共享内存

管道通信

概念对应对象
公共资源管道缓冲区
临界资源被内核保护的管道
临界区read() / write() 操作
由于 Linux 管道内部已经实现同步机制,因此开发者通常无需手动控制互斥。

共享内存通信

概念对应对象
公共资源共享内存区域
临界资源加锁后的共享内存
临界区读写共享内存的代码
共享内存 默认不具备同步机制,因此需要配合:
  • 信号量
  • 互斥锁
    进行访问控制。

并发控制的五个核心概念

在并发程序设计中,需要理解以下关键术语:

概念含义
公共资源可被多个执行单元访问的资源
临界资源需要保护的共享资源
临界区访问临界资源的代码
互斥同一时刻只允许一个执行单元访问资源
原子性操作要么完成,要么不执行
此外还有一个重要概念:
同步(Synchronization)
该概念主要用于描述 执行顺序约束问题,通常在多线程模型中进一步讨论。
整体逻辑关系如下:
进程通信
      ↓
共享资源
      ↓
并发访问问题
      ↓
临界资源
      ↓
临界区
      ↓
互斥控制
      ↓
原子性保证

为什么不能用普通全局变量作为计数器

在进程环境下,直接使用普通变量(如全局整数)无法实现跨进程同步,原因包括:

  1. 进程地址空间隔离
    不同进程拥有独立的虚拟地址空间,因此普通全局变量在不同进程中并不共享。
  2. 写时拷贝(Copy-on-Write)机制
    在 fork() 创建子进程后,父子进程的变量虽然初始值相同,但在写操作发生时会产生副本,因此无法作为共享计数器。
  3. 缺乏原子操作保证
    普通变量的自增或自减操作并非原子操作,在并发访问情况下可能产生竞态条件(Race Condition)。
    因此,如果需要在多个进程之间维护一个共享计数器并保证操作原子性,必须借助操作系统提供的同步机制,如信号量。

信号量存在的意义

信号量主要解决两个核心问题:

  1. 控制对共享资源的并发访问
    在多进程或多线程系统中,多个执行流可能同时访问同一共享资源。
    如果缺乏控制,将会导致:
    • 数据竞争(Race Condition)
    • 数据不一致
    • 程序逻辑错误
      信号量通过计数机制,限制同时访问资源的执行流数量。

信号量的特点

信号量是一种重要的并发控制机制,其核心特点包括:

  1. 本质是一个整型计数器
  2. 用于表示系统中可用资源数量
  3. 通过原子操作保证并发安全
  4. 用于实现互斥和同步
  5. 支持资源预约与访问控制
    在多进程或多线程系统中,信号量是实现共享资源管理和执行流协作的重要工具。

进程访问共享资源时的基本流程

在多进程或多线程并发环境中,如果执行流需要访问某一部分共享资源(Shared Resource),通常需要借助**信号量(Semaphore)**进行资源管理。基本流程如下:

  1. **申请资源(Acquire Resource)**进程在访问共享资源之前,必须首先对信号量执行 P 操作(wait / down):
 P(sem)

该操作会将信号量计数值减 1:
sem = sem - 1
如果操作成功,表示当前进程已经成功预定一个资源单位。
2. 访问共享资源(Critical Section Execution)
当 P 操作成功后,进程即可进入临界区(Critical Section),访问共享资源。
3. 释放资源(Release Resource)
当进程完成资源访问后,需要执行 V 操作(signal / up):

 V(sem)

该操作会将信号量计数值加 1:
sem = sem + 1
表示当前进程释放一个资源单位,使其他等待进程能够继续访问资源。
因此,进程访问共享资源的典型模式为:

P(sem);        // 申请资源
访问共享资源;   // 临界区
V(sem);        // 释放资源

其中:

  • P 操作:表示资源申请或资源预定
  • V 操作:表示资源释放

信号量的预定机制

信号量机制本质上提供了一种资源预约(Reservation)机制。
当进程成功执行 P 操作 时,意味着:

  • 系统中一定存在可用资源
  • 该进程已经成功占用或预定了一个资源单位
    即使该进程尚未立即使用该资源,系统也保证该资源已经为其保留。
    如果 P 操作失败,则说明:
  • 当前系统中已经没有可用资源
  • 该进程必须进入等待状态
    这种机制保证了:
  • 系统资源不会被超额分配
  • 每个资源单位只能被一个执行流占用

信号量必须是共享资源

在并发系统中,所有进程在访问共享资源之前,都必须先对同一个信号量进行操作。
因此必须满足一个条件:
所有相关进程必须能够访问同一个信号量对象。
换言之:

信号量本身也必须是一个共享资源。

否则,不同进程无法对同一个计数器进行操作,也就无法实现并发控制。
因此,在操作系统实现中:

  • System V 信号量
  • POSIX 信号量
    都提供了跨进程共享的信号量机制。

信号量自身安全性问题

所有进程在访问公共资源之前都必须先申请信号量,必须先申请信号量的前提。所有进程必须先看到同一个信号量,信号量是公共资源
由于信号量本身会被多个执行流同时访问,因此会产生一个关键问题:
信号量自身的数据操作是否安全?
信号量内部主要涉及的操作包括:

  • 计数器自减(申请资源)
  • 计数器自增(释放资源)
    如果这些操作不是原子操作(Atomic Operation),就可能出现:
  • 多个进程同时修改计数器
  • 计数器值错误
  • 资源分配失控
    因此,操作系统必须保证:

信号量的 P 操作和 V 操作是原子操作。

即:

  • 在执行过程中不会被其他进程打断
  • 操作不可分割
    这也是信号量能够安全管理资源的关键。
    信号量初始值为1,代表使用整体,为二元信号量

信号量计数值的含义

信号量的值通常表示:
当前系统中可用资源的数量
例如:

信号量值含义
N系统有 N 个可用资源
0没有可用资源
<0有进程正在等待资源

电影院例子对应操作系统中的资源管理模型:

现实世界操作系统概念
放映厅共享资源
座位资源单位
观众进程 / 线程
电影票信号量
购票获取信号量
入座访问资源
系统中存在一个计数器:
count = 座位总数

当观众购票时:

count--

当座位数为 0 时:

  • 说明资源已经被全部占用
  • 不再允许新的观众购票
    这一机制保证:
  • 资源不会被超额分配
  • 每个资源单位只会被一个执行流占用
    这正是信号量在操作系统中的典型应用场景。

信号量与共享资源划分

在并发系统中,共享资源的使用方式通常有两种:

整体使用(互斥访问)

资源只能被一个执行流独占访问,例如:

  • 管道(Pipe)
  • 打印机
  • 文件写操作
    这种情况下通常使用:
    二值信号量(Binary Semaphore)或互斥锁(Mutex)

分块使用(资源池)

共享资源可以划分为多个独立部分,例如:

  • 共享内存的多个数据块
  • 连接池
  • 线程池
  • 缓冲区
    此时可以使用:
    计数信号量(Counting Semaphore)
    表示系统中有 N 个可用资源单元,允许最多 N 个执行流同时访问。

信号量的基本操作

在这里插入图片描述

信号量用于解决 临界资源访问冲突问题。
其核心机制包括:

P 操作(Wait)

减少信号量值:

sem--

如果资源不足,则阻塞等待。

V 操作(Signal)

增加信号量值:

sem++

释放资源并唤醒等待进程。


P/V 操作的原子性

在操作系统中,信号量的两种基本操作为:

P 操作(Proberen / Wait)

作用:

  • 申请资源
  • 减少信号量计数值
    逻辑如下:
P(S):
    S = S - 1
    if S < 0:
        阻塞当前进程

V 操作(Verhogen / Signal)

作用:

  • 释放资源
  • 增加信号量计数值
    逻辑如下:
V(S):
    S = S + 1
    if S <= 0:
        唤醒等待进程

这两个操作必须由操作系统保证其原子性。


信号量的核心作用

信号量可以实现两种并发控制机制:

互斥(Mutual Exclusion)

如果信号量初始值为:

sem = 1

则表示系统中只有 1 个资源单位。
当某个进程执行:

P(sem)

后:

sem = 0

此时其他进程再执行 P 操作将会被阻塞
因此,同一时间只能有一个进程访问共享资源。
这种信号量称为:
二元信号量(Binary Semaphore)
其作用等价于互斥锁(Mutex)。


资源计数(Counting Semaphore)

如果信号量初始值为:

sem = N

表示系统中存在 N 个资源单位。
最多允许 N 个进程同时访问资源。
这种信号量称为:
计数信号量(Counting Semaphore)。


资源分配与资源定位问题

需要注意的是:
信号量只负责解决资源数量控制问题,即:

是否存在可用资源。

但信号量并不会决定:

具体使用哪一个资源。

例如:

  • 在共享内存中有多个数据块
  • 在缓冲区中有多个缓冲单元
    当进程获得信号量后,还需要通过程序逻辑确定:
  • 实际访问的资源位置
  • 避免多个进程访问同一资源单元
    这一部分通常通过:
  • 索引管理
  • 队列
  • 环形缓冲区
  • 其他同步机制
    由程序员在代码中实现。

System V 信号量编程接口

在 Linux System V IPC 机制中,信号量由内核统一管理,并通过一组系统调用进行操作。
相关头文件:

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

semget()

int semget(key_t key, int nsems, int semflg);

作用:

创建或获取一个信号量集合(Semaphore Set)。

参数说明:

参数含义
keyIPC 资源标识符
nsems信号量集合中信号量的数量
semflg创建标志(如 IPC_CREAT)
注意:
  • System V 信号量是以“集合”的形式存在
  • 一次可以创建多个信号量
    例如:
semget(key, 1, IPC_CREAT | 0666);

表示:

  • 创建一个包含 1 个信号量 的信号量集合。
    返回值:
  • 成功:返回 信号量集合 ID
  • 失败:返回 -1

semctl()

int semctl(int semid, int semnum, int cmd, ...);

作用:

对信号量集合执行控制操作。

参数说明:

参数含义
semid信号量集合 ID
semnum信号量下标
cmd控制命令
常见命令:
命令作用
IPC_RMID删除信号量
SETVAL设置初始值
GETVAL获取当前值
IPC_STAT获取信号量状态
示例:
删除信号量:
semctl(semid, 0, IPC_RMID);

信号量的数据结构

System V 信号量在内核中使用如下结构:

struct semid_ds {
    struct ipc_perm sem_perm;  // 权限信息
    time_t sem_otime;          // 最后操作时间
    time_t sem_ctime;          // 最后修改时间
    unsigned long sem_nsems;   // 信号量数量
};

其中:

struct ipc_perm

表示 IPC 权限信息,包括:

  • UID
  • GID
  • 访问权限
    这一结构与:
  • 共享内存
  • 消息队列
    的 IPC 权限结构类似。

信号量的 P/V 操作

System V 信号量通过 semop() 函数执行 P/V 操作。

semop()
int semop(int semid, struct sembuf *sops, size_t nsops);

作用:

对信号量集合执行操作(P/V)。


sembuf 结构
struct sembuf {
    unsigned short sem_num; // 信号量编号
    short sem_op;           // 操作类型
    short sem_flg;          // 操作标志
};

参数说明:

字段含义
sem_num信号量编号
sem_op操作类型
sem_flg操作标志

sem_op 的取值
sem_op含义
-1P 操作(申请资源)
+1V 操作(释放资源)
示例:
P 操作:
struct sembuf op;
op.sem_num = 0;
op.sem_op  = -1;
op.sem_flg = 0;

semop(semid, &op, 1);

V 操作:

op.sem_op = 1;

semop(semid, &op, 1);

批量信号量操作

System V 信号量支持:

一次对多个信号量执行操作

例如:

struct sembuf ops[10];
semop(semid, ops, 10);

这样可以在一次系统调用中对多个信号量执行 P/V 操作。
这种机制具有以下优势:

  • 减少系统调用次数
  • 保证多个操作的原子性
  • 提高并发控制效率

结构体特性

简介

在 System V IPC 机制 中,
不同类型的 IPC 资源:

  • 共享内存(Shared Memory)
  • 消息队列(Message Queue)
  • 信号量(Semaphore)
    虽然功能不同,但 其内核数据结构的第一个成员均为 struct ipc_perm。
    因此,Linux 内核可以利用 结构体首成员地址与结构体对象地址数值相同的特性,
    通过 struct ipc_perm* 指针统一管理所有 IPC 资源对象。

结构体对象的地址,和结构体第一个成员的地址,在数值上是相同的。

例如:

struct Obj
{
    int a;
    char b;
};

struct Obj myobj;

printf("%p\n", &myobj);
printf("%p\n", &myobj.a);

打印出来的地址数值是一样的。
更严谨的表达应该是:
在 C 语言中,结构体对象的地址与其第一个成员的地址在数值上相同,但类型不同。
或者更标准一点可以写成:

The address of a structure object is equal (in value) to the address of its first member, although the pointer types are different.

中文专业描述:
结构体对象的起始地址等于其第一个成员的起始地址(数值相同),但两者的指针类型不同。


类型区别

虽然地址值一样,但类型不同:

表达式类型
&myobjstruct Obj *
&myobj.aint *

所以:

&myobj      // struct Obj*
&myobj.a    // int*

地址值相同,但类型不同。


底层原因

因为 结构体在内存中是连续存储的。
结构体布局:

struct Obj
{
    int a;
    char b;
};

内存布局:

地址1000  -> a (4字节)
地址1004  -> b (1字节)

假设:

&myobj     = 1000
&myobj.a   = 1000
&myobj.b   = 1004

所以:

&myobj == &myobj.a   (数值相同)

Linux IPC 可以这样设计的原因

在这里插入图片描述

比如:

共享内存结构

struct shmid_ds {
    struct ipc_perm shm_perm;
    ...
};

消息队列

struct msqid_ds {
    struct ipc_perm msg_perm;
    ...
};

信号量

struct semid_ds {
    struct ipc_perm sem_perm;
    ...
};

它们 第一个成员都是

struct ipc_perm

所以:

&shmid_ds == &shmid_ds.shm_perm
&msqid_ds == &msqid_ds.msg_perm
&semid_ds == &semid_ds.sem_perm

于是 Linux 内核可以统一管理:

struct ipc_perm *perms[]

数组里放的其实是:

&shm.shm_perm
&msg.msg_perm
&sem.sem_perm

但通过地址偏移又可以还原回完整结构体。
这就是:
Linux 内核的面向对象设计技巧(。
这种设计在内核中通常叫:
结构体首成员复用(Struct embedding)
或者
伪继承(Pseudo inheritance in C)
Linux 内核大量使用这种方式。
例如:

  • 设备驱动
  • 文件系统
  • 网络协议栈
  • IPC
    为什么 System V IPC 三种资源可以统一管理?
    可以这样回答:

因为 System V IPC 的核心数据结构(shmid_ds、msqid_ds、semid_ds)的第一个成员都是 struct ipc_perm。
根据 C 语言内存布局规则,结构体对象地址与第一个成员地址数值相同,因此内核可以使用 ipc_perm* 统一管理不同类型的 IPC 对象,实现类似面向对象的“基类管理子类”的效果。


访问具体资源的方法

当需要访问某个 IPC 资源时,可以:
1️.先从数组中取出指针

perms[i]

2️.根据资源类型进行 类型转换
例如访问共享内存:

struct shmid_ds *shm =
    (struct shmid_ds *)perms[i];

由于:

&shm == &shm.shm_perm

因此强制类型转换后即可访问完整结构体。


类型识别机制

为了避免类型不确定的问题,可以额外维护 资源类型字段:

struct ipc_object
{
    int type;
    struct ipc_perm *perm;
};

其中:

  • type 表示资源类型
  • perm 指向 ipc_perm
    访问流程:
1 判断 type
2 根据 type 进行强转
3 获取具体资源结构

Linux 内核真实实现

在这里插入图片描述

Linux 内核中通过 ipc_ids 结构 来管理 IPC 资源:

struct ipc_ids {
    struct ipc_id *entries;
};

其中:

entries → IPC 对象数组

每个对象内部都包含:

ipc_perm

因此可以通过统一结构管理:

  • 共享内存
  • 消息队列
  • 信号量

本质设计思想

这种设计在系统编程中属于:

  1. 结构体嵌入(Struct Embedding)
    将 公共结构体作为第一个成员嵌入到不同结构体中。
  2. 面向对象模拟(C 语言)
    类似:
基类: ipc_perm
子类: shmid_ds / msqid_ds / semid_ds

实现 类似继承的效果。
3. 多态管理
通过:

ipc_perm *

统一管理不同资源类型

面向对象概念Linux IPC 实现
基类ipc_perm
子类shmid_ds / msqid_ds / semid_ds
基类指针ipc_perm*
子类对象具体 IPC 资源对象
这种模式等价于 基类指针指向子类对象。

与 C++ 多态的类比

在 C++ 中类似:

class IPCPerm {};  
class Shm : public IPCPerm {};  
class Msg : public IPCPerm {};  
class Sem : public IPCPerm {};

然后:
IPCPerm* objs[];
不同对象存入同一数组,通过基类指针统一管理。
Linux 内核采用 C 语言结构体嵌入 + 指针转换 实现了类似机制
如果在结构体中加入 函数指针:

struct ipc_perm {  
    void (*operate)(void *);  
};

不同 IPC 类型可以绑定不同实现:

shm_operate()  
msg_operate()  
sem_operate()

调用方式:
perm->operate(obj);
这样即可实现:
C 语言中的动态分发(类似虚函数机制)。
与 Linux “一切皆文件”的设计一致
Linux 内核大量使用这种设计,例如:

  • 文件系统
  • 设备驱动
  • 网络协议栈
  • IPC 系统
    典型实现方式:
    结构体嵌入 + 函数指针
    用于实现
  • 多态
  • 抽象接口
  • 统一资源管理

因此:Linux 如何统一管理 System V IPC?

Linux 内核在 System V IPC 的实现中,将 ipc_perm 作为共享内存、消息队列和信号量结构体的第一个成员。由于结构体首成员地址与结构体对象地址数值相同,因此内核可以通过 ipc_perm* 指针统一管理不同类型的 IPC 对象,并在访问时通过类型转换恢复具体结构体,从而实现统一资源管理。


Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐