Linux driver development study notes [9]: Linux blocking and non-blocking IO

table of Contents

One, blocking and non-blocking IO

Two, application blocking and non-blocking methods

Three, waiting queue

1. Wait for the head of the queue

2. Waiting for queue items

3. Queue item add/remove waiting queue head

4. Wait for wake up

5. Waiting for the event

Four, polling

1. The select function

2, poll function

3. epoll function

Five, poll operation function under Linux driver

Six, blocking IO program writing example

1. The method of waiting for the event

2. The way to add waiting queue items

Seven, non-blocking IO program writing example


One, blocking and non-blocking IO

The IO here refers to Input/Output, that is, input/output, which is the input/output operation of the application on the drive device. When the application is operating on the device driver, if the device resource cannot be obtained, the blocking IO will suspend the thread corresponding to the application until the device resource can be obtained. For non-blocking IO, the thread corresponding to the application will not hang, it will either keep polling and waiting until the device resources are available, or just give up.

The biggest advantage of blocking access is that the process can enter the dormant state when the device file is inoperable, so that CPU resources can be released. However, the process must be awakened when the device file can be operated. The awakening work is usually done in the interrupt function. The Linux kernel provides a wait queue to realize the wake-up work of blocked processes

1. Blocking IO diagram:

 

2. Schematic diagram of non-blocking IO:

Two, application blocking and non-blocking methods

Blocking method:

    /*open*/
    fd = open(filename, O_RDWR);
    if (fd < 0){
        printf("open %s failed!!\r\n", filename);
        return -1;
    }
    ret = read(fd, &data, sizeof(data));

Non-blocking way:

    /*open*/
    fd = open(filename, O_RDWR | O_NONBLOCK);
    if (fd < 0){
        printf("open %s failed!!\r\n", filename);
        return -1;
    }
    ret = read(fd, &data, sizeof(data));

Three, waiting queue

1. Wait for the head of the queue

To use a waiting queue in the driver, a waiting queue head must be created and initialized. The waiting queue head is represented by the structure wait_queue_head_t . The wait_queue_head_ t structure is defined in the file include/linux/wait.h . The structure content is as follows:

struct __wait_queue_head {
    spinlock_t lock;
    struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;

After defining the waiting queue head, it needs to be initialized. Use the init_waitqueue_head function to initialize the waiting queue head

void init_waitqueue_head(wait_queue_head_t *q)

2. Waiting for queue items

The head of the waiting queue is the head of a waiting queue. Each process accessing the device is a queue item . When the device is unavailable, the waiting queue items corresponding to these processes must be added to the waiting queue. Use the macro DECLARE_WAITQUEUE to define and initialize a waiting queue item. The content of the macro is as follows:

DECLARE_WAITQUEUE(name, tsk)

name is the name of the waiting queue item, tsk indicates which task (process) this waiting queue item belongs to, and is generally set to current . In the Linux kernel, current is equivalent to a global variable, which represents the current process . Therefore, the macro DECLARE_WAITQUEUE creates and initializes a waiting queue item for the currently running process.

3. Queue item add/remove waiting queue head

When the device is inaccessible, the waiting queue item corresponding to the process needs to be added to the previously created waiting queue head, and the process can enter the sleep state only after being added to the waiting queue head. When the device is accessible, remove the waiting queue item corresponding to the process from the waiting queue head.

(1) Add waiting queue item API:

void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)

q : The head of the waiting queue where the waiting queue item will be added.

wait : the waiting queue item to be added.

(2) Remove the waiting queue item API:

void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)

q : The head of the waiting queue where the waiting queue item will be added.

wait : The waiting queue item to be deleted.

4. Wait for wake up

When the device is ready for use, it is necessary to wake up the process that has entered the sleep state. The following two functions can be used to wake up:

void wake_up(wait_queue_head_t *q) 
void wake_up_interruptible(wait_queue_head_t *q)

The parameter q is the head of the waiting queue to be awakened. These two functions will wake up all processes in the head of the waiting queue. The wake_up function can wake up the processes in the TASK_INTERRUPTIBLE and TASK_UNINTERRUPTIBLE states, while the wake_up_interruptible function can only wake up the processes in the TASK_INTERRUPTIBLE state.

5. Waiting for the event

In addition to actively waking up, you can also set the waiting queue to wait for an event. When the event is satisfied, the process in the waiting queue will be automatically awakened. The API functions related to the waiting event are as follows:

wait_event(wq, condition)

Waiting for the waiting queue with wq as the head of the waiting queue to be awakened, provided that the condition must be met (true), otherwise it has been blocked. This function will set the process to TASK_UNINTERRUPTIBLE state

wait_event_timeout(wq, condition,timeout)

The function is similar to wait_event, but this function can add a timeout period in jiffies. This function has a return value. If it returns 0, it means the timeout period has expired and the condition is false. If it is 1, the condition is true, that is, the condition is satisfied.

wait_event_interruptible(wq, condition)

Similar to the wait_event function, but this function sets the process to TASK_INTERRUPTIBLE, which means it can be interrupted by a signal.

wait_event_interruptible_timeout(wq, condition, timeout)

Similar to the wait_event_timeout function, this function also sets the process to TASK_INTERRUPTIBLE, which can be interrupted by a signal.

Four, polling

If the user application accesses the device in a non-blocking manner, the device driver must provide a non-blocking processing method, that is, polling . Poll, epoll, and select can be used to handle polling. The application uses select, epoll, or poll functions to query whether the device can be operated, and if it can be operated, it reads from the device or writes data to the device. When the application calls the select, epoll or poll function, the poll function in the device driver will be executed, so it is necessary to write the poll function in the device driver. Let's first look at the select, epoll or poll functions used in the application

