信号(Signal)
(一)本质与核心价值
信号是 Linux 系统中进程间异步通信的"软中断"机制,核心解决:
- 进程控制 :如终止、暂停、恢复进程(
SIGINT/SIGSTOP/SIGCONT)。 - 异常处理 :捕获非法操作(
SIGSEGV段错误、SIGPIPE管道破裂)。 - 异步通知 :子进程结束通知父进程(
SIGCHLD)、定时任务触发(SIGALRM)等。
特点:异步性 (接收方无法预测信号何时到达)、抢占性(打断当前代码流优先处理信号)。
(二)系统信号全量清单(kill -l 输出)
通过 kill -l 可查看系统支持的 64 个信号(不同系统可能有差异),按功能分类关键信号:
1. 常用基础信号(1~31 为非实时信号,32~64 为实时信号 SIGRTMIN~SIGRTMAX)
| 信号编号 | 信号名 | 触发场景/默认行为 | 关键特性 |
|---|---|---|---|
| 1 | SIGHUP |
终端断开、进程组领导权变化 | 常用于"重启配置"(如 nginx 重载) |
| 2 | SIGINT |
Ctrl + c 触发 |
中断进程,默认终止 |
| 3 | SIGQUIT |
Ctrl + \ 触发 |
终止进程并生成 core dump |
| 4 | SIGILL |
非法指令执行(如二进制文件格式错误) | 进程崩溃,默认终止 |
| 5 | SIGTRAP |
调试陷阱(如 gdb 断点触发) |
调试专用,默认终止 |
| 6 | SIGABRT |
进程主动调用 abort() |
强制终止并生成 core dump |
| 7 | SIGBUS |
总线错误(如内存对齐违规) | 进程崩溃,默认终止 |
| 8 | SIGFPE |
浮点运算错误(除零、溢出等) | 进程崩溃,默认终止 |
| 9 | SIGKILL |
kill -9 强制发送 |
强制终止,无法被捕获/忽略 |
| 10 | SIGUSR1 |
用户自定义信号 | 需编程捕获,实现自定义逻辑 |
| 11 | SIGSEGV |
非法内存访问(越界、空指针) | 进程崩溃(段错误),默认终止 |
| 12 | SIGUSR2 |
用户自定义信号 | 需编程捕获,实现自定义逻辑 |
| 13 | SIGPIPE |
管道写端关闭后仍写操作 | 进程崩溃(管道破裂),默认终止 |
| 14 | SIGALRM |
alarm() 定时触发或系统定时任务 |
定时通知,默认终止 |
| 15 | SIGTERM |
kill 默认信号(kill -15) |
优雅终止进程,可捕获处理 |
| 16 | SIGSTKFLT |
栈溢出(老式架构,现代系统少用) | 进程崩溃,默认终止 |
| 17 | SIGCHLD |
子进程退出、暂停或继续 | 通知父进程回收子进程 |
| 18 | SIGCONT |
显式发送或作业控制恢复 | 恢复暂停态进程执行 |
| 19 | SIGSTOP |
强制暂停(kill -19) |
暂停进程,无法被捕获/忽略 |
| 20 | SIGTSTP |
Ctrl + z 触发 |
暂停进程(终端停止信号) |
| 21 | SIGTTIN |
后台进程读终端 | 暂停进程,默认终止 |
| 22 | SIGTTOU |
后台进程写终端 | 暂停进程,默认终止 |
| 23 | SIGURG |
套接字紧急数据到达 | 通知进程处理紧急数据 |
| 24 | SIGXCPU |
进程 CPU 时间超过限制 | 进程崩溃,默认终止 |
| 25 | SIGXFSZ |
文件大小超过系统限制 | 进程崩溃,默认终止 |
| 26 | SIGVTALRM |
虚拟时钟定时(按进程 CPU 时间计时) | 定时通知,默认终止 |
| 27 | SIGPROF |
剖析时钟定时(含内核时间) | 性能分析工具常用 |
| 28 | SIGWINCH |
终端窗口大小变化 | 通知进程调整输出格式 |
| 29 | SIGIO |
异步 I/O 事件通知 | 高性能 I/O 模型(如 epoll) |
| 30 | SIGPWR |
电源状态变化(如电池低电量、断电) | 通知进程处理电源事件 |
| 31 | SIGSYS |
系统调用参数错误(如调用未实现的系统调用) | 进程崩溃,默认终止 |
2. 实时信号(SIGRTMIN ~ SIGRTMAX)
- 编号:
32~64(具体数量因系统而异,如SIGRTMIN可能是34,需kill -l确认)。 - 特点:高优先级 (比普通信号先处理)、可排队 (避免信号丢失)、用户可控(常用于实时系统、低延迟通知场景)。
(三)信号处理流程与编程
1. 信号处理方式
进程对信号有三种响应策略:
- 缺省(
SIG_DFL) :按系统默认行为处理(如SIGINT默认终止进程)。 - 忽略(
SIG_IGN) :不处理信号(如忽略SIGCHLD需自己回收子进程)。 - 捕获(自定义函数):注册回调函数,信号触发时执行自定义逻辑。
2. 核心函数:signal
-
头文件:
#include <signal.h> -
原型:
ctypedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler); -
作用:注册信号处理函数(绑定信号
signum与处理逻辑handler)。 -
参数:
signum:信号编号(如SIGINT)。handler:处理方式(SIG_IGN/SIG_DFL或自定义函数地址)。
-
返回值:
- 成功:返回之前的信号处理函数(首次注册返回
SIG_DFL)。 - 失败:返回
SIG_ERR(需检查errno)。
- 成功:返回之前的信号处理函数(首次注册返回
示例 :捕获 SIGINT 并自定义处理:
c
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
void handle_sigint(int signum) {
printf("捕获到信号:%d(SIGINT),进程继续运行...\n", signum);
}
int main() {
// 注册信号处理函数
if (signal(SIGINT, handle_sigint) == SIG_ERR) {
perror("signal");
return 1;
}
printf("进程启动,按 Ctrl + c 测试信号捕获...\n");
while (1) {
sleep(1); // 模拟业务逻辑
}
return 0;
}
(四)信号发送与控制
1. 命令行发送信号:kill 命令
- 语法:
kill -[信号编号/名称] 进程PID- 示例:
kill -9 1234:强制终止 PID 为1234的进程(发送SIGKILL)。kill -SIGUSR1 5678:给 PID5678发送SIGUSR1信号。
- 示例:
2. 编程发送信号函数
| 函数名 | 作用 | 关键参数 | 示例(简化) |
|---|---|---|---|
kill |
给指定进程发信号 | pid_t pid, int sig |
kill(1234, SIGINT); |
raise |
给自己所在进程发信号 | int sig |
raise(SIGALRM); |
alarm |
定时发送 SIGALRM 信号 |
unsigned int seconds |
alarm(5);(5 秒后发 SIGALRM) |
pause |
挂起进程,等待信号唤醒 | - | pause();(被信号打断后继续) |
共享内存(Shared Memory)
(一)核心价值
进程间通信(IPC)效率最高的方式,原理:
- 内核开辟一块共享内存区域,多个进程通过内存映射将其映射到自己的虚拟地址空间。
- 进程直接读写内存地址完成数据交换,跳过"内核-用户空间"的拷贝(如管道、消息队列需两次拷贝)。
适合场景:大数据量、低延迟的进程协作(如多进程并行计算、游戏服务器进程间同步)。
(二)操作流程与函数(6 步完整周期)
1. 创建 IPC Key:ftok
- 头文件:
#include <sys/types.h>、#include <sys/ipc.h> - 原型:
key_t ftok(const char *pathname, int proj_id); - 作用:生成唯一的 IPC 键值 ,用于标识共享内存(不同进程需用相同
pathname和proj_id生成相同 key)。 - 参数:
pathname:存在的文件路径(如"/tmp"),建议用固定文件避免冲突。proj_id:项目 ID(1~255 的整数,如'A'等价于65)。
- 返回值:
- 成功:返回
key_t类型的键值。 - 失败:返回
-1(检查errno,如文件不存在、权限不足)。
- 成功:返回
2. 创建共享内存:shmget
- 头文件:
#include <sys/types.h>、#include <sys/shm.h> - 原型:
int shmget(key_t key, size_t size, int shmflg); - 作用:向内核申请共享内存,返回 共享内存 ID(后续操作的标识)。
- 参数:
key:ftok生成的键值。size:共享内存大小(字节),自动对齐为系统页大小 (如4096字节的整数倍)。shmflg:权限标志(如IPC_CREAT | 0664,IPC_CREAT表示不存在则创建,0664是权限)。
- 返回值:
- 成功:返回共享内存 ID(非负整数)。
- 失败:返回
-1(检查errno,如权限不足、内存不足)。
3. 映射共享内存到用户空间:shmat
- 头文件:
#include <sys/types.h>、#include <sys/shm.h> - 原型:
void *shmat(int shmid, const void *shmaddr, int shmflg); - 作用:将内核共享内存映射到进程的虚拟地址空间,返回可直接读写的内存指针。
- 参数:
shmid:shmget返回的共享内存 ID。shmaddr:期望的映射地址(填NULL让系统自动分配)。shmflg:权限(SHM_RDONLY只读;0读写,需配合内存权限)。
- 返回值:
- 成功:返回映射后的内存首地址(
void *类型)。 - 失败:返回
(void *)-1(检查errno,如权限不足)。
- 成功:返回映射后的内存首地址(
4. 读写共享内存
-
直接通过
shmat返回的指针操作内存,如:cchar *shm_ptr = shmat(shmid, NULL, 0); if (shm_ptr != (void *)-1) { // 写数据 sprintf(shm_ptr, "Hello, Shared Memory!"); // 读数据 printf("共享内存内容:%s\n", shm_ptr); }
5. 解除映射:shmdt
- 头文件:
#include <sys/types.h>、#include <sys/shm.h> - 原型:
int shmdt(const void *shmaddr); - 作用:断开共享内存与进程虚拟地址空间的映射(不销毁共享内存,仅解除当前进程的关联)。
- 参数:
shmaddr:shmat返回的内存首地址。 - 返回值:
- 成功:返回
0。 - 失败:返回
-1(检查errno,如地址无效)。
- 成功:返回
6. 销毁共享内存:shmctl
- 头文件:
#include <sys/types.h>、#include <sys/shm.h> - 原型:
int shmctl(int shmid, int cmd, struct shmid_ds *buf); - 作用:控制共享内存(如销毁、查询状态)。
- 参数:
shmid:共享内存 ID。cmd:操作指令(IPC_RMID表示标记销毁,需所有进程解除映射后实际释放)。buf:填NULL(销毁时无需额外参数)。
- 返回值:
- 成功:返回
0。 - 失败:返回
-1(检查errno,如权限不足)。
- 成功:返回
(三)命令行工具
- 查看 IPC 资源 :
ipcs -a(查看共享内存、信号量、消息队列等)。 - 删除共享内存 :
ipcrm -m shmid:通过 ID 删除(如ipcrm -m 1234)。ipcrm -M shmkey:通过ftok生成的 key 删除(如ipcrm -M 0x12345678)。