Search code examples
linuxlinux-device-driverpollingdevice-driver

in linux char device driver, what does the poll_queue_proc function do?


There is a concept of synchronous polling for multiple device files in linux and I'm trying to understand how it works.
in linux 2.6.23 source drivers/char/random.c, I see following code

static DECLARE_WAIT_QUEUE_HEAD(random_read_wait);
static DECLARE_WAIT_QUEUE_HEAD(random_write_wait);

static unsigned int
random_poll(struct file *file, poll_table * wait)
{
    unsigned int mask;

    poll_wait(file, &random_read_wait, wait);
    poll_wait(file, &random_write_wait, wait);
    mask = 0;
    if (input_pool.entropy_count >= random_read_wakeup_thresh)
        mask |= POLLIN | POLLRDNORM;
    if (input_pool.entropy_count < random_write_wakeup_thresh)
        mask |= POLLOUT | POLLWRNORM;
    return mask;
}

the poll_table is defined as below in include/linux/poll.h

typedef void (*poll_queue_proc)(struct file *, wait_queue_head_t *, struct poll_table_struct *);

typedef struct poll_table_struct {
    poll_queue_proc qproc;
} poll_table;

I saw in a book (Ch.5, Essential Linux Device Drivers, Venkateswaran) that "The poll_table is a table of wait queues owned by device drivers that are being polled for data." but the source says it is just a function pointer. and I can't find what this function qproc is doing. Below is the function poll_wait defined in include/linux/poll.h.

static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
{
    if (p && wait_address)
        p->qproc(filp, wait_address, p);
}

and in the book it says(about an example char driver for a mouse), "mouse_poll() uses the library function, poll_wait(), to add a wait queue (mouse_wait) to the kernel poll_table and go to sleep." so poll_wait can sleep, but in the random_poll() function above, we see tow consecutive poll_wait functions. so does the random_poll polls for read and write availability sequentially and sends the mask to the application? I would appreciate if someone can show me the example of poll_queue_proc function. I couldn't find it in the linux driver source(should it appear only in application?).


Solution

  • drivers/char/random.c:random_poll() is called when userspace calls select() (or poll() or epoll_wait() for that matter) with a file descriptor referring to /dev/random.

    These system calls are at the basis of event multiplexing. In the following program, userspace opens a number of input sources (say /dev/random and /dev/ttyS4) and calls select() on both of them to block until any of them has input data to be read. (There are other event sources than input, input is just the simplest.)

    #include <sys/select.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    
    #define _SYSE(ret, msg) do {                    \
        if (ret == -1) {                            \
            perror(msg);                            \
            exit(EXIT_FAILURE);                     \
        }                                           \
    } while (0)
    
    static int /*bool: EOF detected*/ consume_fd(int fd, const char* msg)
    {
        char tmp[64];
        ssize_t nread;
    
        nread = read(fd, tmp, sizeof(tmp));
        _SYSE(nread, "read");
        if (nread == 0 /*EOF*/)
            return 1;
    
        printf("%s: consumed %ld bytes\n", msg, nread);
        return 0;
    }
    
    int main(void)
    {
        int random_fd, tty_fd, nfds = 0;
    
        random_fd = open("/dev/random", O_RDONLY);
        _SYSE(random_fd, "open random");
        if (random_fd > nfds)
            nfds = random_fd+1;
    
        tty_fd = open("/dev/ttyS4", O_RDONLY);
        _SYSE(tty_fd, "open tty");
        if (tty_fd > nfds)
            nfds = tty_fd+1;
    
        while (1) {
            fd_set in_fds;
            int ret;
    
            FD_ZERO(&in_fds);
            FD_SET(random_fd, &in_fds);
            FD_SET(tty_fd, &in_fds);
    
            ret = select(nfds, &in_fds, NULL, NULL, NULL);
            _SYSE(ret, "select");
    
            if (FD_ISSET(random_fd, &in_fds)) {
                int eof_detected = consume_fd(random_fd, "random");
                if (eof_detected) 
                    break;
            }
            if (FD_ISSET(tty_fd, &in_fds)) {
                int eof_detected = consume_fd(tty_fd, "tty");
                if (eof_detected) 
                    break;
            }
        }
        return 0;
    }
    

    Output will appear once either random numbers are available, or the serial line has data. (Note that nowadays /dev/random does not block, but rather generates pseudo random numbers, so output is really fast.)

    It is when the select() call enters the kernel that random_poll() is called, and another, comparable, function somewhere in the TTY layer - simply because select() passes those file descriptors as parameters. Those functions are supposed to simply enqueue the caller into a poll_table that is maintained out of your reach (it represents the calling task for that purpose).

    In a second stage the implementation of select() then suspends the caller until any of the events become true. (See fs/select.c.)