Linux blocking and non-blocking

1. Introduction to blocking and non-blocking

block


non-blocking

fd = open("/dev/xxx_dev", O_RDWR); //open file in blocking mode
fd = open("/dev/xxx_dev", O_RDWR | O_NONBLOCK); /* open in non-blocking mode */

2. Waiting queue

1. Waiting for the head of the queue

The biggest advantage of blocking access is that when the device file is inoperable, the process can enter the dormant state, which can free up CPU resources.

Use the init_waitqueue_head function to initialize the waiting queue head. The function prototype is as follows:

void init_waitqueue_head(wait_queue_head_t *q)

The parameter q is the head of the waiting queue to be initialized.
You can also use the macro DECLARE_WAIT_QUEUE_HEAD to complete the initialization of the definition of the waiting queue head at one time.

2. Waiting for queue items

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) the waiting queue item belongs to, and is generally set to current. In the Linux kernel, current is equivalent to a global variable, indicating the current process. Therefore, the macro DECLARE_WAITQUEUE creates and initializes a waiting queue item for the currently running process.

3. Add/remove queue items to wait for the queue head

The waiting queue item addition API function is as follows:

void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)

Function parameters and return values have the following meanings:
q: The head of the waiting queue to which the waiting queue item is to be added.
wait: The waiting queue item to join.
Return value: None.

The wait queue item removal API function is as follows:

void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)

Function parameters and return values have the following meanings:
q: The head of the waiting queue where the waiting queue item to be deleted is located.
wait: The waiting queue item to delete.
Return value: None.

4. Waiting for wakeup

When the device can be used, it is necessary to wake up the process that enters the dormant 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 woken up, and these two functions will wake up all the processes in the head of the waiting queue. The wake_up function can wake up processes in the TASK_INTERRUPTIBLE and TASK_UNINTERRUPTIBLE states, while the wake_up_interruptible function can only wake up processes in the TASK_INTERRUPTIBLE state.

5. Waiting for events

3. Polling

1. select function

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

Function parameters and return values have the following meanings:
nfds: Among the three types of file description sets to be monitored, add 1 to the largest file descriptor.
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 all of fd_set type. Each bit of a variable of type fd_set represents 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 judge whether to time out according to the timeout parameter. You can set readfs to NULL, indicating that 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 these files for exceptions.

For example, if we want to read data from a device file now, we can define a fd_set variable, which will be passed to the parameter readfds. After we define a fd_set variable, we can use several macros as follows:

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)

FD_ZERO is used to clear all bits of the fd_set variable, FD_SET is used to set a certain position of the fd_set variable to 1, that is, to add a file descriptor to fd_set, the parameter fd is the file descriptor to join. FD_CLR is used to clear a bit of the fd_set variable, that is, to delete a file descriptor from fd_set, and the parameter fd is the file descriptor to be deleted. FD_ISSET is used to test whether a file belongs to a set, and the parameter fd is the file descriptor to be judged.

timeout: Timeout time, when we call the select function to wait for some file descriptors can set the timeout time, when timeout
Time is represented by the structure timeval, and the definition of the structure is as follows:

struct timeval {<!-- -->
long tv_sec; /* seconds */
long tv_usec; /* subtle */
};

When timeout is NULL, it means waiting indefinitely.
Return value: 0, which means a timeout occurs, but no file descriptors can be operated; -1, an error occurs; other values, the number of file descriptors that can be operated.
An example of using the select function to read non-blocking access to a device driver file is as follows:

void main(void)
{<!-- -->
int ret, fd; /* file descriptor to monitor */
fd_set readfds; /* read operation file descriptor set */
struct timeval timeout; /* timeout structure */
\t
fd = open("dev_xxx", O_RDWR | O_NONBLOCK); /* non-blocking access */
\t
FD_ZERO( & amp;readfds); /* clear readfds */
FD_SET(fd, & amp;readfds); /* add fd to readfds */
\t
/* Construct timeout */
timeout.tv_sec = 0;
timeout.tv_usec = 500000; /* 500ms */
\t
ret = select(fd + 1, & amp; readfds, NULL, NULL, & amp; timeout);
switch (ret) {<!-- -->
case 0: /* timeout */
printf("timeout!\r\
");
break;
case -1: /* error */
printf("error!\r\
");
break;
default: /* data can be read */
if(FD_ISSET(fd, & amp;readfds)) {<!-- --> /* Determine whether it is an fd file descriptor */
/* Use the read function to read data */
}
break;
}
}

2. poll function

The poll function prototype is as follows:

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

Function parameters and return values have the following meanings:
fds: The set of file descriptors to be monitored and the events to be monitored are an array, and the elements of the array are all of the structure pollfd type. The pollfd structure is as follows:

struct pollfd {<!-- -->
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* events returned */
};

fd is the file descriptor to be monitored. If fd is invalid, the events monitoring event will be invalid, and revents returns 0. events is the event to be monitored, and 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 on the file descriptor specified by POLLERR.
The file descriptor specified by POLLHUP is hung.
POLLNVAL Invalid request.
POLLRDNORM is equivalent to POLLIN

revents is the return parameter, that is, the returned event, and the specific return event is set by the Linux kernel.
nfds: The number of file descriptors to be monitored by the poll function.
timeout: timeout time in ms.
Return value: Return the number of pollfd structures in the revents field that is not 0, that is, the number of file descriptors that have events or errors; 0, timeout; -1, an error occurs, and set errno to error type.
An example of using the poll function to read non-blocking access to a device driver file is as follows:

void main(void)
{<!-- -->
int ret;
int fd; /* file descriptor to monitor */
struct pollfd fds;
\t
fd = open(filename, O_RDWR | O_NONBLOCK); /* non-blocking access */
\t
/* Construct structure */
fds.fd = fd;
fds.events = POLLIN; /* Whether the monitoring data can be read */
\t 
ret = poll( & amp;fds, 1, 500); /* Poll if the file is operational, timeout 500ms */
if (ret) {<!-- --> /* data is valid */
?…
/* read data */
?…
} else if (ret == 0) {<!-- --> /* timeout */
?…
} else if (ret < 0) {<!-- --> /* error */
?…
}
}

4. The poll operation function under Linux driver

When an application calls the select or poll function for non-blocking access to the driver, the poll function in the driver’s file_operations operation set will implement. So the driver program writer needs to provide the corresponding poll function, and the prototype of the poll function is as follows:

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

Function parameters and return values have the following meanings:
filp: The device file (file descriptor) to open.
wait: structure poll_table_struct type pointer, passed in by the application. Generally, this parameter is passed to the poll_wait function.
Return value: Return the device or resource status to the application. The resource status that can be returned is as follows:

POLLIN has data to read.
POLLPRI has urgent data to read.
POLLOUT can write data.
An error occurred on the file descriptor specified by POLLERR.
The file descriptor specified by POLLHUP is hung.
POLLNVAL Invalid request.
POLLRDNORM is equivalent to POLLIN, normal data can be read.

We need to call the poll_wait function in the poll function of the driver. The poll_wait function will not cause blocking, but just add the application to the poll_table. The prototype of the poll_wait function is as follows:

void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)

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