简体   繁体   English

在 Linux 中接收 SIGINT 和异常句柄

[英]Receiving SIGINT and exception Handles in Linux

Let's say we have a program in C that uses the sleep() function假设我们有一个使用 sleep() 函数的 C 程序

The program executes and goes to sleep.程序执行并进入休眠状态。 Then we type Ctrl + C to send a SIGINT signal to the process.然后我们键入Ctrl + C向进程发送 SIGINT 信号。

We know that the default action upon receipt of a SIGINT is to terminate the process, we also know that the sleep() function resume the process whenever the sleeping process receives a signal.我们知道收到 SIGINT 后的默认操作是终止进程,我们也知道只要休眠进程收到信号,sleep() 函数就会恢复进程。

And my textbook says in order to allow sleep() function to return, we must install a SIGINT handler like this:我的教科书说为了允许 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)
}

Althouth the code seems to be straightforward, I still have questions if I want to dig deeper:虽然代码看起来很简单,但如果我想深入挖掘,我仍然有疑问:

Background: When the process is sleeping and we type Ctrl + C to send SIGINT背景:当进程正在休眠时,我们键入Ctrl + C发送 SIGINT

Q1-My understanding is, Kernel sends SIGINT to the process by updating the SIGINT's corresponging pending bit in the pend bit vector, is my understanding correct? Q1-我的理解是,内核通过更新挂起位向量中 SIGINT 的相应挂起位来向进程发送 SIGINT,我的理解是否正确?

Q2-The processor detects the existance of SIGINT, but since we overwrite the handler to make it return in stead of terminating the process, so our handler get executed, and then Kernel clears SIGINT's corresponging pending bit, is my understanding correct? Q2-处理器检测到 SIGINT 的存在,但是由于我们覆盖了处理程序使其返回而不是终止进程,所以我们的处理程序被执行,然后内核清除了 SIGINT 的相应挂起位,我的理解是否正确?

Q3- Since SIGINT's corresponging pending bit is cleared, then how can sleep() function gets return? Q3- 既然SIGINT对应的pending位被清除了,那么sleep()函数如何返回呢? I think it should be in sleep still because in theory, sleep() function has no way of knowing the existance of SIGINT(has been cleared)我认为它应该仍然处于睡眠状态,因为理论上,sleep() 函数无法知道 SIGINT 的存在(已被清除)

Q3- Since SIGINT's corresponging pending bit is cleared, then how can sleep() function gets return? Q3- 既然SIGINT对应的pending位被清除了,那么sleep()函数如何返回呢?

