【Linux】内核中休眠与唤醒的使用(wait_event、wake_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. 完整工程代码

测试验证的完整工程代码下载地址如下:
https://download.csdn.net/download/ZHONGCAI0901/23646852

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值