我想确切地了解异步信号处理程序在Linux上的执行情况.首先,我不清楚是否有which个线程执行信号处理程序.其次,我想知道让线程执行信号处理程序所遵循的步骤.

关于第一件事,我读了两种不同的、似乎相互矛盾的解释:

  1. "Linux内核"(The Linux Kernel),安德烈·布劳威尔(Andries Brouwer),§5.2 "Receiving signals" states:

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

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

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

    1. sigwait(2)系统调用中阻塞的线程,其参数does包括捕获信号的类型.

    2. 一种线程,其信号掩码does not包括捕获信号的类型.

    其他注意事项:

    • sigwait(2)中阻塞的线程优先于未阻塞该信号类型的线程.
    • 如果超过一个线程满足这些要求(可能有两个线程调用sigwait(2)),那么将 Select 其中一个线程.应用程序无法预测这种 Select .
    • 如果没有符合条件的线程,信号将在进程级别保持"挂起",直到某个线程符合条件.

    此外,"The Linux Signals Handling Model" by Moshe Bar states个"异步信号被传送到第一个发现没有阻塞信号的线程.",我的理解是,信号被传送到某个线程,其sigmask not包含该信号.

哪一个是正确的?

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

推荐答案

Source#1(Andries Brouwer)适用于单线程进程.Source#2(SCO Unix)对于Linux来说是错误的,因为Linux不喜欢sigwait(2)中的线程.关于第一个可用线程,Moshe Bar是正确的.

Which thread gets the signal? Linux的手册页是一个很好的参考.一个进程使用clone(2)和克隆线程来创建多个线程.这些线程属于一个"线程组",共享一个进程ID,

可以将信号作为整体发送到线程组(即, TGID)使用kill(2),或使用特定线程(即,TID) tgkill(2).

信号处理和行动是全过程的:如果

每个线程具有其自己的信号掩码,如由sigprocmask(2)设置的, 但信号也可能是悬而未决的:在整个过程中 (即,可交付给线程组的任何成员),当 WITH KILL(2)发送;或单个线程在发送WITH WITH KILL(2)时 tgkill(2).对sigpending(2)的调用返回信号SET,该信号SET 是整个过程中待处理的信号的联合,并且 正在等待调用线程的信号.

如果kill(2)用于向线程组发送信号,则

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

在正常使用中,调用程序通过

为信号 Select 线程的代码存在于linux/kernel/signal.c中(链接指向GitHub的镜像).请参见函数想要_signal()和完成_signal().代码为信号 Select 第一个可用线程.可用线程是指不阻塞信号且其队列中没有其他信号的线程.代码首先判断主线程,然后以我不知道的顺序判断其他线程.如果没有可用的线程,那么信号将被卡住,直到某个线程解除信号的阻塞或清空其队列.

What happens when a thread gets the signal?如果有一个信号处理程序,那么内核会让线程调用该处理程序.大多数处理程序在线程堆栈上运行.如果进程使用sigaltstack(2)来提供堆栈,使用sigaction(2)和sau ONSTACK来设置处理程序,则处理程序可以在备用堆栈上运行.内核将一些内容推送到所选的堆栈上,并设置线程的一些寄存器.

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

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

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

C++相关问答推荐

如果实际的syscall是CLONE(),那么为什么strace接受fork()呢?

为什么输出不是从上到下C

警告:C++中数组下标的类型为‘char’[-Wchar-subpts]

C由四个8位整数组成无符号32位整数

如何将字符串传递给函数并返回在C中更改的相同字符串?

用C宏替换strncMP函数中的参数

Sizeof(&Q;字符串&Q;)的正确输出是什么?

为什么我可以在GCC的标签后声明变量,但不能声明Clang?

为什么数组的最后一个元素丢失了?

当b是无符号字符时,int a=(b<;<;2)>;>;2;和int a=b&;0x3F;之间有什么区别?

为什么二进制文件的大小不会随着静态数据的大小而增加?

如何在不更改格式说明符的情况下同时支持双精度和长双精度?

Linux/C:带有子进程的进程在添加waitid后都挂起

gdb - 你能找到持有内部 glibc 锁的线程吗?

使用 _Atomic float 时,MSVC 编译的代码会命中调试断言

strlen 可以是[[未排序]]吗?

inline 关键字导致 Clion 中的链接器错误

2 个经过restrict修饰的指针可以比较相等吗?

在 atmega4809 上使用 UART1 发送多个字符会导致发送 0xFF

在 C 中,+=和+对相同的float操作数给出不同的答案是否合法?