Imagine the sleep() function in the kernel as a function that:将内核中的sleep()函数想象成一个函数:

  • allocates and sets fields in some kind of "timer event" structure在某种“定时器事件”结构中分配和设置字段
  • adds the "timer event" to a list of timer events for the timer's IRQ handler to worry about later (when the expiry time has elapsed)将“计时器事件”添加到计时器事件列表中,供计时器的 IRQ 处理程序稍后处理(当到期时间已过时)
  • moves the task from the "RUNNING" state to the "SLEEPING" state (so the scheduler knows not to give the task CPU time), causing scheduler to do a task switch to some other task将任务从“RUNNING”状态移动到“SLEEPING”状态(因此调度程序知道不给任务 CPU 时间),导致调度程序执行任务切换到其他任务
  • configures return parameters for user-space (the amount of time remaining or 0 if the time expired)配置用户空间的返回参数(剩余时间量,如果时间过期则为0
  • figures out why the scheduler gave it CPU time again (did the time expire or was the sleep interrupted by a signal?)弄清楚为什么调度程序再次给它 CPU 时间(时间是否到期或睡眠是否被信号中断?)
  • potentially mangles the stack a bit (so that the kernel returns to the signal handler if the sleep() was interrupted by a signal instead of returning to the code that called sleep() )可能会稍微破坏堆栈(以便在sleep()被信号中断时内核返回到信号处理程序,而不是返回到调用sleep()的代码)
  • returns to user-space返回用户空间

Also imagine that there's a second function (that I'm going to call wake() for no particular reason) that:还想象有第二个函数(我将无特殊原因调用wake() ):

  • removes the "timer event" from the list of timer events (for the timer's IRQ handler to worry)从定时器事件列表中删除“定时器事件”(让定时器的 IRQ 处理程序担心)
  • moves the task from the "SLEEPING" state to the "READY TO RUN" state (so the scheduler knows that the task can be given CPU time again)将任务从“SLEEPING”状态移动到“READY TO RUN”状态(因此调度程序知道可以再次为任务提供 CPU 时间)

Naturally, if the timer's IRQ handler notices that the "timer event" has expired then the timer's IRQ handler would call the wake() function to wake the task up again.当然,如果定时器的 IRQ 处理程序注意到“定时器事件”已经过期,那么定时器的 IRQ 处理程序将调用wake()函数再次唤醒任务。

Now imagine there's a third function (that I'm going to call send_signal() ) which might be called by other functions (eg called by kill() ).现在假设有第三个函数(我将调用它send_signal() ),它可能会被其他函数调用(例如被kill()调用)。 This function might set a "pending signal" flag for the task that's supposed to receive the signal, then check what state the receiving task is in;这个函数可能会为应该接收信号的任务设置一个“挂起信号”标志,然后检查接收任务处于什么状态; and if the receiving task is in the "SLEEPING" state it calls the wake() function to wake it up (and then lets the latter part of the sleep() function worry about delivering the signal back to user-space whenever the scheduler feels like giving the task CPU time later).如果接收任务处于“睡眠”状态,它会调用wake()函数将其唤醒(然后让sleep()函数的后半部分担心在调度程序感觉时将信号传送回用户空间比如稍后给任务 CPU 时间)。

  • Q1: the kernel checks if the process has blocked the received signal, if so, it updates the pending signal bit (unreliable, on systems with relable signals, this should be a counter) in the process entry, for the signal handler to be called when signals are unblocked again (see below). Q1:内核检查进程是否阻塞了接收到的信号,如果是,则更新进程入口中的挂起信号位(不可靠,在有可靠信号的系统上,这应该是一个计数器),以便调用信号处理程序当信号再次畅通时(见下文)。 If not blocked, the system call prepares the return value and errno value and returns to user mode with a special code installed in the program's virtual stack that makes it to call the signal handler (already in user mode) before returning from the generic syscall code.如果没有被阻塞,系统调用准备返回值和errno值并返回到用户模式,并在程序的虚拟堆栈中安装一个特殊代码,使其在从通用syscall代码返回之前调用信号处理程序(已经在用户模式) . The return from the system call gives -1 to the caller code, and the errno variable is set to EINTR .系统调用的返回将-1赋给调用者代码,并将errno变量设置为EINTR This requires the process to have installed a signal handler, because by default the action is to abort the process, so it will not return from the system call it is waiting on.这需要进程安装信号处理程序,因为默认情况下操作是中止进程,因此它不会从正在等待的系统调用中返回。 Think that when one says the kernel the actual code executed is in the system call being awaken and notified of the special condition (a signal received) The interrupted call, detects that a signal handler is to be called, and prepares the user stack to jump to the proper place (the interrupt handler in user code) before returning from the syscall() wrapper.认为当一个人说内核时,实际执行的代码是在系统调用中被唤醒并通知特殊条件(收到信号)中断的调用,检测到要调用信号处理程序,并准备用户堆栈跳转在从syscall()包装器返回之前到适当的位置(用户代码中的中断处理程序)。

  • Q2: pending bit is only used to save that a pending signal handler is to be called, so this is not the case. Q2: pending bit只是用来保存要调用一个pending signal handler,所以不是这样的。 In the execution part of the process, the unix program loader installs some basic code to jump to the signal handler before returning from the system call.在进程的执行部分,unix 程序加载器安装一些基本代码以在从系统调用返回之前跳转到信号处理程序。 This is because the signal handler has to execute in user mode (not in kernel mode) so everything happens upon termination of system call.这是因为信号处理程序必须在用户模式下执行(而不是在内核模式下),所以一切都在系统调用终止时发生。 The signal handler executed is the SIGINT , but the code interrupted is a system call, and nothing happens until the system call returns (with the return code and the errno variable already fixed)执行的信号处理程序是SIGINT ,但中断的代码是系统调用,在系统调用返回之前什么都不会发生(返回代码和errno变量已经固定)

  • Q3: well, your reasoning was based on a wrong premise, that is, the interrupt pending flag is indicating that an interrupt has been received . Q3:嗯,你的推理是基于一个错误的前提,即中断挂起标志表示已经接收到中断 This bit only signals that an unprocessed interrupt has been marked for delivery as soon as you unblock it , and this only happens in another system call (to unblock a signal).该位表示未处理的中断已被标记为在您解除阻塞后立即传递,并且这仅发生在另一个系统调用中(解除阻塞信号)。 As soon as the signal is unblocked, the return code of the sigsetmask(2) syscall will execute the signal handler.一旦信号被解锁, sigsetmask(2)系统调用的返回代码将执行信号处理程序。 In this case, the signal will be delivered to the process as soon as the timer elapses, the system call will be interrupted and, if you have not installed a signal handler for the SIGALRM signal (but sleep(2) implementation does this ---at least, old implementations did) the program will be aborted.在这种情况下,信号将在计时器结束后立即传递给进程,系统调用将被中断,如果您没有为SIGALRM信号安装信号处理程序(但sleep(2)实现会这样做 - - 至少,旧的实现是这样的)程序将被中止。

