1 阻塞 IO 简介
当应用程序对设备驱动进行操作的时候,如果不能获取到设备资源,那么阻塞式 IO 就会将应用程序对应的线程挂起,直到设备资源可以获取为止。
应用程序调用 read 函数从设备中读取数据,当设备不可用或数据未准备好的时候就会进入到休眠态。等设备可用的时候就会从休眠态唤醒,然后从设备中读取数据返回给应用程序。
2 应用程序阻塞读取数据
应用程序可以使用如下所示示例代码来实现阻塞访问:
int fd;
int data = 0;
fd = open("/dev/xxx_dev", O_RDWR); /* 阻塞方式打开 */
ret = read(fd, &data, sizeof(data)); /* 读取数据 */
3 等待队列
阻塞访问最大的好处就是当设备文件不可操作的时候进程可以进入休眠态,这样可以将CPU 资源让出来。但是,当设备文件可以操作的时候就必须唤醒进程,一般在中断函数里面完成唤醒工作。Linux 内核提供了等待队列(wait queue)来实现阻塞进程的唤醒工作。
3.1 等待队列头
void init_waitqueue_head(wait_queue_head_t *q)//初始化等待队列头
3.2 等待队列项
DECLARE_WAITQUEUE(name, tsk)//定义并初始化一个等待队列项
3.3 将等待队列项添加/移除等待队列头
void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)//等待队列项添加到等待队列头,这时候进程才能进入休眠态
void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)//等待队列项移除
3.4 等待唤醒
void wake_up(wait_queue_head_t *q)//唤醒处于 TASK_INTERRUPTIBLE 和TASK_UNINTERRUPTIBLE 状态的进程
void wake_up_interruptible(wait_queue_head_t *q)//唤醒处于 TASK_INTERRUPTIBLE 状态的进程
3.5 等待事件
除了主动唤醒以外,也可以设置等待队列等待某个事件,当这个事件满足以后就自动唤醒等待队列中的进程。
wait_event(wq, condition)//等待以 wq 为等待队列头的等待队列被唤醒,前提是 condition 条件必须满足(为真),否则一直阻塞 。 此 函 数 会 将 进 程 设 置 为TASK_UNINTERRUPTIBLE 状态
wait_event_timeout(wq, condition, timeout)//功能和 wait_event 类似,但是此函数可以添加超时时间,以 jiffies 为单位。此函数有返回值,如果返回 0 的话表示超时时间到,而且 condition为假。为 1 的话表示 condition 为真,也就是条件满足了。
wait_event_interruptible(wq, condition)//与 wait_event 函数类似,但是此函数将进程设置为 TASK_INTERRUPTIBLE,就是可以被信号打断。
wait_event_interruptible_timeout(wq, condition, timeout)//与 wait_event_timeout 函数类似,此函数也将进程设置为 TASK_INTERRUPTIBLE,可以被信号打断。
4 阻塞 IO 实验
使用top命令查看上个实验imx6uirqAPP的CPU使用率。
我们看到imx6uirqApp 这个应用程序的 CPU 使用率竟然高达 99.4%,这仅仅是一个读取按键值的应用程序,这么高的 CPU 使用率显然是有问题的!原因就在于我们是直接在 while 循环中通过 read 函数读取按键值,因此 imx6uirqApp 这个软件会一直运行,一直读取按键值,CPU 使用率肯定就会很高。
**最好的方法就是在没有有效的按键事件发生的时候,imx6uirqApp 这个应用程序应该处于休眠状态,当有按键事件发生以后 imx6uirqApp 这个应用程序才运行,打印出按键值,**这样就会降低 CPU 使用率,本实验就使用阻塞 IO 来实现此功能。
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/string.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/interrupt.h>
#include <linux/wait.h>
#include <linux/ide.h>
#define IMX6UIRQ_CNT 1
#define IMX6UIRQ_NAME "blockio"
#define KEY_NUM 1
#define KEY0VALUE 0x01
#define INVAKEY 0xFF
/*key 结构体*/
struct irq_keydesc
{
int gpio; /*io 编号*/
int irqnum; /*中断号*/
unsigned char value; /*键值*/
char name[10]; /*名字*/
irqreturn_t (*handler)(int, void *); /*中断处理函数*/
};
/*imx6uirq 设备结构体*/
struct imx6uirq_dev
{
dev_t devid;
int major;
int minor;
struct cdev cdev;
struct class *class;
struct device *device;
struct device_node *nd;
struct irq_keydesc irqkey[KEY_NUM];
struct timer_list timer;
atomic_t keyvalue;
atomic_t releasekey;
wait_queue_head_t r_wait; /*新增:读等待队列头*/
};
struct imx6uirq_dev imx6uirq; /*irq设备 */
static int imx6uirq_open(struct inode *inode, struct file *filp)
{
filp->private_data = &imx6uirq;
return 0;
}
static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
int ret = 0;
unsigned char keyvalue;
unsigned char releasekey;
struct imx6uirq_dev *dev = filp->private_data;
#if 0
/*等待事件*/
wait_event_interruptible(dev->r_wait, atomic_read(&dev->releasekey)); /*等待按键有效*/
#endif
#if 0
DECLARE_WAITQUEUE(wait, current); /*定义一个等待队列项*/
if (atomic_read(&dev->releasekey) == 0) /*按键没按下,进入休眠状态*/
{
add_wait_queue(&dev->r_wait, &wait); /*将队列项添加到等待队列头*/
__set_current_state(TASK_INTERRUPTIBLE); /*当前进程设置为可被打断的状态*/
schedule(); /*切换*/
/*唤醒以后从这里运行*/
if (signal_pending(current)) /*signal_pending 函数用于判断当前进程是否有信号处理,
返回值不为0的话表示有信号需要处理*/
{
ret = -ERESTARTSYS;
goto data_error;
}
__set_current_state(TASK_RUNNING); /*将当前任务设置为运行状态*/
remove_wait_queue(&dev->r_wait, &wait); /*将对应的队列项从等待队列头删除*/
}
#endif
DECLARE_WAITQUEUE(wait, current); /*定义一个等待队列项*/
add_wait_queue(&dev->r_wait, &wait); /*将队列项添加到等待队列头*/
__set_current_state(TASK_INTERRUPTIBLE); /*当前进程设置为可被打断的状态*/
schedule(); /*切换,进程进入睡眠*/
/*唤醒以后从这里运行*/
if (signal_pending(current)) /*signal_pending 函数用于判断当前进程是否有信号处理,
返回值不为0的话表示有信号需要处理*/
{
ret = -ERESTARTSYS;
goto data_error;
}
keyvalue = atomic_read(&dev->keyvalue);
releasekey = atomic_read(&dev->releasekey);
if (releasekey) /*有效按键*/
{
if (keyvalue & 0x80) /*释放*/
{
keyvalue &= ~0x80;
ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
}
else
{
goto data_error;
}
atomic_set(&dev->releasekey, 0); /*按下标志清0*/
}
else
{
goto data_error;
}
data_error:
__set_current_state(TASK_RUNNING); /*将当前任务设置为运行状态*/
remove_wait_queue(&dev->r_wait, &wait); /*将对应的队列项从等待队列头删除*/
return ret;
}
/*操作集*/
static const struct file_operations imx6uirq_fops = {
.owner = THIS_MODULE,
.open = imx6uirq_open,
.read = imx6uirq_read,
};
/*按键中断处理函数*/
static irqreturn_t key0_handler(int irq, void *dev_id)
{
struct imx6uirq_dev *dev = dev_id;
dev->timer.data = (volatile unsigned long)dev_id;
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(20)); /*20ms定时*/
return IRQ_HANDLED;
}
/*定时器处理函数*/
static void timer_func(unsigned long arg)
{
int value = 0;
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;
value = gpio_get_value(dev->irqkey[0].gpio);
if (value == 0) /*按下*/
{
atomic_set(&dev->keyvalue, dev->irqkey[0].value);
}
else if (value == 1) /*释放*/
{
atomic_set(&dev->keyvalue, 0x80 | (dev->irqkey[0].value));
atomic_set(&dev->releasekey, 1); /*完整的按键过程*/
}
/*新增:唤醒进程*/
if (atomic_read(&dev->releasekey))/* 完成一次按键过程 */
{
wake_up(&dev->r_wait); //唤醒进程
}
}
/*按键初始化*/
static int keyio_init(struct imx6uirq_dev *dev)
{
int ret = 0;
int i = 0;
/*1,按键初始化*/
dev->nd = of_find_node_by_path("/key");
if (dev->nd == NULL)
{
ret = -EINVAL;
goto fail_nd;
}
for (i = 0; i < KEY_NUM; i++)
{
dev->irqkey[i].gpio = of_get_named_gpio(dev->nd, "key-gpios", i);
}
for (i = 0; i < KEY_NUM; i++)
{
memset(dev->irqkey[i].name, 0, sizeof(dev->irqkey[i].name));
sprintf(dev->irqkey[i].name, "KEY%d", i);
gpio_request(dev->irqkey[i].gpio, dev->irqkey[i].name);
gpio_direction_input(dev->irqkey[i].gpio);
dev->irqkey[i].irqnum = gpio_to_irq(dev->irqkey[i].gpio); /*获取中断号*/
#if 0
dev->irqkey[i].irqnum = irq_of_parse_and_map(dev->nd,i);
#endif
}
dev->irqkey[0].handler = key0_handler;
dev->irqkey[0].value = KEY0VALUE;
/*2,按键中断初始化*/
for (i = 0; i < KEY_NUM; i++)
{
ret = request_irq(dev->irqkey[i].irqnum, dev->irqkey[0].handler,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
dev->irqkey[i].name, &imx6uirq);
if (ret)
{
printk("irq %d failed!\r\n", dev->irqkey[i].irqnum);
goto fail_irq;
}
}
/*初始化定时器*/
init_timer(&imx6uirq.timer);
imx6uirq.timer.function = timer_func;
return 0;
fail_irq:
for (i = 0; i < KEY_NUM; i++)
{
gpio_free(dev->irqkey[i].gpio);
}
fail_nd:
return ret;
}
/*驱动入口函数*/
static int __init imx6uirq_init(void)
{
int ret = 0;
/*1,注册字符设备驱动*/
imx6uirq.major = 0;
if (imx6uirq.major)
{ /*给定主设备号*/
imx6uirq.devid = MKDEV(imx6uirq.major, 0);
ret = register_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
}
else
{
ret = alloc_chrdev_region(&imx6uirq.devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
imx6uirq.major = MAJOR(imx6uirq.devid);
imx6uirq.minor = MINOR(imx6uirq.devid);
}
if (ret < 0)
{
goto fail_devid;
}
printk("imx6uirq major =%d, minor =%d \r\n", imx6uirq.major, imx6uirq.minor);
/*2,初始化cdev*/
imx6uirq.cdev.owner = THIS_MODULE;
cdev_init(&imx6uirq.cdev, &imx6uirq_fops);
/*3,添加cdev*/
ret = cdev_add(&imx6uirq.cdev, imx6uirq.devid, IMX6UIRQ_CNT);
if (ret)
{
goto fail_cdevadd;
}
/*4,创建类*/
imx6uirq.class = class_create(THIS_MODULE, IMX6UIRQ_NAME);
if (IS_ERR(imx6uirq.class))
{
ret = PTR_ERR(imx6uirq.class);
goto fail_class;
}
/*5,创建设备*/
imx6uirq.device = device_create(imx6uirq.class, NULL, imx6uirq.devid, NULL, IMX6UIRQ_NAME);
if (IS_ERR(imx6uirq.device))
{
ret = PTR_ERR(imx6uirq.device);
goto fail_device;
}
/*初始化IO*/
ret = keyio_init(&imx6uirq);
if (ret < 0)
{
goto fail_keyinit;
}
/*初始化原子变量*/
atomic_set(&imx6uirq.keyvalue, INVAKEY);
atomic_set(&imx6uirq.releasekey, 0);
/*新增:初始化等待队列头*/
init_waitqueue_head(&imx6uirq.r_wait);
return 0;
fail_keyinit:
fail_device:
class_destroy(imx6uirq.class);
fail_class:
cdev_del(&imx6uirq.cdev);
fail_cdevadd:
unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);
fail_devid:
return ret;
}
/*驱动出口函数*/
static void __exit imx6uirq_exit(void)
{
int i = 0;
/*1、释放中断*/
for (i = 0; i < KEY_NUM; i++)
{
free_irq(imx6uirq.irqkey[i].irqnum, &imx6uirq);
}
/*2、释放IO*/
for (i = 0; i < KEY_NUM; i++)
{
gpio_free(imx6uirq.irqkey[i].gpio);
}
/*3、删除定时器*/
del_timer(&imx6uirq.timer);
/*注销字符设备驱动*/
cdev_del(&imx6uirq.cdev);
unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);
device_destroy(imx6uirq.class, imx6uirq.devid);
class_destroy(imx6uirq.class);
}
module_init(imx6uirq_init);
module_exit(imx6uirq_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("supersmart");
测试APP
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
/*
*argc:应用程序参数个数
* argv[]:具体的参数内容,字符串形式
* ./imx6uirqAPP <filename> <0:1> 0 关灯,1 开灯
* ./imx6uirqAPP /dev/imx6uirq
*/
#define CLOSE_CMD _IO(0xEF, 1) //关闭命令
#define OPEN_CMD _IO(0xEF, 2) //打开命令
#define SETPERIOD_CMD _IOW(0xEF, 3, int) //设置周期
int main(int argc, char *argv[])
{
int fd, ret;
char *filename;
unsigned char data;
if (argc != 2)
{
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
fd = open(filename, O_RDWR);//采用阻塞方式访问
if (fd < 0)
{
printf("file %s open failed!\r\n", filename);
return -1;
}
/*循环读取*/
while (1)
{
ret = read(fd, &data, sizeof(data));
if (ret < 0)
{
}
else
{
if(data)
{
printf("key value = %#x \r\n",data);
}
}
}
close(fd);
return 0;
}
5 运行测试
1、编译驱动程序和测试 APP
2、运行测试
加载blockio设备驱动,运行imx6uirqAPP,使用top命令查看进程CPU占用率
查看 imx6uirqAPP 这个应用程序的PID为86,CPU占用率为0.0%。
使用以下命令杀死 imx6uirqAPP 这个应用程序
使用ps命令查看当前系统运行的进程
可以看到 imx6uirqAPP 这个进程已经不见了。
详细内容参考 [I.MX6U 嵌入式 Linux 驱动开发指南 V1.6] 第五十二章