引言
在Linux系统编程中,进程间通信(IPC)是多进程协作的核心技术。前面我们学习了管道,今天我们将深入讲解另外三种重要的IPC机制:共享内存 、信号量 和消息队列。
这三种机制各有特点:
-
共享内存:最高效的数据交换方式(无需数据拷贝)
-
信号量:进程同步机制(协调资源访问顺序)
-
消息队列:结构化数据传递(按类型接收消息)
它们与管道一起,构成了Linux IPC的核
第一部分:共享内存(Shared Memory)
一、共享内存的基本概念
共享内存是最快的IPC机制。它的原理是:将同一块物理内存映射到多个进程的逻辑地址空间中,进程可以直接读写这块内存,无需数据拷贝。
心体系。通过ipcs命令可以查看系统中的所有IPC资源。

二、共享内存的操作流程
| 步骤 | 操作 | 函数 | 说明 |
|---|---|---|---|
| 1 | 创建/获取 | shmget() |
创建共享内存或获取已存在的 |
| 2 | 映射 | shmat() |
将共享内存映射到进程地址空间 |
| 3 | 使用 | 指针读写 | 直接通过指针访问共享内存 |
| 4 | 断开映射 | shmdt() |
解除当前进程的映射(非删除) |
| 5 | 删除 | shmctl() |
彻底删除共享内存 |
三、共享内存接口详解
1. shmget------创建/获取共享内存
cpp
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
// 返回值:成功返回共享内存ID,失败返回-1
| 参数 | 说明 |
|---|---|
key |
唯一标识符,多个进程通过相同的key获取同一块共享内存 |
size |
共享内存大小(字节) |
shmflg |
标志位:IPC_CREAT(创建)、0600(权限) |
2. shmat------映射共享内存
cpp
void *shmat(int shmid, const void *shmaddr, int shmflg);
// 返回值:成功返回映射地址,失败返回(void*)-1
| 参数 | 说明 |
|---|---|
shmid |
shmget返回的共享内存ID |
shmaddr |
指定映射地址,通常设为NULL(让系统自动选择) |
shmflg |
标志位,通常设为0 |
3. shmdt------断开映射
cpp
int shmdt(const void *shmaddr);
// 返回值:成功返回0,失败返回-1
4. shmctl------控制共享内存
cpp
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
// 返回值:成功返回0,失败返回-1
| 参数 | 说明 |
|---|---|
cmd |
IPC_RMID(删除)、IPC_STAT(获取状态) |
buf |
状态结构体,删除时可设为NULL |
四、共享内存代码示例
写入端程序(write.c)
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define SHM_SIZE 128
#define KEY 1234
int main() {
// 1. 创建共享内存
int shmid = shmget(KEY, SHM_SIZE, IPC_CREAT | 0600);
if (shmid == -1) {
perror("shmget error");
exit(1);
}
// 2. 映射共享内存
char *shmaddr = (char *)shmat(shmid, NULL, 0);
if (shmaddr == (void *)-1) {
perror("shmat error");
exit(1);
}
// 3. 写入数据
printf("请输入消息: ");
fgets(shmaddr, SHM_SIZE, stdin);
shmaddr[strlen(shmaddr) - 1] = '\0'; // 去除换行符
printf("写入完成: %s\n", shmaddr);
// 4. 断开映射
shmdt(shmaddr);
return 0;
}
读取端程序(read.c)
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define SHM_SIZE 128
#define KEY 1234
int main() {
// 1. 获取共享内存(已存在)
int shmid = shmget(KEY, SHM_SIZE, 0600);
if (shmid == -1) {
perror("shmget error");
exit(1);
}
// 2. 映射共享内存
char *shmaddr = (char *)shmat(shmid, NULL, 0);
if (shmaddr == (void *)-1) {
perror("shmat error");
exit(1);
}
// 3. 读取数据
printf("读取到: %s\n", shmaddr);
// 4. 断开映射
shmdt(shmaddr);
return 0;
}
运行结果:
# 先运行写入端
./write
请输入消息: hello world
写入完成: hello world# 再运行读取端
./read
读取到: hello world
五、查看与管理共享内存
查看所有IPC资源
ipcs
查看共享内存
ipcs -m
输出示例:
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x000004d2 0 user 600 128 0
删除共享内存
ipcrm -m [shmid]
第二部分:共享内存与信号量的综合应用
一、为什么需要信号量?
共享内存本身不提供同步机制。当多个进程同时读写共享内存时,可能会出现竞态条件。例如:读取端在写入端还未写入数据时就读取,会读到无效数据。
解决方案: 使用信号量实现进程同步,控制读写顺序。
二、同步逻辑设计
| 信号量 | 初始值 | 控制目标 | 操作逻辑 |
|---|---|---|---|
SEM_WRITE |
1 | 写端写入权限 | 写入前P,写入后V |
SEM_READ |
0 | 读端读取权限 | 读取前P,读取后V |
三、完整代码实现
sem.h(头文件):
cpp
#ifndef SEM_H
#define SEM_H
int sem_init(int key, int nsems, int *init_vals);
void sem_p(int semid, int sem_num);
void sem_v(int semid, int sem_num);
void sem_destroy(int semid);
#endif
sem.c(实现文件):
cpp
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/sem.h>
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};
int sem_init(int key, int nsems, int *init_vals) {
int semid;
// 尝试创建信号量集
semid = semget(key, nsems, IPC_CREAT | IPC_EXCL | 0600);
if (semid == -1) {
// 已存在,直接获取
semid = semget(key, nsems, 0600);
} else {
// 创建成功,初始化信号量值
union semun a;
for (int i = 0; i < nsems; i++) {
a.val = init_vals[i];
semctl(semid, i, SETVAL, a);
}
}
return semid;
}
void sem_p(int semid, int sem_num) {
struct sembuf buf = {sem_num, -1, SEM_UNDO};
if (semop(semid, &buf, 1) == -1) {
perror("P操作失败");
}
}
void sem_v(int semid, int sem_num) {
struct sembuf buf = {sem_num, 1, SEM_UNDO};
if (semop(semid, &buf, 1) == -1) {
perror("V操作失败");
}
}
void sem_destroy(int semid) {
semctl(semid, 0, IPC_RMID);
}
写入端(write_sync.c):
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include "sem.h"
#define SHM_SIZE 128
#define SHM_KEY 1234
#define SEM_KEY 5678
#define SEM_NUMS 2
int main() {
// 初始化信号量:SEM_WRITE=1(写权限),SEM_READ=0(读权限)
int init_vals[2] = {1, 0};
int semid = sem_init(SEM_KEY, SEM_NUMS, init_vals);
// 创建共享内存
int shmid = shmget(SHM_KEY, SHM_SIZE, IPC_CREAT | 0600);
char *shmaddr = (char *)shmat(shmid, NULL, 0);
while (1) {
sem_p(semid, 0); // P(写权限) - 申请写入
printf("请输入消息: ");
fgets(shmaddr, SHM_SIZE, stdin);
shmaddr[strlen(shmaddr) - 1] = '\0';
if (strcmp(shmaddr, "end") == 0) {
sem_v(semid, 1); // 通知读端结束
break;
}
printf("写入: %s\n", shmaddr);
sem_v(semid, 1); // V(读权限) - 通知读取端
}
shmdt(shmaddr);
return 0;
}
读取端(read_sync.c):
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include "sem.h"
#define SHM_SIZE 128
#define SHM_KEY 1234
#define SEM_KEY 5678
#define SEM_NUMS 2
int main() {
// 获取信号量
int init_vals[2] = {0, 0};
int semid = sem_init(SEM_KEY, SEM_NUMS, init_vals);
// 获取共享内存
int shmid = shmget(SHM_KEY, SHM_SIZE, 0600);
char *shmaddr = (char *)shmat(shmid, NULL, 0);
while (1) {
sem_p(semid, 1); // P(读权限) - 等待写入
if (strcmp(shmaddr, "end") == 0) break;
printf("读取到: %s\n", shmaddr);
sem_v(semid, 0); // V(写权限) - 释放写入权限
}
// 断开映射并删除信号量
shmdt(shmaddr);
sem_destroy(semid);
return 0;
}
编译与运行:
# 编译
gcc sem.c write_sync.c -o write
gcc sem.c read_sync.c -o read# 先启动读取端(会阻塞等待数据)
./read &# 再启动写入端
./write
第三部分:消息队列(Message Queue)
一、消息队列的基本概念
消息队列是内核维护的消息链表 ,每个消息有独立的类型标识符,接收方可以按类型选择性读取。数据会一直驻留在队列中,直到被读取或系统重启。

