A few days ago, when I saw the driver, I encountered a waiting queue. I went online to search for the queue and checked it with the code. in kernel, wait_queue is widely used. wait_queue is used for implement in device driver and semaphore. Therefore, it is a basic data structure in the kernel.
First, we must understand that all processes in Linux are managed by the task_struct structure. A task_struct structure will be allocated when a process is generated, and processes will be managed through this structure. The task_struct structure exists in a flat address space. At any time, the Linux kernel can refer to all management intelligence of all processes. The kernel stack is also located in a flat address space. (Flat means "Independent Continuous intervals ")
The following are the main members of tesk_struct:
--------------------------------------------------------------------------------
Struct task_struct {
Struct files_struct * files; // file descriptor
Struct signal_struct * SIG; // signal control signal handler
Struct mm_struct * mm; // Memory Management Module
Long STAT // Process status
Struct list_head runlist; // used to join the run queue
Long priority; // basic priority
Long counter; // change priority
Char comm []; // command name
Struct thread_struct TSS; // context storage domain
...
};
Now we only need to know the state in it. The State has the following states:
Status description
Task_running execution status
Task_interruptible wait status. Acceptable Signal
Task_uninterruptible wait status. Unacceptable Signal
Task_zombie botnets. Status after exit
Task_stopped delay status
We need to know that there is no multi-process in the kernel, and there is only one process (smp is not clear), which is different from user space. in the user space, we can make a process run while (1), other processes can also be used, but not in the kernel, the reason is above.
Suppose we generate a buffer in the kernel. the user can read or write data to the buffer through system call such as read and write. If a user writes data to the buffer, the buffer is full. How do you deal with this situation? First, an error message is sent to the user, indicating that the buffer is full and cannot be written. Second, block the user's requirements. When someone reads the buffer content and leaves a blank space, the user is allowed to write data. But the question is, how do you block user requirements. Do you want to use
While (is_full );
Write_to_buffer;
Is such a program code? Think about what will happen if you do this? First, the kernel will always be executed in this while. Second, if the kernel is always executed in this while, it indicates that it cannot operate the maintain system. Then the system is equivalent to dropping. Here, is_full is a variable. Of course, you can make is_full a function. In this function, you will do other things to make the kernel operational, so the system will not. This is a method. Also, you can read the content in the buffer while, and then change the value of is_full, however, important data may be read when we do not want to be read. This is troublesome and inflexible. if we use wait_queue, the program looks pretty and understandable, as shown below:
Struct wait_queue_head_t WQ;/* global variable */
Declare_wait_queue_head (WQ );
While (is_full ){
Interruptible_sleep_on (& WQ );
} Write_to_buffer ();
Interruptible_sleep_on (& WQ) is used to put the current process, that is, the process that requires data writing to the buffer into the wait_queue of WQ. In interruptible_sleep_on, schedule () will be called to perform schedule. Whoever calls schedule will get down and let others run it. When they wake up, they will get started and execute schedule () code. So what did the guy who called schedule wake up? At this time, we need to use another function, wake_up_interruptible (), as shown below:
If (! Is_empty ){
Read_from_buffer ();
Wake_up_interruptible (& WQ );
}
This is the use of wait_queue, which is quite understandable. How does wait_queue work? Wait_queue_head_t is a simple structure, in which the Code is as follows:
--------------------------------------------------------------------------------
Struct _ wait_queue_head {
Wq_lock_t lock;
Truct list_head task_list;
# If waitqueue_debug
Long _ magic;
Long _ creator; # endif
};
Typedef struct _ wait_queue_head wait_queue_head_t;
Task_list is a linked list of sleep processes. The data items in the linked list are of the wait_queue_t type, and the linked list is the universal linked list defined in. The wait_queue_t code is as follows:
Struct _ wait_queue {
Unsigned int flags;
# Define wq_flag_exclusive 0x01
Struct task_struct * task;
Struct list_head task_list;
# If waitqueue_debug
Long _ magic;
Long _ waker; # endif
};
Typedef struct _ wait_queue wait_queue_t;
In fact, the main structure is wait_queue_t. Let's take a look at the code of interruptible_sleep_on. The Code is as follows:
--------------------------------------------------------------------------------
# Define sleep_on_var/
Unsigned long flags ;/
Wait_queue_t wait ;/
Init_waitqueue_entry (& wait, current); // use the current process to generate a wait_queue_t
# Define sleep_on_head/
Spin_lock_irqsave (& Q-> lock, flags );/
_ Add_wait_queue (Q, & wait); // put wait at the beginning of the wait_queue_t list to which Q belongs
Spin_unlock (& Q-> lock );
# Define sleep_on_tail/
Spin_lock_irq (& Q-> lock );/
_ Remove_wait_queue (Q, & wait );/
Spin_unlock_irqrestore (& Q-> lock, flags );
Void interruptible_sleep_on (wait_queue_head_t * q)
{
Sleep_on_var
Current-> state = task_interruptible;
Sleep_on_head
Schedule (); // The process in the task_interruptible State will not be executed.
Sleep_on_tail
}
Static inline void _ add_wait_queue (wait_queue_head_t * head, wait_queue_t * New)
{
# If waitqueue_debug
If (! Head |! New)
Wq_bug ();
Check_magic_wqhead (head );
Check_magic (New->__ magic );
If (! Head-> task_list.next |! Head-> task_list.prev)
Wq_bug ();
# Endif
List_add (& New-> task_list, & head-> task_list );
}
Static inline void _ remove_wait_queue (wait_queue_head_t * head, wait_queue_t * old)
{
# If waitqueue_debug
If (! Old)
Wq_bug ();
Check_magic (old->__ magic );
# Endif
List_del (& old-> task_list );
}
Static inline void _ list_del (struct list_head * Prev, struct list_head * Next)
{
Next-> Prev = Prev;
Prev-> next = next;
}
/*** List_del-deletes entry from list.
* @ Entry: the element to delete from the list
* Note: list_empty on entry does not return true after this, the entry is in an undefined state.
*/
Static inline void list_del (struct list_head * entry)
{
_ List_del (Entry-> Prev, entry-> next );
Entry-> next = (void *) 0;
Entry-> Prev = (void *) 0;
}
The above code should be better understood. we first generate a wait_queue_t for the current process, change the state of the current process to task_interruptible, and then add the wait_queue_t to the declared and initialized global variable q. when the shedule is called, the process referred to by current will be put into the scheduling queue for execution. After schedule () is executed, the current task cannot continue. When the current is triggered by a wake up later, it will be executed after schedule (), that is, from sleep_on_tail. Now, of course, we understand that what wake_up_interruptible needs to do is to change the process status to running. The Code is as follows:
--------------------------------------------------------------------------------
# Define wake_up_interruptible (x) _ wake_up (x), task_interruptible, 1)
Void _ wake_up (wait_queue_head_t * q, unsigned int mode, int nr_exclusive)
{
Unsigned long flags;
If (unlikely (! Q ))
Return;
Spin_lock_irqsave (& Q-> lock, flags );
_ Wake_up_common (Q, mode, nr_exclusive, 0 );
Spin_unlock_irqrestore (& Q-> lock, flags );
}
Static inline void _ wake_up_common (wait_queue_head_t * q, unsigned int mode, int nr_exclusive, int sync)
{
Struct list_head * TMP;
Unsigned int state;
Wait_queue_t * curr;
Task_t * P
List_for_each (TMP, & Q-> task_list ){
Curr = list_entry (TMP, wait_queue_t, task_list );
P = curr-> task;
State = p-> state;
If (State & mode) & try_to_wake_up (p, mode, sync) & (curr-> flags & wq_flag_exclusive )&&! -- Nr_exclusive ))
Break;
}
}
Static int try_to_wake_up (task_t * P, unsigned int state, int sync)
{
Unsigned long flags;
Int success = 0;
Long old_state;
Runqueue_t * rq;
Sync & = sync_wakeups; repeat_lock_task:
RQ = task_rq_lock (p, & flags );
Old_state = p-> state;
If (old_state & State)
{// Modify if the status is the same
If (! P-> array ){
/*
* Fast-migrate the task if it's not running or runnable * currently. do not violate hard affinity.
*/
If (unlikely (Sync &&! Task_running (RQ, p) & (task_cpu (p )! = Smp_processor_id () & (p-> cpus_allowed & (1ul <smp_processor_id ()))))
{
Set_task_cpu (p, smp_processor_id ());
Task_rq_unlock (RQ, & flags );
Goto repeat_lock_task;
}
If (old_state = task_uninterruptible)
RQ-> nr_uninterruptible --;
If (Sync)
_ Activate_task (p, rq );
Else {
Activate_task (p, rq );
Resched_task (RQ-> curr );
}
Success = 1;
}
If (p-> state> = task_zombie)
Bug ();
P-> state = task_running;
}
Task_rq_unlock (RQ, & flags );
Return success;
}
Because schedule has a large amount of code, it will not be pasted out.