A blocking operation means that when a device operation fails to obtain the resource, the process suspends until the operation meets the operational conditions. A non-blocking process does not stop when it cannot perform device operations. The suspended process enters the sleep state and is removed from the running queue of the scheduler until the waiting conditions are met.
In Linux
For the above routines, we add that if you change the READ function in the driver:
Static ssize_t globalvar_read (struct file * filp, char * Buf, size_t Len, loff_t * Off) { // Obtain the semaphore: It may be blocked. If (down_interruptible (& SEM )) { Return-erestartsys; }// Wait for data to be obtained: blocking may occur. If (wait_event_interruptible (outq, flag! = 0 )) { Return-erestartsys; } Flag = 0; // Critical resource access If (copy_to_user (BUF, & global_var, sizeof (INT ))) { Up (& SEM ); Return-efault; } // Release the semaphore Up (& SEM ); Return sizeof (INT ); } |
That is, wait_event_interruptible (outq, flag! = 0) and down_interruptible (& SEM) Order, this driver will become unavailable. In fact, when two events that may be blocked occur at the same time, that is, when two wait_event or down events are put together, it will become very dangerous and there is a high possibility of deadlock, at this time, we should pay special attention to the order in which they appear. Of course, we should try our best to avoid this situation!
+ There is also a topic that is closely related to device blocking and non-blocking access, that is, select and poll are essentially the same. The former is introduced in bsd unix, and the latter is introduced in System V. Poll and select are used to query the status of the device so that the user program can know whether non-blocking access can be made to the device. They all need the support of the poll function in the device driver.
The most important API used in the driver's poll function is poll_wait. Its prototype is as follows:
Void poll_wait (struct file * filp, wait_queue_heat_t * queue, poll_table * Wait ); |
The poll_wait function adds the current process to the waiting list (poll_table) specified by the wait parameter. Next we will add a poll function to the driver of globalvar:
Static unsigned int globalvar_poll (struct file * filp, poll_table * Wait) { Unsigned int mask = 0;Poll_wait (filp, & outq, wait ); // Is the data available? If (flag! = 0) { Mask | = Pollin | pollrdnorm; // indicates that data is available. } Return mask; } |
It should be noted that the poll_wait function is not blocked. The poll_wait (filp, & outq, wait) Statement in the program does not mean that the program has been waiting for the outq semaphore to be available, the true blocking action is completed in the upper select/poll function. Select/poll calls their own poll support functions for each device to be monitored in a loop so that the current process is added to the wait list of each device. If no listening device is ready, the kernel will schedule (call schedule) so that the CPU enters the blocking state. When schedule returns, it will cyclically check whether any operation can be performed; otherwise, select/poll returns immediately if any device is ready.
We compile a user-state application to test the modified driver. The select function introduced in bsd unix is used in the program. Its prototype is:
Int select (INT numfds, fd_set * readfds, fd_set * writefds, fd_set * limit TFDs, struct timeval * timeout ); |
Readfds, writefds, and limit TFDs are the collection of read, write, and exception handling file descriptors monitored by select (). The value of numfds is the file descriptor with the highest number to be checked plus 1. The timeout parameter is a pointer to the struct timeval type, which enables select () to return if no file descriptor is ready after waiting for the timeout time. Struct timeval data structure:
Struct timeval { Int TV _sec;/* seconds */ Int TV _usec;/* microseconds */ }; |
In addition, we will also use the following APIs:
Fd_zero (fd_set * Set)-clears a file descriptor set;
Fd_set (int fd, fd_set * Set)-adds a file descriptor to the file descriptor set;
Fd_clr (int fd, fd_set * Set)-clears a file descriptor from the file descriptor set;
Fd_isset (int fd, fd_set * Set)-determines whether the file descriptor is set.
The following user-state test program waits for/dev/globalvar to read, but sets a 5-second wait timeout. If no data is readable for more than 5 seconds, output "no data within 5 seconds ":
# Include <sys/types. h> # Include <sys/STAT. h> # Include <stdio. h> # Include <fcntl. h> # Include <sys/time. h> # Include <sys/types. h> # Include <unistd. h>Main () { Int FD, num; Fd_set rfds; Struct timeval TV; FD = open ("/dev/globalvar", o_rdwr, s_irusr | s_iwusr ); If (FD! =-1) { While (1) { // Check whether globalvar has input. Fd_zero (& rfds ); Fd_set (FD, & rfds ); // Set the timeout value to 5 s. TV. TV _sec = 5; TV. TV _usec = 0; Select (FD + 1, & rfds, null, null, & TV ); // Is the data available? If (fd_isset (FD, & rfds )) { Read (FD, & num, sizeof (INT )); Printf ("The globalvar is % d/N", num ); // Enter 0 to exit If (num = 0) { Close (FD ); Break; } } Else Printf ("no data within 5 seconds./N "); } } Else { Printf ("device open failure/N "); } } |
Start two terminals and run the program respectively: one for writing globalvar and the other for reading globalvar. When we input a value to globalvar on the write terminal, the read terminal can output the value immediately. When we do not input the value for five consecutive seconds, "No data within 5 seconds" is output on the reading terminal, for example: