我昨天发布了一个类似的问题,但我在概述问题方面做得很差,从那以后我认为我已经取得了进步。
我的最小工作示例仍然很长,所以我会发布相关的片段,但完整的示例可以在这里找到。
我的问题很简单,我有两个POSIX消息队列,它们被创建为异步的,并且都由同一线程上的同一处理程序处理。我的问题是在更基本的层面上,如果一个单独的线程顺序地发送到两个队列,那么sig处理程序只对第一个队列运行一次。根据GNU的说法,这是有道理的,因为当信号调用处理程序时,它会被自动阻止。
因此,当我配置我的struct sigaction
时,我确保从我设置为sa_mask
的sigset_t
中移除目标信号(SIGIO)。我的假设是,然后使用SA_NODEFER
,如sigaction(2)中所解释的,信号处理程序将能够被递归调用(不确定递归是否是正确的词)。
将信号处理程序附加到消息队列的相关代码
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 failedn");
goto error;
}
printf("Handler set in PID: %d for TID: %dn", 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 handlern");
sigaddset(&sigs, SIGIO);
pthread_sigmask(SIG_UNBLOCK, &sigs, NULL);
}
...
}
但SIGIO似乎没有被阻止。我的推理告诉我,如果两个消息队列MQ1和MQ2在SIGIO上异步使用相同的处理程序,则应该发生以下情况。考虑到两个线程的时间和信号的延迟,我很难真正知道。Better说我的一些有根据的猜测是:
mq_send
到MQ1,然后是来自线程1的mq_send
到MQ2- MQ1的信号处理程序应在给定线程2上MQ1的SIGIO的情况下激发
- MQ2的信号处理程序将中断线程2上的MQ1的信号处理
- MQ2的信号处理程序在线程2上完成
- MQ1的信号处理程序在线程2上完成
运行我前面链接的示例,观察到以下异常
mq_send
到MQ1,然后是来自线程1的mq_send
到MQ2- MQ1的信号处理程序启动并完成
这让我认为SIGIO在信号处理程序中被阻塞或忽略了。考虑到我读到的sa_mask
和我使用pthread_sigmask
进行的理智检查,我不确定为什么我会看到这种行为。我希望我在手册的某个地方遗漏了一些知识。
我的问题是在更基本的层面上,因为如果一个单独的线程顺序地发送到两个队列,那么sig处理程序只为第一个队列运行一次。。。这让我觉得
SIGIO
在信号处理程序中被阻塞或忽略了。
SIGIO
是一个标准信号,而不是实时信号。来自POSIX信号概念:
在信号生成和交付或接受之间的时间内,信号被称为"待定"。通常,应用程序无法检测到此间隔。
如果生成了挂起信号的后续出现,则实现定义为在除了需要排队的情况之外的情况下是否多次传递或接受信号。范围
SIGRTMIN
到SIGRTMAX
之外的多个同时未决信号被传递到进程或被进程接受的顺序是未指定的。
在Linux上,标准信号不排队,而是在信号已挂起时丢弃。从Linuxman signal(7)
:
标准信号的排队和传递语义
如果一个进程有多个标准信号挂起,则中的顺序信号被传递到哪个是未指定的。
标准信号不排队。如果一个标准的多个实例当该信号被阻塞时生成信号,则只有一个该信号的实例被标记为挂起(并且该信号将在解除阻止时仅交付一次)。如果标准信号已经挂起,
siginfo_t
结构(请参阅sigaction(2))不会被重写相同信号的后续实例的到达。因此进程将接收与第一个信号的实例。
解决问题的一个方法是使用SIGEV_THREAD
通知而不是SIGEV_SIGNAL
,这样回调就会被另一个线程调用。这也消除了信号处理程序的限制,在信号处理程序中只能调用异步信号安全函数。