1

I'm currently working on an embedded application (running on linux 4.14) that implements two threads (main and a communication thread) using POSIX threads.

The communication thread creates a POSIX queue that handles command requests from the main thread (by calling mq_send()). It also can handle incoming data from a serial line that raises an SIGIO signal.

Here is a sample code

Main thread:

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 Thread :

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
        }
    }
}

When I try to debug my application using GDB, everything works fine, mq_received() is exited each time the system receives data from the serial line. Here is the console output sample :

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

64 is the size of the Dummy structure, -1 is the returned value when SIGIO has been raised.

If the application is started directly on the system, SIGIO is raised (I can see a debug print on the console) but it seems mq_receive() never exit. The console outputs only:

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

GDB session starts with following "options" :

handle all nostop pass noprint

I can't determine if the behaviour I observe is due to GDB signal handling or a race/timing problem or just a developper issue !

Anything wrong in sample code I provided ?

Thanks !

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
  • If you're not using [`pthread_sigmask`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_sigmask.html) to control which thread receives the SIGIO, you'll have unpredictable behavior. Note that it would be even better to restrict SIGIO delivery to its own thread: even if you guarantee delivery to the _com\_fw\_worker_ thread, you might interrupt something other than `mq_receive` or miss the signal entirely. – pilcrow Feb 04 '20 at 17:22
  • So, the best pratice would be delivering SIGIO to the thread that control serial line (actually main thread), then signal the com thread with SIGUSR1 for example ? So, that SIGUSR1 would be catched by com thread only. – Kevin WYSOCKI Feb 05 '20 at 07:52

1 Answers1

0

You've two separate issues here: handling the SIGIO in a predictable thread, and interrupting a long or blocking call.

For the first, you'll want to explicitly mask out SIGIO in all but one thread. POSIX specifies that a signal is "delivered to exactly one of those threads within the process which ... has not blocked delivery of the signal." So, the implementation gets to pick which thread receives the SIGIO, and it needn't choose the same thread every time the SIGIO is generated. If only one thread is capable of receiving the signal, that thread will get it.

You can do this with pthread_sigmask(...). (Do not use sigprocmask in a multithreaded program, as its behavior is then unspecified.)

Second, you want to reliably interrupt mq_receive. This cannot be done, and is a classic signal handling race as written:

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

You can minimize this window by selectively unmasking SIGIO just for the blocking call, and perhaps checking a flag you set in the handler, but you cannot eliminate it with the above approach.

Now, that race may be okay for your program: especially if you apply a timeout to the mq_receive and throw a flag in the signal handler, you will notice the SIGIO "eventually," and that may be good enough for you.

If not, you'll need to explore other options. Linux implements its mqd_t as a file descriptor, which is explicitly permitted though not required by the spec. With that, your issue becomes mingling signals and I/O, which can be done with some care (as by pselect or a self-pipe). mq_notify is another approach. Indeed, you could even mq_send a special message that says, "I saw a SIGIO — do something about it." Etc.

pilcrow
  • 56,591
  • 13
  • 94
  • 135