第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 捕获信号的步骤

  1. 进程遇到中断或异常时,进程切换到内核态处理
  2. 期间某个信号发给进程,放到挂起信号队列中
  3. 执行完中断或异常返回用户态前,检查是否存在挂起信号,如果有则开始处理信号
  4. 处理信号时,会创建用户态堆栈,并把信号处理程序放入到进程的程序计数器中(进程程序执行的开头),把信号返回代码sigreturn()放在用户态堆栈的结尾
  5. 内核态 -> 用户态,执行信号处理程序,结束时调用sigreturn()系统调用,再次进入内核态
  6. 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()

posted @ 2025-04-11 00:19  moonのsun  阅读(26)  评论(0)    收藏  举报