Waiting queue in Linux kernel-init_waitqueue_head, etc.
Waiting queue in Linux Kernel
LinuxThe kernel wait queue is based on the double-loop linked list and is closely integrated with the process scheduling mechanism to implement the core asynchronous event notification mechanism. In linux2.4.21, the waiting queue is in the source code tree include/Linux/Wait. H. This is a typical double loop linked list connected through list_head,
As shown in.
There are two data structures in this linked list: the waiting queue header (wait_queue_head_t) and the waiting queue item (wait_queue_t ). Both the waiting queue header and the waiting queue item contain a list_head domain as the "connector ". Because we only need to add and delete queues, and do not modify the objects (waiting for queue items, we only need to provide a lock to protect the entire infrastructure and all objects, which stores the lock in the waiting queue header, which is of the wq_lock_t type. In implementation, the read/write lock or spin lock can be supported and switched through a macro definition. If a read/write lock is used, wq_lock_t is defined as the rwlock_t type. If a spin lock is used, wq_lock_t is defined as the spinlock_t type. In either case, configure macros such as wq_read_lock, wq_read_unlock, wq_read_lock_irqsave, empty, wq_write_lock_irq, wq_write_unlock, wq_write_lock_irqsave, and unlock.
Waiting queue Header
Struct _ wait_queue_head {
Wq_lock_t lock;
Struct list_head task_list;
};
Typedef struct _ wait_queue_head wait_queue_head_t;
As mentioned above, the main body of the waiting queue is a process, which is reflected in each waiting queue item and is a task structure pointer (struct task_struct * task ). Flags is the wait sign for the process. Currently, only mutex is supported.
Waiting queue items
Struct _ wait_queue {
Unsigned int flags;
# Define wq_flag_exclusive0x01
Struct task_struct * task;
Struct list_head task_list;
};
Typedef struct _ wait_queue wait_queue_t;
Declaration and initialization
# Define declare_waitqueue (name, tsk) \
Wait_queue_t name = _ waitqueue_initializer (name, tsk)
# DEFINE _ waitqueue_initializer (name, tsk ){ \
Task: Tsk, \
Task_list:{Null, null }, \
_ Waitqueue_debug_init (name )}
Use the declare_waitqueue macro to initialize the waiting queue items into a corresponding task structure, and set related pointers for connection to null. Code for debugging is added.
# Define declare_wait_queue_head (name )\
Wait_queue_head_t name = _ wait_queue_head_initializer (name)
# DEFINE _ wait_queue_head_initializer (name ){ \
Lock: Waitqueue_rw_lock_unlocked, \
Task_list:{& (Name). task_list, & (name). task_list },\
_ Waitqueue_head_debug_init (name )}
Use the declare_wait_queue_head macro to initialize a waiting queue header, empty its linked list, and set the linked list to "Unlocked" status. Code for debugging is added.
Static inline void init_waitqueue_head (wait_queue_head_t * q)
This function initializes an existing waiting queue header, sets the entire queue to "Unlocked" state, and points the linked list pointer Prev and next to itself.
{
Q-> lock = waitqueue_rw_lock_unlocked;
Init_list_head (& Q-> task_list );
}
Static inline void init_waitqueue_entry (wait_queue_t * q, struct task_struct * P)
This function initializes an existing waiting queue item. It sets the corresponding task structure and clears the flag 0.
{
Q-> flags = 0;
Q-> task = P;
}
Static inline int waitqueue_active (wait_queue_head_t * q)
This function checks whether the waiting queue is empty.
{
Return! List_empty (& Q-> task_list );
}
Static inline void _ add_wait_queue (wait_queue_head_t * head, wait_queue_t * New)
Add the specified waiting queue item new to the head of the linked list where the waiting queue head is located. This function is assumed to have obtained the lock.
{
List_add (& New-> task_list, & head-> task_list );
}
Static inline void _ add_wait_queue_tail (wait_queue_head_t * head, wait_queue_t * New)
Add the specified waiting queue item new to the end of the linked list where the waiting queue head is located. This function is assumed to have obtained the lock.
{
List_add_tail (& New-> task_list, & head-> task_list );
}
Static inline void _ remove_wait_queue (wait_queue_head_t * head, wait_queue_t * old)
Delete the specified waiting queue item old from the linked list where the waiting queue head is located. This function assumes that the lock has been obtained and the old is in the linked list where the head is located.
{
List_del (& old-> task_list );
}
Sleep and wake-up operations
Operations on the waiting queue include sleep and wakeup (related functions are stored in/kernel/sched. C and include/Linux/sched. H of the source code tree ). The idea is to change the task status of the current process and request rescheduling, because the status of this process has changed and is no longer in the ready queue of the scheduling table, therefore, the task status cannot be changed back to the ready state.
Common sleep operations include interruptible_sleep_on and sleep_on. The two functions are similar, except that the former sets the state of the process from the ready state (task_running) to task_interruptible, and allows it to be awakened by sending signal (the sleep state that can be interrupted ); the latter sets the Process status to task_uninterruptible, in which state, no singal is received.
Take interruptible_sleep_on as an example. The expanded code is:
Void interruptible_sleep_on (wait_queue_head_t * q)
{
Unsigned long flags;
Wait_queue_t wait;
/*Construct the waiting queue items corresponding to the current process
*/
Init_waitqueue_entry (& wait, current );
/* Change the status of the current process from task_runningTask_interruptible */
Current-> state = task_interruptible;
/* Add the waiting queue item to the specified linked list
*/
Wq_write_lock_irqsave (& Q-> lock, flags );
_ Add_wait_queue (Q, & wait );
Wq_write_unlock (& Q-> lock );
/* Process rescheduling and giving up the execution right
*/
Schedule ();
/* This process is awakened and re-authorized. The first thing is to delete the waiting queue items from the linked list.
*/
Wq_write_lock_irq (& Q-> lock );
_ Remove_wait_queue (Q, & wait );
Wq_write_unlock_irqrestore (& Q-> lock, flags );
/* At this point, wait until the process ends and the process can normally execute the following logic
*/
}
The corresponding wake-up operations include wake_up_interruptible and wake_up. The wake_up function can not only wake up a process in the task_uninterruptible state, but also wake up a process in the task_interruptible state. Wake_up_interruptible is only responsible for awakening the process in the task_interruptible state. The two macros are defined as follows:
# Define wake_up (X) _ Wake_up (x), task_uninterruptible | task_interruptible, 1)
# Define wake_up_interruptible (X)_ Wake_up (x), task_interruptible, 1)
_ Wake_upThe function is mainly used to obtain the Lock of queue operations. The specific task is to call _ wake_up_common.
Void _ wake_up (wait_queue_head_t * q, unsigned int mode, int nr)
{
If (q ){
Unsigned long flags;
Wq_read_lock_irqsave (& Q-> lock, flags );
_ Wake_up_common (Q, mode, NR, 0 );
Wq_read_unlock_irqrestore (& Q-> lock, flags );
}
}
/* The core Wakeup function.Non-exclusive wakeups (nr_exclusive = 0) Just wake everything up.If it's an exclusive wakeup (nr_exclusive = Small + ve number) Then we wake all the non-exclusive tasks and one exclusive task.
There are circumstances in which we can try to wake a task which has already started to run but is not in State task_running.Try_to_wake_up () returns zero in this (rare) case, And we handle it by contonuing to scan the queue .*/
Static inline void _ wake_up_common (wait_queue_head_t * q, unsigned int mode, int nr_exclusive, const int sync)
The Q Parameter indicates the waiting queue to be operated, and the mode indicates the status of the task to be awakened, such as task_uninterruptible or task_interruptible. Nr_exclusive is the number of mutex processes to be awakened, and non-mutex processes encountered before this will be unconditionally awakened. Sync indicates ???
{
Struct list_head * TMP;
Struct task_struct * P;
Check_magic_wqhead (Q );
Wq_check_list_head (& Q-> task_list );
/* Traverse the waiting queue
*/
List_for_each (TMP, & Q-> task_list ){
Unsigned int state;
/* Get the current waiting queue item
*/
Wait_queue_t * curr = list_entry (TMP, wait_queue_t, task_list );
Check_magic (curr->__ magic );
/* Obtain the corresponding process
*/
P = curr-> task;
State = p-> state;
/* If we need to process this state
*/
If (State & mode ){
Wq_note_waker (curr );
If (try_to_wake_up (p, sync) & (curr-> flags & wq_flag_exclusive )&&! -- Nr_exclusive)
Break;
}
}
}
/* Wake up a process and put it in the running queue if it is not in the running queue. "Current" process is always in the running Queue (when T when the actual re-schedule is in progress), and as such you're allowed
To do the simpler "Current-> state = task_running" to mark yourself runnable without the overhead of this .*/
Static inline int try_to_wake_up (struct task_struct * P, int synchronous)
{
Unsigned long flags;
Int success = 0;
/* Because we need to operate the running queue, the corresponding lock must be obtained.
*/
Spin_lock_irqsave (& runqueue_lock, flags );
/* Set the process statusTask_running */
P-> state = task_running;
/* If the process is already in the running queue, release the lock and exit.
*/
If (task_on_runqueue (p ))
Goto out;
/* Otherwise, add the process to the running queue.
*/
Add_to_runqueue (P );
/* If the synchronization flag is set
*/
If (! Synchronous |! (P-> cpus_allowed & (1ul <smp_processor_id ())))
Reschedule_idle (P );
/* Wake up successfully. Release the lock and exit.
*/
Success = 1;
Out:
Spin_unlock_irqrestore (& runqueue_lock, flags );
Return success;
}
Waiting queue Application Mode
The application waiting for the queue involves two processes, A and B. A is the consumer of resources, and B is the producer of resources. A must ensure that the resource has been produced during consumption, so define a resource waiting queue. This queue is used by both process a and process B. We can define it as a global variable.
Declare_wait_queue_head (rsc_queue );/*
Global Variables
*/
In process A, the execution logic is as follows:
While (resource is unavaiable ){
Interruptible_sleep_on (& WQ );
}
Consume_resource ();
In process B, the execution logic is as follows:
Produce_resource ();
Wake_up_interruptible (& WQ );