Linux 进程间通信(IPC):信号、消息队列、共享内存与信号量详解

1. 引言

在 Linux 多进程编程中,进程间通信(Inter-Process Communication,IPC)是核心话题。本文将系统梳理信号(Signal)以及 System-V IPC 的三大件:消息队列、共享内存、信号量,涵盖核心 API、参数含义、注意事项与生活化比喻,帮助读者快速建立知识框架。

2. 信号(Signal)

信号是 Linux 中一种异步通知机制,用于告知进程发生了某个事件。这里重点介绍两个最常用的信号发送函数:alarmkill

2.1 alarm:定时闹钟

c 复制代码
unsigned int alarm(unsigned int seconds);
  • 参数seconds 秒数
  • 返回值:上一次 alarm 剩余秒数;没有则返回 0
  • 作用 :隔 seconds 秒之后,给当前进程发送 SIGALRM 闹钟信号
  • 注意点
    • 调用 alarm 会覆盖上一次闹钟
    • seconds=0 代表取消闹钟
    • 闹钟信号只会发一次
  • 比喻:手机定时闹钟,到点给你发提醒,响一次就结束

2.2 kill:发送信号

c 复制代码
int kill(pid_t pid, int sig);
  • 参数
    • pid:目标进程 ID
    • sig:要发送的信号编号
  • 返回值:成功返回 0;失败返回 -1
  • 作用:向 pid 对应的进程发送指定信号
  • 注意点
    • 不是只能杀死进程,可以发任意信号
    • 权限不够会失败
    • pid>0 发给指定进程
  • 比喻:给别的程序发一条通知消息,可以是终止、暂停等不同指令

3. IPC 对象总览

System-V IPC 包含三类对象:消息队列、共享内存、信号量

每一个 key(由 ftok 生成),可以同时拥有:1 个消息队列 + 1 块共享内存 + 1 组信号量数组。

查看与删除命令:

bash 复制代码
ipcs -q/m/s      # 查看消息队列/共享内存/信号量
ipcrm -Q/M/S key # 通过key删除对象
ipcrm -q/m/s id  # 通过id号删除对象

3.1 ftok:生成 IPC 的 key 值

c 复制代码
key_t ftok(const char *pathname, int proj_id);
  • 参数
    • pathname:存在的文件路径
    • proj_id:项目 ID,只使用低 8 位
  • 返回值:成功返回 key_t 类型 key;失败返回 -1
  • 作用:根据文件路径 + 项目 ID,生成一个唯一 key,作为 IPC 对象名字
  • 注意点
    • path 文件不能删除
    • 不同文件 + 相同 proj_id 得到不同 key
  • 比喻:根据文件和编号生成一个房间门牌号,进程之间靠这个门牌号找到同一个 IPC 对象

4. 消息队列

消息队列是内核维护的一个消息链表,进程可以向其中投递或读取消息。

流程ftok 生成 key → msgget 创建消息队列 → msgsnd 发消息 → msgrcv 收消息 → msgctl 删除队列

4.1 msgget:创建/获取消息队列

c 复制代码
int msgget(key_t key, int msgflg);
  • 参数
    • key:ftok 得到的 key 值
    • msgflgIPC_CREAT 不存在就创建;IPC_EXCL 存在就报错;还要或上权限 0666
  • 返回值:成功返回消息队列 ID;失败 -1
  • 作用:根据 key 创建 / 打开消息队列,拿到操作 id
  • 注意点 :key 填 IPC_PRIVATE 会创建私有队列,只能父子进程使用
  • 比喻:拿到信箱的编号,后续收发信件都用这个编号

4.2 msgsnd:发送消息

c 复制代码
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
  • 参数
    • msqid:消息队列 id
    • msgp:自定义消息结构体首地址,第一个成员必须是 long mtype(消息类型)
    • msgsz:消息正文字节大小,不包含 mtype
    • msgflg:一般写 0,队列满时阻塞;IPC_NOWAIT 队列满直接返回不阻塞
  • 返回值:成功 0;失败 -1
  • 作用:往消息队列投递一条消息
  • 注意点:消息结构体格式强制要求;msgsz 只算数据部分,不要把 long 类型算进去
  • 比喻:把信件投入信箱

4.3 msgrcv:接收消息

