信号
- 软件层级上对中断机制的一种模拟,实现异步
- 信号四要素:man 7 signal
- 编号:不同发行版本可能信号不同
- 名称:在指定信号中推荐使用
- 事件:定义发生信号的动作,如
SIGSEGV由非法内存访问触发 - 默认处理动作:
- Term
- Ign:ignore,SIGCHLD就是默认忽略
- Core:终止进程,并产生core文件
- Stop:挂起进程,如SIGSTOP
- Cont:continue,如SIGCONT,继续执行挂起进程
- 特别:只能执行默认动作
- SIGKILL:
kill -9 进程号产生并发送,强制停止进程 - SIGSTOP:挂起进程,CTRL+z能发送给shell前台应用
进程不会去主动检测信号
SIGKILL和SIGSTOP直接在内核态终止/挂起进程
其他信号处理都是从内核态转向用户态的时候自动进行,所以进程如果一直在用户态运行,是感知不到信号到来
用户态转内核态:
系统调用
事件片用完
硬件中断:如外设完成I/O操作
异常:如缺页异常或除0错误
- SIGKILL是用来强制停止进程的,但是无法处理僵尸进程,因为僵尸进程已经“死”了,只是父进程还存在,僵尸进程的PCB留着等待父进程读取状态
- 生命周期
PCB中存在变量专门管理信号,生命周期本质上就是内核操纵这些变量(未决位图pending、阻塞位图block、处理函数指针列数组handler,信号队列(实时信号才会重复出现))
- 此处位图就是使用变量的位来表示信号,比如0位则是SIGHUP,若位为1则代表有此信号
- 实时信号(34-64)使用队列存储,避免信号丢失,而普通信号(1-31)仅记录最后一次触发
- 产生
- 硬件:如CTRL+C
- 软件:用户自定义或出现非法指令之类的
- 未决
- 信号产生到处理之前
- 信号产生时,内核通过目标进程的 PID 找到其 PCB,修改
pending位图 - 如果被阻塞,则会一直处于未决状态
SIGKILL和SIGSTOP不能被阻塞或忽略,内核会直接处理
- 递达
- 执行处理信号动作,默认或自定义
- 从内核态到用户态时,先检测是否有信号,若有,查不在阻塞位图的未决信号,然后调用处理函数
- 处理函数若是默认动作,则直接在内核态完成,若是用户自定义,则转到用户态执行,然后转回内核态,再跳转回进程代码
信号产生(软件)
使用API产生信号,并将信号注册到指定进程中,注册见信号未决部分
1.Kill
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
功能:向指定进程(或进程组)发送信号。
参数:
pid > 0:发送给特定进程。pid = 0:发送给当前进程组所有进程。pid = -1:发送给所有有权限的进程。
权限检查:非root用户无法向其他用户的进程发送信号(除非进程属于同一组或权限放宽)。
进程组广播:kill(0, SIGTERM)会向当前进程组所有成员发送SIGTERM
内核流程:调用 sys_kill() → kill_something_info() → 更新目标进程的 pending 位图
2.raise
#include <signal.h>
int raise(int sig);
功能:向当前进程发送信号(等价于 kill(getpid(), sig)
3.abort
#include <stdlib.h>
void abort(void);
功能:强制终止当前进程,产生 SIGABRT 信号并生成 core 文件
4.alarm闹钟
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
功能:设置单次定时器,到期发送 SIGALRM, 默认动作Term,返回剩余的时间
注意:每个进程只会有一个计时器,再次调用覆盖之前的计时器,alarm(0)取消当前计时,无论进程状态,都会一直计时
5.setitimer定时器
6.sigqueue
#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value);
功能:发送带附加数据的实时信号(34-64)
参数:
value:可携带整型或指针数据(通过siginfo_t结构传递)
内核流程:将信号加入目标进程的 sigqueue 队列,支持信号排队
信号未决
注册
注册就是在传递,“快递员”是内核,本质上是在操作未决位图和sigqueue,注意未决位图用户不能写,只能读取
注册过程(非可靠信号、普通信号、1-31号):
在未决位图中对应比特位置1
若sigqueue中没有此信号节点,则添加(保证只能有一个)
注册过程(可靠信号、实时信号、31之后的信号)
- 在未决位图中对应比特位置1
- 直接将此信号节点加入sigqueue(信号可以叠加)
阻塞
阻塞位图并不会干扰信号注册,但是会阻塞(延迟)信号递达 阻塞位图用户可操作
位图相关函数
给未决位图和阻塞位图提供操作,sigset_t则代表位图
1. 位图(信号集)操作
下面函数就是对set进行操作(除最后一个)
#include<signal.h>
int sigemptyset(sigset_t *set); //set集合置空
int sigfillset(sigset_t *set); //所有信号加入set集合
int sigaddset(sigset_t *set,int signo); //信号加入set集合
int sigdelset(sigset_t *set,int signo); //set集合移除信号
int sigismember(const sigset_t *set, int signo);//判断信号是否存在
sigprocmask阻塞位图操作
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
- how,该做什么样的操作
- SIG_BLOCK:向阻塞位图添加set信号集,block(new) = block(old) | set
- SIG_UNBLOCK:向阻塞位图删除set信号集,block(new)= block(old) & (~set)
- SIG_SETMASK:用set信号集替换阻塞位图,block(new)= set
- set:用来设置阻塞位图
- 对set的操作要使用上面的操作
- oldset:原来的阻塞位图
sigpending未决位图读取
int sigpending(sigset_t *set);
- 读取未决位图
- 参数set就是用来接受输出的
信号递达
此处就是开始处理信号,调用信号处理函数
信号捕获:如果处理函数是用户自定义函数,在递达时就调用这个函数,执行此函数期间,默认阻塞该信号
用户自定义处理函数
signal
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
功能 :注册信号处理函数
参数:
- signum:更改的信号值
- handler:函数指针,要更改的动作是什么
- SIG_IGN:忽略该型号
- SIG_DFL:执行系统默认
- 自定义函数名
注意:不推荐,不同Linux版本有不同的行为
sigaction
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
功能:更灵活的信号处理函数注册,支持实时信号和数据传递。
参数:
signum:信号编号。act:新处理方式(struct sigaction)。oldact:旧处理方式(可置NULL)。
结构体 sigaction:
C
struct sigaction {
void (*sa_handler)(int); // 处理函数
sigset_t sa_mask; // 处理函数执行期间阻塞的信号位图,临时屏蔽一些信号
int sa_flags; // 标志位
//保存的值0,在处理信号的时候,调用sa_handler保存的函数
//SA_SIGINFO,OS在处理信号的时候,调用的就是sa_sigaction函数指针当中
void (*sa_sigaction)(int, siginfo_t *, void *); // 带数据的处理函数
};
特殊信号汇总
- SIGKILL/SIGSTOP只能使用默认动作,不可捕获、忽略
- SIGCHLD:并不是只有终止时才产生
只要状态改变就产生该信号
- 子进程终止时
- 子进程收到SIGSTOP信号停止时
- 子进程处于停止态,收到SIGCONT后唤醒时
- 僵尸进程处理:
若父进程没有显式调用wait/waitpid且没有显式设置处理函数,则会导致僵尸进程
- 若父进程通过
signal(SIGCHLD, SIG_IGN)显式忽略该信号,内核会直接回收子进程资源,避免僵尸进程 - 自定义处理函数:
void handler(int sig) {
while (waitpid(-1, NULL, WNOHANG) > 0); // 循环回收所有终止的子进程
}
signal(SIGCHLD, handler);