Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SIGIO arriving for file descriptors I did not set it for and when no IO is possible

Tags:

c

linux

signals

I am trying to receive a signal when I/O is possible on a file descriptor. The program needs to be doing something else when it is not doing I/O, so using select(2) is not an option.

When I run the sample code is below, it is printing the message from inside the handler as fast as it can, even when there is no data on stdin. Even weirder is that the file descriptor reported in the siginfo_t structure varies from run to run. I only set it up for stdin (fd 0); why would the handler report any other value? Sometimes I see 0, sometimes, I see 1, most of the time I see '?', which indicates a value other than 0, 1, or 2.

This is on OpenSUSE 12.3, Linux kernel 3.7.10-1.16, but I see what appears to be the same problem on CentOS 6.4 with its stock kernel.

I am using write in the handler, because signal(7) says that it is reentrant and hence legal for use in a signal handler. This is also why I do not print the value of sinfo->si_fd; snprintf is not reentrant. For a while I was suspecting the stdio library of using SIGIO, which is why there are no stdio calls anywhere in the sample program (other than possibly in the library function err(3)).

Thank you for the time spent reading my code.

#include <fcntl.h>
#include <time.h>
#include <string.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <err.h>
#include <errno.h>

int needRead = 0;
const unsigned int bufsize = 256;

void handler(int sig, siginfo_t *sinfo, void *value)
{
    char *cp;

    cp = "in handler. fd: ";
    write(2, cp, strlen(cp));
    switch(sinfo->si_fd) {
        case 0: cp = "0\n"; break;
        case 1: cp = "1\n"; break;
        case 2: cp = "2\n"; break;
        default: cp = "?\n"; break;
    }
    write(2, cp, strlen(cp));

    needRead = 1;
}

int main(int argc, char *argv[])
{
    struct sigaction act;
    unsigned int counter = 0;
    int flags;
    char *outp = ".";

    /* set up the signal handler for SIGIO */
    act.sa_sigaction = handler;
    act.sa_flags = 0;
    act.sa_flags = SA_RESTART;
    sigemptyset(&act.sa_mask);
    if (sigaction(SIGIO, &act, NULL) == -1)
        err(1, "attempt to set up handler for SIGIO failed");

    /* arrange to get the signal */
    if (fcntl(0, F_SETOWN, getpid()) == -1)
        err(1, "fnctl to set F_SETOWN failed");
    flags = fcntl(0, F_GETFL);
    if (flags >= 0 && fcntl(0, F_SETFL, flags | O_ASYNC ) == -1)
        err(1, "fnctl F_SETFL to set O_ASYNC failed");

    while (1) {
        char in_buf[bufsize];
        int nc;

        counter++;

        write(STDERR_FILENO, outp, strlen(outp));

        if (needRead) {
            needRead = 0;
            if ((nc = read(STDIN_FILENO, in_buf, bufsize)) == -1) {
                err(1, "read from stdin failed");
            } else {
                outp = "Read '";
                write(STDERR_FILENO, outp, strlen(outp));
                write(STDERR_FILENO, in_buf, nc);
                outp = "'\n";
                write(STDERR_FILENO, outp, strlen(outp));
            }
        }
    }
    return 0;
}
like image 519
Kenneth Avatar asked Nov 08 '13 19:11

Kenneth


1 Answers

Ah, interesting.

The short answer is, a SIGIO is repeatedly arriving for stdin because stdin is writeable, and, separately, your SIGIO delivery is not quite properly set up.

Why is si_fd apparently unreliable?

First, you need to specify SA_SIGINFO in sa_flags before you can safely use an sa_sigaction handler.

Second, you need to #define _GNU_SOURCE and explicitly F_SETSIG to SIGIO before Linux will fill in si_fd (and si_band, for that matter) for you. A bit silly, IMHO, but so it is. Without this, the value passed in to si_fd is not meaningful, as you discovered.

Why is SIGIO being delivered over and over?

I'm guessing your program's stdin is inherited from your invoking shell, which I'd guess is a terminal device and is writeable. Just as fd 0 would continuously select(2) writeable, so will it continuously generate SIGIOs for you.

In any case, si_band holds the answer. Enable F_SETSIG, #include <poll.h>, and inspect si_band for POLLIN, POLLOUT, etc., to determine which I/O events tripped the signal.

Really, stdin is writeable?

Yeah. Try these for comparison:

$ [ -w /dev/stdin ] && echo Yes, stdin is writeable
Yes, stdin is writeable    

# Endless SIGIOs
$ ./my-sigio-prog
^C

# No SIGIO
$ ./my-sigio-prog < /dev/null

# Two SIGIOs, after a delay.  One for line-buffered "foo\n" and one for EOF
$ { sleep 3; echo foo; sleep 3; } | ./my-sigio-prog
like image 170
pilcrow Avatar answered Sep 18 '22 15:09

pilcrow