Linux学习-通信(信号,共享内存)
信号(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>
- 原型:
typedef 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
并自定义处理:
#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
返回的指针操作内存,如:char *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
)。