NOTE笔记

When I say that the program is aborted by the kernel but in both cases, the signals involved ( SIGINT and SIGALRM ) don't make it to dump a core file.当我说程序被内核中止时,但在这两种情况下,所涉及的信号( SIGINTSIGALRM )都不会转储核心文件。 The program is aborted without generating core .该程序在未生成core的情况下中止。 This is different to the behaviour of the abort() routine, which sends a SIGABRT and so, it makes de kernel to dump a core file of the process.这与发送SIGABRTabort()例程的行为不同,它使 de kernel 转储进程的核心文件。

Your understanding is correct.你的理解是正确的。

Think about it.想想看。 The process is blocked in the kernel.该进程在内核中被阻塞。 We need to return to user space to run the handler.我们需要返回到用户空间来运行处理程序。 How can we do that without interrupting whatever blocking kernel call was running?我们如何才能在不中断任何正在运行的阻塞内核调用的情况下做到这一点? We only have one process/thread context to work with here.我们在这里只有一个进程/线程上下文可以使用。 The process can't be both sleeping and running a signal handler.该进程不能同时处于休眠状态和运行信号处理程序。

The sequence is:顺序是:

  1. Process blocks in some blocking kernel call.某些阻塞内核调用中的进程块。
  2. Signal is sent to it.信号发送给它。
  3. Bit is set, process is made ready-to-run.位已设置,进程已准备好运行。
  4. Process resumes running in kernel mode, checks for pending non-blocked signals.进程在内核模式下恢复运行,检查挂起的非阻塞信号。
  5. Signal dispatcher is invoked.调用信号调度程序。
  6. Process context is modified to execute signal handler upon resumption.进程上下文被修改为在恢复时执行信号处理程序。
  7. Process is resumed in user space进程在用户空间恢复
  8. Signal handler runs.信号处理程序运行。
  9. Signal handler returns.信号处理程序返回。
  10. Kernel is invoked by end of signal handler.内核在信号处理程序结束时被调用。
  11. Kernel makes decision whether to resume system call or return interruption error.内核决定是恢复系统调用还是返回中断错误。

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

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