/** * Author:hasen * Reference: Linux device Driver Development Details * Introduction: Android Small rookie Linux * device driver Development Learning Journey * Topic: blocking and non-blocking I/o * date:2014-11-05 * * Blocking An action is when a device operation is performed, and if a resource is not available, the process is suspended until the operational conditions are met. The suspended process goes into hibernation and is removed from the running queue of the scheduler until the waiting condition is met. The non-blocking process does not hang when it is unable to operate the device, it either discards or keeps querying until the condition is satisfied to operate. Drivers often need to provide such a capability, when the application makes system calls such as read (), write (), if the device's resources are not available, and the user wants to access the device in a blocking manner, the driver should be in device-driven xxx_read (), Xxx_write () The process is blocked until the resource is available, and the application's read (), write () calls are returned, the entire process still has the correct device access, the user is unaware, and if the user accesses the device file in a non-blocking manner, the device-driven xxx_ Operations such as read (), Xxx_write () should return immediately, and system calls such as read (), write () are returned. The blocked process goes into hibernation and needs to have a place to wake up the dormant process. The most likely wake-up process is inside the interrupt. The following code snippet shows the code that reads a single character in a blocking and non-blocking manner, respectively. In the actual serial programming, if the non-blocking mode is used, the serial port can be accessed asynchronously by means of the signal (sigaction) to improve the CPU utilization. Code Snippet 1: block read serial port one character char buf; fd = open ("/dev/ttys1", O_RDWR); /* Open the serial port */...res = Read (fd,&buf,1) in a blocked manner; /* return */if (res = = 1) printf ("%c\n", buf) when there is input on the serial port, code Snippet 2: Read the serial port non-blocking character char buf; fd = open ("/dev/ttys1", o_rdwr| O_nonblock)/* Open the serial port */...while (read (fd,&buf,1) = = 1) Continue in a non-blocking manner; /* No input on the serial port also returns, so to loop try to read serial */printf ("%c\n", buf); Waiting queue Linux drivers, you can use the wait teamColumn (wait queue) is a wake-up process that implements blocking. Wait queue is very early as a basic functional unit in the Linux kernel, it is a queue-based data structure, and the process scheduling mechanism tightly integrated, can be used to implement the kernel of the asynchronous event notification mechanism. The wait queue can be used to synchronize access to system resources. The semaphore is implemented in the kernel relying on the wait queue. The wait queue operation is as follows: (1) define "Wait queue header" wait_queue_head_t My_queue;(2) Initialize "Wait queue header" Init_waitqueue_head (&my_queue); The following macro enables you to define and initialize the wait queue header Declare_wait_queue_head (name) (3) to define the wait queue the macro is used to define and initialize a wait queue named name Declare_waitqueue (Name,tsk) (4) Add/Remove wait queue void Fastcall add_wait_queue (wait_queue_head_t *q,wait_queue_t *wait); void Fastcall Remove_wait_queue ( wait_queue_head_t *q,wait_queue_t *wait);/*add_wait_queue is used to add wait queue wait to the wait queue list that the wait queue header Q points to, and Remove_wait_ The queue is used to remove the wait queue wait from the waiting queue list that is pointed to by the secondary waiting team header Q/(5) Wait event wait_event (queue,condition); wait_event_interruptible (Queue, condition); Wait_event_timeout (queue,condition,timeout); Wait_event_interruptible_timeout (Queue,condition, timeout); Waits for the first parameter queue to be woken up as a waiting queue for the queue header, and the second parameter condition must be satisfied, otherwise it continues to block. The difference between wait_event () and wait_event_interruptible () is that the latter can be interrupted by a signal, while the former cannot. The addition of timeout means that the timeout for blocking waits, in Jiffy, is returned when a timeout of the third parameter arrives, regardless of whether the condition is satisfied. (6) Wake-up queue void Wake_up (wait_queue_head_t *queue); void wake_up_interruptible (wait_queue_head_t *queue); This will wake up the queue as a queue for all waiting queues in the wait queue that belong to the waiting queue header for the waiting queue corresponding to the process. Wake_up and Wait_event or wait_event_timeout paired use, wake_up_interruptible and wait_event_interruptible or Wait_event_ Interruptible_timeout is used in pairs, wake_up can awaken processes in task_interruptible and tash_uninterruptible, while Wait_event_ Interruptible can only wake up the process in task_interruptible. (7) on the waiting queue sleep sleep_on (wait_queue_head_t *q); interruptible_sleep_on (wait_queue_head_t *q); sleep_on () The function is to set the current process state to task_uninterruptible, define a wait queue, and then subordinate it to the wait queue Q until the resource is available or available, and the Q-guided wait queue is awakened. The role of the interruptible_sleep_on () function is to set the current process state to task_uninterruptible and define a wait queue, which is then attached to the wait queue Q until the resource is available or obtained. The Q-boot queue is awakened or the process receives a signal. Among the many Linux drivers, sleep_on () or interruptible_sleep_on () is not called, but the process state is adapted and switched directly. code example: Changing the process state in the driver code and calling schedule () static int xxx_write (struct file *filp,const char *buffer,size_t count,loff_t *ppos) {... Declare_waitqueue (wait,current);/* Define wait Queue */add_wait (&xxx_wait,&wait);/* Add wait queue */ret = count;/* Wait for device buffer writable */ Do{avail = device_writable (...); if (AVAIl < 0) __set_current_state (task_interruptible);/* Change process status */if (Avail < 0) {if (File->f_flags & O_nonoblock) { /* Non-blocking */if (!ret) ret =-eagain; goto out;} Schedule ();/* Schedule Other processes to execute */if (signal_pending (current)) {/* If the signal wakes up */if (!ret) ret =-erestartsys; goto out;}}} while (avail<0);/* Write device buffers */device_write (...); Out:remove_wait_queue (&xxx_wait, &wait);/* will wait for the queue to move out of the waiting queue header */ Set_current_state (task_running);/* Set process status to task_runnnig*/} The above code example is important for understanding process switching, so it should be read several times until fully understood, here are a few points: (1) If non-blocking access (O _nonoblock is set), when the device is busy, return "-eagain" directly. (2) For blocking access, the state is toggled and explicitly dispatched by "schedule ()" To schedule other processes to execute. (3) When you wake up, be aware that the process is task_interruptible, that is, a shallow sleep, because it is dispatched, so it is possible to wake it up with a signal, so we first know by "signal_pending" whether the signal is awakened, If it is, return to "-erestartsys" immediately. Polling operations in the user program, select () and poll () are also topics that are closely related to device blocking and non-blocking access. Applications that use non-blocking I/O typically use the Select () and poll () system call queries for non-blocking access to the device. The Select () and poll () system calls will eventually cause the poll () function in the device driver to be executed. Epoll () is an extended poll (). The most widely used application is the Select () system call, whose function prototype is: int select (int numfds,fd_set *readfds,fd_set *writefds,fd_set * Exceptfds,struct timeval *timeout);, Readfds,writefds,exceptfds is a collection of file descriptors for read, write, and exception handling that are monitored by select (), and the Numfds value is the highest number of file descriptions that need to be checked multibyte the 1,timeout parameter is a pointer to a struct A pointer of type Timeval that enables select () to return if no file descriptor is ready after waiting for timeout time. The struct timeval is defined as follows: struct timeval{int tv_sec;/* seconds */int tv_usec;/* microseconds */}; The following actions are used to set, clear, and determine the collection of file descriptors: Fd_zero (Fd_set *set) ;/* Clears a file description descriptor */fd_set (int fd, fd_set *set);/* Adds a file descriptor to the file Description descriptor */fd_clr (int fd, fd_set *set);/* Clears a file descriptor from the file descriptor set */FD _isset (int fd, fd_set *set);/* Determine if the file descriptor is set/* The prototype of the poll () function in the polling programming device driver in device driver is: unsigned int (*poll) (struct poll_table *wait) The first argument is the file struct pointer, and the second argument is the polling table pointer. This function does two tasks: (1) call the Poll_wait () function on the waiting queue that may cause changes in the device file state, and add the corresponding wait queue header to the poll_table. (2) Returns a mask that indicates whether the device is capable of non-blocking read, write access. The key prototype for the poll_wait () function to register the wait queue to the poll_table is as follows: void poll_wait (struct file *filp,wait_queue_head_t *queue,poll_table * Wait);p the name of the oll_wait () function is very easy to misunderstand, thinking that it and wait_event (), and so on, will be blocked to wait for a time to occur, in fact, this function does not cause blocking. The work of the poll_wait () function is to add the current process to the wait list (poll_table) specified by the wait parameter. The driver poll () function should return the available state of the device resource, which is the bit "or" result of a macro such as Pollin, Pollout, Pollpri, Pollerr, Pollnval, and so on. Each macro is meant to indicate a state of the device, such as Pollin (defined as 0x0001) means that the device can read without blocking, pollout (defined as 0x0004) means that the device can be written without blocking. The typical template for the poll () function is static unsigned int xxx_poll (struct file *filp,poll_table *wait) {unsigned int mask = 0; struct Xxx_dev *dev = Filp->private_data;/* Gets the device struct pointer */...poll_wait (filp,&dev->r_wait,wait);/* Read wait queue header */poll_wait (filp,& DEV->W_WAIT,WAIT);/* Write wait queue header */if (...)/* readable */mask |= Pollin | */* Pollrdnorm data can be obtained */if (...)//writable */mask |= pollout | Pollwrnorm;/* Marking data can be written to */...return mask;} Summary: Blocking and non-blocking access are two different modes of I/O operations, which let the process sleep while the operation is temporarily unavailable, while the latter is not. Blocking I/O in device drivers is generally implemented based on the wait queue, which can be used to sequence the events in the synchronization drive. Applications using non-blocking I/O can also use polling functions to query whether a device can be accessed immediately, user space calls the Select () and poll () interfaces, and device drivers provide the poll () function. The device-driven poll () itself does not block, but the poll () and select () system calls are blocked waiting for at least one of the file descriptor collections to be accessible or timed out.
Hasen Linux device Driver Development learning journey-blocking and non-blocking I/O