捋一下等待队列waitqueue

waitqueue

首先结构体的名字中确实带了queue,但是本质上是一个双向链表。

struct wait_queue_head {        //等待队列头
	struct list_head	head;
};

struct wait_queue_entry {        //等待队列实体,描述一个个任务
	struct list_head	entry;
};

等待队列是成对使用,比如可中断的wq和不可中断的wq

wait_event/wake_up

wait_event_interruptible/wake_up_interruptible

加入队列用不可中断API,唤醒用可中断API,行吗?不行

废话到此结束,下面讲下硬核知识点。首先思考几个问题:

  • 为什么要成对使用,也就是两个API同步是可中断或者不可中断
  • 可中断和不可中断的区别,代码是如何体现的
  • 如何体现将当前task挂到wq的
  • 一次性唤醒几个task

wait_event/wake_up

wait_event的本质是schedule,wake_up的本质是 try_to_wake_up

wait_event(_interruptible)

不加括号的任务state为TASK_UNINTERRUPTIBLE,含括号的任务state为TASK_INTERRUPTIBLE。

实现上呢是一个循环,使用condition来退出,而condition需要手动设置。下面函数拆解

使用该函数会自动建立一个等待任务,体现在init_wait_entry

wait_event
    init_wait_entry:
	wq_entry->flags = flags;            //记录该任务是否是互斥的
	wq_entry->private = current;      //调用wait_event的对象
	wq_entry->func = autoremove_wake_function;    //唤醒该等待者的方法

1 标记是否是互斥,2 一个个等待任务是以current标识的。

建立完对象后将其挂入目标等待队列头的wq中,体现在prepare_to_wait_event函数中有一个链表add操作,该子函数在for(;;)循环中,第一次循环如果是exclusive,尾插,如果是非exclusive,头插,然后设置state。后面每进一次循环只做设置state的动作。

prepare_to_wait_event:
    if (list_empty(&wq_entry->entry)) {//该等待队列未加入队列头
        if (wq_entry->flags & WQ_FLAG_EXCLUSIVE)
	    __add_wait_queue_entry_tail(wq_head, wq_entry);
        else
	    __add_wait_queue(wq_head, wq_entry);
    }
    set_current_state(state);    //可中断或不可中断

然后理论上是个无止境的循环。

___wait_event:
	for (;;) {						
		long __int = prepare_to_wait_event(&wq_head, &__wq_entry, state);       //返回值记录了是否有信号干预它
								
		if (condition)	条件满足退出循环			
			break;					
//如果设置了interruptible,并且有信号打断,直接无视condition,退出等待	
		if (___wait_is_interruptible(state) && __int) {	
			__ret = __int;				
			goto __out;			
		}								
		cmd;						
	}							
	finish_wait(&wq_head, &__wq_entry);			
__out:	__ret;							

设置interruptible的好处就是,多了一个唤醒的方法,如果条件一直不成立至少还能运行,比如杀死这个任务,kill信号也可以唤醒它。

如果条件不满足,且没有中断信号,那就一直执行cmd,默认是schedule(),当然可以使用wait_event_cmd,任意指定命令。

条件满足退出循环执行finish_wait,将任务从TASK_INTERRUPTIBLE或TASK_UNINTERRUPT设置为TASK_RUNNING。同时从当前等待队列退出。

finish_wait:
	__set_current_state(TASK_RUNNING);
	if (!list_empty_careful(&wq_entry->entry))
		list_del_init(&wq_entry->entry);

使用wait_event_interruptible可以根据返回值来判断是条件唤醒还是信号唤醒。

wake_up(_interruptible)

