前面我学习了管道(无名管道、有名管道)这种进程通信方式。今天继续学习两种非常重要的 IPC 机制:信号(Signal)和System V 共享内存。 信号是异步通知机制,用来给进程发送事件通知;共享内存是所有 IPC 里速度最快的通信方式,适合大批量数据传输。
一、信号 Signal
1.信号基础概念
信号是 Linux 系统的一种异步通信方式,内核用来通知进程发生了某个事件。进程收到信号之后,可以选择:
- 默认处理(SIG_DFL)
- 忽略信号(SIG_IGN)
- 自定义捕获信号,执行我们自己写的回调函数
重点考点:
SIGKILL(9)和SIGSTOP(19)无法被捕获、无法阻塞、无法忽略,只能执行系统默认行为!
2. 常见信号一览
| 信号名 | 编号 | 作用 |
|---|---|---|
| SIGALRM | 14 | alarm 定时器产生的闹钟信号 |
| SIGTERM | 15 | 终止信号,kill默认发送这个,可以被捕获 |
| SIGUSR1 | 10 | 用户自定义信号 1 |
| SIGUSR2 | 12 | 用户自定义信号 2 |
| SIGCHLD | 17 | 子进程退出 / 停止,通知父进程(回收子进程核心信号) |
| SIGCONT | 18 | 唤醒被暂停的进程 |
| SIGSTOP | 19 | 暂停进程,不可捕获 |
| SIGTSTP | 20 | 终端 Ctrl+Z,后台暂停 |
| SIGINT | 2 | Ctrl+C 中断信号 |
3. 信号相关 API
1. kill () —— 给指定进程发送信号
int kill(pid_t pid, int sig);- 功能:向 pid 对应的进程发送编号为 sig 的信号
- 参数:
pid:接收信号的进程 PIDsig:信号编号(kill -l可以查看所有信号)
- 返回值:成功返回 0,失败返回 - 1
2. signal () —— 信号捕获、注册信号处理函数
sighandler_t signal(int signum, sighandler_t handler);handler 支持三种写法:
SIG_DFL:使用系统默认处理方式SIG_IGN:忽略这个信号- 自定义函数指针:收到信号后执行自定义回调函数
函数原型原始写法:
void (*signal(int signum, void (*handler)(int)))(int);可读性差,所以系统封装了sighandler_t类型简化书写。
4. 信号简单示例(捕获 SIGINT)
#include <stdio.h> #include <sys/types.h> #include <signal.h> #include <stdlib.h> #include <unistd.h> void myhandle(int num) { printf("ctrl c 被按下"); } int main(int argc, char **argv) { signal(SIGINT, myhandle); while (1) { printf("i am working pid: %d\n", getpid()); sleep(1); } return 0; }注意:这个程序按 Ctrl+C 不会直接退出,因为我们捕获了 SIGINT;但是如果发送
kill -9 pid,程序会直接被杀掉,SIGKILL 不能捕获。
二、System V 共享内存(Share Memory)
1. 共享内存是什么?
System V 共享内存是 UNIX/Linux 提供的进程间通信方式,也是速度最快的 IPC。 原理:在内核开辟一块内存区域,多个进程把这块内存映射到自己的虚拟地址空间,多个进程直接读写同一块内存,不需要内核拷贝数据。
2. 整体流程
生成key ==> 申请共享内存对象 ==> 映射到本地地址 ==> 读写数据 ==> 撤销映射 ==> 删除共享内存对象
3. 和管道的对比
| 对比项 | 管道 | 共享内存 |
|---|---|---|
| 读写权限 | 单向管道半双工 | 双方都可以读写 |
| 阻塞特性 | 自带读写阻塞 | 没有读写阻塞,必须配合信号 / 信号量做同步 |
| 数据保留 | 读走数据就没了 | 不手动删除,数据会一直保留 |
| 空间大小 | 受限 | 自定义大小,适合大批量数据传输 |
| 同步机制 | 自带同步 | 无同步,需要额外 IPC 同步 |
重点:共享内存没有自带同步,一定要自己加同步保护(信号、信号量),否则会出现竞态条件!
4.全套 API 详解
1. ftok () —— 生成唯一 key
key_t ftok(const char *pathname, int proj_id);- 功能:根据文件路径 + 项目 id,生成唯一的 IPC 键值 key
- 参数:
- pathname:一个存在的文件路径(文件不要删除)
- proj_id:自定义整数,一般用单个 ASCII 字符
- 返回值:成功返回唯一 key,失败 - 1
2. shmget () —— 申请共享内存
int shmget(key_t key, size_t size, int shmflg);- 功能:用 key 向内核申请一块共享内存,返回 shmid
- 参数:
- key:ftok 生成的键值
- size:共享内存大小,一般 4K 对齐
- shmflg:权限标志,
IPC_CREAT创建;IPC_CREAT|IPC_EXCL检测是否已经存在
- 返回值:成功返回 shmid,失败 - 1
3. shmat () —— 映射共享内存到本地地址
void *shmat(int shmid, const void *shmaddr, int shmflg);- 功能:把内核的共享内存映射到当前进程的虚拟地址
- 参数:
- shmid:shmget 返回的 id
- shmaddr:填 NULL,让系统自动分配地址
- shmflg:0 = 读写;
SHM_RDONLY只读
- 返回值:成功返回映射后的地址,失败返回
(void *)-1
映射之后,就可以像普通内存一样读写,
memcpy、strcpy都可以用
4. shmdt () —— 撤销映射
int shmdt(const void *shmaddr);- 功能:断开当前进程和共享内存的映射关系
- 参数:shmat 返回的映射地址
- 返回值:成功 0,失败 - 1
⚠️ 撤销映射 ≠ 删除共享内存,只是当前进程不再访问这块内存!
5. shmctl () —— 控制共享内存(删除)
int shmctl(int shmid, int cmd, struct shmid_ds *buf);- 功能:修改属性、删除共享内存
- 参数:
- shmid:共享内存 id
cmd = IPC_RMID:删除共享内存对象- buf:填 NULL,只做删除
- 返回值:成功 0,失败 - 1
5. 配套命令行工具
#查看所有SystemV IPC:共享内存、消息队列、信号量 ipcs -a #删除指定共享内存 ipcrm -m shmid6. 共享内存读写示例
写端(shm_write.c)
#include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include <signal.h> #include <stdlib.h> #include <string.h> #include <unistd.h> int main(int argc, char **argv) { //申请key值 key_t key = ftok("./", 100); if (-1 == key) { perror("ftok"); return 1; } printf("key is 0x%x\n", key); //申请内存 int shmid = shmget(key, 4096, IPC_CREAT | 0666); if (-1 == shmid) { perror("shmget"); return 1; } //内存映射到用户空间 void *p = shmat(shmid, NULL, !SHM_RDONLY); if ((void*)-1 == p) { perror("shmat"); return 1; } //内存写入 char buf[50] = "hello"; memcpy(p, buf, strlen(buf)); //解除映射 shmdt(p); return 0; }读端(shm_read.c)
#include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include <signal.h> #include <stdlib.h> #include <string.h> #include <unistd.h> int main(int argc, char **argv) { //申请key值 key_t key = ftok("./", 100); if (-1 == key) { perror("ftok"); return 1; } printf("key is 0x%x\n", key); //申请内存 int shmid = shmget(key, 4096, IPC_CREAT | 0666); if (-1 == shmid) { perror("shmget"); return 1; } //内存映射到用户空间 void *p = shmat(shmid, NULL, !SHM_RDONLY); if ((void*)-1 == p) { perror("shmat"); return 1; } //内存读出 printf("mem:%s\n", (char*)p); //解除映射 shmdt(p); return 0; }三、总结
- 信号:异步事件通知,
SIGKILL、SIGSTOP不可捕获,kill发信号、signal注册捕获函数; - 共享内存:最快 IPC,流程:
ftok -> shmget -> shmat -> 读写 -> shmdt -> shmctl IPC_RMID; - 共享内存没有同步机制,工程里一般搭配信号或者信号量,解决多进程同时读写的冲突;
- 命令行
ipcs/ipcrm用来调试 SystemV 共享内存。