二、消息队列接口
| 操作 | 函数 | 说明 |
|---|---|---|
| 创建/获取 | msgget() |
创建或获取消息队列 |
| 发送消息 | msgsnd() |
向队列发送消息 |
| 接收消息 | msgrcv() |
从队列接收消息(可按类型过滤) |
| 控制 | msgctl() |
删除消息队列 |
三、消息队列代码示例
消息结构体定义
cpp
// 消息结构体(首成员必须是long类型)
struct msgbuf {
long mtype; // 消息类型(必须 ≥ 1)
char mtext[128]; // 消息内容
};
发送端程序(send.c)
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define KEY 1234
struct msgbuf {
long mtype;
char mtext[128];
};
int main() {
// 1. 创建消息队列
int msgid = msgget(KEY, IPC_CREAT | 0600);
if (msgid == -1) {
perror("msgget error");
exit(1);
}
struct msgbuf msg;
// 2. 发送多条不同类型的消息
msg.mtype = 1;
strcpy(msg.mtext, "Hello, type 1");
msgsnd(msgid, &msg, sizeof(msg.mtext), 0);
msg.mtype = 2;
strcpy(msg.mtext, "Hello, type 2");
msgsnd(msgid, &msg, sizeof(msg.mtext), 0);
msg.mtype = 1;
strcpy(msg.mtext, "Another type 1");
msgsnd(msgid, &msg, sizeof(msg.mtext), 0);
printf("消息发送完成\n");
return 0;
}
接收端程序(recv.c)
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define KEY 1234
struct msgbuf {
long mtype;
char mtext[128];
};
int main() {
// 1. 获取消息队列
int msgid = msgget(KEY, 0600);
if (msgid == -1) {
perror("msgget error");
exit(1);
}
struct msgbuf msg;
// 2. 只接收类型为1的消息(类型2的消息会保留在队列中)
printf("=== 接收类型1的消息 ===\n");
for (int i = 0; i < 2; i++) {
msgrcv(msgid, &msg, sizeof(msg.mtext), 1, 0);
printf("收到: %s\n", msg.mtext);
}
// 3. 再接收类型2的消息
printf("=== 接收类型2的消息 ===\n");
msgrcv(msgid, &msg, sizeof(msg.mtext), 2, 0);
printf("收到: %s\n", msg.mtext);
// 4. 删除消息队列
msgctl(msgid, IPC_RMID, NULL);
return 0;
}
运行结果:
# 运行发送端
./send
消息发送完成# 运行接收端
=== 接收类型1的消息 ===
收到: Hello, type 1
收到: Another type 1
=== 接收类型2的消息 ===
收到: Hello, type 2
四、消息类型匹配规则
msgtyp值 |
行为 |
|---|---|
> 0 |
只接收该类型消息(如msgtyp=1只接收类型1) |
= 0 |
接收队列中第一个消息(FIFO,不区分类型) |
< 0 |
接收小于等于|msgtyp|的最小类型消息 |
五、查看与管理消息队列
查看消息队列
ipcs -q
输出示例:
------ Message Queues --------
key msqid owner perms used-bytes messages
0x000004d2 0 user 600 256 2
删除消息队列
ipcrm -q [msqid]
总结
一、共享内存核心要点
| 操作 | 函数 | 说明 |
|---|---|---|
| 创建 | shmget() |
指定key和大小 |
| 映射 | shmat() |
返回内存指针 |
| 断开 | shmdt() |
解除映射 |
| 删除 | shmctl(IPC_RMID) |
彻底删除 |
二、消息队列核心要点
| 操作 | 函数 | 说明 |
|---|---|---|
| 创建 | msgget() |
指定key |
| 发送 | msgsnd() |
消息必须有类型(≥1) |
| 接收 | msgrcv() |
可按类型过滤 |
| 删除 | msgctl(IPC_RMID) |
删除队列 |
三、共享内存 vs 消息队列
| 特性 | 共享内存 | 消息队列 |
|---|---|---|
| 数据存储 | 物理内存 | 内核链表 |
| 通信模式 | 双向 | 双向 |
| 同步机制 | 需要信号量配合 | 自带类型过滤 |
| 数据持久性 | 直到删除 | 直到删除 |
| 效率 | 最高(无数据拷贝) | 中等 |
| 适用场景 | 大数据量快速交换 | 结构化消息传递 |
四、完整的IPC体系
| IPC机制 | 主要功能 | 参考文章 |
|---|---|---|
| 管道/有名管道 | 简单数据流传输 | IPC专题(一) |
| 信号量 | 进程同步 | IPC专题(二) |
| 共享内存 | 高效数据交换 | 本文 |
| 消息队列 | 结构化消息传递 | 本文 |
共享内存和消息队列是Linux IPC的重要组成部分。共享内存解决了数据交换效率问题,消息队列解决了结构化消息传递问题。理解它们的工作原理和使用方法,是掌握多进程编程的关键。
学习建议:
-
理解共享内存的映射原理(物理内存映射到逻辑地址)
-
注意共享内存需要配合信号量实现同步
-
掌握消息队列的类型匹配规则
-
学会使用
ipcs查看和管理IPC资源