1. The select function

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)

nfds : Among the three types of file description sets to be monitored, the largest file descriptor plus one.

readfds, writefds and exceptfds : these three pointers point to the descriptor set. These three parameters indicate which descriptors are concerned, which conditions need to be met, etc., these three parameters are of type fd_set, and each bit of the variable of type fd_set Both represent a file descriptor. readfds is used to monitor the read changes of the specified descriptor set, that is, to monitor whether these files can be read. As long as there is a file in these sets that can be read, seclect will return a value greater than 0 to indicate that the file can be read. If there is no file to read, it will determine whether it has timed out according to the timeout parameter. You can set readfs to NULL, which means you don't care about any file read changes. writefds is similar to readfs, except that writefs is used to monitor whether these files can be written. exceptfds is used to monitor the abnormalities of these files.

For example, if we want to read data from a device file, we can define a variable fd_set , which must be passed to the parameter readfds. When we define a fd_set variable, we can use the following macros to operate:

void FD_ZERO(fd_set *set) 
void FD_SET(int fd, fd_set *set) 
void FD_CLR(int fd, fd_set *set) 
int FD_ISSET(int fd, fd_set *set)

2, poll function

In a single thread, the number of file descriptors that the select function can monitor has the maximum limit, generally 1024. You can modify the kernel to increase the number of file descriptors that can be monitored, but this will reduce efficiency! At this time, you can use the poll function. The poll function is essentially not different from select, but the poll function does not have a maximum file descriptor limit.

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

The set of file descriptors to be monitored by fds and the events to be monitored are an array, and the array elements are all structures

struct pollfd {

     int fd; /* file descriptor*/

     short events; /* requested event*/

     short revents; /* Returned events*/

};

events are the events 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.

An error occurred in the file descriptor specified by POLLERR.

The file descriptor specified by POLLHUP is suspended.

POLLNVAL invalid request.

POLLRDNORM is equivalent to POLLIN

nfds : The number of file descriptors to be monitored by the poll function.

timeout : timeout period, in ms.

3. epoll function

The traditional selcet and poll functions will suffer from inefficiency as the number of monitored fd increases, and the poll function must traverse all the descriptors every time to check the ready descriptors, which is a waste of time. For this reason, epoll came into being. Epoll is prepared to handle large concurrency, and epoll functions are often used in network programming. The application needs to use the epoll_create function to create an epoll handle first

int epoll_create(int size)

Five, poll operation function under Linux driver

When the application calls the select or poll function to perform non-blocking access to the driver, the poll function in the file_operations operation set of the driver is executed. Therefore, the writer of the driver needs to provide the corresponding poll function. The prototype of the poll function is as follows:

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

filp : The device file (file descriptor) to be opened.

wait : pointer to the structure poll_table_struct, passed in by the application. Generally, this parameter is passed to the poll_wait function.

Six, blocking IO program writing example

https://github.com/denghengli/linux_driver/tree/master/15_blockio

1. The method of waiting for the event

#include <linux/wait.h> 
#include <linux/ide.h> 

/*定时器回调函数,在设备资源可用的时候唤醒等待队列中的线程*/
static void timer_func(unsigned long arg)
{
    struct key_dev *dev = (struct key_dev*)arg;
    ...
    wake_up(&dev->r_wait);/*唤醒等待队列中的线程*/
}
static  ssize_t key_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    ...
    /*等待等待队列中的 reles_value 事件,使访问的线程进入阻塞*/
    wait_event_interruptible(dev->r_wait, reles_value);
    ...
    return ret;
}
static int key_open(struct inode *inode, struct file *filp)
{ 
    ...
    /*初始化等待队列头*/  
    init_waitqueue_head(&dev->r_wait);
    return 0;
}

2. The way to add waiting queue items

#include <linux/wait.h> 
#include <linux/ide.h> 

/*定时器回调函数,在设备资源可用的时候唤醒等待队列中的线程*/
static void timer_func(unsigned long arg)
{
    struct key_dev *dev = (struct key_dev*)arg;
    ...
    wake_up(&dev->r_wait);/*唤醒等待队列中的线程*/
}
static  ssize_t key_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    ...
	/*定义并初始化一个等待队列项*/
	DECLARE_WAITQUEUE(wait, current);
	if (reles_value == 0){
        add_wait_queue(&dev->r_wait, &wait);//将队列项加入等待队列
        __set_current_state(TASK_INTERRUPTIBLE);/* 设置任务状态,设置为可被打断的状态,可以接受信号 */
        schedule(); /* 进行一次任务切换 */
        
        /*等待被唤醒,可能被信号和可用唤醒*/
        if(signal_pending(current)) { /* 判断是否为信号引起的唤醒 */
        __set_current_state(TASK_RUNNING); /*设置为运行状态 */
        remove_wait_queue(&dev->r_wait, &wait); /*将等待队列移除 */
        return -ERESTARTSYS;
        }
        /*可用设备唤醒*/
        __set_current_state(TASK_RUNNING); /*设置为运行状态 */
        remove_wait_queue(&dev->r_wait, &wait); /*将等待队列移除 */
	}
    ...
    return ret;
}
static int key_open(struct inode *inode, struct file *filp)
{ 
    ...
    /*初始化等待队列头*/  
    init_waitqueue_head(&dev->r_wait);
    return 0;
}

Seven, non-blocking IO program writing example

https://github.com/denghengli/linux_driver/tree/master/16_noblockio

 

 

Guess you like

Origin blog.csdn.net/m0_37845735/article/details/107307000