本文转载自微信公众号《Linux内核那些事》,作者songsong001。转载本文请联系Linux内核那些事儿公众号。当一个进程想要获取一些资源(比如从网卡读取数据),但是资源没有准备好(比如网卡还没有接收到数据),此时内核必须切换到其他进程运行,直到资源准备好唤醒进程。waitqueue(等待队列)是内核用来管理等待资源的进程。当某个进程获取的资源没有准备好时,可以通过调用add_wait_queue()函数将该进程添加到waitqueue中,然后切换到其他进程继续执行。当资源就绪时,资源提供者通过调用wake_up()函数唤醒等待的进程。使用waitqueue进行等待队列初始化,首先需要声明一个wait_queue_head_t结构体的变量。wait_queue_head_t结构体定义如下:struct__wait_queue_head{spinlock_tlock;structlist_headtask_list;};waitqueue本质上是一个链表,wait_queue_head_t结构体是waitqueue的头部,锁字段用于保护等待队列的数据在销毁时多核环境,task_list字段用于保存等待资源的进程列表。wait_queue_head_t结构体可以通过调用init_waitqueue_head()函数来初始化,其实现如下:,首先调用spin_lock_init()来初始化自旋锁锁,然后调用INIT_LIST_HEAD()来初始化进程链表。将等待进程加入等待队列要将等待进程加入waitqueue,首先声明一个wait_queue_t结构体的变量,wait_queue_t结构体定义如下:;void*private;wait_queue_func_tfunc;structlist_headtask_list;};下面解释一下各个成员的作用:flags:可以设置为WQ_FLAG_EXCLUSIVE,表示等待进程应该独占资源(解决惊群现象)。private:一般用来保存等待进程的进程描述符task_struct。func:唤醒函数,一般设置为default_wake_function()函数,当然也可以设置为自定义的唤醒函数。task_list:用于连接其他等待资源的进程。wait_queue_t结构变量可以通过调用init_waitqueue_entry()函数来初始化,实现如下:staticinlinevoidinit_waitqueue_entry(wait_queue_t*q,structtask_struct*p){q->flags=0;q->private=p;q->func=default_wake_function;}也可以通过调用init_waitqueue_func_entry()函数初始化为自定义唤醒函数:staticinlinevoidinit_waitqueue_func_entry(wait_queue_t*q,wait_queue_func_tfunc){q->flags=0;q->private=NULL;q->func=func;}初始化wait_queue_t结构变量后,可以调用add_wait_queue()函数将等待进程加入等待队列,实现如下:voidadd_wait_queue(wait_queue_head_t*q,wait_queue_t*wait){unsignedlongflags;wait->flags&=~WQ_FLAG_EXCLUSIVE;spin_lock_irqsave(&q->lock,flags);__add_wait_queue(q,wait);spin_unlock_irqrestore(&q->lock,flags);}staticinlinevoid__add_wait_queue(wait_queue_head_t*head,wait_queue_t*new){list_add(&new->task_list,&head->task_list);}add_wait_queue()函数实现起来非常简单。首先通过调用spin_lock_irqsave()对其进行锁定,然后调用list_add()函数将该节点添加到等待队列中。wait_queue_head_t结构体和wait_queue_t结构体的关系如下:waitqueue休眠等待进程当进程加入等待队列后,当前进程可以休眠,将CPU让给其他进程运行。要使进程休眠,可以使用以下方法:set_current_state(TASK_INTERRUPTIBLE);schedule();代码set_current_state(TASK_INTERRUPTIBLE)可以将当前进程的运行状态设置为可中断睡眠状态,调用schedule()函数可以让当前进程放弃CPU,切换到其他进程执行。唤醒等待队列当资源准备好后,可以唤醒等待队列中的进程,可以通过wake_up()函数唤醒等待队列中的进程。wake_up()最终会调用__wake_up_common(),其实现如下:task_list,task_list){unsignedflags=curr->flags;if(curr->func(curr,mode,sync,key)&&(flags&WQ_FLAG_EXCLUSIVE)&&!--nr_exclusive)break;}}可以看出唤醒waitingqueue是变量waitingqueue等待进程,然后调用唤醒函数将它们唤醒。
