Linux内核函数wait_event_interruptible的condition参数踩坑

wait_event_interruptible简介

wait_event_interruptible(以及wait_event打头的其他变体)是Linux的wait queue机制提供的线程同步接口,它的定义如下

#define wait_event_interruptible(wq, condition)             \
({                                  \
    int __ret = 0;                          \
    might_sleep();                          \
    if (!(condition))                       \
        __ret = __wait_event_interruptible(wq, condition);  \
    __ret;                              \
}) 

它的用法很简单,Linux Device Driver第三版示例代码如下

static DECLARE_WAIT_QUEUE_HEAD(wq);
static int flag = 0;
ssize_t sleepy_read (struct file *filp, char __user *buf, size_t count, loff_t *pos)
{
	printk(KERN_DEBUG "process %i (%s) going to sleep\n", current->pid, current->comm);
	wait_event_interruptible(wq, flag != 0);  //等待flag != 0的发生,即其他线程将flag改成非0值
	flag = 0;
	printk(KERN_DEBUG "awoken %i (%s)\n", current->pid, current->comm);
	return 0; /* EOF */
}

踩坑1 我不想在condition里使用全局变量

定义全局变量flag太丑了,较好的做法是使用file结构体的private_data字段来获取设备的控制结构体,这样 condition表达式就可以使用局部变量 了:

ssize_t sleepy_read (struct file *filp, char __user *buf, size_t count, loff_t *pos)
{
	struct tmc_dev *dev = filp->private_data;
	printk(KERN_DEBUG "process %i (%s) going to sleep\n", current->pid, current->comm);
	wait_event_interruptible(wq, dev->flag != 0);  //等待flag != 0的发生,即其他线程将flag改成非0值
	dev->flag = 0;
	printk(KERN_DEBUG "awoken %i (%s)\n", current->pid, current->comm);
	return 0; /* EOF */
}

虽然dev指向的对象大概率也是全局变量,至少是分配在堆上的,但sleepy_read 函数用局部变量dev可以避免将来全局变量改名对自身的影响。
可能有人会担心线程sleep时dev指针会无效,从而导致condition的评估失败,不用担心,condition被评估时线程一定是处于RUNNING态的,此时线程的上下文(包括函数调用栈)都已被恢复,所以dev指针是有效的。为啥这么说?因为休眠线程是被内核或别的线程通过wake_up_interruptible()唤醒,唤醒时必然恢复上下文。

踩坑2 我的condition表达式太长,一行放不下

可以把condition表达式拆成多行,但要记 得在行尾添加续行符\

ssize_t sleepy_read (struct file *filp, char __user *buf, size_t count, loff_t *pos)
{
	struct tmc_dev *dev = filp->private_data;
	printk(KERN_DEBUG "process %i (%s) going to sleep\n", current->pid, current->comm);
	wait_event_interruptible(wq, dev->flag != 0 && \
		dev->state == STATE_COMPLETE);  //等待flag != 0且dev->state == STATE_COMPLETE的发生
	dev->flag = 0;
	dev->state == STATE_IDLE;
	printk(KERN_DEBUG "awoken %i (%s)\n", current->pid, current->comm);
	return 0; /* EOF */
}

为啥要加续行符?因为wait_event_interruptible()其实是个宏函数,不论宏函数还是普通常量宏,都是宏定义,而C语言只允许单行的宏定义,要想多行,必须在行尾加续行符。如果不加,其行为是未定义的,Linux内核也hold不住。

你可能感兴趣的:(驱动开发,C/C++,wait_queue,等待队列)