Synchronization Mechanism of Linux kernel: Waiting For queue
The waiting queue of Linux kernel is based on the double-loop linked list and closely integrates 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 dual-loop linked list connected by 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. It is a pointer (struct task_struct * task) pointing to the task_struct structure of the process ). Flags indicates the status of the current process for the process. The status of the waiting process must be set to interruptible or uninterruptible.
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 to the corresponding process structure task, 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 so that its linked list is empty (that is, the waiting queue does not have a waiting queue item) and set the linked list to "Unlocked. 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 (process) 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_running to task_interruptible */
Current-> state = task_interruptible;
/* Add the waiting queue entry to the specified linked list */
Wq_write_lock_irqsave (& Q-> lock, flags); // get the lock, close the locks, and keep the status word in flags.
_ Add_wait_queue (Q, & wait );
Wq_write_unlock (& Q-> lock );
/* Re-schedule the process and discard 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); // release the lock, open the interrupt, and restore the status from the 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)
The _ wake_up 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 );
}
}
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 representation?
{
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. "Currently" the process is always in the running Queue (when T when the actual re-schedule is in progress ), and as such you're 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 status to task_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 variable */
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 );