__wake_up_common:
        curr = list_first_entry(&wq_head->head, wait_queue_entry_t, entry);
        if(&curr->entry == &wq_head->head)    //链表为空
		return nr_exclusive;
        list_for_each_entry_safe_from(curr, next, &wq_head->head, entry) {
		unsigned flags = curr->flags;
		ret = curr->func(curr, mode, wake_flags, key);
		if (ret < 0)        //唤醒失败退出
			break;
//退出条件,唤醒成功+exclusive属性+唤醒了足够的exclusive属性对象
		if (ret && (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
			break;
	}

在wait_event函数中会将任务加入某个等待队列头的等待队列中,exclusive属性使用尾插,非exclusive属性使用头插。wake_up中对非空链表进行从头开始的遍历,对每次遍历到的对象执行唤醒操作,所以优先唤醒的是非exclusive的对象。

执行了wake_up(_interruptible)会将整个等待队列头里的非exclusive对象都会唤醒,而唤醒exclusive对象的个数可由nr_exclusive决定。

唤醒函数:autoremove_wake_function

int autoremove_wake_function(struct wait_queue_entry *wq_entry, unsigned mode, int sync, void *key)
{
//成功唤醒返回1,失败返回0
	int ret = default_wake_function(wq_entry, mode, sync, key);
	if (ret)
		list_del_init(&wq_entry->entry);
	return ret;
}

default_wake_function会调用try_to_wakeup,将这个任务加入就绪队列等待调度。

	if (!(p->state & state))
		goto unlock;

try_to_wakeup中注意看如上这个判断,决定了这对API必须要么同时是可中断类型,要么是不可中断类型,否则无法执行唤醒操作。

唤醒成功后会将这个对象从等待队列中删除

exclusive等待队列

前面分析过,对于wait_event(_interruptible),执行了wake_up(_interruptible)会将整个等待队列头里的对象都会唤醒。如果只想要唤醒一个怎么整,很简单新建一个队列头,把新任务挂上去,如果有很多个呢那就创建更多的队列头,这不失为一种方法。但还有一种办法,设置任务之间是互斥的。

在__wake_up_common中有这样一个判断:

if (ret && (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
			break;

如果设置了互斥标志,互斥个数为1,则跳出队列遍历继而退出剩余成员的唤醒。

可以使用下面的API来设置等待任务为exclusive

wait_event_interruptible_exclusive(wq, condition)	
init_wait_entry(&__wq_entry, exclusive ? WQ_FLAG_EXCLUSIVE : 0);	

在init_wait_entry中设置成员的标志为WQ_FLAG_EXCLUSIVE

wake_up函数默认互斥数nr_exclusive为1,使用wake_up(_interruptible)即可一次只唤醒一个互斥任务

#define wake_up(x)			__wake_up(x, TASK_NORMAL, 1, NULL)

如果唤醒一个不够,还有其他变种,wake_up_nr可以唤醒nr-1个互斥任务,wake_up_all可以全部唤醒。注意这两个唤醒函数必须和exclusive类型的wait_event使用。

#define wake_up_nr(x, nr)		__wake_up(x, TASK_NORMAL, nr, NULL)
#define wake_up_all(x)			__wake_up(x, TASK_NORMAL, 0, NULL)

建议用法

假设只有一个等待队列头

  • 如有两个任务挂在同一个头上,逐个唤醒
DECLARE_WAIT_QUEUE_HEAD(wq_head);
condition = 0;

A: wait_event_interruptible_exclusive(wq_head, condition);
B: wait_event_interruptible_exclusive(wq_head, condition);

C: wake_up_interruptible(&wq_head);
    condition = 1;
    wake_up_interruptible(&wq_head);
    condition = 1;

  • 如有两个任务挂在同一个头上,同时唤醒
DECLARE_WAIT_QUEUE_HEAD(wq_head);
condition = 0;

A: wait_event_interruptible(wq_head, condition);
B: wait_event_interruptible(wq_head, condition);

C: wake_up_interruptible(&wq_head);
    condition = 1;

上述都可通过信号来额外唤醒。

那么如果认真看到至此,并捋一遍代码,开篇提到的问题,全部迎刃而解了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值