linux 阻塞IO与非阻塞IO
一, 简介
当应用程序对设 备驱动进行操作的时候,如果不能获取到设备资源,那么阻塞式 IO 就会将应用程序对应的线程 挂起,直到设备资源可以获取为止。对于非阻塞 IO,应用程序对应的线程不会挂起,它要么一 直轮询等待,直到设备资源可以使用,要么就直接放弃。
二,阻塞IO与非阻塞IO实现原理(驱动)
相同:两者都使用到了等待队列
不同:非阻塞IO应用程序调用 select 或 poll 函数来对驱动程序进行非阻塞访问的时候,驱动程序file_operations 操作集中的 poll 函数就会执行。
2.1 等待队列
阻塞访问最大的好处就是当设备文件不可操作的时候进程可以进入休眠态,这样可以将 CPU 资源让出来。但是,当设备文件可以操作的时候就必须唤醒进程,一般在中断函数里面完 成唤醒工作。
Linux 内核提供了等待队列(wait queue)来实现阻塞进程的唤醒工作,如果我们要 在驱动中使用等待队列,必须创建并初始化一个等待队列头
2.1.1 等待队列头
//等待队列头使用结构体 wait_queue_head 表示
struct wait_queue_head {
spinlock_t lock;
struct list_head head;
};
typedef struct wait_queue_head wait_queue_head_t;
/**
* 功能:
* 初始化等待队列头
* 参数:
* wq_head 就是要初始化的等待队列头
*/
void init_waitqueue_head(struct wait_queue_head *wq_head)
也可以使用宏 DECLARE_WAIT_QUEUE_HEAD 来一次性完成等待队列头的定义的初始化
2.1.2 等待队列项
等待队列头就是一个等待队列的头部,每个访问设备的进程都是一个队列项,当设备不可 用的时候就要将这些进程对应的队列项添加到等待队列里面
struct wait_queue_entry {
unsigned int flags;
void *private;
wait_queue_func_t func;
struct list_head entry;
};
//CLARE_WAITQUEUE 定义并初始化一个等待队列项
/**
* 参数:
* name 就是等待队列项的名字
* tsk 表示这个等待队列项属于哪个任务(进程),一般设置为current,在Linux内核中 current 相当于一个全局变量,表示当前进程
*/
DECLARE_WAITQUEUE(name, tsk)
2.1.3 将队列项添加/移除等待队列头
/**
* 功能:
* 将队列项添加等待队列头
* 参数:
* wq_head:等待队列项要加入的等待队列头
* wq_entry:要加入的等待队列项
* 返回值:
* 无
*/
void add_wait_queue(struct wait_queue_head *wq_head,
struct wait_queue_entry *wq_entry)
/**
* 功能:
* 将队列项移除等待队列头
* 参数:
* wq_head:要删除的等待队列项所处的等待队列头
* wq_entry:要删除的等待队列项
* 返回值:
* 无
*/
void remove_wait_queue(struct wait_queue_head *wq_head,
struct wait_queue_entry *wq_entry)
2.1.4 等待唤醒
/**
* 功能:
* 当设备可以使用的时候就要唤醒进入休眠态的进程
* 参数:
* wq_head 就是要唤醒的等待队列头
* 返回值:
* 无
*/
void wake_up(struct wait_queue_head *wq_head)
void wake_up_interruptible(struct wait_queue_head *wq_head)
wake_up 函数可以唤醒处于 TASK_INTERRUPTIBLE 和 TASK_UNINTERRUPTIBLE 状 态的进程,而 wake_up_interruptible 函数只能唤醒处于 TASK_INTERRUPTIBLE 状态的进程
TASK_RUNNING : 进程处于可运行状态,但并不意味着进程已经实际上已分配到 CPU ,它可能会一直等到调度器选中它。该状态只是确保进程一旦被 CPU 选中时立马可以运行,而无需等待外部事件。
TASK_INTERRUPTIBLE : 这是针对等待某事件或其他资源而睡眠的进程设置的。在内核发送信号给该进程时表明等待的事件已经发生或资源已经可用,进程状态变为 TASK_RUNNING,此时只要被调度器选中就立即可恢复运行。
TASK_UNINTERRUPTIBLE : 处于此状态,不能由外部信号唤醒,只能由内核亲自唤醒。
2.1.5 等待事件
设置等待队列等待某个事件,当这个事件满足以后就自动唤醒 等待队列中的进程
/**
* 功能:
* 等待以 wq_head 为等待队列头的等待队列被唤醒,前提是 condition 条件必须满足(为真),
* 否则一直阻塞。此函数会将进程设置为 TASK_UNINTERRUPTIBLE 状态
*/
wait_event(wq_head, condition)
/**
* 功能:
* 等待以 wq_head 为等待队列头的等待队列被唤醒,前提是 condition 条件必须满足(为真),
* 否则一直阻塞。此函数会将进程设置为 TASK_UNINTERRUPTIBLE 状态
* 返回值:
* 如果返回 0 的话表示超时时间到,而且 condition 为假。
* 为 1 的话表示condition 为真
*/
wait_event_timeout(wq_head, condition, timeout)
/**
* 功能:
* 与 wait_event 函数类似,但是此函数将进程设置为 TASK_INTERRUPTIBLE,就是可以被信号打断。
*/
wait_event_interruptible(wq_head, condition)
/**
* 功能:
* 与 wait_event_timeout 函数类似,但是此函数将进程设置为 TASK_INTERRUPTIBLE,就是可以被信号打断。
* 返回值:
* 如果返回 0 的话表示超时时间到,而且 condition 为假。
* 为 1 的话表示condition 为真
*/
wait_event_interruptible_timeout(wq_head, condition, timeout)
2.2 Linux 驱动下的 poll 操作函数
应用程序调用 select 或 poll 函数来对驱动程序进行非阻塞访问的时候,驱动程序 file_operations 操作集中的 poll 函数就会执行
/**
* 功能:
* 与 wait_event_timeout 函数类似,但是此函数将进程设置为 TASK_INTERRUPTIBLE,就是可以被信号打断。
* 参数:
* filp:要打开的设备文件(文件描述符)
* wait:结构体 poll_table_struct 类型指针,由应用程序传递进来的。一般将此参数传递给poll_wait 函数
* 返回值:
* 向应用程序返回设备或者资源状态
*/
unsigned int (*poll) (struct file *filp, struct poll_table_struct *wait)
>> 可返回资源状态
>> POLLIN 有数据可以读取 >> POLLOUT 可以写数据
>> POLLPRI 有紧急的数据需要读取 >> POLLERR 指定的文件描述符发生错误
>> POLLHUP 指定的文件描述符挂起 >> POLLNVAL 无效的请求
>> POLLRDNORM 等同于 POLLIN,普通数据可读
需要在驱动程序的 poll 函数中调用 poll_wait 函数,poll_wait 函数不会引起阻塞,只是 将应用程序添加到 poll_table 中
/**
* 参数:
* wait_address 是要添加到 poll_table 中的等待队列头
* p 就是 poll_table,就是file_operations 中 poll 函数的 wait 参数
* 返回值:
* 向应用程序返回设备或者资源状态
*/
void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
三,应用程序
3.1 非阻塞IO
非阻塞io需要在open设备文件时添加参数 O_NONBLOCK表示以非阻塞方式打开设备,应用程序以非阻塞的方式访问设备,设备驱动程序就要提供非阻塞的处理方式, 也就是轮询。poll、epoll 和 select 可以用于处理轮询,应用程序通过 select、epoll 或 poll 函数来 查询设备是否可以操作,如果可以操作的话就从设备读取或者向设备写入数据。
3.1.1 select 函数
/**
* 参数:
* nfds: 所要监视的这三类文件描述集合中,最大文件描述符加 1
* readfds: 用于监视指定描述符集的读变化,也就是监视这些文件是否可以读取。可以将 readfs设置为 NULL,表示不关心任何文件的读变化
* writefs: 用于监视这些文件是否可以进行写操作
* exceptfds: 用于监视这些文件的异常
* timeout: 超时时间
* 返回值:
* 0,表示的话就表示超时发生,但是没有任何文件描述符可以进行操作;-1,发生错误;其他值,可以进行操作的文件描述符个数。
*/
int select(int nfds,
fd_set *readfds,
fd_set *writefds,
fd_set *exceptfds,
struct timeval *timeout)
struct timeval {
long tv_sec; /* 秒 */
long tv_usec; /* 微妙 */
};
3.1.2 简单使用
fd_set readfds; /* 读操作文件描述符集 */
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);
if(ret == 0) {
/* 超时 */
}else if(ret == -1) {
/* 错误 */
}else {
if(FD_ISSET(fd, &readfds)) { /* 判断是否为 fd 文件描述符 */
/* 使用 read 函数读取数据 */
}
}
3.1.3 poll 函数
/**
* 参数:
* fds:要监视的文件描述符集合以及要监视的事件,为一个数组,数组元素都是结构体 pollfd类型的
* nfds:poll 函数要监视的文件描述符数量
* timeout:超时时间,单位为 ms
* 返回值:
* 返回 revents 域中不为 0 的 pollfd 结构体个数,也就是发生事件或错误的文件描述符数量;0,超时;-1,发生错误,并且设置 errno 为错误类型
*/
int poll(struct pollfd *fds,
nfds_t nfds,
int timeout)
struct pollfd {
int fd; /* 文件描述符 */
short events; /* 请求的事件 */
short revents; /* 返回的事件 */
};
>> 可监视的事件类型
>> POLLIN 有数据可以读取 >> POLLOUT 可以写数据
>> POLLPRI 有紧急的数据需要读取 >> POLLERR 指定的文件描述符发生错误
>> POLLHUP 指定的文件描述符挂起 >> POLLNVAL 无效的请求
>> POLLRDNORM 等同于 POLLIN,普通数据可读
3.1.4 简单使用
struct pollfd fds;
/* 构造结构体 */
fds.fd = fd;
fds.events = POLLIN; /* 监视数据是否可以读取 */
ret = poll(&fds, 1, 500); /* 轮询文件是否可操作,超时 500ms */
if (ret) { /* 数据有效 */
/* 读取数据 */
} else if (ret == 0) { /* 超时 */
} else if (ret < 0) { /* 错误 */
}
3.1.5 epoll 函数
/**
* 参数:
* size:从 Linux2.6.8 开始此参数已经没有意义了,随便填写一个大于 0 的值就可以
* 返回值:
* epoll 句柄,如果为-1 的话表示创建失败
*/
int epoll_create(int size)
/**
* 参数:
* epfd:要操作的 epoll 句柄,也就是使用 epoll_create 函数创建的 epoll 句柄。
* op:表示要对 epfd(epoll 句柄)进行的操作,可以设置为
* >> EPOLL_CTL_ADD 向 epfd 添加文件参数 fd 表示的描述符
* >> EPOLL_CTL_MOD 修改参数 fd 的 event 事件
* >> EPOLL_CTL_DEL 从 epfd 中删除 fd 描述符
* fd:要监视的文件描述符
* event:要监视的事件类型,为 epoll_event 结构体类型指针
* 返回值:
* 0,成功;-1,失败,并且设置 errno 的值为相应的错误码
*/
int epoll_ctl(int epfd,
int op,
int fd,
struct epoll_event *event)
struct epoll_event {
uint32_t events; /* epoll 事件 */
epoll_data_t data; /* 用户数据 */
};
>> epoll_event 的 events 成员变量表示要监视的事件
>> EPOLLIN 有数据可以读取 >> EPOLLOUT 可以写数据
>> EPOLLPRI 有紧急的数据需要读取 >> EPOLLERR 指定的文件描述符发生错误
>> EPOLLHUP 指定的文件描述符挂起 >> EPOLLET 设置 epoll 为边沿触发,默认触发模式为水平触发
>> EPOLLONESHOT 一次性的监视,当监视完成以后还需要再次监视某个 fd,那么就需要将fd 重新添加到 epoll 里面
//一切都设置好以后应用程序就可以通过 epoll_wait 函数来等待事件的发生
/**
* 参数:
* epfd:要等待的 epoll
* events:指向 epoll_event 结构体的数组
* maxevents:events 数组大小,必须大于 0
* timeout:超时时间,单位为 ms
* 返回值:
* 0,超时;-1,错误;其他值,准备就绪的文件描述符数量
*/
int epoll_wait(int epfd,
struct epoll_event *events,
int maxevents,
int timeout)
3.1.5 区别
select 函数能够监视的文件描述符数量有最大的限制
poll 函数没有最大文件描述符限制
epoll 更多的是用在大规模的并发服务器上
使用前应用程序都必须设置非阻塞flag(O_NONBLOCK)
传统的 selcet 和 poll 函数都会随着所监听的 fd 数量的增加,出现效率低下的问题,而且 poll 函数每次必须遍历所有的描述符来检查就绪的描述符,这个过程很浪费时间,epoll应运而生
3.2 阻塞IO
与一般使用无区别。
四,Linux驱动实例-按键
4.1 阻塞IO
Debug() == 1:使用等待事件
Debug() == 0:使用队列项添加/移除等待队列头
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <linux/timer.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define Debug() 1
#define KEY_CNT 1 /* 设备号个数 */
#define KEY_NAME "keyirq" /* 名字 */
/* key 设备结构体*/
struct key_dev {
dev_t devid; /* 设备号 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
struct device_node *nd; /* 设备节点 */
int key_gpio; /* key 所使用的 GPIO 编号 */
struct timer_list timer; /* 定义一个定时器 */
int irq_num; /* 中断号 */
atomic_t keyvalue; /* 原子变量 */
wait_queue_head_t r_wait; /* 读等待队列头 */
};
static struct key_dev keydev; /* 按键设备 */
static irqreturn_t key_interrupt(int irq, void *dev_id)
{
/* 按键防抖处理,开启定时器延时 15ms */
mod_timer(&keydev.timer, jiffies + msecs_to_jiffies(15));
return IRQ_HANDLED;
}
/**
* 初始化按键IO
*/
static int key_parse_dt(void)
{
int ret;
const char *str;
/* 设置 key 所使用的 GPIO */
/* 获取设备节点:keyled */
keydev.nd = of_find_node_by_path("/key");
if(keydev.nd == NULL) {
printk("key node nost find!\r\n");
return -EINVAL;
}
/* 获取 compatible 属性内容 */
ret = of_property_read_string(keydev.nd,"compatible",&str);
if(ret < 0) {
printk("keydev: Failed to get compatible property\n");
return -EINVAL;
}
if (strcmp(str, "alientek,key")) {
printk("keydev: Compatible match failed\n");
return -EINVAL;
}
ret = of_property_read_string(keydev.nd,"status",&str);
if(ret < 0)
return -EINVAL;
if(strcmp(str,"okay")) {
return -EINVAL;
}
/* 获取设备树中的 key 属性,得到 key 所使用的 key 编号 */
keydev.key_gpio = of_get_named_gpio(keydev.nd, "key-gpio", 0);
if(keydev.key_gpio < 0) {
printk("can't get key-gpio\r\n");
return -EINVAL;
}
printk("key-gpio num = %d\r\n", keydev.key_gpio);
/* 获取 GPIO 对应的中断号 */
keydev.irq_num = irq_of_parse_and_map(keydev.nd, 0);
if(!keydev.irq_num)
return -EINVAL;
return 0;
}
static int key_gpio_init(void)
{
int ret;
unsigned long irq_flags;
/* 向 gpio 子系统申请使用 GPIO */
ret = gpio_request(keydev.key_gpio, "key-gpio");
if (ret) {
printk(KERN_ERR "keydev: Failed to request key-gpio\n");
return ret;
}
/* 将 GPIO 设置为输入模式 */
gpio_direction_input(keydev.key_gpio);
/* 申请中断 下降沿触发*/
ret = request_irq(keydev.irq_num, key_interrupt, IRQF_TRIGGER_FALLING, "Key0_IRQ", NULL);
if (ret) {
gpio_free(keydev.key_gpio);
return ret;
}
return 0;
}
static void timerout(struct timer_list *arg)
{
int key_value;
key_value = gpio_get_value(keydev.key_gpio);
atomic_set(&keydev.keyvalue, key_value);
wake_up_interruptible(&keydev.r_wait); /* 唤醒 */
}
/**
* 打开设备
*/
static int keyirq_open(struct inode *inode, struct file *filp)
{
filp->private_data = &keydev; /* 设置私有数据 */
return 0;
}
/**
* 从设备读数据
*/
static ssize_t keyirq_read(struct file *filp, char __user *buf,size_t cnt, loff_t *offt)
{
int ret = 0;
int value;
struct key_dev *dev = filp->private_data;
DECLARE_WAITQUEUE(wait, current);
#if Debug()
printk("等待事件\r\n");
printk("keyvalue %d\r\n",atomic_read(&dev->keyvalue));
ret = wait_event_interruptible(dev->r_wait, 2 !=atomic_read(&dev->keyvalue));
#else
printk("等待队列项\r\n");
printk("keyvalue %d\r\n",atomic_read(&dev->keyvalue));
if(2 == atomic_read(&dev->keyvalue)) {
add_wait_queue(&dev->r_wait,&wait);
__set_current_state(TASK_INTERRUPTIBLE);
printk("在等待队列中\r\n");
schedule();
__set_current_state(TASK_RUNNING);
remove_wait_queue(&dev->r_wait,&wait);
}
printk("等待项被唤醒\n");
#endif
value = atomic_read(&dev->keyvalue);
ret = copy_to_user(buf, &value, sizeof(value));
atomic_set(&dev->keyvalue, 2);
printk("end_keyvalue %d\r\n",atomic_read(&dev->keyvalue));
return ret;
}
/**
* 释放设备
*/
static int keyirq_release(struct inode *inode, struct file *filp)
{
return 0;
}
/**
* 设备操作函数
*/
static struct file_operations keyirq_fops = {
.owner = THIS_MODULE,
.open = keyirq_open,
.read = keyirq_read,
.release = keyirq_release,
};
/**
* 驱动函数入口
*/
static int __init keyirq_init(void)
{
int ret;
/* 设备树解析 */
ret = key_parse_dt();
if(ret)
return ret;
/* GPIO 中断初始化 */
ret = key_gpio_init();
if(ret)
return ret;
/* 初始化等待队列头 */
init_waitqueue_head(&keydev.r_wait);
/* 初始化原子变量 */
keydev.keyvalue= (atomic_t)ATOMIC_INIT(0);
/* 原子变量初始值为 INVAKEY */
atomic_set(&keydev.keyvalue, 2);
if(keydev.major) {
keydev.devid = MKDEV(keydev.major, 0);
ret = register_chrdev_region(keydev.devid, KEY_CNT,KEY_NAME);
if(ret < 0) {
printk("设备号申请失败\r\n");
goto free_gpio;
}
}else {
ret = alloc_chrdev_region(&keydev.devid, 0, KEY_CNT,KEY_NAME); /* 申请设备号 */
if(ret < 0) {
printk("动态申请设备号失败\r\n");
return -EIO;
}
keydev.major = MAJOR(keydev.devid); /* 获取分配号的主设备号 */
keydev.minor = MINOR(keydev.devid); /* 获取分配号的次设备号 */
}
/* 初始化 cdev */
keydev.cdev.owner = THIS_MODULE;
cdev_init(&keydev.cdev, &keyirq_fops);
/* 添加一个 cdev */
ret = cdev_add(&keydev.cdev, keydev.devid, KEY_CNT);
if(ret < 0)
goto del_unregister;
/* 创建类 */
keydev.class = class_create(THIS_MODULE, KEY_NAME);
if (IS_ERR(keydev.class))
goto del_cdev;
/* 创建设备 */
keydev.device = device_create(keydev.class, NULL, keydev.devid,NULL, KEY_NAME);
if (IS_ERR(keydev.device))
goto destroy_class;
/* 初始化 timer,设置定时器处理函数,还未设置周期,所有不会激活定时器 */
timer_setup(&keydev.timer, timerout, 0);
printk("驱动初始化成功\r\n");
return 0;
destroy_class:
device_destroy(keydev.class, keydev.devid);
del_cdev:
cdev_del(&keydev.cdev);
del_unregister:
unregister_chrdev_region(keydev.devid, KEY_CNT);
free_gpio:
free_irq(keydev.irq_num, NULL);
gpio_free(keydev.key_gpio);
return -EIO;
//return -EIO 通常是指示模块加载或初始化失败
}
/**
* 驱动函数出口
*/
static void __exit keyirq_exit(void)
{
/* 注销字符设备驱动 */
cdev_del(&keydev.cdev); /* 删除 cdev */
unregister_chrdev_region(keydev.devid, KEY_CNT); /* 注销设备号 */
del_timer_sync(&keydev.timer); /* 删除 timer */
device_destroy(keydev.class, keydev.devid); /*注销设备 */
class_destroy(keydev.class); /* 注销类 */
free_irq(keydev.irq_num, NULL); /* 释放中断 */
gpio_free(keydev.key_gpio); /* 释放 IO */
printk("驱动注销成功\r\n");
}
module_init(keyirq_init);
module_exit(keyirq_exit);
MODULE_AUTHOR("dzl");
MODULE_LICENSE("GPL");
MODULE_INFO(intree, "Y");
阻塞等待按键按下唤醒
4.2 非阻塞IO
4.2.1 驱动
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <linux/poll.h>
#include <asm/mach/map.h>
#include <linux/timer.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define KEY_CNT 1 /* 设备号个数 */
#define KEY_NAME "keyirq" /* 名字 */
/* key 设备结构体*/
struct key_dev {
dev_t devid; /* 设备号 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
struct device_node *nd; /* 设备节点 */
int key_gpio; /* key 所使用的 GPIO 编号 */
struct timer_list timer; /* 定义一个定时器 */
int irq_num; /* 中断号 */
atomic_t keyvalue; /* 原子变量 */
wait_queue_head_t r_wait; /* 读等待队列头 */
};
static struct key_dev keydev; /* 按键设备 */
static irqreturn_t key_interrupt(int irq, void *dev_id)
{
/* 按键防抖处理,开启定时器延时 15ms */
mod_timer(&keydev.timer, jiffies + msecs_to_jiffies(15));
return IRQ_HANDLED;
}
/**
* 初始化按键IO
*/
static int key_parse_dt(void)
{
int ret;
const char *str;
/* 设置 key 所使用的 GPIO */
/* 获取设备节点:keyled */
keydev.nd = of_find_node_by_path("/key");
if(keydev.nd == NULL) {
printk("key node nost find!\r\n");
return -EINVAL;
}
/* 获取 compatible 属性内容 */
ret = of_property_read_string(keydev.nd,"compatible",&str);
if(ret < 0) {
printk("keydev: Failed to get compatible property\n");
return -EINVAL;
}
if (strcmp(str, "alientek,key")) {
printk("keydev: Compatible match failed\n");
return -EINVAL;
}
ret = of_property_read_string(keydev.nd,"status",&str);
if(ret < 0)
return -EINVAL;
if(strcmp(str,"okay")) {
return -EINVAL;
}
/* 获取设备树中的 key 属性,得到 key 所使用的 key 编号 */
keydev.key_gpio = of_get_named_gpio(keydev.nd, "key-gpio", 0);
if(keydev.key_gpio < 0) {
printk("can't get key-gpio\r\n");
return -EINVAL;
}
printk("key-gpio num = %d\r\n", keydev.key_gpio);
/* 获取 GPIO 对应的中断号 */
keydev.irq_num = irq_of_parse_and_map(keydev.nd, 0);
if(!keydev.irq_num)
return -EINVAL;
return 0;
}
static int key_gpio_init(void)
{
int ret;
unsigned long irq_flags;
/* 向 gpio 子系统申请使用 GPIO */
ret = gpio_request(keydev.key_gpio, "key-gpio");
if (ret) {
printk(KERN_ERR "keydev: Failed to request key-gpio\n");
return ret;
}
/* 将 GPIO 设置为输入模式 */
gpio_direction_input(keydev.key_gpio);
/* 获取设备树中指定的中断触发类型 */
irq_flags = irq_get_trigger_type(keydev.irq_num);
if ((IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING) == irq_flags)
printk("双边沿触发中断\r\n");
/* 申请中断 */
ret = request_irq(keydev.irq_num, key_interrupt, irq_flags, "Key0_IRQ", NULL);
if (ret) {
gpio_free(keydev.key_gpio);
return ret;
}
return 0;
}
static void timerout(struct timer_list *arg)
{
int key_value;
key_value = gpio_get_value(keydev.key_gpio);
atomic_set(&keydev.keyvalue, key_value);
wake_up_interruptible(&keydev.r_wait); /* 唤醒 */
}
/**
* 打开设备
*/
static int keyirq_open(struct inode *inode, struct file *filp)
{
filp->private_data = &keydev; /* 设置私有数据 */
return 0;
}
/**
* 从设备读数据
*/
static ssize_t keyirq_read(struct file *filp, char __user *buf,size_t cnt, loff_t *offt)
{
int ret = 0;
int value;
struct key_dev *dev = filp->private_data;
if (filp->f_flags & O_NONBLOCK) { /* 非阻塞方式访问 */
if(2 == atomic_read(&dev->keyvalue)) {
printk("错误码\r\n");
return -EAGAIN;
}
}
value = atomic_read(&dev->keyvalue);
ret = copy_to_user(buf, &value, sizeof(value));
atomic_set(&dev->keyvalue, 2);
return ret;
}
static unsigned int keyirq_poll(struct file *filp,struct poll_table_struct *wait)
{
unsigned int mask = 0;
poll_wait(filp, &keydev.r_wait, wait);
if(2 != atomic_read(&keydev.keyvalue))
mask = POLLIN | POLLRDNORM;
return mask;
}
/**
* 释放设备
*/
static int keyirq_release(struct inode *inode, struct file *filp)
{
return 0;
}
/**
* 设备操作函数
*/
static struct file_operations keyirq_fops = {
.owner = THIS_MODULE,
.open = keyirq_open,
.read = keyirq_read,
.release = keyirq_release,
.poll = keyirq_poll,
};
/**
* 驱动函数入口
*/
static int __init keyirq_init(void)
{
int ret;
/* 设备树解析 */
ret = key_parse_dt();
if(ret)
return ret;
/* GPIO 中断初始化 */
ret = key_gpio_init();
if(ret)
return ret;
/* 初始化等待队列头 */
init_waitqueue_head(&keydev.r_wait);
/* 初始化原子变量 */
keydev.keyvalue= (atomic_t)ATOMIC_INIT(0);
/* 原子变量初始值为 INVAKEY */
atomic_set(&keydev.keyvalue, 2);
if(keydev.major) {
keydev.devid = MKDEV(keydev.major, 0);
ret = register_chrdev_region(keydev.devid, KEY_CNT,KEY_NAME);
if(ret < 0) {
printk("设备号申请失败\r\n");
goto free_gpio;
}
}else {
ret = alloc_chrdev_region(&keydev.devid, 0, KEY_CNT,KEY_NAME); /* 申请设备号 */
if(ret < 0) {
printk("动态申请设备号失败\r\n");
return -EIO;
}
keydev.major = MAJOR(keydev.devid); /* 获取分配号的主设备号 */
keydev.minor = MINOR(keydev.devid); /* 获取分配号的次设备号 */
}
/* 初始化 cdev */
keydev.cdev.owner = THIS_MODULE;
cdev_init(&keydev.cdev, &keyirq_fops);
/* 添加一个 cdev */
ret = cdev_add(&keydev.cdev, keydev.devid, KEY_CNT);
if(ret < 0)
goto del_unregister;
/* 创建类 */
keydev.class = class_create(THIS_MODULE, KEY_NAME);
if (IS_ERR(keydev.class))
goto del_cdev;
/* 创建设备 */
keydev.device = device_create(keydev.class, NULL, keydev.devid,NULL, KEY_NAME);
if (IS_ERR(keydev.device))
goto destroy_class;
/* 初始化 timer,设置定时器处理函数,还未设置周期,所有不会激活定时器 */
timer_setup(&keydev.timer, timerout, 0);
printk("驱动初始化成功\r\n");
return 0;
destroy_class:
device_destroy(keydev.class, keydev.devid);
del_cdev:
cdev_del(&keydev.cdev);
del_unregister:
unregister_chrdev_region(keydev.devid, KEY_CNT);
free_gpio:
free_irq(keydev.irq_num, NULL);
gpio_free(keydev.key_gpio);
return -EIO;
//return -EIO 通常是指示模块加载或初始化失败
}
/**
* 驱动函数出口
*/
static void __exit keyirq_exit(void)
{
/* 注销字符设备驱动 */
cdev_del(&keydev.cdev); /* 删除 cdev */
unregister_chrdev_region(keydev.devid, KEY_CNT); /* 注销设备号 */
del_timer_sync(&keydev.timer); /* 删除 timer */
device_destroy(keydev.class, keydev.devid); /*注销设备 */
class_destroy(keydev.class); /* 注销类 */
free_irq(keydev.irq_num, NULL); /* 释放中断 */
gpio_free(keydev.key_gpio); /* 释放 IO */
printk("驱动注销成功\r\n");
}
module_init(keyirq_init);
module_exit(keyirq_exit);
MODULE_AUTHOR("dzl");
MODULE_LICENSE("GPL");
MODULE_INFO(intree, "Y");
4.2.2 应用程序
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <poll.h>
int main(int argc, char *argv[])
{
fd_set readfds;
int key_val;
int fd,ret;
fd = open(argv[1], O_RDONLY | O_NONBLOCK);
if(fd < 0) {
printf("ERROR: %s file open failed!\n");
return -1;
}
FD_ZERO(&readfds);
FD_SET(fd, &readfds);
for ( ; ; ) {
ret = select(fd + 1, &readfds, NULL, NULL, NULL);
if(FD_ISSET(fd, &readfds)) {
read(fd, &key_val, sizeof(int));
if (0 == key_val)
printf("Key Press\n");
else if (1 == key_val)
printf("Key Release\n");
}
}
close(fd);
return 0;
}
标签:IO,int,MP157,keydev,阻塞,ret,key,wait,struct
From: https://blog.csdn.net/2302_76897887/article/details/141599396