Linux IO multiplexing (advanced character device 3)

1. Introduction to IO multiplexing in Linux

IO multiplexing is a synchronous IO model. IO multiplexing allows one process to monitor multiple file descriptors. Once a file descriptor is ready, the application is notified to perform corresponding read and write operations. When no file descriptors are ready, the application blocks, freeing up CPU resources.
At the application layer, Linux provides three models for implementing IO multiplexing, namely select, poll and epoll. Both the poll function and the select function can monitor multiple file descriptors and obtain prepared file descriptors through polling. But the epoll function turns active polling into passive notification, passively receiving notifications when events occur. In a single thread, the select function can monitor a maximum of 1024 file descriptors, and there is no difference between the poll function and the select function, except that the poll function does not have a maximum file descriptor limit.

1.1. Poll function of application layer

The poll function in Linux applications is as follows:

int poll(struct pollfd *fds, nfds_t nfds,int timeout);

The first parameter fds: The set of file descriptors to be monitored and the events to be monitored are an array. The array elements are all of the structure pollfd type. The pollfd structure is as follows:

struct pollfd
{<!-- -->
int fd; //monitored file descriptor
short events; //Events to wait for
short revents; //actual events
};

In the pollfd structure, the first member fd is the monitored file descriptor. The second member events is the event to be monitored. The types of events that can be monitored are as follows:

POLLIN has data to read
POLLPRI has urgent data to read
POLLOUT can write data
POLLERR An error occurred in the file descriptor specified
POLLHUP The specified file descriptor is hung
POLLNVAL Invalid request
POLLRDNORM is equivalent to POLLIN

The third member is the return event, and the specific return event is set by the Linux kernel.
Second parameter nfds: The number of file descriptors to be monitored by the poll function
The third parameter timeout: Specify the waiting time, the unit is ms. Regardless of whether the I/O is ready or not, POLL will return when the time is up. If timepoll is greater than 0, wait for the specified time. If timeout is equal to 0, return immediately. If timeout is equal to -1, it will be returned after the event occurs.

1.2. Poll function of driver layer

When an application uses the select or poll function to perform non-blocking access to the driver, the poll function of the file_operations operation set in the driver will be executed. Therefore, the poll function in the driver needs to be improved. The poll function prototype in the driver is as follows:

unsigned int (*poll)(struct file *filp,struct poll_table_struct *wait);

Function parameters:
filp: the file descriptor to be opened
wait: structure poll_table_struct type pointer, this parameter is passed by the application. Generally this parameter is passed to the poll_wait function.
Return value:
Return resource status to the application. The resource status that can be returned is as follows:
POLLIN has data that can be read
POLLPRI has urgent data that needs to be read.
POLLOUT can write data
POLLERR An error occurred in the specified file descriptor
POLLHUP The specified file descriptor is hung
POLLNVAL invalid request
POLLRDNORM is equivalent to POLLIN, normal data can be read.
Function functions:
This function needs to perform the following two tasks. First, call poll_wait() on the waiting queue that may cause the device file status to change, and add the corresponding waiting queue head to the poll_table. Then return a mask indicating whether non-blocking read and write access to the device is possible.
Call the poll_wait function in the driver’s poll function, pay attention! The poll_wait function does not cause blocking. The poll_wait function prototype is as follows:

void poll_wait(struct file *filp,wait_queue_head_t *queue,poll_table *wait);

The parameter queue is the waiting queue head to be added to poll_table, and the parameter wait is poll_table, which is the wait parameter of the poll function in file_operations.

2. Code examples

2.1, Application layer program

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <poll.h>

int main(int argc, char *argv[])
{<!-- -->
    int fd;//File descriptor to be monitored
    char buf1[32] = {<!-- -->0};
    char buf2[32] = {<!-- -->0};
    struct pollfd fds[1];
    int ret;
    fd = open("/dev/test", O_RDWR); //Open the /dev/test device, blocking access
    if (fd < 0)
    {<!-- -->
        perror("open error \
");
        return fd;
    }
    fds[0] .fd =fd;
    fds[0].events = POLLIN;
    printf("read before \
");
    while (1)
    {<!-- -->
        ret = poll(fds,1,3000);
        if(!ret)
        {<!-- -->
            printf("time out !!\
");
        }else if(fds[0].revents == POLLIN)
        {<!-- -->
            read(fd,buf1,sizeof(buf1)); //Read data from the /dev/test file
            printf("buf is %s \
",buf1);
            sleep(1);
        }
    }
    printf("read after\
");
    close(fd); //Close the file
    return 0;
}

2.2. Driver layer program

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/wait.h>
#include <linux/poll.h>

struct device_test{<!-- -->
   
    dev_t dev_num; //Device number
     int major; //major device number
    int minor; //Minor device number
    struct cdev cdev_test; // cdev
    struct class *class; //class
    struct device *device; //device
    char kbuf[32];
    int flag; //flag bit
};


struct device_test dev1;

DECLARE_WAIT_QUEUE_HEAD(read_wq); //Define and initialize the waiting queue head

/*Open device function*/
static int cdev_test_open(struct inode *inode, struct file *file)
{<!-- -->
    file->private_data= & amp;dev1;//Set private data
   
    return 0;
}

