Linux kernel wait queue mechanism principle analysis

  
1.
Wait queue data structure

The wait queue is implemented by a doubly linked list whose elements include pointers to process descriptors. Each wait queue has a wait queue head, which is a data structure of type wait_queque_head_t:
struct __wait_queue_head {
spinlock_t lock;
struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;
where lock is used to prevent concurrent access, and the task_list field is the head of the waiting list.

element type queues waiting for the list wait_queue_t, we can call wait queue entry:
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;
Each wait queue item represents a sleep process that waits for an event to occur. Its descriptor address is usually placed in the private field. The Task_list field contains a pointer that links an element to the process list waiting for the same event.
The func field of the wait queue element is used to indicate how the sleep process should wake up in the wait queue (mutually exclusive and non-mutually).

entire queue structure as shown below:


See below waiting queue works. .

2
during sleep queue before the wait queue

typically used to define a waiting queue head: static wait_queue_head_t wq, and then calls the function wait_event_ * The current process waiting for a condition condition is inserted into the waiting queue wq and sleeps, and waits until the condition condition is satisfied, and the kernel wakes up a certain process or all processes sleeping on the waiting queue wq.

definition of wait queue head nothing to talk about, following calls from wait_event_ * start analyzing:
Here we give the more common wait_event_interruptible:
/**
* wait_event_interruptible - sleep until a condition Gets true
* @wq: the waitqueue to wait on
* @condition: a C expression for the event to wait for
*
* The process is put to sleep (TASK_INTERRUPTIBLE) until the< Br>* @condition evaluates to true or a signal is received.
* The @condition is checked each time the waitqueue @wq is woken up.
*
* wake_up() has to be called after changing Any variable that could
* change the result of the wait condition.
*
* The function will return -ERESTARTSYS if it was interrupted by a
* signal and 0 if @condition evaluated to true.
*/
#define wait_event_interruptible(wq, condition) \\
({ \\
int __ret = 0; \\
if (!(condition)) \\
__wait_event_interruptible(wq, Condition, __ret); \\
__ret; \\
})
This is very simple, judge the condition Whether it is satisfied, if not satisfied, call the __wait_event_interruptible function.

#define __wait_event_interruptible (wq, condition, ret) \\
do {\\
DEFINE_WAIT (__ wait); \\
\\
for (;;) {\\
prepare_to_wait (&wq, &__wait, TASK_INTERRUPTIBLE); \\
if (condition) \\
break; \\
if (!signal_pending(current)) { \\
schedule(); \\
continue; \\
} \\
ret = -ERESTARTSYS; \\
break; \\
} \\
finish_wait(&wq, &__wait); \\
} while ( 0)

__wait_event_interruptible wait_queue_t first defines a type of queue entries __wait:
#define DEFINE_WAIT (name) \\
wait_queue_t name = {\\
.private = current, \\ < Br>.func = autoremove_wake_function, \\
.task_list = LIST_HEAD_INIT((name).task_list), \\
}
You can find the private member of __wait (usually used to store the process descriptor) It has been initialized to current, indicating that the waiting queue entry corresponds to the current process. The func member is the wakeup function corresponding to the wait queue entry. After the process is woken up, it will execute it and has been initialized to the default autoremove_wake_function function.

prepare_to_wait then calls the function in a for (;;) cycle:
void fastcall prepare_to_wait (wait_queue_head_t * q, wait_queue_t * wait, int state)
{
unsigned long flags; < Br>
wait->flags &= ~WQ_FLAG_EXCLUSIVE;
spin_lock_irqsave(&q->lock, flags);
if (list_empty(&wait->task_list))
__add_wait_queue(q, wait);
/*
* don't alter the task state if this is just going to
* queue an async wait queue callback
*/
if ( Is_sync_wait(wait))
set_current_state(state);
spin_unlock_irqrestore(&q->lock, flags);
}
prepare_to_wait does two things, will be the previously defined wait queue entry __wait is inserted into the wait queue header wq, and then the current process is set to the TASK_INTERRUPTIBLE state. After the preparation_to_wait is executed, check if the condition is satisfied. If it happens to be satisfied at this time, it is not necessary to sleep. If you have not met, you are ready to sleep.

sleep is done by calling schedule () function, as has previously been set to the current process TASK_INTERRUPTIBLE state, thus here then execute schedule (), then carry out the process of switching, and then it will never again dispatched to the process Runs until the process is woken up (ie changed to TASK_RUNNING state).
This will determine if there is any signal before executing the schedule() switch process. If there is, it will return ERESTARTSYS immediately. If not, execute schedule() to sleep.

for (;;) loop effect is to allow the process to be awakened once again to check whether the condition is satisfied. The main reason is to prevent multiple processes on the waiting queue from being awake at the same time. It is possible that other processes have preempted the resources to occupy the resources in the past and become unavailable. Therefore, it is best to judge. (Of course, the kernel also provides a wake-up only one or more processes (exclusive wait for the process) the way, are interested can refer to relevant information)

process wakes up after the last step is to call finish_wait (& wq, The &__wait) function performs cleanup. Finish_wait sets the state of the process to TASK_RUNNING again and removes the process from the wait queue.
void fastcall finish_wait (wait_queue_head_t * q, wait_queue_t * wait)
{
unsigned long flags;

__set_current_state (TASK_RUNNING);

if (list_empty_careful (& amp!; Wait->task_list)) {
spin_lock_irqsave(&q->lock, flags);
list_del_init(&wait->task_list);
spin_unlock_irqrestore(&q->lock , flags);
}
}

again is returned after your previous call wait_event_interruptible (wq, condition) is blocked in place to continue down.
Copyright © Windows knowledge All Rights Reserved