【问题标题】:POSIX signal being blocked in signal handler despite not being in sa_mask尽管不在 sa_mask 中,但 POSIX 信号在信号处理程序中被阻塞
【发布时间】:2020-05-04 11:44:23
【问题描述】:

我昨天发布了一个类似的question,但我在概述我的问题方面做得很差,从那以后我认为我已经取得了进展。

我的最小工作示例仍然很长,因此我将发布相关的 sn-ps,但可以在 here 找到完整示例。

我的问题很简单,我有两个 POSIX 消息队列,它们被创建为异步的,并且都由同一个线程上的同一个处理程序处理。我的问题是在一个更基本的层面上,如果一个单独的线程按顺序发送到两个队列,那么 sig 处理程序只为第一个队列运行一次。根据GNU,这是有道理的,因为当信号调用处理程序时,它会自动被阻塞。

因此,当我配置我的struct sigaction 时,我确保从我设置为sa_masksigset_t 中删除目标信号(SIGIO)。我的假设是,然后使用SA_NODEFER,如sigaction(2) 中所述,信号处理程序将能够被递归调用(不确定递归是否是正确的词)。

sa_mask 指定应该被阻止的信号掩码(即, 在信号处理程序执行期间添加到调用信号处理程序的线程的信号掩码中)。在 此外, 触发处理程序的信号将被阻塞,除非 使用 SA_NODEFER 标志。

将信号处理程序附加到消息队列的相关代码

assert((conn->fd = mq_open(conn->name, O_CREAT | O_RDONLY | O_NONBLOCK,
               0644, &attr)));

/** Setup handler for SIGIO */
/** sigaction(2) specifies that the triggering signal is blocked in the handler  */
/**     unless SA_NODEFER is specified */
sa.sa_flags = SA_SIGINFO | SA_RESTART | SA_NODEFER; 
sa.sa_sigaction = sigHandler;
/** sa_mask specifies signals that will be blocked in the thread the signal  */
/**     handler executes in */
sigfillset(&sa.sa_mask);
sigdelset(&sa.sa_mask, SIGIO);
if (sigaction(SIGIO, &sa, NULL)) {
    printf("Sigaction failed\n");
    goto error;
}

printf("Handler set in PID: %d for TID: %d\n", getpid(), gettid());

/** fcntl(2) - FN_SETOWN_EX is used to target SIGIO and SIGURG signals to a  */
/**     particular thread */
struct f_owner_ex cur_tid = { .type = F_OWNER_TID, .pid = gettid() };
assert(-1 != fcntl(conn->fd, F_SETOWN_EX, &cur_tid));

作为健全性检查,我检查了处理程序内的信号掩码以检查 SIGIO 是否被阻塞。

void sigHandler(int signal, siginfo_t *info, void *context)
{
    sigset_t sigs;
    sigemptyset(&sigs);
    pthread_sigmask(0, NULL, &sigs);
    if (sigismember(&sigs, SIGIO)) {
        printf("SIGIO being blocked in handler\n");
        sigaddset(&sigs, SIGIO);
        pthread_sigmask(SIG_UNBLOCK, &sigs, NULL);
    }
...
}

但是 SIGIO 似乎没有被阻止。我的推理告诉我,鉴于两个消息队列 MQ1 和 MQ2 在 SIGIO 上异步使用相同的处理程序,应该会发生以下情况。考虑到两个线程的时间和信号的延迟,我很难真正知道。最好说我的一些有根据的猜测是:

  • mq_send 到 MQ1 直接跟 mq_send 从线程 1 到 MQ2
  • MQ1 的信号处理程序应该在线程 2 上给定来自 MQ1 的 SIGIO 触发
  • MQ2 的信号处理程序会在线程 2 上中断 MQ1 的信号处理程序
  • MQ2 的信号处理程序在线程 2 上完成
  • MQ1 的信号处理程序在线程 2 上完成

运行我之前链接的示例,观察到以下行为

  • mq_send 到 MQ1 直接跟 mq_send 从线程 1 到 MQ2
  • MQ1 的信号处理程序触发并完成

这让我觉得 SIGIO 在信号处理程序期间以某种方式被阻止或忽略。鉴于我对sa_mask 的阅读以及使用pthread_sigmask 进行的健全性检查,我不确定为什么会出现我所看到的行为。我希望我在手册页的某个地方漏掉了一些小知识。

【问题讨论】:

  • 您的代码在信号处理程序中调用非异步安全函数时表现出未定义的行为。

标签: c pthreads signals posix sigaction


【解决方案1】:

我的问题是在一个更基本的层面上,如果一个单独的线程顺序发送到两个队列,那么 sig 处理程序只为第一个队列运行一次......这让我认为SIGIO 以某种方式被阻止或在信号处理期间被忽略。

SIGIO 是标准信号,不是实时信号。 来自POSIX Signal Concepts

在信号生成和交付或接受之间的这段时间内,信号被称为“待处理”。通常,应用程序无法检测到此间隔。

...

如果生成了后续出现的未决信号,则在不需要排队的情况下是否多次传递或接受信号是由实现定义的。未指定在SIGRTMINSIGRTMAX 范围之外的多个同时挂起的信号被传递到进程或被进程接受的顺序。

在 Linux 上,标准信号不会排队,而是在一个已经挂起时被丢弃。来自Linux man signal(7)

标准信号的排队和传递语义

如果一个进程有多个标准信号待处理,则 未指定传递哪些信号。

标准信号不排队。如果一个标准的多个实例 当信号被阻塞时产生信号,那么只有一个 信号的实例被标记为待处理(并且信号将是 解锁时仅交付一次)。在这种情况下 标准信号已经挂起,siginfo_t 结构(参见 与该信号关联的 sigaction(2)) 不会被覆盖 同一信号的后续实例的到达。就这样 进程将收到与第一个相关的信息 信号的实例。


解决您的问题的一个方法是使用SIGEV_THREAD 通知而不是SIGEV_SIGNAL,以便您的回调被另一个线程调用。这也消除了信号处理程序的限制,您只能调用async-signal-safe functions

【讨论】:

  • 所以我猜对于 OP 观察到两个信号之一丢失的最可能的解释,即使它们已被解除阻塞并且处理程序已向SA_NODEFER 注册,将是第二个信号到达(并且是丢弃)在第一个甚至被分派给处理程序之前。考虑到信号的异步特性,这当然是完全可能的。
  • @JohnBollinger 没错。正如 POSIX 所说,“在信号生成和传递或接受之间的时间段内,信号被称为“未决”。通常,应用程序无法检测到此间隔。”跨度>
  • 感谢@MaximEgorushkin SIGEV_THREAD 变通方法很有效。非常感谢!
猜你喜欢
  • 1970-01-01
  • 2015-06-01
  • 2013-11-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多