1 非阻塞 IO简介
应用程序使用非阻塞访问方式从设备读取数据,当设备不可用或数据未准备好的时候会立即向内核返回一个错误码,表示数据读取失败。应用程序会再次重新读取数据,这样一直往复循环,直到数据读取成功。如下图所示
应用程序要采用非阻塞的方式来访问驱动设备文件,可以使用如下所示代码:
int fd;
int data = 0;
fd = open("/dev/xxx_dev", O_RDWR | O_NONBLOCK); /* 非阻塞方式打开 */
ret = read(fd, &data, sizeof(data)); /* 读取数据 */
使用 open 函数打开“/dev/xxx_dev”设备文件的时候添加了参数“O_NONBLOCK”,表示以非阻塞方式打开设备,这样从设备中读取数据的时候就是非阻塞方式的了。
2 应用程序select
使用 select 函数对某个设备驱动文件进行读非阻塞访问的操作示例如下所示:
void main(void) {
int ret, fd; /* 要监视的文件描述符 */
fd_set readfds; /* 读操作文件描述符集 */
struct timeval timeout; /* 超时结构体 */
fd = open("dev_xxx", O_RDWR | O_NONBLOCK); /* 非阻塞式访问 */
FD_ZERO(&readfds); /* 清除 readfds */
FD_SET(fd, &readfds); /* 将 fd 添加到 readfds 里面 */
/* 构造超时时间 */
timeout.tv_sec = 0;
timeout.tv_usec = 500000; /* 500ms */
ret = select(fd + 1, &readfds, NULL, NULL, &timeout);
switch (ret) {
case 0: /* 超时 */
printf("timeout!\r\n");
break;
case -1: /* 错误 */
printf("error!\r\n");
break;
default: /* 可以读取数据 */
if(FD_ISSET(fd, &readfds)) { /* 判断是否为 fd 文件描述符 */
/* 使用 read 函数读取数据 */
}
break;
}
}
3 应用程序poll函数
使用 poll 函数对某个设备驱动文件进行读非阻塞访问的操作示例如下所示:
void main(void) { int ret;
int fd; /* 要监视的文件描述符 */
struct pollfd fds;
fd = open(filename, O_RDWR | O_NONBLOCK); /* 非阻塞式访问 */
/* 构造结构体 */
fds.fd = fd;
fds.events = POLLIN; /* 监视数据是否可以读取 */
ret = poll(&fds, 1, 500); /* 轮询文件是否可操作,超时 500ms */
if (ret) { /* 数据有效 */
......
/* 读取数据 */
......
} else if (ret == 0) { /* 超时 */
......
} else if (ret < 0) { /* 错误 */
......
}
}
4 Linux 驱动下的 poll 操作函数
如果用户应用程序以非阻塞的方式访问设备,设备驱动程序就要提供非阻塞的处理方式,也就是轮询。poll、epoll 和 select 可以用于处理轮询,应用程序通过 select、epoll 或 poll 函数来查询设备是否可以操作,如果可以操作的话就从设备读取或者向设备写入数据。当应用程序调用 select、epoll 或 poll 函数的时候设备驱动程序中的 poll 函数就会执行,因此需要在设备驱动程序中编写 poll 函数。
**当应用程序调用 select 或 poll 函数来对驱动程序进行非阻塞访问的时候,驱动程序file_operations 操作集中的 poll 函数就会执行。**所以驱动程序的编写者需要提供对应的 poll 函数,poll 函数原型如下所示:
unsigned int (*poll) (struct file *filp, struct poll_table_struct *wait)
我们需要在驱动程序的 poll 函数中调用 poll_wait 函数,poll_wait 函数不会引起阻塞,只是将应用程序添加到 poll_table 中,poll_wait 函数原型如下:
void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
参数 wait_address 是要添加到 poll_table 中的等待队列头,参数 p 就是 poll_table,就是file_operations 中 poll 函数的 wait 参数。
5 驱动程序
imx6uirq.c 文件内容如下
#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>
#include <linux/poll.h>
#define IMX6UIRQ_CNT 1
#define IMX6UIRQ_NAME "noblockio"
#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 (filp->f_flags & O_NONBLOCK) /*新增重点:非阻塞访问*/
{
if (atomic_read(&dev->releasekey) == 0)/*没有按键按下*/
{
return -EAGAIN;
}
}
else /*阻塞*/
{
/*等待事件*/
wait_event_interruptible(dev->r_wait, atomic_read(&dev->releasekey)); /*等待按键有效*/
}
#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
#if 0
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;
}
#endif
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:
#if 0
__set_current_state(TASK_RUNNING); /*将当前任务设置为运行状态*/
remove_wait_queue(&dev->r_wait, &wait); /*将对应的队列项从等待队列头删除*/
#endif
return ret;
}
/*
* @description : poll 函数,用于处理非阻塞访问
* @param - filp : 要打开的设备文件(文件描述符)
* @param - wait : 等待列表(poll_table)
* @return : 设备或者资源状态,
*/
/*重点:当应用程序调用 select 或者 poll 函数的时候 imx6uirq_poll 函数就会执行
*
*/
static unsigned int imx6uirq_poll(struct file *filp, struct poll_table_struct *wait)
{
int mask = 0;
struct imx6uirq_dev *dev = filp->private_data;
poll_wait(filp, &dev->r_wait, wait);//将等待队列头添加到 poll_table 中
/*判断是否可读*/
if (atomic_read(&dev->releasekey)) /*按键按下,可读*/
{
mask |= POLLIN | POLLRDNORM; /*返回POLLIN*/
}
return mask;
}
/*操作集*/
static const struct file_operations imx6uirq_fops = {
.owner = THIS_MODULE,
.open = imx6uirq_open,
.read = imx6uirq_read,
.poll = imx6uirq_poll
};
/*按键中断处理函数*/
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");
6 测试APP
.imx6uirqAPP 测试应用程序内容如下
#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>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <poll.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[])
{
//fd_set readfds;
struct pollfd fds;
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 | O_NONBLOCK); /*非阻塞打开*/
if (fd < 0)
{
printf("file %s open failed!\r\n", filename);
return -1;
}
#if 0
/*循环读取*/
while (1)
{
FD_ZERO(&readfds);
FD_SET(fd, &readfds);
timeout.tv_sec = 1;
timeout.tv_usec = 0; /*1s*/
ret = select(fd + 1, &readfds, NULL, NULL, &timeout);
switch (ret)
{
case 0: /*超时*/
printf("select timeout! \r\n");
break;
case -1: /*错误*/
break;
default: /*可以读取数据*/
if (FD_ISSET(fd, &readfds))
{
ret = read(fd, &data, sizeof(data));
if (ret < 0)
{
}
else
{
if (data)
{
printf("key value = %#x \r\n", data);
}
}
}
break;
}
}
#endif
/*循环读取*/
while (1)
{
fds.fd = fd;
fds.events = POLLIN;
ret = poll(&fds, 1, 500); /*超时500ms*/
if (ret == 0) /*超时*/
{
printf("timeout \r\n");
}
else if (ret < 0) /*错误*/
{
}
else /*可读取*/
{
if (fds.revents | POLLIN) /*可读取*/
{
ret = read(fd, &data, sizeof(data));
if (ret < 0)
{
}
else
{
if (data)
{
printf("key value = %#x \r\n", data);
}
}
}
}
}
close(fd);
return 0;
}