/*Write data function to device*/
static ssize_t cdev_test_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{<!-- -->
     struct device_test *test_dev=(struct device_test *)file->private_data;

    if (copy_from_user(test_dev->kbuf, buf, size) != 0) // copy_from_user: User space transfers data to kernel space
    {<!-- -->
        printk("copy_from_user error\r\
");
        return -1;
    }
    test_dev->flag=1;
    wake_up_interruptible( & amp;read_wq);

    return 0;
}

/**Read data from device*/
static ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{<!-- -->
    
    struct device_test *test_dev=(struct device_test *)file->private_data;
    if(file->f_flags & amp; O_NONBLOCK ){<!-- -->
        if (test_dev->flag !=1)
        return -EAGAIN;
    }
    wait_event_interruptible(read_wq,test_dev->flag);

    if (copy_to_user(buf, test_dev->kbuf, strlen( test_dev->kbuf)) != 0) // copy_to_user: transfer data from kernel space to user space
    {<!-- -->
        printk("copy_to_user error\r\
");
        return -1;
    }

    return 0;
}

static int cdev_test_release(struct inode *inode, struct file *file)
{<!-- -->
    
    return 0;
}

static __poll_t cdev_test_poll(struct file *file, struct poll_table_struct *p){<!-- -->
     struct device_test *test_dev=(struct device_test *)file->private_data; //Set private data
     __poll_t mask=0;
     poll_wait(file, & amp;read_wq,p); //Application blocking

     if (test_dev->flag == 1)
     {<!-- -->
         mask |= POLLIN;
     }
     return mask;
     
}

/*Device operation function*/
struct file_operations cdev_test_fops = {<!-- -->
    .owner = THIS_MODULE, //Point the owner field to this module to avoid uninstalling the module while the module's operations are being used.
    .open = cdev_test_open, //Point the open field to the chrdev_open(...) function
    .read = cdev_test_read, //point the open field to the chrdev_read(...) function
    .write = cdev_test_write, //Point the open field to the chrdev_write(...) function
    .release = cdev_test_release, //Point the open field to the chrdev_release(...) function
    .poll = cdev_test_poll, //Point the poll field to the chrdev_poll(...) function
};

static int __init chr_fops_init(void) //Driver entry function
{<!-- -->
    /*Register character device driver*/
    int ret;
    /*1 Create device number*/
    ret = alloc_chrdev_region( & amp;dev1.dev_num, 0, 1, "alloc_name"); //Dynamic allocation of device numbers
    if (ret < 0)
    {<!-- -->
       goto err_chrdev;
    }
    printk("alloc_chrdev_region is ok\
");

    dev1.major = MAJOR(dev1.dev_num); //Get the major device number
   dev1.minor = MINOR(dev1.dev_num); //Get the minor device number

    printk("major is %d \r\
", dev1.major); //Print the major device number
    printk("minor is %d \r\
", dev1.minor); //Print the minor device number
     /*2 Initialize cdev*/
    dev1.cdev_test.owner = THIS_MODULE;
    cdev_init( & amp;dev1.cdev_test, & amp;cdev_test_fops);

    /*3 Add a cdev to complete the character device registration to the kernel*/
   ret = cdev_add( & amp;dev1.cdev_test, dev1.dev_num, 1);
    if(ret<0)
    {<!-- -->
        goto err_chr_add;
    }
    /*4 Create class*/
  dev1. class = class_create(THIS_MODULE, "test");
    if(IS_ERR(dev1.class))
    {<!-- -->
        ret=PTR_ERR(dev1.class);
        goto err_class_create;
    }
    /*5 Create device*/
  dev1.device = device_create(dev1.class, NULL, dev1.dev_num, NULL, "test");
    if(IS_ERR(dev1.device))
    {<!-- -->
        ret=PTR_ERR(dev1.device);
        goto err_device_create;
    }

return 0;

 err_device_create:
        class_destroy(dev1.class); //Delete class

err_class_create:
       cdev_del( & amp;dev1.cdev_test); //Delete cdev

err_chr_add:
        unregister_chrdev_region(dev1.dev_num, 1); //Unregister the device number

err_chrdev:
        return ret;
}
static void __exit chr_fops_exit(void) //Driver exit function
{<!-- -->
    /*Log out character device*/
    unregister_chrdev_region(dev1.dev_num, 1); //Unregister the device number
    cdev_del( & amp;dev1.cdev_test); //Delete cdev
    device_destroy(dev1.class, dev1.dev_num); //Delete device
    class_destroy(dev1.class); //Delete class
}
module_init(chr_fops_init);
module_exit(chr_fops_exit);

2.3, Linux IO multiplexing API key points

Application layer

struct pollfd fds[1];
fds[0] .fd =fd;
fds[0].events = POLLIN;
ret = poll(fds,1,3000);

Driver layer

static __poll_t cdev_test_poll(struct file *file, struct poll_table_struct *p)
{<!-- -->
     struct device_test *test_dev=(struct device_test *)file->private_data; //Set private data
     __poll_t mask=0;
     poll_wait(file, & amp;read_wq,p); //Application blocking

     if (test_dev->flag == 1)
     {<!-- -->
         mask |= POLLIN;
     }
     return mask;
     
}