Linux 中信号是如何“传递”的?

Linux 中信号是如何“传递”的?

我对用于描述 Linux 信号传递的术语感到困惑。大多数文本都会说“信号被传递到进程”或“信号被传递到线程”。

据我了解,当内核调用该处理程序时,信号被“传递”到驻留在进程中的信号处理程序。该进程本身是异步运行的,这个“交付”过程类似于 CPU 调用中断处理程序。中断处理程序(信号处理程序)不是进程线程,也不是该进程下运行的任何线程,对吗?它是由内核启动的一个单独的线程。

因此,信号不会传递给线程或进程,而是传递给驻留在进程中的信号处理程序,并且不一定与任何特定线程关联。如果这不正确,请告诉我,例如,信号处理程序和 pthread 之间的关联,它证明了“信号传递到 pthread”这一术语的合理性。

答案1

信号处理程序只是给定进程地址空间中的一个函数。每当收到信号时就会执行此函数。它没有什么特别的(尽管有些操作不应该在信号处理程序中执行),并且它不驻留在特殊线程中。

虽然信号通常被描述为软件中断,但它们实际上并不是异步的。*当信号发送到进程时,内核将其添加到进程的挂起信号集中。它不会立即导致任何事情发生。该信号实际上只会下一个上下文中的任何内容都会切换回用户空间(无论是系统调用返回还是调度程序切换到该进程)。如果一个进程出于某种原因永远不会从内核切换到用户,则该信号将保留在待处理信号集中并且永远不会执行。

当进程建立信号处理程序时,它会向内核提供函数的地址。当进程要接收信号时,下一次从内核空间到用户空间的上下文切换不会恢复进程进入内核之前的执行上下文(通常,上下文在进入内核时保存,退出内核时恢复)。相反,它将在信号处理程序的位置“恢复”执行。当信号处理程序返回时,它执行调用的代码rt_sigreturn(),这会恢复真实的执行上下文,允许进程从中断处继续。

当一个进程有多个线程时(即给定线程组中有多个进程),信号被随机发送到线程组中的其中一个线程。这是因为线程通常共享内存和许多其他资源并运行相同的代码。

* 虽然从硬件的角度来看它们不是异步的,但就用户空间应用程序而言它们实际上是异步的。这就是为什么它们有时被称为软件中断。

† 当我提到上下文切换时,我指的是特权或者进程切换(即同一进程内内核和用户之间的简单模式转换以及进程或内核线程之间的“真实”上下文切换)。

答案2

中断处理程序(信号处理程序)不是进程线程,也不是该进程下运行的任何线程,对吗?

内核不会启动新线程来执行信号处理程序。它在现有线程上执行信号处理程序。我们可以说信号被传递到该特定线程。基本上,线程会放弃之前所做的任何事情,并执行信号处理程序。信号处理程序返回后,它会回到之前所做的事情。 (forest 的答案更详细地介绍了内核如何准确地安排此操作。)但是此操作与普通函数调用之间的一个关键区别在于,您无法控制它何时发生。例如,在 32 位访问是原子的而 64 位访问不是原子的平台上,int64_t当信号处理程序调用发生时,线程可能正在写入变量。因此,即使在单线程程序中,信号处理程序也必须防止“线程与自身竞争”,可以这么说。因此,您可以在信号处理程序中安全执行的操作集非常有限。

信号的发送者可以选择将其发送到特定线程,例如通过调用tgkill,或将整个进程作为目标。当信号发送到进程时,内核会选择进程中的线程之一将其传递给。看如果多线程 Linux 进程收到信号,会发生什么?

请注意,如果信号的行为是终止接收者(例如 SIGKILL,或SIGTERM使用默认处理程序),那么即使您将其定向到特定线程,整个进程也会终止。

答案3

我在 Stack Overflow 上有一个非常相似的问题的答案:https://stackoverflow.com/questions/6949025/how-are-asynchronous-signal-handlers-execulated-on-linux/6949377#6949377

信号已传递到一个线程通过挂起其执​​行,将其执行上下文(寄存器状态,信号掩码等)保存到ucontext_t推送到其堆栈的对象中,修改执行上下文以使程序计数器指向注册的信号处理程序,并使程序的其他方面context 符合进入函数和恢复执行的 ABI 要求。表示返回地址的上下文部分也已修改为指向将执行sigreturn系统调用的代码,从保存的ucontext_t对象恢复状态。

相关内容