Linux进程间通信(IPC):消息队列、共享内存与信号灯详解
本文基于Linux基础课程笔记,系统梳理进程间通信的三种核心机制:消息队列、共享内存和信号灯(有名信号量),涵盖概念、API函数、操作步骤及代码示例。
一、进程间通信概述
进程空间相互独立,无法像线程那样直接共享全局变量或堆区数据。因此,进程间通信(IPC,Inter-Process Communication)需要借助内核提供的第三方机制。Linux支持多种IPC方式:
- 管道(无名管道、有名管道)
- 信号(Signal)
- 消息队列(Message Queue)
- 共享内存(Shared Memory)
- 信号灯(Semaphore,即信号量数组)
本文重点讲解消息队列 、共享内存 和信号灯 ,它们统称为 IPC对象,具有以下共性:
- 每个IPC对象都有一个唯一的键值(key_t),用于标识。
- 通过
ipcs命令查看系统当前的IPC对象,通过ipcrm删除。 - 操作流程类似:创建键值 → 创建/获取对象 → 使用 → 控制(删除)。
二、IPC对象基础:ftok 与键值
所有IPC机制都需要一个key_t类型的键值,通常使用ftok函数生成。
ftok 函数
c
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
- 功能:根据一个存在的路径名和一个项目ID(8位整数)生成一个唯一的键值。
- 参数 :
pathname:必须是一个存在的目录或文件路径。proj_id:项目标识符,只使用低8位。
- 返回值 :成功返回
key_t键值,失败返回-1。
注意 :多个进程必须使用相同的
pathname和proj_id才能获得相同的键值,从而访问同一个IPC对象。
三、消息队列(Message Queue)
消息队列是内核中的一个消息链表,进程可以向其中发送或接收消息。消息具有类型,接收方可以根据类型选择接收特定消息。
1. 操作步骤
- 使用
ftok生成键值。 - 使用
msgget创建或获取消息队列ID。 - 发送消息使用
msgsnd,接收消息使用msgrcv。 - 使用
msgctl删除消息队列。
2. 核心函数
msgget - 创建/获取消息队列
c
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
- 参数 :
key:ftok生成的键值。msgflg:权限标志,通常为IPC_CREAT | 0664(不存在则创建,并设置权限)。
- 返回值:成功返回消息队列ID(非负整数),失败返回-1。
msgsnd - 发送消息
c
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
- 参数 :
msqid:消息队列ID。msgp:指向消息结构的指针。消息结构必须包含一个long mtype字段,后面是消息正文。msgsz:消息正文的大小(不包括mtype)。msgflg:通常为0(阻塞发送)。
- 返回值:成功返回0,失败返回-1。
消息结构定义示例:
c
struct msgbuf {
long mtype; // 消息类型,必须 > 0
char mtext[128]; // 消息正文
};
msgrcv - 接收消息
c
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
- 参数 :
msgtyp:指定接收的消息类型。> 0:接收该类型的第一个消息。== 0:接收队列中第一个消息。< 0:接收类型值小于等于msgtyp绝对值的消息(优先级)。
msgflg:通常为0(阻塞接收)。
- 返回值:成功返回实际接收的字节数,失败返回-1。
msgctl - 控制消息队列
c
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
- 常用cmd :
IPC_RMID:删除消息队列(buf传NULL)。IPC_STAT:获取状态信息。
- 返回值:成功返回0,失败返回-1。
3. 查看与删除命令
bash
ipcs -q # 查看所有消息队列
ipcrm -q <msqid> # 根据ID删除
ipcrm -Q <key> # 根据键值删除
四、共享内存(Shared Memory)
共享内存是最快的IPC方式,因为它允许两个进程直接映射同一块物理内存,数据拷贝次数最少。
1. 操作步骤
ftok生成键值。shmget创建共享内存。shmat将共享内存映射到进程的虚拟地址空间(获得访问指针)。- 使用指针进行读写(需配合信号量同步)。
shmdt解除映射(不删除内存)。shmctl删除共享内存。
2. 核心函数
shmget - 创建共享内存
c
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
- 参数 :
size:共享内存大小(字节),通常为页大小的整数倍。shmflg:IPC_CREAT | 0664。
- 返回值:成功返回共享内存ID,失败返回-1。
shmat - 映射到进程空间
c
void *shmat(int shmid, const void *shmaddr, int shmflg);
- 参数 :
shmaddr:通常传NULL,由系统自动选择映射地址。shmflg:通常为0(可读可写);也可用SHM_RDONLY只读。
- 返回值 :成功返回映射后的指针,失败返回
(void *)-1。
shmdt - 解除映射
c
int shmdt(const void *shmaddr);
- 成功返回0,失败返回-1。
shmctl - 控制共享内存
c
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
- 常用cmd :
IPC_RMID删除共享内存(标记为销毁,当所有进程解除映射后真正释放)。
3. 命令
bash
ipcs -m # 查看共享内存
ipcrm -m <shmid> # 删除
4. 注意事项
- 共享内存本身不提供同步机制,需要配合信号量 或互斥锁防止数据竞争。
- 使用完毕后务必
shmdt并shmctl删除,否则会造成内存泄漏。
五、信号灯(有名信号量 / 信号量数组)
在进程间通信中,信号量用于同步 和互斥 ,通常是一个信号量数组 ,每个元素是一个计数器。它不同于线程中的无名信号量(sem_init),而是基于IPC对象的有名信号量。
1. 操作步骤
ftok生成键值。semget创建信号量数组(指定信号量个数)。semctl初始化或删除信号量。semop执行PV操作(申请/释放)。semctl删除信号量数组。
2. 核心函数
semget - 创建/获取信号量数组
c
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
- 参数 :
nsems:信号量个数(通常为1)。semflg:IPC_CREAT | 0664。
- 返回值:成功返回信号量数组ID,失败返回-1。
semctl - 控制信号量
c
int semctl(int semid, int semnum, int cmd, ...);
-
常用cmd :
SETVAL:设置第semnum个信号量的值为val(需第四个参数union semun)。IPC_RMID:删除信号量数组。
-
union semun (需自己定义):
cunion semun { int val; struct semid_ds *buf; unsigned short *array; }; -
示例初始化 :
cunion semun su; su.val = 1; // 初始值为1,表示可用资源数为1 semctl(semid, 0, SETVAL, su);
semop - PV操作
c
int semop(int semid, struct sembuf *sops, size_t nsops);
-
struct sembuf结构:cstruct sembuf { unsigned short sem_num; // 信号量下标 short sem_op; // 操作数:+1 释放,-1 申请 short sem_flg; // 标志,通常为0(阻塞) }; -
示例 :
cstruct sembuf p = {0, -1, 0}; // 申请(P操作) struct sembuf v = {0, +1, 0}; // 释放(V操作) semop(semid, &p, 1); // 阻塞直到资源可用 // 临界区 semop(semid, &v, 1);
3. 命令
bash
ipcs -s # 查看信号量数组
ipcrm -s <semid> # 删除
六、总结对比
| 机制 | 特点 | 适用场景 | 同步需求 |
|---|---|---|---|
| 消息队列 | 有类型,可选择性接收,数据按消息组织 | 小数据量,需要类型过滤 | 无需同步 |
| 共享内存 | 速度最快,无拷贝,数据直接访问 | 大数据量,高频读写 | 必须同步 |
| 信号灯 | 提供计数器,实现同步/互斥 | 保护临界资源,控制访问顺序 | 本身就是同步工具 |
七、常见错误与调试
- 删除IPC对象失败 :使用
ipcrm时确保权限足够,或对象已存在。 - 共享内存映射失败 :检查
size是否过大或系统限制。 - 消息队列阻塞:检查消息类型是否匹配,或队列已满。
- 信号量死锁:确保PV操作成对出现,避免进程异常退出导致资源未释放。
附:查看系统IPC限制
bash
cat /proc/sys/kernel/msgmax # 消息最大字节数
cat /proc/sys/kernel/shmmax # 共享内存最大字节数
掌握这三大IPC机制,足以应对大多数进程间协作场景。实际编程中,推荐优先考虑共享内存+信号量组合,兼顾效率与同步安全性。
本文为学习笔记,如有错误欢迎指正。