c 复制代码
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
  • 参数
    • msqid:队列 id
    • msgp:存放消息的缓冲区
    • msgsz:缓冲区最大字节数(正文)
    • msgtyp:要接收哪一类消息;0 读取队列第一条;正数读取对应类型消息
    • msgflg:0 没有消息就阻塞等待;IPC_NOWAIT 无消息直接返回
  • 返回值:成功返回收到的正文字节数;失败 -1
  • 作用:从消息队列取出消息,可以按消息类型筛选
  • 注意点:可以定向接收某一类消息;消息读完队列就删掉这条消息
  • 比喻:从信箱取出信件,可以指定只拿某一类信件

4.4 msgctl:消息队列控制

c 复制代码
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
  • 参数
    • msqid:队列 id
    • cmd
      • IPC_STAT:获取队列状态信息
      • IPC_SET:修改权限
      • IPC_RMID删除消息队列
    • buf:状态结构体指针;删除时传 NULL
  • 返回值:成功 0;失败 -1
  • 作用:获取状态、修改权限、销毁消息队列
  • 注意点 :程序退出消息队列不会自动销毁,必须手动 IPC_RMID 或者 ipcrm 命令删除,否则会残留在系统
  • 比喻:查看信箱信息、修改信箱权限、拆掉整个信箱

练习:send.c 终端读字符串发送消息队列;recv.c 读取消息打印输出。

5. 共享内存

共享内存是 IPC 里面速度最高 的方式,直接映射物理内存到进程虚拟地址,不需要拷贝数据;但是没有自带同步机制,一般配合信号量做互斥。

操作步骤

  1. ftok 生成 key
  2. shmget 创建 / 打开共享内存,拿到 shmid
  3. shmat 将共享内存映射到当前进程虚拟地址空间,得到内存指针
  4. 直接读写这块内存
  5. shmdt 解除映射(只是断开本进程,不销毁共享内存)
  6. shmctl (IPC_RMID) 销毁共享内存对象

5.1 shmget:创建共享内存

c 复制代码
int shmget(key_t key, size_t size, int shmflg);
  • 参数
    • key:ftok 生成 key
    • size:共享内存字节大小,一般是 4096 整数倍(页大小)
    • shmflgIPC_CREAT|0666IPC_EXCL 存在报错
  • 返回值:成功返回共享内存 id;失败 -1
  • 作用:创建一块共享内存,申请物理内存页
  • 注意点:size 建议按页对齐;创建之后 size 不能修改
  • 比喻:申请一块公共黑板,多个进程都可以来这块黑板写字

5.2 shmat:映射共享内存

c 复制代码
void *shmat(int shmid, const void *shmaddr, int shmflg);
  • 参数
    • shmid:共享内存 id
    • shmaddr:填 NULL,让操作系统自动选虚拟地址(推荐)
    • shmflg:0 可读可写;SHM_RDONLY 只读映射
  • 返回值 :成功返回映射后的虚拟地址指针;失败 (void*)-1
  • 作用:把共享内存挂载到自己进程地址空间,拿到指针直接读写
  • 注意点:返回值是 void*,需要强转成自己需要的类型;出错返回 -1,判断不能直接 ==NULL
  • 比喻:把公共黑板搬到自己家窗户边,伸手就能读写黑板内容

5.3 shmdt:解除映射

c 复制代码
int shmdt(const void *shmaddr);
  • 参数shmaddr:shmat 返回的映射首地址
  • 返回值:成功 0;失败 -1
  • 作用:断开当前进程和共享内存的映射
  • 注意点 :shmdt 不会销毁共享内存对象,只是当前进程不能再访问;别的进程还能用
  • 比喻:把自家窗户关上,看不到黑板了,黑板本身还在

5.4 shmctl:共享内存控制

c 复制代码
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
  • 参数
    • shmid:共享内存 id
    • cmd
      • IPC_STAT 获取状态
      • IPC_SET 修改权限
      • IPC_RMID 标记销毁共享内存
    • buf:状态结构体,删除时传 NULL
  • 返回值:成功 0;失败 -1
  • 作用:获取状态、修改权限、销毁共享内存
  • 注意点IPC_RMID 只是标记删除;所有进程都 shmdt 解除映射之后,内存才真正释放
  • 比喻:标记黑板要拆掉;等所有人都离开黑板之后,黑板才真正拆除

