【问题标题】:mq_receive() doesn't return after signal has been generated生成信号后 mq_receive() 不返回
【发布时间】:2020-05-20 13:51:18
【问题描述】:

我目前正在开发一个使用 POSIX 线程实现两个线程(主线程和通信线程)的嵌入式应用程序(在 linux 4.14 上运行)。

通信线程创建一个 POSIX 队列来处理来自主线程的命令请求(通过调用 mq_send())。它还可以处理来自引发 SIGIO 信号的串行线路的传入数据。

这是一个示例代码

主线程:

pthread_t   com_thread;
mqd_t       cmd_queue;

void main (void) 
{
    struct mq_attr attr;

    init_serial();        // Does serial line init and set a sigaction() with SIGIO that store serial data

    // Create queue
    attr.mq_flags   =   0;
    attr.mq_maxmsg  =   100;
    attr.mq_msgsize =   sizeof(struct Dummy);
    attr.mq_curmsgs =   0;

    cmd_queue = mq_open(QUEUE_NAME,                         // Queue name
                        O_RDWR | O_CREAT,                   // Flags
                        S_IRWXU|S_IRWXG|S_IRWXO,            // Mode
                        &attr);                             // Attributes

    // Create thread
    pthread_create(&com_thread, NULL, com_fw_handler, NULL);

    while(1) 
    {
        // Do some stuff ...

        // Send command request
        mq_send(cmd_queue, (const char*)cmd_request, sizeof(struct Dummy), 0);
    }
}

Com 线程:

static void * com_fw_handler (void * ptr) 
{
    struct Dummy request_from_queue;
    sigset_t sig_set;
    int ret = 0;

    // Allow SIGIO signal
    sigemptyset(&sig_set);
    sigaddset(&sig_set, SIGIO);
    sigprocmask(SIG_UNBLOCK, &sig_set, NULL);

    while(1) 
    {
        // Wait for a command request or SIGIO
        do 
        {
            ret = mq_receive(cmd_queue, (char*)request_from_queue, sizeof(request_from_queue), NULL);

            printf("mq_received() returned %d\n", ret);

            if(ret > 0)
            {
                // Handle command request
            }
        }while(ret > 0);

        // If mq_receive() exited because SIGIO has been raised
        if((ret < 0) && (errno == EINTR))
        {
            // Handle incoming data from serial
        }
    }
}

当我尝试使用 GDB 调试我的应用程序时,一切正常,每次系统从串行线路接收数据时都会退出 mq_received()。这是控制台输出示例:

mq_received() returned 64
mq_received() returned -1
mq_received() returned 64
mq_received() returned -1
......

64 是 Dummy 结构的大小,-1 是引发 SIGIO 时的返回值。

如果应用程序直接在系统上启动,则会引发 SIGIO(我可以在控制台上看到调试打印),但 mq_receive() 似乎永远不会退出。 仅控制台输出:

mq_received() returned 64
mq_received() returned 64
.......

GDB 会话以以下“选项”开始:

handle all nostop pass noprint

我无法确定我观察到的行为是由于 GDB 信号处理还是比赛/计时问题或仅仅是开发人员问题!

我提供的示例代码有什么问题吗?

谢谢!

【问题讨论】:

  • 如果您不使用pthread_sigmask 来控制哪个线程接收 SIGIO,您将遇到不可预知的行为。请注意,将 SIGIO 传递限制到它自己的线程会更好:即使您保证传递到 com_fw_worker 线程,您也可能会中断 mq_receive 以外的其他内容或完全错过信号。跨度>
  • 那么,最好的做法是将 SIGIO 传递给控制串行线路的线程(实际上是主线程),然后用 SIGUSR1 向 com 线程发送信号?因此,该 SIGUSR1 将仅被 com 线程捕获。

标签: linux multithreading queue signals posix


【解决方案1】:

这里有两个不同的问题:在可预测的线程中处理 SIGIO,以及中断长调用或阻塞调用。

首先,您需要在除一个线程之外的所有线程中显式屏蔽 SIGIO。 POSIX 指定一个信号是"delivered to exactly one of those threads within the process which ... has not blocked delivery of the signal." 因此,实现可以选择哪个线程接收 SIGIO,并且不需要每次生成 SIGIO 时都选择同一个线程。如果只有一个线程能够接收信号,则该线程将得到它。

您可以使用pthread_sigmask(...) 执行此操作。 (不要在多线程程序中使用sigprocmask,因为它的行为是unspecified。)

其次,你想可靠地中断mq_receive。这是无法做到的,并且是经典的信号处理竞赛:

while (1) {
  ....                // < - SIGIO here or prior.  What happens?
  r = mq_receive(...)
  ....                // < - SIGIO here or later.  What happens?
}

您可以通过选择性地为阻塞调用取消屏蔽 SIGIO 来最小化此窗口,并可能检查您在处理程序中设置的标志,但您无法使用上述方法消除它。

现在,对于您的程序来说,这场比赛可能没问题:特别是如果您对 mq_receive 应用超时并在信号处理程序中抛出一个标志,您将“最终”注意到 SIGIO这对你来说可能已经足够了。

如果没有,您将需要探索其他选项。 Linux implements its mqd_t as a file descriptor,虽然spec 不要求,但已明确允许。这样一来,您的问题就变成了混合信号和 I/O,这可以小心完成(如 pselect 或 self-pipe)。 mq_notify 是另一种方法。事实上,你甚至可以mq_send 一条特别的消息,上面写着“我看到了一个 SIGIO——对它做点什么”。等等。

【讨论】:

    猜你喜欢
    • 2019-06-21
    • 2014-07-04
    • 2022-07-07
    • 1970-01-01
    • 1970-01-01
    • 2015-11-16
    • 2016-06-07
    • 1970-01-01
    • 2015-08-04
    相关资源
    最近更新 更多