Linux kernel sleep and wake

Source: Internet
Author: User

Dormant (blocked) processes are in a special non-executable state. Process hibernation is caused by a variety of reasons, but must be to wait for some events. The event may be a period of time to read more data from file I/O, or a hardware event. A process is also forced into hibernation by the possibility of attempting to acquire an already occupied kernel semaphore. A common cause of hibernation is a file i/o--, such as a process performing a read () operation on a file, which needs to be read from disk. Also, the process needs to wait while getting the keyboard input. In either case, the kernel operates the same way: the process marks itself as dormant, moves out of the executable red-black tree, puts in the wait queue, and then calls schedule () to select and execute a different process. The process of awakening is the opposite: the process is set to executable state and then moved from the wait queue to the executable red-black tree.

Hibernation consists of two related process states: Task_interruptible and task_uninterruptible. The only difference is that the process in task_uninterruptible ignores the signal, while a process in the task_interruptible state receives a signal that wakes up early and signals the signal. Processes in both states are on the same waiting queue, waiting for certain events to run.

1. Waiting Queue

Hibernation is processed by waiting for a queue. The wait queue is a simple list of processes that wait for certain events to occur. The kernel uses wake_queue_head_t to represent the waiting queue. The wait queue can be created statically by Declare_waitqueue () or dynamically by Init_waitqueue_head (). The process puts itself in the waiting queue and is set to a non-executable state. Processes on the queue are awakened when events related to the wait queue occur. To avoid a competitive condition, sleep and wake-up implementations cannot be flawed.

For hibernation, some simple interfaces have been used before. However, those interfaces can lead to competitive conditions: it is possible that the process begins to hibernate after the decision condition becomes true, which causes the process to hibernate indefinitely. Therefore, the recommended operation for hibernation in the kernel is relatively complex:

    /* ' Q ' is the waiting queue we want to hibernate *      /define_wait (wait);            Add_wait_queue (q, &wait);      while (!condition) {/* ' condition ' is the event we are waiting for *              /prepare_to_wait (&q, &wait, task_interruptible);              if (signal_pending (current))/                      * Process signal *              /Schedule ();      }      Finish_wait (&q, &wait);  

The process adds itself to a wait queue by performing the following steps:

    1. call Macro define_wait () to create an item to wait for the queue.
    2. call Add_wait_queue () to join itself in the queue (List operation). The queue wakes the process as it waits for the condition to be met. Of course we have to write the code somewhere else and execute the WAKE_UP () operation on the waiting queue when the event occurs.
    3. call the Prepare_to_wait () method to change the state of the process to task_interruptible or task_ Uninterruptible. And the function will add the process back to the waiting queue if necessary, which is required in the next loop traversal.

In the above code, the function in schedule () found another process to run, the original process to sleep, when the original process to meet the conditions or to receive the signal when the state of the process will be set to task_running, when the process can be scheduled again, When the process is scheduled to execute again, then schedule () The next line of code to execute, that is, to proceed with the while loop, exit the loop if the condition is met, and execute the finish_wait () function, which sets the state of the process to task_running. and removed from the wait queue, if the process is awakened because it received a signal, and then resumes execution while the condition is not satisfied, then signal_pending (current) checks whether there is signal processing for the present process, returns no 0 to indicate that a signal needs to be processed, returns- The Erestartsys represents a system call before the signal function is re-executed after it has been processed.

void Finish_wait (wait_queue_head_t *q, wait_queue_t *wait) {unsigned long flags;  __set_current_state (task_running);    if (!list_empty_careful (&wait->task_list)) {Spin_lock_irqsave (&q->lock, flags);    List_del_init (&wait->task_list);  Spin_unlock_irqrestore (&q->lock, flags); }}

2. Wake Up

The wake-up operation is performed through the function wake_up (), which wakes up all the processes on the specified wait queue. It calls the function try_to_wake_up (), which is responsible for setting the process to the task_running state, calling Enqueue_task () to place the process into a red-black tree, setting the Need_ if the awakened process has a higher priority than the currently executing process. Resched logo. usually which code urges the wait condition to be reached, it is responsible for subsequent calls to the WAKE_UP () function . For example, when the disk data arrives, the VFS is responsible for calling WAKE_UP () on the wait queue to wake up the process waiting for the data in the queue.

There is a little need to note about sleep, there is a false wake (signal). Sometimes the process is awakened not because the condition it waits for is reached, so a loop is needed to ensure that the condition it waits for is actually reached

3. Function PrototypesSimple Hibernation

The code that completes the wake-up task must also be able to find our process in order to wake the dormant process. A data structure called a waiting queue needs to be maintained. The wait queue is a list of processes that contain all the processes waiting for a particular event.
Linux maintains a "Wait queue header" to manage, wait_queue_head_t, defined in <linux/wait.h>
struct __wait_queue_head {
wq_lock_t lock;
struct List_head task_list;
};
typedef struct __WAIT_QUEUE_HEAD wait_queue_head_t;
Initialization method:
static method:
Declare_wait_queue_head (name)
Dynamic methods:
wait_queue_head_t My_queue;
Init_waitqueue_head (&my_queue);

The simplest form of hibernation in Linux is the following macro,
Wait_event (queue, condition)/* process will be put into non-disruptive hibernation (uninterruptible sleep) */
Wait_event_interruptible (queue, condition)/* process can be interrupted by a signal sleep, return non 0 value indicates sleep is interrupted by signal */
Wait_event_timeout (queue, condition, timeout)/* Wait for a limited time jiffy,condition satisfy one of its returns 0*/
Wait_event_interruptible_timeout (queue, condition, timeout)
Queue is waiting for the header, the method of passing the value
Condition is any Boolean expression that evaluates condition multiple times before and after hibernation, and wakes up as true