6. 信号量(System-V 有名信号量数组)

信号量本质是计数器,用来做进程互斥、同步;不是用来传数据,是用来做锁。

流程ftok 生成 key → semget 创建信号量数组 → semctl 初始化信号量初值 → semop P/V 操作(申请 / 释放锁)→ semctl IPC_RMID 删除信号量数组

6.1 semget:创建信号量数组

c 复制代码
int semget(key_t key, int nsems, int semflg);
  • 参数
    • key:ftok 的 key
    • nsems:信号量数组里面有多少个信号量
    • semflgIPC_CREAT|0666IPC_EXCL 存在报错
  • 返回值:成功返回信号量集合 id;失败 -1
  • 作用:创建一组(多个)信号量
  • 注意点:nsems 是数组元素个数;比如只需要一把锁填 1
  • 比喻:申请一组门禁计数器,可以有多把锁

6.2 semctl:信号量控制

c 复制代码
int semctl(int semid, int semnum, int cmd, ...);
  • 参数
    • semid:信号量集合 id
    • semnum:操作数组里面第几个信号量,下标从 0 开始
    • cmd
      • SETVAL:设置信号量初值(初始化锁)
      • IPC_RMID:删除整个信号量数组,此时 semnum 忽略
  • 返回值:成功返回 0;失败 -1
  • 作用:初始化信号量、删除信号量集合
  • 注意点 :创建完信号量默认值是随机,必须手动 SETVAL 初始化,这是高频踩坑点
  • 比喻:给门禁锁设置初始可用数量;拆掉整套门禁

6.3 semop:P/V 操作

c 复制代码
int semop(int semid, struct sembuf *sops, size_t nsops);
  • 参数
    • semid:信号量集合 id
    • sops:struct sembuf 操作数组,一次可以做多组 P/V
    • nsops:sops 数组有多少个元素
  • 返回值:成功 0;失败 -1
  • 作用:P 操作(申请锁,sem_op=-1);V 操作(释放锁,sem_op=+1)
c 复制代码
struct sembuf
{
    unsigned short sem_num;  // 操作第几号信号量
    short sem_op;            // 操作类型:-1 为 P 操作,+1 为 V 操作
    short sem_flg;           // 一般填 0
};
  • 比喻:P 操作是进门拿钥匙(锁的数量减一),V 操作是出门还钥匙(锁的数量加一)

7. 总结

机制 核心 API 特点 典型用途
信号 alarm / kill 异步通知,非数据传输 定时、进程控制
消息队列 msgget / msgsnd / msgrcv / msgctl 按类型收发,有格式要求 结构化消息传递
共享内存 shmget / shmat / shmdt / shmctl 速度最快,无同步机制 大数据量高频读写
信号量 semget / semctl / semop 计数器,做锁 互斥与同步

理解这些 IPC 机制,是掌握 Linux 多进程协作的关键。建议结合代码练习,重点体会共享内存与信号量的配合使用。

相关推荐
智脑API33 分钟前
CCSwitch Claude Code 如何配置 MCP?服务器启动、工具权限与安全测试
运维·服务器·claude·codex·ccswitch
闲云野鹤在人间1 小时前
KVM虚拟化实战|CentOS‑Stream8 两种安装方式+模板制作+基础使用
linux·运维·服务器·centos
小张同学a.1 小时前
Docker 容器实战 2—— docker 仓库
linux·运维·服务器·docker·容器
Android系统攻城狮2 小时前
Linux PipeWire深度解析之pw_core_get_registry调用流程与实战(九十一)
linux·运维·服务器·音频进阶·pipewire音频实战进阶
其实防守也摸鱼2 小时前
智能体推荐:精选 AI Agent 工具与实战指南
运维·开发语言·人工智能·学习·web安全·自动化
小义_2 小时前
JDK 深度解析
java·linux·开发语言·python·面试
邪修king2 小时前
Re:Linux 系统编程篇 (十二):进程篇(一)基础与 fork 系统调用全解
linux·运维·服务器
牢姐与蒯2 小时前
Linux进程(五).之环境变量
linux·运维·服务器·ubuntu
ZGi.ai2 小时前
ZGI 文件产物:生成报告后怎样交付
大数据·运维·workflow·权限管理·zgi·智能体交付·文件产物