第11章 信号
1 信号是什么
信号是Unix系统中进程间通信的工具,每个信号对应一个ID;
进程收到某个信号后,会调用设定好的回调函数,以实现进程间的通知和响应。
2 信号的特点
2.1 常规信号和实时信号
- Linux中有31个常规信号,以及一些实时信号
- 信号的一个重要特点是可以被发送给状态未知的进程 —— 如果进程没有运行,则信号由内核保存,直到进程恢复执行
- 同种类型的常规信号不排队:如果一个常规信号在发送前产生多次,只有一个发送到接收进程
- 实时信号需要排队发送,这样相同类型的信号才可以被多次发送 —— Linux内核并不使用实时信号,可以不用关注
2.2 挂起信号
- 已经产生还没有传递到接收进程的信号称为挂起信号
- 一个进程给定类型的挂起信号只有一个
- 一般情况下是由于进程在休眠所以发给进程的信号被挂起
- 每当内核处理完中断、异常或系统调用,CPU执行恢复成进程的用户态时,就会检查是否存在挂起信号
2.3 其他
- 当进程执行信号处理程序时,自动屏蔽同类型信号直到处理结束,因此信号处理程序不必是可重入的
- 不能给进程0(swapper)发信号
3 常见的信号
SIGABRT:异常结束
SIGKILL:强迫进程终止
SIGSEGV:当进程引用一个无效地址时,内核会给该进程发送SIGSEGV
SIGCHLD:当子进程终止时,子进程会给父进程发送该信号
SIGSYS:异常的系统调用
需要明确信号的意义,以及发送方和接收方。
4 与信号有关的数据结构
① struct signal_struct *signal
指向进程描述符中的信号描述符的指针,里面存放进程组共享的挂起信号队列shared_pending;
信号描述符被属于同一进程组的所有进程共享,也就是被调用clone()系统调用创建的所有进程共享。
② struct sighand_struct *sighand
指向进程描述符中的信号处理程序描述符的指针;
信号处理程序是用户态进程定义的函数,运行在用户态;而handle_signal()运行在内核态,因此执行信号处理程序时,会有一次内核态到用户态的切换。
③ struct sigpending pending
当前进程存放私有挂起信号的数据结构
5 捕获信号
5.1 进程的用户态和内核态
一般的进程都是在用户态创建和执行;
如果发生中断或者异常,CPU会切换到内核态,内核态并不是进程上下文,因为内核态并不是一种进程;
但是,如果CPU上正在执行的某进程(用户态)触发了中断、异常或系统调用而导致CPU处于内核态,
我们也会称为进程从用户态切换到了内核态,因为此时并没有进程的调度,只有用户态和内核态的切换!
(第7章 进程调度 - 内核态用户态的切换与进程调度的关系)
5.2 信号处理程序
信号处理程序是用户态进程定义的函数,存放在用户态代码中;
调用逻辑: 内核态do_signal() -> 内核态handle_signal() -> 用户态 信号处理程序;
进程从内核态恢复到用户态正常执行之前,需要先检查信号并执行信号处理函数,因此多切入切出一次用户态。
5.3 捕获信号的步骤
- 进程遇到中断或异常时,进程切换到内核态处理
- 期间某个信号发给进程,放到挂起信号队列中
- 执行完中断或异常返回用户态前,检查是否存在挂起信号,如果有则开始处理信号
- 处理信号时,会创建用户态堆栈,并把信号处理程序放入到进程的程序计数器中(进程程序执行的开头),把信号返回代码sigreturn()放在用户态堆栈的结尾
- 内核态 -> 用户态,执行信号处理程序,结束时调用sigreturn()系统调用,再次进入内核态
- sigreturn()系统调用把用户态堆栈恢复到原来的状态,系统调用结束,用户进程正常执行 —— 内核态->用户态
整个过程多进一次用户态执行信号处理程序,执行前通过set_frame()建立用户态堆栈,并修改进程程序计数器,
执行结束后通过sigreturn()恢复用户态之前的堆栈,进程得以正常执行。
6 信号引起系统调用执行中断
系统调用时,进程从用户态切换到内核态,此时没有发生进程调度,进程的状态仍旧是TASK_RUNNING!
如果系统调用时内核不能满足资源需求,可能会休眠等待,此时进程的状态就是TASK_INTERRUPTIBLE或TASK_UNINTERRUPTIBLE
—— 从这个角度来讲系统调用导致的内核态就是进程的内核态(如果是由于中断陷入内核态则不会休眠和切换进程)!
进程发出系统调用发现缺少某资源 -> 内核把进程置为TASK_INTERRUPTIBLE或TASK_UNINTERRUPTIBLE(休眠态);
外部信号发给该进程 ->内核不完成系统调用就把进程置为 TASK_RUNNING;
进程切回用户态(用户态进程得以继续执行),先响应挂起信号,然后再收到系统调用未完成的返回值:EINTR;
进程根据返回值自行决定是否再次尝试该系统调用。
7 与信号有关的系统调用
注意,本章讲的是信号而不是系统调用……
kill(pid, sig):两个参数pid和sig,表示向进程sig发送信号sig,而不是杀死某个进程
sigaction(sig, act, oact):为当前进程的某个信号sig指定信号处理程序act;oact忽略
sigpending():检查挂起的阻塞信号
sigsuspend():挂起进程,也就是把进程置为TASK_INTERRUPTIBLE状态,然后schedule()选择另一个进程运行。
参考: C 库函数 - kill()
本文来自博客园,作者:moonのsun,转载请注明原文链接:https://www.cnblogs.com/moon-sun-blog/p/18815682

浙公网安备 33010602011771号