首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >异步信号处理程序是如何在Linux上执行的?

异步信号处理程序是如何在Linux上执行的?
EN

Stack Overflow用户
提问于 2011-08-05 05:53:14
回答 2查看 15.3K关注 0票数 59

我想确切地知道异步信号处理程序在Linux上是如何执行的。首先,我不清楚哪个线程执行信号处理程序。其次,我想知道让线程执行信号处理程序所遵循的步骤。

关于第一个问题,我读到了两种不同的、似乎相互矛盾的解释:

  1. The Linux Kernel,作者: Andries Brouwer,§5.2 "Receiving signals" states

当信号到达时,进程中断,保存当前寄存器,并调用信号处理程序。当信号处理程序返回时,中断的活动将继续。

  1. StackOverflow question "Dealing With Asynchronous Signals In Multi Threaded Program"让我认为Linux的行为是like SCO Unix's

当一个信号被传递到一个进程时,如果它被捕获了,它将由一个且只有一个满足以下条件的线程来处理:

代码语言:javascript
复制
1. A thread blocked in a [**sigwait**(2)](http://uw714doc.sco.com/en/man/html.2/sigwait.2.html) system call whose argument _does_ include the type of the caught signal.
2. A thread whose signal mask _does not_ include the type of the caught signal.

其他注意事项:

代码语言:javascript
复制
- A thread blocked in [**sigwait**(2)](http://uw714doc.sco.com/en/man/html.2/sigwait.2.html) is given preference over a thread not blocking the signal type.
- If more than one thread meets these requirements (perhaps two threads are calling [**sigwait**(2)](http://uw714doc.sco.com/en/man/html.2/sigwait.2.html)), then one of them will be chosen. This choice is not predictable by application programs.
- If no thread is eligible, the signal will remain ``pending'' at the process level until some thread becomes eligible.

此外,"The Linux Signals Handling Model" by Moshe Bar states“异步信号被传递到没有阻塞信号的第一线程”,我将其解释为信号被传递到其sigmask不包含该信号的某个线程。

哪一个是正确的?

在第二个问题上,所选线程的堆栈和寄存器内容发生了什么?假设运行信号处理程序的线程T正在执行do_stuff()函数。线程T的堆栈是否直接用于执行信号处理程序(例如,将信号trampoline的地址压入T的堆栈,并将控制流转到信号处理程序)?或者,是否使用单独的堆栈?它怎麽工作?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2018-01-11 11:28:41

源代码#1 (Andries Brouwer)对于单线程进程是正确的。源代码#2 (SCO Unix)对于Linux是错误的,因为Linux不喜欢sigwait(2)中的线程。Moshe关于第一个可用线程的说法是正确的。

哪个线程得到了信号?的手册页面是一个很好的参考资料。进程使用带有CLONE_THREAD的clone(2)创建多个线程。这些线程属于一个“线程组”,并共享一个进程ID。

可以使用kill(2)

信号发送到作为整体的线程组(即,TGID),或者使用tgkill(2)将其发送到特定线程(即,TID)。

信号处理和操作是进程范围的:如果一个未处理的信号被传递到一个线程,那么它将影响(终止、停止、继续、被忽略)线程组的所有成员。

每个线程都有自己的信号掩码,由sigprocmask(2)设置,但信号可以是挂起的:对于整个进程(即,可传递给线程组的任何成员),当使用kill(2)发送时;或者对于单个线程,当使用tgkill(2)发送时。对sigpending(2)的调用返回一个信号集,该信号集是整个进程的未决信号和调用线程的未决信号的并集。

如果kill(2)用于向线程组发送信号,并且该线程组已经为该信号安装了一个处理程序,那么该处理程序将在没有阻塞该信号的线程组的一个任意选择的成员中被调用。如果组中的多个线程正在等待使用sigwaitinfo(2)接受相同的信号,内核将任意选择这些线程中的一个线程来接收使用kill(2)发送的信号。

Linux不是SCO Unix,因为Linux可能会向任何线程发出信号,即使某些线程正在等待信号(使用sigwaitinfo、sigtimedwait或sigwait),而有些线程没有。sigwaitinfo(2)的手册警告说,

在正常使用中,调用程序通过先前对sigprocmask(2)的调用阻止set中的信号(因此,如果这些信号在连续调用sigwaitinfo()或sigtimedwait()之间变得挂起,则不会发生这些信号的默认处理),并且不为这些信号建立处理程序。在多线程程序中,信号应该在所有线程中被阻塞,以防止信号在调用sigwaitinfo()或sigtimedwait()的线程以外的线程中根据其默认配置进行处理。

为信号挑选线程的代码位于linux/kernel/signal.c中(链接指向GitHub的镜像)。请参阅函数wants_signal()和completes_signal()。代码为信号挑选第一个可用的线程。可用的线程是不阻塞信号并且队列中没有其他信号的线程。代码碰巧先检查主线程,然后按我不知道的顺序检查其他线程。如果没有线程可用,则该信号将被阻塞,直到某个线程解除阻塞该信号或清空其队列。

当线程获得信号时会发生什么?如果有一个信号处理程序,那么内核会使线程调用该处理程序。大多数处理程序都在线程的堆栈上运行。如果进程使用sigaltstack(2)提供堆栈,使用sigaction(2)和SA_ONSTACK设置处理程序,则处理程序可以在备用堆栈上运行。内核将一些东西推送到选定的堆栈上,并设置线程的一些寄存器。

要运行处理程序,线程必须在用户空间中运行。如果线程在内核中运行(可能是因为系统调用或页面错误),那么在进入用户空间之前,它不会运行处理程序。内核可以中断一些系统调用,因此线程现在运行处理程序,而无需等待系统调用完成。

信号处理程序是一个C函数,因此内核在调用C函数时遵循体系结构的约定。每种架构,如arm、i386、powerpc或sparc,都有自己的约定。对于powerpc,为了调用处理程序( signum ),内核将寄存器r3设置为signum。内核还将处理程序的返回地址设置为信号trampoline。按照惯例,返回地址进入堆栈或寄存器。

内核在每个进程中放置一个信号跳床。这个跳床调用sigreturn(2)来恢复线程。在内核中,sigreturn(2)从堆栈中读取一些信息(比如保存的寄存器)。在调用处理程序之前,内核已将此信息推送到堆栈上。如果存在中断的系统调用,内核可能会重新启动调用(仅当处理程序使用SA_RESTART时),或者使用EINTR使调用失败,或者返回一个简短的读或写。

票数 9
EN

Stack Overflow用户

发布于 2011-08-05 06:28:17

如果你考虑到Linux黑客倾向于混淆线程和进程之间的区别,这两种解释实际上并不矛盾,这主要是因为历史上的错误,试图假装线程可以被实现为共享内存的进程。:-)

如上所述,解释#2更加详细、完整和正确。

对于堆栈和寄存器内容,每个线程可以注册自己的备用信号处理堆栈,进程可以基于每个信号选择哪些信号将在备用信号处理堆栈上传递。中断的上下文(寄存器、信号掩码等)将与弹床返回地址一起保存在线程的(可能是备用的)堆栈上的ucontext_t结构中。如果愿意,安装了SA_SIGINFO标志的信号处理程序可以检查这个ucontext_t结构,但是它们能用它做的唯一可移植的事情就是检查(并可能修改)保存的信号掩码。(我不确定修改它是否符合标准,但它非常有用,因为它允许信号处理程序在返回时自动替换中断代码的信号掩码,例如,让信号被阻塞,这样它就不会再次发生。)

票数 26
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/6949025

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档