学习资料https://pan.baidu.com/s/1NZpC4NyH0OHGOPPbO9J10g?pwd=h9m2 提取码: h9m2
一. 信号的基本概念
信号是 Linux 进程之间的一种通信机制,本质是软中断。
read/write等系统调用都可能被信号中断。
- 每个信号用
SIGxxx整型宏表示,定义在<signal.h>;kill -l看列表,man 7 signal看说明。 - 使用信号的目的:① 让进程知道发生了某事件;② 执行自己代码中的信号处理程序。
信号的 3 个来源:用户按键(Ctrl+C、Ctrl+\)/进程执行出错(除 0、非法访问)/其他进程用kill、sigqueue发送。
信号的 3 种状态:
| 状态 | 含义 |
|---|---|
| 递送 delivery | 进程对信号采取动作(执行处理函数或忽略) |
| 未决 pending | 产生到递送之间 |
| 阻塞 block | 进程指定阻塞该信号,保持未决直到解除阻塞 |
若处理方式是"忽略",信号永远不会递送/未决,直接丢弃。
二. 信号的分类
| 分类 | 可靠信号 | 不可靠信号 |
|---|---|---|
| 取值 | SIGRTMIN~SIGRTMAX | 小于SIGRTMIN |
| 行为 | 来不及处理时排队,不丢失 | 来不及处理则丢弃,只留一个 |
- Linux 共 64 种信号:前 32 种非实时(不支持排队 = 不可靠),后 32 种实时(支持排队 = 可靠)。
- 按来源还分:同步信号(进程自身操作产生,如被零除)/异步信号(进程外事件,如击键)。
常用信号(man 7 signal完整列表)
| 信号 | 说明 | 默认处理 |
|---|---|---|
SIGINT | Ctrl+C | 终止 |
SIGQUIT | Ctrl+\ | 终止 + core |
SIGKILL | 不可捕捉、不可忽略 | 终止 |
SIGSTOP | 不可捕捉、不可忽略 | 暂停 |
SIGSEGV | 内存非法访问 | 终止 + core |
SIGPIPE | 往已关闭的管道/socket 写 | 终止 |
SIGCHLD | 子进程停止或终止 | 忽略 |
SIGALRM | alarm/setitimer定时到期 | 终止 |
SIGUSR1/2 | 用户自定义 | 终止 |
SIGTSTP | Ctrl+Z挂起 | 暂停 |
SIGCONT | 让暂停的进程继续 | 继续/忽略 |
三. 信号的处理
3.1 三种处理方式
| 方式 | 说明 |
|---|---|
| 默认处理 | 多数导致进程终止 |
| 忽略 | signal(SIGINT, SIG_IGN);SIGKILL/SIGSTOP不能被忽略 |
| 捕捉处理 | 注册处理函数(最常用);SIGKILL/SIGSTOP不能被捕捉 |
3.2signal(老机制,有缺陷)
#include<signal.h>result=signal(intsignum,void(*action)(int));action:SIG_DFL(默认)/SIG_IGN(忽略)/函数名。- 成功返回之前的处理函数指针,失败返回 -1。
voidsigHandler(intsignum){printf("The sign no is:%d\n",signum);signal(SIGINT,sigHandler);// ⚠ 必须重新注册,否则恢复默认}intmain(){signal(SIGINT,sigHandler);signal(SIGQUIT,SIG_IGN);// 忽略 ^\ while (1) sleep(1);}⚠
signal的三个问题:① 处理函数执行中又来同类信号(多余丢失);② 又来异类信号(中断当前处理);③ 阻塞系统调用(如read)被信号打断后如何继续。→用sigaction替代。
3.3sigaction(推荐)
intsigaction(intsignum,conststructsigaction*act,structsigaction*oldact);structsigaction{void(*sa_handler)(int);// 老式处理函数void(*sa_sigaction)(int,siginfo_t*,void*);// 带附加信息(可获知信号产生原因)sigset_tsa_mask;// 处理期间额外阻塞的信号集intsa_flags;// 行为选项};sa_flags | 作用 |
|---|---|
SA_RESTART | 被信号打断的阻塞系统调用(read/write/ioctl)自动重启;不设则返回 -1,errno=EINTR |
SA_SIGINFO | 用sa_sigaction(3 参数)而不是sa_handler |
SA_NODEFER | 处理时不阻塞新信号(此时sa_mask失效) |
SA_RESETHAND | 处理完自动撤消注册(即signal的老行为) |
信号集操作函数
intsigemptyset(sigset_t*set);// 清空intsigfillset(sigset_t*set);// 填满所有信号intsigaddset(sigset_t*set,intsignum);// 添加intsigdelset(sigset_t*set,intsignum);// 移除intsigismember(constsigset_t*set,intsignum);// 是否包含sigprocmask:全程阻塞(与sa_mask的区别是它对所有场合生效)
intsigprocmask(inthow,constsigset_t*newset,sigset_t*oldset);how | 含义 |
|---|---|
SIG_BLOCK | 把newset加入原阻塞集 |
SIG_UNBLOCK | 从原阻塞集移除newset |
SIG_SETMASK | 把阻塞集设为newset |
示例:可靠信号排队、不可靠信号丢失
sem_t信号集设SIGINT(不可靠)+SIGRTMIN(可靠)→sigprocmask(SIG_BLOCK)屏蔽 → sigaction 注册(SA_SIGINFO)→sleep(60)期间用 kill-SIGRTMIN<pid>发多次 →sigprocmask(SIG_SETMASK,&oldmask)解除阻塞 结果:6个实时信号全部被处理,普通 SIGINT 只处理1次四. 信号发送函数
前提:发送进程的用户 ID 与接收进程相同,或发送者是超级用户。
| 函数 | 原型 | 作用 |
|---|---|---|
kill | int kill(pid_t pid, int sig); | 向指定进程发信号 |
raise | int raise(int sig); | 向自身发信号,等价kill(getpid(), sig) |
sigqueue | int sigqueue(pid_t pid, int sig, const union sigval value); | 发信号并可附带数据 |
unionsigval{intsival_int;void*sival_ptr;};
sigqueue的value只有在注册时sa_flags含SA_SIGINFO才会传给处理函数。
五. 可重入函数
- 可重入:函数可被多个任务并发调用而不产生数据错误。信号处理函数中必须只用可重入函数。
- 可重入函数中:不用静态变量、不用
malloc/free、不用标准 I/O 库,用全局变量要小心。 - 常见可重入函数:
readwriteopencloseforkexitkillraisesignalsigactionwaitwaitpidsleeplseekstatduppipesocketsendrecv等(完整列表见man 7 signal的 Async-signal-safe functions)。
六. 父子进程的信号处理
- 子进程继承父进程的信号处理方式,直到调用
exec。 - 子进程
exec后,父进程中设为"捕捉"的信号恢复为默认处理,其余不变。
act.sa_sigaction=intsig_handler;act.sa_flags=SA_SIGINFO;sigaction(SIGINT,&act,NULL);printf("The parent pid = %d\n",getpid());pid=fork();// if (pid == 0) execvp("ls", arg); // 打开后子进程 SIGINT 变回默认(终止)while(1);七. 信号处理的应用
7.1 被中断的系统调用
- 慢速设备(终端、文件、网络、磁盘)的系统调用会被信号中断;用
sigaction且设SA_RESTART时自动重启,否则返回 -1 +EINTR。
7.2 用SIGCHLD防止僵尸进程
子进程退出会向父进程发SIGCHLD(默认被忽略),在处理函数里wait/waitpid即可回收。
voidsigchld_handler(intsig){intstatus;waitpid(-1,&status,WNOHANG);if(WIFEXITED(status))printf("child process exit normally\n");elseif(WIFSTOPPED(status))printf("child process is stopped\n");}intmain(){pid_tpid;signal(SIGCHLD,sigchld_handler);pid=fork();if(pid==0)abort();// 子进程异常退出elseif(pid>0){sleep(2);printf("parent process\n");}elseexit(0);}八. 系统定时信号
8.1 睡眠函数
unsignedintsleep(unsignedintseconds);// 秒voidusleep(unsignedlongusec);// 微秒unsignedintalarm(unsignedintseconds);// seconds 秒后产生 SIGALRMintpause(void);// 挂起,直到收到一个信号才返回用alarm+pause实现sleep
voidalarm_handler(inta){printf("sleep is over");}intmysleep(intseconds){signal(SIGALRM,alarm_handler);alarm(seconds);pause();}⚠
sleep内部就是用alarm实现的,不要和alarm混用。
8.2 三个计时器
| 计时器 | 计算内容 | 信号 |
|---|---|---|
ITIMER_REAL真实 | 程序运行的实际时间 | SIGALRM |
ITIMER_VIRTUAL虚拟 | 用户态消耗的时间 | SIGVTALRM |
ITIMER_PROF实用 | 用户态 + 内核态 | SIGPROF |
例:用户态 10s + 内核态 8s + 睡眠 7s → 真实 25s、虚拟 10s、实用 18s。
intgetitimer(intwhich,structitimerval*value);intsetitimer(intwhich,conststructitimerval*value,structitimerval*ovalue);structitimerval{structtimevalit_interval;// 重复间隔structtimevalit_value;// 初始倒计时};structtimeval{longtv_sec;longtv_usec;};// 秒 / 微秒signal(SIGALRM,alarmsign_handler);structitimervaltval;timeChange(1,&tval.it_value);// 初始 1 毫秒timeChange(1000,&tval.it_interval);// 每 1000 毫秒触发一次setitimer(ITIMER_REAL,&tval,NULL);while(getchar()!='#');// 输入 # 退出