The basic function of the wake process is wake_up
void Wake_up (wait_queue_head_t *queue); /* Wake up all processes waiting on a given queue */
void Wake_up_interruptible (wait_queue_head_t *queue);

In practice, wait_event and wake_up,wait_event_interruptible and wake_up_interruptible are generally used in pairs.

Advanced hibernation
To put a process into hibernation:
(1) assigning and initializing a wait_queue_t structure, which is then added to the correct wait queue

struct __wait_queue {unsigned int flags; #define Wq_flag_exclusive 0x01 void *private;        wait_queue_func_t func; struct list_head task_list;}; typedef struct __WAIT_QUEUE wait_queue_t; (2) Sets the status of the process, marked as hibernation. 2.6 Kernel, use the following function:
void set_current_state (int new_state);
There are several task states defined in <linux/sched.h>: task_running means that the process can run. There are 2 states indicating that a process is in sleep: task_interruptible and Task_untinterruptible

(3) The final step is to discard the processor. However, you must first check the conditions into hibernation. If you do not check it will introduce race: If you are busy with the above process when there are other threads just trying to wake you up, you may miss waking and sleeping for a long time. So the typical code under
if (!condition)
Schedule (); /* Call the Scheduler and let cpu*/
If the code is returned only from schedule, the process is in the task_running state. If you skip the call to schedule without sleep, you must reset the task state to task_running, and you need to remove the process from the wait queue, or it may wake up multiple times.

Manual hibernation
The above process hibernation step can be set by hand:
(1) Create and initialize a wait queue. It is often done by a macro definition:
Define_wait (my_wait);
Name is the name of the waiting queue entry. It can also be done in 2 steps:
wait_queue_t my_wait;
Init_wait (&my_wait);
A common practice is to put a define_wait at the top of the loop to achieve sleep

(2) Add the wait queue entry to the queue and set the status of the process:
void Prepare_to_wait (wait_queue_head_t *queue,
wait_queue_t *wait,
int state);
Queue and wait are respectively waiting queue headers and process portals. State is the new status of the process: task_interruptible (interruptible hibernation, recommended) or task_uninterruptible (non-disruptive hibernation, not recommended)

(3) Call schedule after checking to confirm that hibernation is still required
if (!condition)
Schedule (); /* Call the Scheduler and let cpu*/

(4) Schedule return, it is time to clean up:
void Finish_wait (wait_queue_head_t *queue, wait_queue_t *wait);

Seriously looking at the simple dormant wait_event (queue, condition) and wait_event_interruptible (queue, condition) the underlying source will find that in fact they are just a combination of functions in manual hibernation. So it's better to use wait_event for fear of trouble.

Exclusive wait
When a process calls Wake_up on the wait queue, all the processes waiting on the queue are set to run. This is the right approach in many cases. Sometimes, however, only one process that is awakened will succeed in getting the resources that are needed, while the rest will hibernate again. This can severely degrade system performance if the number of processes waiting in the queue is large. For this reason, kernel developers have added an "exclusive wait" option. It has 2 important differences from a normal sleep:
(1) When the wait queue entry is set with the Wq_flag_excluseve flag, it is added to the tail of the waiting queue; otherwise, it is added to the head.
(2) When WAKE_UP is called on a waiting queue, it stops waking after waking the first process with the WQ_FLAG_EXCLUSIVE flag. But the kernel still wakes up all the non-exclusive waits every time.
There are 2 conditions to be met with an exclusive wait:
(1) to compete effectively with resources;
(2) When a resource is available, waking up a process is sufficient to fully consume resources.
To allow a process to enter an exclusive wait, call:
void Prepare_to_wait_exclusive (wait_queue_head_t *queue, wait_queue_t *wait, int state);
Note: The wait_event and its variants cannot be used for exclusive waits.

wake-up function

It is very rare to call a wake-up function other than wake_up_interruptible, but for completeness, here is the entire collection:
WAKE_UP (wait_queue_head_t *queue);
Wake_up_interruptible (wait_queue_head_t *queue);
The wake_up wakes each non-exclusive wait process in the queue and an exclusive wait process. Wake_up_interruptible also, except that it skips a process that is in non-disruptive hibernation. They cause one or more processes to be woken up, dispatched (if they are called from an atomic context) before they are returned, and this does not occur.
WAKE_UP_NR (wait_queue_head_t *queue, int nr);
WAKE_UP_INTERRUPTIBLE_NR (wait_queue_head_t *queue, int nr);
These functions are similar to WAKE_UP, except that they can wake up to NR of exclusive waits, not just one. Note that pass 0 is interpreted as requesting that all mutually exclusive waits be awakened
Wake_up_all (wait_queue_head_t *queue);
Wake_up_interruptible_all (wait_queue_head_t *queue);
This wake_up wakes all processes, regardless of whether they are exclusively waiting (the interruptible type still skips the process of making the non-interruptible wait)
Wake_up_interruptible_sync (wait_queue_head_t *queue);
A process that is awakened may preempt the current process and be dispatched to the processor before WAKE_UP returns. However, if you need to not be dispatched out of the processor, you can use the wake_up_interruptible "Sync" variant. This function is most commonly used when the caller first completes the remaining small amount of work and does not want to be dispatched out of the processor.


Reference

Http://www.cnblogs.com/noaming1900/archive/2011/01/14/1935526.html
Http://www.cnblogs.com/noaming1900/archive/2011/01/14/1935490.html
http://blog.csdn.net/yusiguyuan/article/details/47805091
Http://www.cnblogs.com/zhuyp1015/archive/2012/06/09/2542894.html
http://guojing.me/linux-kernel-architecture/posts/wait-queue/

Linux kernel sleep and wake

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.