昨天我发布了类似的question,但是我在概述问题上做得很差,从那时起,我认为我已经取得了进步。
我的最小工作示例仍然很长,因此我将发布相关摘录,但完整的示例可以在here中找到。
我的问题很简单,我有两个POSIX消息队列,它们被创建为异步的,并且都由同一线程上的同一处理程序处理。我的问题在一个更基本的层面上在于,如果一个单独的线程按顺序发送到两个队列,那么sig处理程序仅对第一个队列运行一次。考虑到根据GNU,当信号调用处理程序时,它会自动被阻止,这是有道理的。
如此配置struct sigaction
时,请确保从我设置为sigset_t
的sa_mask
中删除目标信号(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,然后从线程1直接从mq_send
到MQ2运行我之前链接的示例,观察到以下行为
mq_send
到MQ1,然后从线程1直接从mq_send
到MQ2这使我认为在信号处理程序期间SIGIO被某种方式阻止或忽略。鉴于我已经阅读了sa_mask
的内容以及使用pthread_sigmask
进行的健全性检查,我不确定为什么会出现我看到的行为。我希望我在手册页中的某个地方错过了一些小知识。
我的问题在一个更基本的层面上,如果一个单独的线程按顺序发送到两个队列,那么sig处理程序仅对第一个队列运行一次...这使我认为
SIGIO
被阻止或忽略了在信号处理程序中。
SIGIO
是标准信号,不是实时信号。从POSIX Signal Concepts:
在信号生成到其传递或接受之间的时间,该信号被称为“待定”。通常,应用程序无法检测到此间隔。
...
如果产生了未决信号的后续出现,则在除需要排队的情况以外的其他情况下,是否多次交付或接受该信号由实现定义。未指定
SIGRTMIN
至SIGRTMAX
范围之外的多个同时挂起的信号被传送到进程或被进程接受的顺序。
在Linux上,标准信号不会排队,而在一个信号已经挂起时会被丢弃。从Linux man signal(7)
:
标准信号的排队和传递语义
如果有多个标准信号正在处理中,则顺序为未指定发送信号的位置。
标准信号不排队。如果一个标准有多个实例该信号被阻止时会生成信号,然后只有一个信号的实例被标记为待处理(信号将解锁时仅发送一次)。在这种情况下标准信号已经挂起,
man signal(7)
结构(请参见与该信号关联的sigaction(2))不会被覆盖同一信号后续实例的到达。就这样进程将接收与第一个关联的信息信号的实例。
解决您问题的一种方法是使用siginfo_t
通知而不是SIGEV_THREAD
,以便您的回调由另一个线程调用。这也消除了只能调用SIGEV_SIGNAL
的信号处理程序的限制。