使用非阻塞I/O的应用程序通常会使用select()和poll()系统调用查询是否可对设备进行无阻塞的访问.
select() poll() epoll()系统调用最终会使设备驱动中的poll()函数被执行.
POLL机制
达到的效果:
应用程序读取数据时, 查看是否有数据, 没有数据休眠一段时间(休眠期间若有数据, 那么会唤醒线程), 再查询, 这个循环超时后返回
关键函数poll_wait
头文件 linux/poll.h
static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
{
if (p && p->_qproc && wait_address)
p->_qproc(filp, wait_address, p);
}
poll_wait()并不会引发阻塞, 其工作是把当前进程添加到wait参数指向的等待列表(poll_table)中, 实际作用是让唤醒参数queue对应的等待队列可以唤醒因select()而睡眠的进程.
驱动中的poll()实现
设备驱动中的poll()函数原型是:
unsigned int (*poll)(struct file *filp, struct poll_table* wait);
编写驱动
使用poll机制时,驱动程序的核心就是提供对应的drv_poll函数。
在drv_poll函数中要做2件事:
① 把当前线程挂入队列wq:poll_wait
APP调用一次poll,可能导致drv_poll被调用2次,但是我们并不需要把当前线程挂入队列2次。
可以使用内核的函数poll_wait把线程挂入队列,如果线程已经在队列里了,它就不会再次挂入。
② 返回设备状态:
APP调用poll函数时,有可能是查询“有没有数据可以读”:POLLIN,也有可能是查询“你有没有空间给我写数据”:POLLOUT。
所以drv_poll要返回自己的当前状态:(POLLIN | POLLRDNORM) 或 (POLLOUT | POLLWRNORM)。
POLLRDNORM等同于POLLIN,为了兼容某些APP把它们一起返回。
POLLWRNORM等同于POLLOUT ,为了兼容某些APP把它们一起返回。
#include <linux/module.h>
#include <linux/poll.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>
struct gpio_key{
int gpio;
struct gpio_desc *gpiod;
int flag;
int irq;
} ;
static struct gpio_key *gpio_keys_array_100ask;
/* 主设备号 */
static int major = 0;
static struct class *gpio_key_class;
/* 环形缓冲区 */
#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));
}
static void put_key(int key)
{
if (!is_key_buf_full())
{
g_keys[w] = key;
w = NEXT_POS(w);
}
}
static int get_key(void)
{
int key = 0;
if (!is_key_buf_empty())
{
key = g_keys[r];
r = NEXT_POS(r);
}
return key;
}
static DECLARE_WAIT_QUEUE_HEAD(gpio_key_wait);
/* 实现对应的open/read/write等函数,填入file_operations结构体 */
static ssize_t gpio_key_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
//printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
int err;
int key;
wait_event_interruptible(gpio_key_wait, !is_key_buf_empty());
key = get_key();
err = copy_to_user(buf, &key, 4);
return 4;
}
static unsigned int gpio_key_drv_poll(struct file *fp, poll_table * wait)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
poll_wait(fp, &gpio_key_wait, wait); //查询一次后 进入休眠
return is_key_buf_empty() ? 0 : POLLIN | POLLRDNORM;
}
/* 定义自己的file_operations结构体 */
static struct file_operations gpio_key_drv = {
.owner = THIS_MODULE,
.read = gpio_key_drv_read,
.poll = gpio_key_drv_poll,
};
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); //唤醒, 会使得poll跳出poll_wait
return IRQ_HANDLED;
}
/* 1. 从platform_device获得GPIO
* 2. gpio=>irq
* 3. request_irq
*/
static int gpio_key_probe(struct platform_device *pdev)
{
int err;
struct device_node *node = pdev->dev.of_node;
int count;
int i;
enum of_gpio_flags flag;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
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_keys_array = kzalloc(sizeof(struct gpio_key) * count, GFP_KERNEL);
for (i = 0; i < count; i++)
{
gpio_keys_array[i].gpio = of_get_gpio_flags(node, i, &flag);
if (gpio_keys_array[i].gpio < 0)
{
printk("%s %s line %d, of_get_gpio_flags fail\n", __FILE__, __FUNCTION__, __LINE__);
return -1;
}
gpio_keys_array[i].gpiod = gpio_to_desc(gpio_keys_array[i].gpio);
gpio_keys_array[i].flag = flag & OF_GPIO_ACTIVE_LOW;
gpio_keys_array[i].irq = gpio_to_irq(gpio_keys_array[i].gpio);
}
for (i = 0; i < count; i++)
{
err = request_irq(gpio_keys_array[i].irq, gpio_key_isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "gpio_keys", &gpio_keys_array[i]);
}
/* 注册file_operations */
major = register_chrdev(0, "gpio_keys", &gpio_key_drv); /* /dev/gpio_key */
gpio_key_class = class_create(THIS_MODULE, "gpio_keys_class");
if (IS_ERR(gpio_key_class)) {
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
unregister_chrdev(major, "gpio_keys");
return PTR_ERR(gpio_key_class);
}
device_create(gpio_key_class, NULL, MKDEV(major, 0), NULL, "gpio_keys"); /* /dev/gpio_keys */
return 0;
}
static int gpio_key_remove(struct platform_device *pdev)
{
//int err;
struct device_node *node = pdev->dev.of_node;
int count;
int i;
device_destroy(gpio_key_class, MKDEV(major, 0));
class_destroy(gpio_key_class);
unregister_chrdev(major, "gpio_keys");
count = of_gpio_count(node);
for (i = 0; i < count; i++)
{
free_irq(gpio_keys_array[i].irq, &gpio_keys_array[i]);
}
kfree(gpio_keys_array);
return 0;
}
static const struct of_device_id gpio_keys[] = {
{ .compatible = "jzy,gpio_key" },
{ },
};
/* 1. 定义platform_driver */
static struct platform_driver gpio_keys_driver = {
.probe = gpio_key_probe,
.remove = gpio_key_remove,
.driver = {
.name = "gpio_keys",
.of_match_table = gpio_keys,
},
};
/* 2. 在入口函数注册platform_driver */
static int __init gpio_key_init(void)
{
int err;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
err = platform_driver_register(&gpio_keys_driver);
return err;
}
/* 3. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数
* 卸载platform_driver
*/
static void __exit gpio_key_exit(void)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
platform_driver_unregister(&gpio_keys_driver);
}
/* 7. 其他完善:提供设备信息,自动创建设备节点 */
module_init(gpio_key_init);
module_exit(gpio_key_exit);
MODULE_LICENSE("GPL");
编写应用程序
POLLIN 有数据可读
POLLRDNORM 等同于POLLIN
POLLRDBAND Priority band data can be read,有优先级较较高的“band data”可读
Linux系统中很少使用这个事件
POLLPRI 高优先级数据可读
POLLOUT 可以写数据
POLLWRNORM 等同于POLLOUT
POLLWRBAND Priority data may be written
POLLERR 发生了错误
POLLHUP 挂起
POLLNVAL 无效的请求,一般是fd未open
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <poll.h>
int main(int argc, char **argv)
{
int fd;
int val;
struct pollfd fds[1];
int timeout_ms = 5000;
int ret;
/* 1. 判断参数 */
if (argc != 2)
{
printf("Usage: %s <dev>\n", argv[0]);
return -1;
}
/* 2. 打开文件 */
fd = open(argv[1], O_RDWR);
if (fd == -1)
{
printf("can not open file %s\n", argv[1]);
return -1;
}
fds[0].fd = fd;
fds[0].events = POLLIN;
while (1)
{
/* 3. 读文件 */
ret = poll(fds, 1, timeout_ms);
if ((ret == 1) && (fds[0].revents & POLLIN))
{
read(fd, &val, 4);
printf("get button : 0x%x\n", val);
}
else
{
printf("timeout\n");
}
}
close(fd);
return 0;
}