Blocking sleep implementation mechanism and blocking sleep Mechanism
Before looking at the implementation mechanism of blocking sleep, let's look at the waiting queue that is widely used in the kernel.
The waiting queue of the Linux kernel is a double-loop linked list structure, which is closely integrated with the process scheduling mechanism and can be used to implement the core asynchronous event notification mechanism. It has two data structures: waiting queue header (wait_queue_head_t) and waiting queue item (wait_queue_t ). Both the waiting queue header and the waiting queue item contain a list_head (double-stranded table ). Use such a double-stranded table to link the waiting process.
The data structure of the two is as follows:
Struct _ wait_queue_head {spinlock_t lock; // spin lock to achieve mutually exclusive access to the queue struct list_head task_list; // bidirectional cyclic linked list to store the waiting process .}; Typedef struct _ wait_queue_head train; struct _ wait_queue {unsigned int flags; void * private; inclufunc; struct list_head task_list;}; typedef struct _ wait_queue wait_queue_t;
We know that IO is blocked by default (unless O_NONBLOC is set). If a process calls read but no data is available, the process must be blocked. this process is immediately awakened when data reaches and the data is returned to the caller. Otherwise, if a process calls write and has no space in the buffer, the process must be blocked, and it must be in a different waiting queue than the one used for read. when some data is written to the hardware device and the space in the output buffer becomes idle, the process is awakened and the write call is successful.
Here is an example of a read operation:
Static ssize_t scull_p_read (struct file * filp, char _ user * buf, size_t count, loff_t * f_pos) {struct scull_pipe * dev = filp-> private_data; if (down_interruptible (& dev-> sem) // lock return-ERESTARTSYS; while (dev-> rp = dev-> wp) // nothing readable {up (& dev-> sem); // unlock if (filp-> f_flags & O_NONBLOCK) // return-EAGAIN immediately in non-blocking mode; // block access and wait for sleep until the read conditions are met. If (wait_event_interruptible (dev-> inq, (dev-> rp! = Dev-> wp) return-ERESTARTSYS; if (down_interruptible (& dev-> sem) // re-lock return-ERESTARTSYS;} // read data. ...... Up (& dev-> sem); wake_up_interruptible (& dev-> outq); return count ;}
From the preceding example, we can see that wait_event_interruptible () is called to implement blocking and waiting. Let's take a look at the wait_event_interruptible () implementation.
# Define wait_event_interruptible (wq, condition) ({int _ ret = 0; if ((! Condition) _ wait_event_interruptible (wq, condition, _ ret); _ ret;}) # define _ wait_event_interruptible (wq, condition, _ ret) do {DEFINE_WAIT (_ wait); // defines the waiting queue _ wait for (;) {prepare_to_wait (& wq, & __ wait, TASK_INTERRUPTIBLE ); // Add the wait queue _ wait to the wq-headed waiting queue linked list, and set the process status to TASK_INTERRUPTIBLE if (condition) // if the condition is met, the break is thrown out; if (! Signal_pending (current) {// schedule () is not awakened by the signal; // discard the CPU and schedule other processes to execute continue;} ret =-ERESTARTSYS; break ;} finish_wait (& wq, & __ wait); // remove the wait queue _ wait from the waiting queue linked list pointed by the wq header, set the Process status to TASK_RUNNING} while (0)
To sum up, the steps for blocking sleep are generally:
1) assign and initialize a wait_queue_t structure, and then add it to the correct waiting queue. when everything is in place, the person in charge of wakeup can find the correct process.
2) set the process status to mark it as sleep (TASK_UNINTERRUPTIBLE, TASK_INTERRUPTIBLE)
3) call schedule () to let out the CPU.
Copyright Disclaimer: This article is an original article by the blogger and cannot be reproduced without the permission of the blogger.