0

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

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

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

因此,当我配置我的时,struct sigaction我确保从sigset_t我设置为sa_mask. 我的假设是,然后使用sigaction(2)SA_NODEFER中解释的那样,信号处理程序将能够被递归调用(不确定递归是否在这里是正确的词)。

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我不确定为什么我会得到我所看到的行为。我希望我在联机帮助页的某个地方错过了一些小知识。

4

1 回答 1

3

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

SIGIO是标准信号,不是实时信号。来自POSIX 信号概念

在信号的产生和它的交付或接受之间的时间里,这个信号被称为“未决”。通常,应用程序无法检测到此间隔。

...

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

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

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

如果一个进程有多个标准信号等待处理,则传递信号的顺序是未指定的。

标准信号不排队。如果在该信号被阻塞时生成了一个标准信号的多个实例,那么只有一个信号实例被标记为待处理(并且该信号在解除阻塞时只会被传递一次)。在标准信号已经挂起的情况下,siginfo_t与该信号关联的结构(参见 sigaction(2))不会在同一信号的后续实例到达时被覆盖。因此,该过程将接收与信号的第一个实例相关联的信息。


您的问题的一种解决方法是使用SIGEV_THREAD通知而不是SIGEV_SIGNAL,以便您的回调被另一个线程调用。这也消除了信号处理程序的限制,您只能调用异步信号安全函数

于 2020-01-18T23:52:19.880 回答