繁体   English   中英

在 Linux 中接收 SIGINT 和异常句柄

[英]Receiving SIGINT and exception Handles in Linux

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

程序执行并进入休眠状态。 然后我们键入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对应的pending位被清除了,那么sleep()函数如何返回呢? 我认为它应该仍然处于睡眠状态,因为理论上,sleep() 函数无法知道 SIGINT 的存在(已被清除)

Q3- 既然SIGINT对应的pending位被清除了,那么sleep()函数如何返回呢?

将内核中的sleep()函数想象成一个函数:

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

还想象有第二个函数(我将无特殊原因调用wake() ):

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

当然,如果定时器的 IRQ 处理程序注意到“定时器事件”已经过期,那么定时器的 IRQ 处理程序将调用wake()函数再次唤醒任务。

现在假设有第三个函数(我将调用它send_signal() ),它可能会被其他函数调用(例如被kill()调用)。 这个函数可能会为应该接收信号的任务设置一个“挂起信号”标志,然后检查接收任务处于什么状态; 如果接收任务处于“睡眠”状态,它会调用wake()函数将其唤醒(然后让sleep()函数的后半部分担心在调度程序感觉时将信号传送回用户空间比如稍后给任务 CPU 时间)。

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

  • Q2: pending bit只是用来保存要调用一个pending signal handler,所以不是这样的。 在进程的执行部分,unix 程序加载器安装一些基本代码以在从系统调用返回之前跳转到信号处理程序。 这是因为信号处理程序必须在用户模式下执行(而不是在内核模式下),所以一切都在系统调用终止时发生。 执行的信号处理程序是SIGINT ,但中断的代码是系统调用,在系统调用返回之前什么都不会发生(返回代码和errno变量已经固定)

  • Q3:嗯,你的推理是基于一个错误的前提,即中断挂起标志表示已经接收到中断 该位表示未处理的中断已被标记为在您解除阻塞后立即传递,并且这仅发生在另一个系统调用中(解除阻塞信号)。 一旦信号被解锁, sigsetmask(2)系统调用的返回代码将执行信号处理程序。 在这种情况下,信号将在计时器结束后立即传递给进程,系统调用将被中断,如果您没有为SIGALRM信号安装信号处理程序(但sleep(2)实现会这样做 - - 至少,旧的实现是这样的)程序将被中止。

笔记

当我说程序被内核中止时,但在这两种情况下,所涉及的信号( SIGINTSIGALRM )都不会转储核心文件。 该程序在未生成core的情况下中止。 这与发送SIGABRTabort()例程的行为不同,它使 de kernel 转储进程的核心文件。

你的理解是正确的。

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

顺序是:

  1. 某些阻塞内核调用中的进程块。
  2. 信号发送给它。
  3. 位已设置,进程已准备好运行。
  4. 进程在内核模式下恢复运行,检查挂起的非阻塞信号。
  5. 调用信号调度程序。
  6. 进程上下文被修改为在恢复时执行信号处理程序。
  7. 进程在用户空间恢复
  8. 信号处理程序运行。
  9. 信号处理程序返回。
  10. 内核在信号处理程序结束时被调用。
  11. 内核决定是恢复系统调用还是返回中断错误。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM