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 队列里把它取出来唤醒。所以,需要做以下几件事:
- 初始化 wq 队列
- 在驱动的 read 函数中,调用
wait_event_interruptible
:它本身会判断 event 是否为 FALSE,如果为 FASLE 表示无数据,则休眠。当从wait_event_interruptible
返回后,把数据复制回用户空间。 - 在中断服务程序里:设置 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