在Linux中接收SIGINT和异常处理

时间:2018-12-05 06:11:13

标签: c linux process exception-handling kernel

假设我们在C语言中有一个使用sleep()函数的程序

程序执行并进入睡眠状态。然后我们输入ctrl-c向该进程发送一个SIGINT信号。

我们知道接收到SIGINT时的默认操作是终止该进程,我们也知道每当睡眠进程收到信号时,sleep()函数都会恢复该进程。

我的教科书上说,为了允许sleep()函数返回,我们必须安装一个SIGINT处理程序,如下所示:

void handler(int sig){
    return; /* Catch the signal and return */
}
...
int main(int argc, char **argv) {
   ...
   if (signal(SIGINT, handler) == SIG_ERR) /* Install SIGINT handler */
      unix_error("signal error\n");
   ...
   sleep(1000)
}

虽然代码看起来很简单,但是如果我想更深入地研究,我仍然有疑问:

背景:当进程处于休眠状态时,我们键入ctrl-c发送SIGINT

Q1-我的理解是,内核会通过更新暂挂位向量中SIGINT的对应挂起位来将SIGINT发送到进程,我的理解正确吗?

Q2-处理器检测到SIGINT的存在,但是由于我们覆盖了处理程序以使其返回而不是终止进程,因此我们的处理程序得以执行,然后内核清除SIGINT的对应挂起位,我的理解正确吗?

Q3-由于SIGINT的相应挂起位被清除,那么sleep()函数如何返回?我认为它应该仍然处于睡眠状态,因为从理论上讲,sleep()函数无法知道SIGINT的存在(已被清除)

3 个答案:

答案 0 :(得分:2)

  

Q3-由于SIGINT的相应挂起位已清除,那么sleep()函数如何返回?

将内核中的sleep()函数想象为以下函数:

  • 以某种“计时器事件”结构分配和设置字段
  • 将“定时器事件”添加到定时器事件列表中,以供定时器的IRQ处理程序稍后担心(当到期时间过去时)
  • 将任务从“正在运行”状态移动到“睡眠”状态(因此调度程序知道不给任务CPU时间),从而导致调度程序将任务切换到其他任务
  • 为用户空间(剩余时间或0,如果时间已过期)配置返回参数
  • 弄清楚了为什么调度程序再次给它分配了CPU时间(时间到期了还是睡眠被信号打断了?)
  • 可能会使堆栈有些混乱(如果sleep()被信号中断,则内核将返回信号处理程序,而不是返回到称为sleep()的代码)
  • 返回用户空间

还可以想象还有第二个函数(我将无故调用wake()):

  • 从计时器事件列表中删除“计时器事件”(供计时器的IRQ处理程序使用)
  • 将任务从“ SLEEPING”状态移动到“ READY TO RUN”状态(因此调度程序知道可以再次为该任务分配CPU时间)

自然地,如果计时器的IRQ处理程序注意到“计时器事件”已过期,则计时器的IRQ处理程序将调用wake()函数以再次唤醒任务。

现在想象有第三个函数(我将调用send_signal()),该函数可能会被其他函数(例如,kill()调用)调用。此功能可能会为应该接收信号的任务设置“待处理信号”标志,然后检查接收任务处于什么状态;并且如果接收任务处于“ SLEEPING”状态,它将调用wake()函数将其唤醒(然后让sleep()函数的后半部分担心将信号传递回用户空间每当调度程序想要给任务CPU稍后的时间时。

答案 1 :(得分:1)

  • Q1:内核检查进程是否阻塞了接收到的信号,如果是,它将更新挂起的信号位(不可靠,在具有可靠信号的系统上,这应该是一个计数器)在流程条目中,用于在再次解除阻止信号时调用信号处理程序(请参见下文)。如果未阻止,则系统调用将准备返回值和errno值,并返回到用户模式,并在程序的虚拟堆栈中安装特殊代码,该特殊代码使该程序在从返回之前调用信号处理程序(已在用户模式下)。通用syscall代码。系统调用的返回将-1赋予调用者代码,并且errno变量被设置为EINTR。这要求该进程已安装信号处理程序,因为默认情况下该操作是中止该进程,因此它不会从正在等待的系统调用中返回。想一想,当说出“ 内核”时,实际执行的代码在系统调用中被唤醒,并被告知特殊情况(收到的信号)。中断的调用检测到要调用信号处理程序,并准备从syscall()包装返回之前,使用户堆栈跳转到适当的位置(用户代码中的中断处理程序)。

  • Q2:未决位仅用于保存要调用未决信号处理程序的 ,因此不是这种情况。在该过程的执行部分中,unix程序加载器在从系统调用返回之前,安装一些基本代码以跳转到信号处理程序。这是因为信号处理程序必须在用户模式下执行(而不是在内核模式下执行),所以一切都会在系统调用终止时发生。执行的信号处理程序是SIGINT,但是被中断的代码是系统调用,直到系统调用返回(返回代码和errno变量已固定)之后,什么都不会发生。

    < / li>
  • 问题3:好吧,您的推理基于错误的前提,即中断待处理标志表示已接收到中断。此位表示已取消阻塞后立即将未处理的中断标记为要发送,并且仅在另一个系统调用中发生(取消阻塞信号)。一旦信号被解除阻塞,sigsetmask(2) syscall的返回代码将执行信号处理程序。在这种情况下,信号将在计时器经过后立即传递到进程,系统调用将被中断,并且如果您尚未安装SIGALRM信号的信号处理程序(但sleep(2)实现会执行此操作-至少,旧的实现会执行此操作)。该程序将被中止。

注意

当我说程序被内核中止时,但是在两种情况下,所涉及的信号(SIGINTSIGALRM)都不会使其转储核心文件。该程序被中止而没有生成core这与abort()例程的行为不同,该例程发送一个SIGABRT,因此,它使de kernel可以转储进程的核心文件。

答案 2 :(得分:0)

您的理解是正确的。

考虑一下。该进程在内核中被阻止。我们需要返回用户空间来运行处理程序。在不中断正在运行的阻塞内核调用的情况下,我们如何做到这一点?在这里,我们只有一个进程/线程上下文可以使用。该过程不能同时处于睡眠状态和正在运行信号处理程序。

顺序为:

  1. 处理某些阻塞的内核调用中的阻塞。
  2. 已发送信号给它。
  3. 已设置位,过程已准备就绪,可以运行。
  4. 进程继续以内核模式运行,检查未决的未阻塞信号。
  5. 信号调度程序被调用。
  6. 修改流程上下文以在恢复后执行信号处理程序。
  7. 进程在用户空间中恢复
  8. 信号处理程序运行。
  9. 信号处理程序返回。
  10. 内核由信号处理程序的末尾调用。
  11. 内核决定是恢复系统调用还是返回中断错误。