6

我有一个p使用信号处理程序注册的进程SIGALRM。设置了一个定时器来定期向SIGALRM进程发送信号p。进程中还有多个线程正在运行p。信号处理程序在被触发和执行时是否不可抢占?或者说,是不是信号处理程序的执行不会被进程中的任何线程中断p

PS:我认为信号处理程序是在内核中执行的(是吗?)并且内核对用户模式线程是不可抢占的?如果说错了请指正...

4

3 回答 3

16

几乎 -不要- 在信号处理程序中处理共享数据几乎总是会导致一个痛苦的世界,处理线程也会让自己一团糟。

默认情况下,信号处理程序运行时会阻塞信号(至少在 linux 上,这可能并非普遍适用),因此至少信号处理程序不会被自己抢占。但是,如果您有多个线程,并且信号没有在其他线程中被阻塞,则信号处理程序很可能会在多个线程中同时运行。

一个线程将接收信号并执行处理程序,哪个线程或多或少是随机的,尽管您可以通过在您不想处理信号的所有线程中阻塞信号来控制它。

但是,任何其他线程都禁止处理信号的线程可能并行运行。处理信号的线程几乎可以在程序中的任何点运行信号处理程序(只要信号没有被阻塞)。因此,您需要某种锁定来保护该数据。问题是您不能使用任何正常的线程锁定原语,它们不是信号异步安全的。这意味着如果您尝试在信号处理程序中获取 pthread_mutex_t,您很容易使程序死锁。

您可以在信号处理程序中安全调用的唯一函数是此处列出的函数。关于保护共享数据,您可以使用 sigblock()/sigunblock() 作为一种保护,确保在您访问共享数据时信号处理程序不会运行 - 并且信号必须在所有线程,否则它只会在没有被阻塞的线程之一中运行 - 沿着这条路走下去是疯狂的。

您可以在信号处理程序中安全访问的唯一共享数据几乎是一种sig_atomic_t类型,实际上其他类型的原始类型通常也是安全的。

你真正应该在信号处理程序中做的只是

  • 设置全局标志
  • 在合适的时候检查代码中其他地方的标志,并采取行动

或者

  • 有某种主循环,使用 select()/poll() 或类似方法监视文件描述符中的事件。
  • 在主循环中创建一个管道并监控它
  • 将一个字节写入信号处理程序中的管道
  • 运行您的代码来处理信号,包括在主循环检测到该管道上的事件时保护任何共享数据

或者

  • 保留一个备用线程
  • 阻止所有线程中的给定信号
  • 在使用信号掩码调用 sigsuspend() 时拥有备用线程循环,以确保传递该信号。
  • 运行您的代码,包括保护任何共享数据以在 sigsuspend() 返回时处理信号
于 2011-05-25T18:23:44.217 回答
5

信号处理程序在被触发和执行时是否不可抢占?

不,信号处理程序像任何其他用户级函数一样是抢占式的。

我认为信号处理程序是在内核中执行的(是吗?)

不,信号处理程序不在内核模式下执行。

内核在从内核模式切换到用户模式时检查进程的挂起信号。如果它找到一个挂起的信号,它会设置用户的堆栈帧,以便在返回用户模式后,进程开始执行信号处理程序。此后进程开始在用户模式下执行信号处理程序,就像任何其他用户级函数一样。执行完成后,进程切换到内核模式。然后内核恢复进程的原始上下文,在信号处理之前执行。
所有这些模式切换并不神奇。内核在用户堆栈中更改相应的返回地址。

于 2011-05-26T12:28:42.637 回答
2

最简洁的答案是不”。

阅读sigaction,尤其是 sa_mask 字段。默认情况下,即使在信号处理程序中,您的线程也可以被另一个信号中断。

此外,短语“被进程 p 中的任何线程中断”是没有意义的。一般来说,线程是并发运行的;它们不会互相“打断”(调用 pthread_kill() 除外)。

于 2011-05-25T18:21:17.777 回答