Linux内核中休眠与唤醒的使用(wait_eventwake_up)

Posted ZHONGCAI0901

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux内核中休眠与唤醒的使用(wait_eventwake_up)相关的知识,希望对你有一定的参考价值。

1. 前言

wait_event_interruptible()函数中会将当前进程的状态设置成TASK_INTERRUPTIBLE,然后调用schedule(),它会将位于TASK_INTERRUPTIBLE状态的进程从run queue队列中删除。从run queue队列中删除的结果是,当前这个进程将不再参与调度,除非通过其他将这个进程重新放入run queue队列中,wake_up()函数就是这个作用。

由于这一段代码位于一个由condition控制的for(;;)循环中,所以当由shedule()返回时(当然是被wake_up()之后,通过其他进程的schedule()而 再次调度本进程),如果条件condition不满足,本进程将自动再次被设置为TASK_INTERRUPTIBLE状态,接下来执行schedule()的结果是再次被 从run queue队列中删除。这时候就需要再次通过wake_up()重新添加到 run queue队列中。

如此反复,直到condition为真的时候被wake_up()

#define ___wait_event(wq, condition, state, exclusive, ret, cmd)	\\
({									\\
	__label__ __out;						\\
	wait_queue_t __wait;						\\
	long __ret = ret;	/* explicit shadow */			\\
									\\
	init_wait_entry(&__wait, exclusive ? WQ_FLAG_EXCLUSIVE : 0);	\\
	for (;;) {							\\
		long __int = prepare_to_wait_event(&wq, &__wait, state);\\
									\\
		if (condition)						\\
			break;						\\
									\\
		if (___wait_is_interruptible(state) && __int) {		\\
			__ret = __int;					\\
			goto __out;					\\
		}							\\
									\\
		cmd;							\\
	}								\\
	finish_wait(&wq, &__wait);					\\
__out:	__ret;								\\
})

#define __wait_event_interruptible(wq, condition)			\\
	___wait_event(wq, condition, TASK_INTERRUPTIBLE, 0, 0,		\\
		      schedule())

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

2. 休眠和唤醒内核函数介绍

  • 休眠函数
    参考内核源码:include\\linux\\wait.h
函数说明
wait_event_interruptible(wq, condition)休眠,直到 condition 为真;
休眠期间是可被打断的,可以被信号打断
wait_event(wq, condition)休眠,直到 condition 为真;
退出的唯一条件是 condition 为真,信号也不好使
wait_event_interruptible_timeout
(wq, condition, timeout)
休眠,直到 condition 为真或超时;
休眠期间是可被打断的,可以被信号打断
wait_event_timeout(wq, condition, timeout)休眠,直到 condition 为真;
退出的唯一条件是 condition 为真,信号也不好使

比较重要的参数就是:
1. wq:waitqueue,等待队列
休眠时除了把程序状态改为非 RUNNING 之外,还要把进程/进程放入 wq 中,以后中断服务程序要从 wq 中把它取出来唤醒。
2. condition
这可以是一个变量,也可以是任何表达式。表示“一直等待,直到 condition 为真”

  • 唤醒函数
    参考内核源码:include\\linux\\wait.h
函数说明
wake_up_interruptible(x)唤醒 x 队列中状态为“TASK_INTERRUPTIBLE”的线程,
只唤醒其中的一个线程
wake_up_interruptible_nr(x, nr)唤醒 x 队列中状态为“TASK_INTERRUPTIBLE”的线程,
只唤醒其中的 nr 个线程
wake_up_interruptible_all(x)唤醒 x 队列中状态为“TASK_INTERRUPTIBLE”的线程,
唤醒其中的所有线程
wake_up(x)唤醒 x 队列中状态为“TASK_INTERRUPTIBLE”或
“TASK_UNINTERRUPTIBLE”的线程,只唤醒其中的一个线程
wake_up_nr(x, nr)唤醒 x 队列中状态为“TASK_INTERRUPTIBLE”或
“TASK_UNINTERRUPTIBLE”的线程,只唤醒其中 nr个线程
wake_up_all(x)唤醒 x 队列中状态为“TASK_INTERRUPTIBLE”或
“TASK_UNINTERRUPTIBLE”的线程,唤醒其中的所有线程

