上一节学习了阻塞式IO驱动的编写,而应用程序对驱动设备的输入/输出操作还有另一种方式。即非阻塞式IO。
一、非阻塞式IO
应用程序使用非阻塞访问方式从设备读取数据,当设备不可用或数据未准备好的时候会立即向内核返回一个错误码,表示数据读取失败。应用程序会再次重新读取数据,这样一直往复循环,直到数据读取成功。
二、轮询
如果用户应用程序以非阻塞的方式访问设备,设备驱动程序就要提供非阻塞的处理方式,也就是轮询。 poll、 epoll 和 select 可以用于处理轮询,应用程序通过 select、 epoll 或 poll 函数来查询设备是否可以操作,如果可以操作的话就从设备读取或者向设备写入数据。当应用程序调用 select、 epoll 或 poll 函数的时候设备驱动程序中的 poll 函数就会执行,因此需要在设备驱动程序中编写 poll 函数。
实验流程如下:
1、应用程序select函数
int select(int nfds,
fd_set *readfds,
fd_set *writefds,
fd_set *exceptfds,
struct timeval *timeout)
/*函数参数和返回值含义如下:
nfds: 所要监视的这三类文件描述集合中, 最大文件描述符加 1。
readfds、 writefds 和 exceptfds:这三个指针指向描述符集合,这三个参数指明了关心哪些
描述符、需要满足哪些条件等等,
timeout:超时时间,当我们调用 select 函数等待某些文件描述符可以设置超时时间
返回值: 0,表示的话就表示超时发生,但是没有任何文件描述符可以进行操作; -1,发生
错误;其他值,可以进行操作的文件描述符个数。*/
当我们定义好一个 fd_set 变量以后可以使用如下所示几个宏进行操作:
void FD_ZERO(fd_set *set) //FD_ZERO 用于将 fd_set 变量的所有位都清零
void FD_SET(int fd, fd_set *set) //FD_SET 用于将 fd_set 变量的某个位置 1,
//也就是向 fd_set 添加一个文件描述符,参数 fd 就是要加入的文件描述符
void FD_CLR(int fd, fd_set *set) //FD_CLR 用于将 fd_set变量的某个位清零,也就是将一个件
//描述符从 fd_set 中删除,参数 fd 就是要删除的文件描述符
int FD_ISSET(int fd, fd_set *set) //FD_ISSET 用于测试一个文件是否属于某个集合,参数 fd 就
//是要判断的文件描述符。
超时时间使用结构体 timeval 表示,结构体定义如下所示:
当 timeout 为 NULL 的时候就表示无限期的等待。
struct timeval {
long tv_sec; /* 秒 */
long tv_usec; /* 微妙 */
};
2、应用程序poll函数
poll 函数本质上和 select 没有太大的差别,但是 poll 函数没有最大文件描述符限制, Linux 应用程序中 poll 函数原型如下所示:
int poll(struct pollfd *fds,
nfds_t nfds,
int timeout)
/*函数参数和返回值含义如下:
fds: 要监视的文件描述符集合以及要监视的事件,为一个数组,数组元素都是结构体 pollfd
类型的, pollfd 结构体如下所示:
*/
struct pollfd { int fd; /* 文件描述符 */
short events; /* 请求的事件 */
short revents; /* 返回的事件 */
};
/*fd 是要监视的文件描述符,如果 fd 无效的话那么 events 监视事件也就无效,并且 revents
返回 0。 events 是要监视的事件,可监视的事件类型如下所示:
POLLIN 有数据可以读取。
POLLPRI 有紧急的数据需要读取。
POLLOUT 可以写数据。
POLLERR 指定的文件描述符发生错误。
POLLHUP 指定的文件描述符挂起。
POLLNVAL 无效的请求。
POLLRDNORM 等同于 POLLIN
*/
/*revents 是返回参数,也就是返回的事件, 由 Linux 内核设置具体的返回事件。
nfds: poll 函数要监视的文件描述符数量。
timeout: 超时时间,单位为 ms。
返回值:返回 revents 域中不为 0 的 pollfd 结构体个数,也就是发生事件或错误的文件描述
符数量; 0,超时; -1,发生错误,并且设置 errno 为错误类型。*/
3、Linux 驱动下的 poll 操作函数
当应用程序调用 select 或 poll 函数来对驱动程序进行非阻塞访问的时候,驱动程序file_operations 操作集中的 poll 函数就会执行。所以驱动程序的编写者需要提供对应的 poll 函
数, poll 函数原型如下所示:
unsigned int (*poll) (struct file *filp, struct poll_table_struct *wait)
/*函数参数和返回值含义如下:
filp: 要打开的设备文件(文件描述符)。
wait: 结构体 poll_table_struct 类型指针, 由应用程序传递进来的。一般将此参数传递给
poll_wait 函数。
返回值:向应用程序返回设备或者资源状态,可以返回的资源状态如下:
POLLIN 有数据可以读取。
POLLPRI 有紧急的数据需要读取。
POLLOUT 可以写数据。
POLLERR 指定的文件描述符发生错误。
POLLHUP 指定的文件描述符挂起。
POLLNVAL 无效的请求。
POLLRDNORM 等同于 POLLIN,普通数据可读
*/
同时,我们需要在驱动程序的 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 参数。*/
三、实验代码
1、驱动程序
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.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/of_gpio.h>
#include <linux/gpio.h>
#include <linux/slab.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
#include <linux/string.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/wait.h>
#include <linux/ide.h>
#include <linux/poll.h>
#define IMX6ULIRQ_COUNT 1
#define IMX6ULIRQ_NAME "IMX6ULIRQ"
#define KEY_NUM 1 /*因为一块板子上按键不只有1个,所有创建一个结构体数组来描述按键*/
#define KEY0VALUE 0X01
#define INVALKEY 0XFF
/*按键结构体*/
struct irq_keydesc{
int gpio; /*io编号*/
int irqnum; /*中断编号*/
unsigned char value; /*按键值*/
char name[10]; /*中断名字*/
irqreturn_t (*handler) (int, void *); /*中断处理函数*/
};
/*设备结构体*/
struct imx6ulirq_dev{
dev_t devid; /*设备号*/
int major; /*主设备号*/
int minor; /*次设备号*/
struct cdev cdev; /*字符设备*/
struct class *class; /*创建类*/
struct device *device; /*创建设备*/
struct device_node *node; /*设备节点*/
struct irq_keydesc irqkey[KEY_NUM];
struct timer_list timer; /*添加定时器*/
atomic_t keyval; /*按键值*/
atomic_t releasekey;
wait_queue_head_t r_wait; /*读等待队列头*/
};
struct imx6ulirq_dev imx6ulirq;
static int imx6ulirq_open(struct inode *inode, struct file *filp){
filp->private_data = &imx6ulirq; /* 设置私有数据 */
return 0;
}
static int imx6ulirq_release(struct inode *inode, struct file *filp){
return 0;
}
static ssize_t imx6ulirq_read(struct file *filp, char __user *buf,size_t cnt, loff_t *offt){
int ret = 0;
unsigned char keyval;
unsigned char releasekey;
struct imx6ulirq_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)); /*等待按键有效*/
}
keyval = atomic_read(&dev->keyval);
releasekey = atomic_read(&dev->releasekey);
if(releasekey){ /*如果releasekey为真表示一次完整的按键过程*/
if(keyval & 0X80){ /*前面将真实按键值或了0X80,现在判断是否为真,若为真则返回原按键值*/
keyval &= ~0X80;
ret = copy_to_user(buf, &keyval, sizeof(keyval));
}
else{
goto failed_error;
}
atomic_set(&dev->releasekey, 0); /*按下标志清0*/
}
else{
goto failed_error;
}
failed_error:
#if 0
__set_current_state(TASK_RUNNING); /*将当前任务设置为运行状态*/
remove_wait_queue(&dev->r_wait, &wait); /*移除等待队列项目*/
#endif
return ret;
}
static unsigned int imx6ulirq_poll(struct file *filp, poll_table *wait){
int mask = 0;
struct imx6ulirq_dev *dev = filp->private_data;
poll_wait(filp, &dev->r_wait, wait);
/*是否可读*/
if(atomic_read(&dev->releasekey)){ /*按键按下,可读*/
mask = POLLIN | POLLRDNORM; /*返回POLLIN*/
}
return mask;
}
static const struct file_operations imx6ulirq_fops = { /*字符设备操作函数集合*/
.owner = THIS_MODULE,
.open = imx6ulirq_open,
.read = imx6ulirq_read,
.release = imx6ulirq_release,
.poll = imx6ulirq_poll,
};
/*中断处理函数*/
static irqreturn_t key0_handler(int irq, void *dev_id){
struct imx6ulirq_dev *dev = dev_id;
dev->timer.data = (volatile long)dev_id;
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(15)); /*15ms定时*/
return IRQ_HANDLED;
}
/*定时器超市处理函数*/
static void timer_func(unsigned long arg) {
int value = 0;
struct imx6ulirq_dev *dev = (struct imx6ulirq_dev *)arg;
value = gpio_get_value(dev->irqkey[0].gpio);
if(value == 0 ){
atomic_set(&dev->keyval, dev->irqkey[0].value); /*如果按键按下设置按键为设置的值*/
}
else{
atomic_set(&dev->keyval, 0X80 | (dev->irqkey[0].value));/*松开则设置按键为设置的值高位或1,来区分*/
atomic_set(&dev->releasekey, 1); /*releasekey设置为1表示一次完整的按键过程*/
}
/*唤醒进程*/
if(atomic_read(&dev->releasekey)){
wake_up(&dev->r_wait);
}
}
/*按键初始化*/
static int keyio_init(struct imx6ulirq_dev *dev){
int ret = 0;
int i = 0;
/*1、按键初始化*/
dev->node = of_find_node_by_path("/key");
if(dev->node == NULL){
ret = -EINVAL;
goto failed_findnode;
}
/*循环找到节点*/
for (i = 0; i < KEY_NUM; i++){
dev->irqkey[i].gpio = of_get_named_gpio(dev->node, "key-gpios", i);
if(dev->irqkey[i].gpio < 0){
printk("can't get gpio %d\r\n",i);
ret = -EINVAL;
goto failed_findnode;
}
}
/*循环申请gpio*/
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);
ret = gpio_request(dev->irqkey[i].gpio, dev->irqkey[i].name);
if(ret){
printk("Failed to request gpio \r\n");
ret = -EINVAL;
goto failed_findnode;
}
ret = gpio_direction_input(dev->irqkey[i].gpio);
if(ret < 0){
goto failed_setinput;
}
dev->irqkey[i].irqnum = gpio_to_irq(dev->irqkey[i].gpio); /*获取中断号*/
}
/*2、按键中断初始化*/
dev->irqkey[0].handler = key0_handler;
dev->irqkey[0].value = KEY0VALUE;
for(i = 0;i< KEY_NUM; i++){
ret = request_irq(dev->irqkey[i].irqnum, dev->irqkey[i].handler,
IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING, dev->irqkey[i].name, &imx6ulirq);
if(ret){
printk("irq %d request failed!\r\n",i);
goto failed_irq;
}
}
/*初始化定时器*/
init_timer(&dev->timer);
dev->timer.function = timer_func; /*定时器超时处理函数*/
return 0;
failed_irq:
failed_setinput:
for(i = 0;i< KEY_NUM;i++) {
gpio_free(dev->irqkey[i].gpio);
}
failed_findnode:
return ret;
}
/*入口函数*/
static int __init imx6ulirq_init(void){
int ret = 0;
/*注册字符设备*/
imx6ulirq.major = 0; /*内核自动申请设备号*/
if(imx6ulirq.major){ /*如果定义了设备号*/
imx6ulirq.devid = MKDEV(imx6ulirq.major, 0);
ret = register_chrdev_region(imx6ulirq.devid, IMX6ULIRQ_COUNT, IMX6ULIRQ_NAME);
}
else{ /*否则自动申请设备号*/
ret = alloc_chrdev_region(&imx6ulirq.devid, 0, IMX6ULIRQ_COUNT, IMX6ULIRQ_NAME);
imx6ulirq.major = MAJOR(imx6ulirq.devid); /*保存主设备号*/
imx6ulirq.minor = MINOR(imx6ulirq.devid); /*保存次设备号*/
}
if(ret < 0){
goto failed_devid;
}
printk("imx6ulirq_dev major = %d minor = %d \r\n",imx6ulirq.major,imx6ulirq.minor); /*打印主次设备号*/
/*添加字符设备*/
imx6ulirq.cdev.owner = THIS_MODULE;
cdev_init(&imx6ulirq.cdev, &imx6ulirq_fops);
ret = cdev_add(&imx6ulirq.cdev, imx6ulirq.devid, IMX6ULIRQ_COUNT);
if(ret < 0){ /*添加字符设备失败*/
goto failed_cdev;
}
/*自动添加设备节点*/
/*创建类*/
imx6ulirq.class = class_create(THIS_MODULE, IMX6ULIRQ_NAME); /*class_creat(owner,name);*/
if(IS_ERR(imx6ulirq.class)){ /*判断是否创建类成功*/
ret = PTR_ERR(imx6ulirq.class);
goto failed_class;
}
/*创建设备*/
imx6ulirq.device = device_create(imx6ulirq.class, NULL, imx6ulirq.devid, NULL, IMX6ULIRQ_NAME);
if(IS_ERR(imx6ulirq.device)){ /*判断是否创建类成功*/
ret = PTR_ERR(imx6ulirq.device);
goto failed_device;
}
/*初始化IO*/
ret = keyio_init(&imx6ulirq);
if(ret < 0) {
goto failed_keyinit;
}
/*初始化原子变量*/
atomic_set(&imx6ulirq.keyval, INVALKEY);
atomic_set(&imx6ulirq.releasekey, 0);
/*初始化等待队列头*/
init_waitqueue_head(&imx6ulirq.r_wait);
return 0;
failed_keyinit:
failed_device:
class_destroy(imx6ulirq.class);
failed_class:
cdev_del(&imx6ulirq.cdev);
failed_cdev:
unregister_chrdev_region(imx6ulirq.devid, IMX6ULIRQ_COUNT);
failed_devid:
return ret;
}
/*出口函数*/
static void __exit imx6ulirq_exit(void){
int i = 0;
/*释放中断*/
for(i = 0;i< KEY_NUM;i++) {
free_irq(imx6ulirq.irqkey[i].irqnum, &imx6ulirq);
}
/*释放io*/
for(i = 0;i< KEY_NUM;i++) {
gpio_free(imx6ulirq.irqkey[i].gpio);
}
/*删除定时器*/
del_timer_sync(&imx6ulirq.timer);
/*注销字符设备*/
cdev_del(&imx6ulirq.cdev);
/*卸载设备*/
unregister_chrdev_region(imx6ulirq.devid, IMX6ULIRQ_COUNT);
device_destroy(imx6ulirq.class, imx6ulirq.devid);
class_destroy(imx6ulirq.class);
}
/*模块入口和出口*/
module_init(imx6ulirq_init);
module_exit(imx6ulirq_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ZYC");
2、app
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include <sys/ioctl.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <poll.h>
int main(int argc, char *argv[])
{
int fd, ret;
char *filename;
unsigned char data;
//fd_set readfds;
struct pollfd fds;
struct timeval timeout;
if(argc != 2){
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
/* 打开 key 驱动 */
fd = open(filename, O_RDWR | O_NONBLOCK); /*非阻塞打开*/
if(fd < 0){
printf("file %s open failed!\r\n", argv[1]);
return -1;
}
#if 0
/*循环读取*/
while(1){
FD_ZERO(&readfds);
FD_SET(fd, &readfds);
timeout.tv_usec = 0;
timeout.tv_usec = 500000; /*超时时间500ms*/
ret = select(fd+1, &readfds, NULL, NULL, &timeout);
switch(ret){
case 0: /*超时*/
break;
case -1: /*错误*/
break;
default: /*可以读取数据*/
if(FD_ISSET(fd, &readfds)){
ret = read(fd, &data, sizeof(data));
if(ret < 0){
}
else{
if(data)
printf("keyvalue = %#x \r\n", data);
}
}
break;
}
}
#endif
while(1){
fds.fd = fd;
fds.events = POLLIN;
ret = poll(&fds, 1, 500); /*超时时间500ms*/
if(ret == 0){/*超时*/
}
else if(ret < 0){ /*错误*/
}
else{ /*读取数据*/
if(fds.events & POLLIN){ /*可读取*/
ret = read(fd, &data, sizeof(data));
if(ret < 0){
}
else{
if(data)
printf("keyvalue = %#x \r\n", data);
}
}
}
}
ret= close(fd); /* 关闭文件 */
if(ret < 0){
printf("file %s close failed!\r\n", argv[1]);
return -1;
}
return 0;
}