Linux-休眠与唤醒
前言
当应用程序必须等待某个时间发生,比如必须等待按键被按下时,可以使用“休眠-唤醒”机制,这一机制近似于,你是个早起困难户,想要早起,必须等待闹钟响了,才会起床。
一、“休眠-唤醒”机制
当应用程序调用read等函数读取数据时->应用程序进入内核驱动,调用驱动中对应的函数,若有数据则复制到应用空间,并且立刻返回->但是若没有数据,应用程序就会进入休眠->当有数据的时候,即按下按键,驱动程序中的中断服务函数就会被调用,在这个函数中,会记录数据,同时唤醒应用程序->唤醒应用程序之后,应用程序继续运行内核中对应的代码,也就是驱动中对应的函数,将数据复制到应用空间,并且立刻返回。
二、重要的函数及其数据结构
wait内核函数
休眠,直到 condition 为真;休眠期间是可被打断的,可以被信号打断
wait_event_interruptible(wq, condition)
参数1:等待队列,休眠的时候,会将进程/线程放入等待队列中,中断服务程序会从 wq 中把它取出来唤醒。
参数2:一直等待,直到 condition为真
休眠,直到 condition 为真;退出的唯一条件是 condition 为真,信号也不好使
wait_event(wq, condition)
休眠,直到 condition 为真或超时;休眠期间是可被打断的,可以被信号打断
wait_event_interruptible_timeout(wq,condition, timeout)
休眠,直到 condition 为真;退出的唯一条件是 condition 为真,信号也不好使
wait_event_timeout(wq, condition,
timeout)
唤醒函数
唤醒 x 队列中状态为“ TASK_INTERRUPTIBLE”的线程,只唤
醒其中的一个线程
wake_up_interruptible(x)
唤醒 x 队列中状态为“ TASK_INTERRUPTIBLE”的线程,只唤
醒其中的 nr 个线程
wake_up_interruptible_nr(x, nr)
唤 醒 x 队 列 中 状 态 为 “ TASK_INTERRUPTIBLE ” 或
“TASK_UNINTERRUPTIBLE”的线程,只唤醒其中的一个线程
wake_up(x)
唤醒 x 队列中状态为“ TASK_INTERRUPTIBLE”的线程,唤醒
其中的所有线程
wake_up_interruptible_all(x)
唤 醒 x 队 列 中 状 态 为 “ TASK_INTERRUPTIBLE ” 或
“TASK_UNINTERRUPTIBLE”的线程,只唤醒其中 nr 个线程
wake_up_interruptible_all(x)
唤 醒 x 队 列 中 状 态 为 “ TASK_INTERRUPTIBLE ” 或
“TASK_UNINTERRUPTIBLE”的线程,唤醒其中的所有线程
wake_up_all(x)
三.驱动编程步骤
1.初始化等待队列wq
2.read函数中调用wait_event_interruptible
它本身会判断 event 是否为 FALSE,如果为 FASLE 表示无数据,则休眠。
当从 wait_event_interruptible 返回后,把数据复制回用户空间。
3.在中断服务程序里:
设置 event 为 TRUE,并调用 wake_up_interruptible 唤醒线程。
附录(源码)
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>
#include <linux/sched.h>
struct gpio_key {
int gpio;
struct gpio_desc *gpiod;
int flag;
int irq;
};
static struct gpio_key *gpio_key_imx6ull;
/*
*创建设备号需要的一些参数
*/
static int major = 0;//主设备号
static struct class *cls;//类
static struct device *dev;//设备
/* 创建环形缓冲区
*用于保存数据,防止数据丢失
*/
#define BUF_LEN 128
static int g_keys[BUF_LEN];//环形缓冲区的大小
static int r, w;
#define NEXT_POS(x) ((x+1) % BUF_LEN)//指向缓冲区的下一位
//队列为空
static int is_key_buf_empty(void)
{
return (r == w);
}
//队列满
static int is_key_buf_full(void)
{
return (r == NEXT_POS(w));
}
//入队列write
static void put_key(int key)
{
if (!is_key_buf_full())
{
g_keys[w] = key;
w = NEXT_POS(w);
}
}
//出队列read
static int get_key(void)
{
int key = 0;
if (!is_key_buf_empty())
{
key = g_keys[r];
r = NEXT_POS(r);
}
return key;
}
//定义gpio_key_wait等待队列,并初始化
static DECLARE_WAIT_QUEUE_HEAD(gpio_key_wait);
//中断服务1程序,用于唤醒队列
static irqreturn_t gpio_key_isr(int irq, void *dev_id)
{
struct gpio_key *gpio_key = dev_id;
int val;
int key;
val = gpiod_get_value(gpio_key->gpiod);
printk("key %d %d\n", gpio_key->gpio, val);
key = (gpio_key->gpio << 8) | val;
put_key(key);
wake_up_interruptible(&gpio_key_wait);//唤醒等待队列
return IRQ_HANDLED;
}
static ssize_t gpio_key_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
int err;
int key;
//如果!is_key_buf_empty()为假,则休眠,否则就是有数据
wait_event_interruptible(gpio_key_wait, !is_key_buf_empty());
key = get_key();
err = copy_to_user(buf, &key, 4);
return 4;
}
static struct file_operations gpio_key_drv = {
.owner = THIS_MODULE,
.read = gpio_key_drv_read,
};
//设备匹配成功后执行
static int gpio_key_drv_probe(struct platform_device *pdev)
{
int count;
int i;
int err;
int ret;
struct device_node *node = pdev->dev.of_node;
enum of_gpio_flags flag;
//创建设备号
major = register_chrdev(0, "gpio_key", &gpio_key_drv);
//创建类
cls = class_create(THIS_MODULE, "gpio_key_class");
if(IS_ERR(cls)){
printk("class_create failed\n");
ret = PTR_ERR(cls);
goto err_0;
}
//创建设备
dev = device_create(cls, NULL, MKDEV(major, 0), NULL, "gpio_key"); /* /dev/100ask_gpio_key */
if (IS_ERR(dev)) {
printk( "device_create failed\n");
ret = PTR_ERR(dev);
goto err_1;
}
count = of_gpio_count(node);
if (!count)
{
printk("%s %s line %d, there isn't any gpio available\n", __FILE__, __FUNCTION__, __LINE__);
return -1;
}
//获取设备树中的信息
gpio_key_imx6ull = kzalloc(sizeof(struct gpio_key) * count, GFP_KERNEL);
for (i = 0; i < count; i++)
{
gpio_key_imx6ull[i].gpio = of_get_gpio_flags(node, i, &flag);
if (gpio_key_imx6ull[i].gpio < 0)
{
printk("%s %s line %d, of_get_gpio_flags fail\n", __FILE__, __FUNCTION__, __LINE__);
return -1;
}
gpio_key_imx6ull[i].gpiod = gpio_to_desc(gpio_key_imx6ull[i].gpio);
gpio_key_imx6ull[i].flag = flag & OF_GPIO_ACTIVE_LOW;
gpio_key_imx6ull[i].irq = gpio_to_irq(gpio_key_imx6ull[i].gpio);
}
for (i = 0; i < count; i++)
{
err = request_irq(gpio_key_imx6ull[i].irq, gpio_key_isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "100ask_gpio_key", &gpio_key_imx6ull[i]);
}
return 0;
err_0:
unregister_chrdev(major, "gpio_key");
return ret;
err_1:
class_destroy(cls);
return ret;
}
static int gpio_key_drv_remove(struct platform_device *pdev)
{
int count;
int i;
struct device_node *node = pdev->dev.of_node;
count = of_gpio_count(node);
for (i = 0; i < count; i++)
{
free_irq(gpio_key_imx6ull[i].irq, &gpio_key_imx6ull[i]);
}
kfree(gpio_key_imx6ull);
return 0;
device_destroy(cls, MKDEV(major, 0));
class_destroy(cls);
unregister_chrdev(major, "gpio_key");
}
//匹配列表
static const struct of_device_id gpio_key_device[] = {
{ .compatible = "atkalpha-key" },
{ },
};
//platform驱动结构体
static struct platform_driver gpio_key_driver = {
.probe = gpio_key_drv_probe,
.remove = gpio_key_drv_remove,
.driver = {
.name = "imx6ull_gpio_key",//驱动名字,用于和设备匹配
.of_match_table = gpio_key_device,//设备树匹配
},
};
static int __init gpio_key_drv_init(void)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return platform_driver_register(&gpio_key_driver);
}
static void __exit gpio_key_drv_exit(void)
{
platform_driver_unregister(&gpio_key_driver);
}
module_init(gpio_key_drv_init);
module_exit(gpio_key_drv_exit);
MODULE_LICENSE("GPL");