3. 使用休眠与唤醒的驱动框架

下面是通过按键来使用“休眠 - 唤醒”,框架如下:

要休眠的线程,放在 wq 队列里,中断处理函数从 wq 队列里把它取出来唤醒。所以,需要做以下几件事:

  1. 初始化 wq 队列
  2. 在驱动的 read 函数中,调用 wait_event_interruptible:它本身会判断 event 是否为 FALSE,如果为 FASLE 表示无数据,则休眠。当从 wait_event_interruptible 返回后,把数据复制回用户空间。
  3. 在中断服务程序里:设置 event 为 TRUE,并调用 wake_up_interruptible 唤醒线程。

4. 编写测试程序

在驱动测试程序中,要先定义“wait queue”:

static DECLARE_WAIT_QUEUE_HEAD(gpio_key_wait);

在驱动的读函数里调用wait_event_interruptible

ssize_t gpio_key_read(struct file *pFile, char __user *pBuf, size_t size, loff_t *ppos)
{
	ssize_t len;
	int err;

	wait_event_interruptible(gpio_key_wait, key_event_flag);

	len = sizeof(struct app_key_event);
	err = copy_to_user(pBuf, &appKeyPayload, len);

	key_event_flag = false;

	return len;
}

wait_event_interruptible(gpio_key_wait, key_event_flag);中,不一定会进入休眠,它会先判断key_event_flag是否为TRUE,不为TRUE那么就进入休眠。我们可以通过按下按键后,进入中断服务程序后,设置为key_event_flag为TRUE,然后唤醒当前任务。

static irqreturn_t gpio_key_isr(int irq, void *dev_id)
{
	int val;
	struct gpio_key_cfg *pGpioKey = dev_id;

	val = gpiod_get_value(pGpioKey->gpiod);

	printk("key %d %d\\n", pGpioKey->gpio_num, val);

	appKeyPayload.gpio_num = pGpioKey->gpio_num;
	appKeyPayload.value = val;

	key_event_flag = true;

	wake_up_interruptible(&gpio_key_wait);

	return IRQ_HANDLED;
}

下面是应用测试程序,在while中不断的read获取key值并打印,具体代码如下:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

/*
 * ./app_key_test /dev/gpio_key0
 *
 */

struct app_key_event
{
	int gpio_num;
	int value;
};

int main(int argc, char *argvp[])
{
	int fd;
	struct app_key_event keyEventData;

	/* 1.判断参数 */
	if(argc != 2)
	{
		printf("Usage: %s <dev>\\n", argvp[0]);
		return -1;
	}

	/* 2.打开文件 */
	fd = open(argvp[1], O_RDWR);
	if(fd == -1)
	{
		printf("can not open file %s\\n", argvp[1]);
		return -1;
	}

	/* 3.读取KEY Event   */
	while(1)
	{
		read(fd, &keyEventData, sizeof(struct app_key_event));
		printf("get key event: <%d><%d>\\n", keyEventData.gpio_num, keyEventData.value);
	}

	return 0;
};

5. 验证测试

编译烧录后,验证测试结果如下:

6. 完整工程代码

测试验证的完整工程代码下载地址如下:
正在上传中…

以上是关于Linux内核中休眠与唤醒的使用(wait_eventwake_up)的主要内容,如果未能解决你的问题,请参考以下文章

Linux内核中休眠与唤醒的使用(wait_eventwake_up)

wait_event族函数浅析

wait_event()和wait_event_interruptible()的介绍

Linux 内核 唤醒等待队列的时间多久?

Linux内核中等待队列的几种用法

linux 内核睡眠与唤醒