zoukankan      html  css  js  c++  java
  • Linux驱动入门(六)poll机制实现按键驱动

    copy from :https://blog.csdn.net/weixin_42462202/article/details/100017339

    文章目录
    Linux驱动入门(六)poll机制实现按键驱动
    一、poll应用编程
    二、驱动poll机制的实现
    三、源码
    四、测试

    如何使用read阻塞去读取按键驱动,那么就意味着一个线程只能监听一个按键,如果想要监听多个按键,就需要启动多个线程,开启线程会耗费资源,并且多线程会增加编程难度,那么有没有一种机制,使得一个线程可以监听多路IO呢?
    Linux提供的select、poll、epoll机制,select、poll、epoll可以监听多路IO的状态,直到有IO满足条件(可读或可写等)时返回

    其中select、poll系统调用的实现原理是类似的,epoll有别于这两者

    但是对于底层驱动的实现都是一样的,都是实现驱动的poll函数

    本文将介绍如何实现一个支持多路IO复用的驱动程序

    一、poll应用编程
    这里以poll为例,编写一个应用程序

    poll函数原型

    /*
    * fds:这是一个数组,每一个数组元素表示要监听的文件描述符以及相应的事件
    * nfds:数组的个数
    * timeout:超时时间
    * 返回值:成功返回准备好的事件个数,超时放回0,出错放回-1
    */
    int poll(struct pollfd *fds, nfds_t nfds, int timeout);

    struct pollfd结构体

    struct pollfd {
    int fd; //要监听的文件描述符
    short events; //要监听的事件
    short revents; //放回结果
    };

    对于events和revents部分取值如下

    POLLIN //可读
    POLLOUT //可写

    示例

    #include <stdio.h>
    #include <poll.h>
    #include <string.h>

    #define MAX_FD 100

    int main(int argc, char* argv[])
    {
    struct pollfd fds[MAX_FD];
    int nfds;
    char buf[1024];
    int len;
    int i;

    memset(fds, 0, sizeof(fds));

    /* 添加要监听的事件 */
    fds[0].fd = 0; //标准输入
    fds[0].events = POLLIN; //可读

    /* 添加其他要监听的事件 */

    while(1)
    {
    nfds = poll(fds, MAX_FD, -1); //阻塞等待
    if(nfds < 0)
    {
    printf("poll err. ");
    return -1;
    }

    for(i = 0; i < MAX_FD; i++)
    {
    if(fds[i].revents | POLLIN) //可读
    {
    len = read(fds[i].fd, buf, 1024);
    if(len < 0)
    {
    printf("read err. ");
    return -1;
    }

    buf[len] = '';
    printf("read buf: %s ", buf);
    }
    }
    }

    return 0;
    }


    上述例子中只是监听了一路IO(标准输入),你可以编译运行查看运行效果

    二、驱动poll机制的实现
    实际上select、poll、epoll的实现都是利用等待队列机制,将执行select、poll、epoll的进程挂到每一个驱动程序的等待队列中,然后睡眠等待,直到被唤醒

    驱动程序的poll函数至少会被select、poll、epoll系统调用调用两次

    第一次是将select、poll、epoll所在的进程调用驱动程序的poll函数,将自己挂到驱动程序的等待队列中

    第二次是select、poll、epoll所在的进程被驱动程序唤醒,再次驱动程序的poll函数,获取驱动程序满足的条件(可读或可写)

    Linux内核给驱动程序实现poll机制提供了一个函数

    /*
    * wait_address:驱动程序的等待队列头
    * p:有select、poll、epoll传递过来的参数
    */
    void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p);

    该函数的作用就是将调用select、poll、epoll的进程挂到驱动程序的等待队列中

    驱动程序一般如下面实现poll机制

    static DECLARE_WAIT_QUEUE_HEAD(button_wq_head); //定义并初始化一个等待队列头

    static unsigned int button_poll(struct file *file, struct poll_table_struct *poll_table)
    {
    unsigned int mask = 0;

    /* 第一次被调用时,将调用select、poll、epoll的进程加入驱动的等待队列中 */
    poll_wait(file, &button_wq_head, poll_table);

    /* 第二次被调用时,如果条件满足,返回状态,之后poll会返回 */
    if(conditon)
    mask = POLLIN | POLLRDNORM;

    return mask;
    }

    实现一个支持多路复用的驱动程序很简单,只需要加入上述几行代码

    但是要弄清除这里面的来龙去脉并不是一件容易的事,至少你需要熟悉内核的等待队列,然后分析poll、select、epoll的源码,如果你有这个兴趣的话,可以看一下下面几篇文章

    Linux poll内核源码剖析

    Linux select内核源码剖析

    Linux epoll内核源码剖析

    三、源码
    #include <linux/module.h>
    #include <linux/init.h>
    #include <linux/fs.h>
    #include <linux/cdev.h>
    #include <linux/slab.h>
    #include <linux/device.h>
    #include <linux/gpio.h>
    #include <linux/interrupt.h>
    #include <linux/poll.h>

    #include <asm/io.h>
    #include <asm/uaccess.h>

    static dev_t dev_id;
    static struct cdev *button_dev;
    static struct class *button_class;

    /* 定义并初始化一个等待队列头 */
    static DECLARE_WAIT_QUEUE_HEAD(button_wq_head);
    static int button_val;
    static int button_press;

    static irqreturn_t button_irq(int irq, void *data)
    {
    /* 判断等待队列中是否有等待元素 */
    if(!waitqueue_active(&button_wq_head))
    return IRQ_HANDLED;

    /* 读取按键值 */
    button_val = gpio_get_value(S5PV210_GPH0(2));
    button_press = 1;

    /* 唤醒等待队列 */
    wake_up_interruptible(&button_wq_head);

    return IRQ_HANDLED;
    }

    static int button_open(struct inode *inode, struct file *file)
    {
    int ret;

    ret = request_irq(IRQ_EINT2, button_irq, IRQF_TRIGGER_FALLING, "button_irq", NULL);

    return 0;
    }

    static ssize_t button_read(struct file *file, char __user *data, size_t size, loff_t *loff)
    {
    int ret;
    int val;

    val = button_val;
    ret = copy_to_user(data, &val, sizeof(val));

    return sizeof(val);
    }

    static int button_release(struct inode *inode, struct file *file)
    {
    free_irq(IRQ_EINT2, NULL);

    return 0;
    }

    static unsigned int button_poll(struct file *file, struct poll_table_struct *poll_table)
    {
    unsigned int mask = 0;

    if (!button_press)
    {
    poll_wait(file, &button_wq_head, poll_table);
    }
    else
    {
    button_press = 0;
    mask = POLLIN | POLLRDNORM;
    }

    return mask;
    }

    static struct file_operations button_fops = {
    .owner = THIS_MODULE,
    .open = button_open,
    .read = button_read,
    .release = button_release,
    .poll = button_poll,
    };

    static __init int button_init(void)
    {
    /* 申请设备号 */
    alloc_chrdev_region(&dev_id, 1, 1, "button");

    /* 分配字符设备 */
    button_dev = cdev_alloc();

    /* 设置字符设备 */
    cdev_init(button_dev, &button_fops);

    /* 注册字符设备 */
    cdev_add(button_dev, dev_id, 1);

    /* 创建设备节点 */
    button_class = class_create(THIS_MODULE, "button"); //创建类
    device_create(button_class, NULL, dev_id, NULL, "button"); //创建设备节点

    gpio_request(S5PV210_GPH0(2), "button");

    return 0;
    }

    static __exit void button_exit(void)
    {
    /* 注销设备节点 */
    device_destroy(button_class, dev_id);
    class_destroy(button_class);

    /* 注销字符设备 */
    cdev_del(button_dev);
    kfree(button_dev);

    /* 注销注册的设备号 */
    unregister_chrdev_region(dev_id, 1);

    gpio_free(S5PV210_GPH0(2));
    }

    module_init(button_init);
    module_exit(button_exit);

    MODULE_LICENSE("GPL");

    四、测试
    将上述驱动保存为button_drv.c,执行Makefile,加载驱动模块

    测试应用程序

    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <poll.h>

    #define BUTTON_DEV "/dev/button"

    int main(int argc, char* argv[])
    {
    int val;
    struct pollfd fds[1];

    int fd = open(BUTTON_DEV, O_RDONLY);
    if(fd < 0)
    {
    printf("failed to open %s ", BUTTON_DEV);
    return -1;
    }

    fds[0].fd = fd;
    fds[0].events = POLLIN;

    while(1)
    {
    poll(fds, 1, -1);
    printf("poll return ");

    if(fds[0].revents & POLLIN)
    {
    read(fd, &val, sizeof(val));
    if(val == 0)
    {
    printf("button press ");
    }
    }
    }

    close(fd);

    return 0;
    }

    编译运行

    运行效果,只有按键按下时,poll才会放回
    ————————————————
    版权声明:本文为CSDN博主「JT同学」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/weixin_42462202/article/details/100017339

    Always Believe Something Beauitful Will Be Happen
  • 相关阅读:
    Creating a generic Web Parts for hosting ASP.NET User Controls
    Speed Up SQL Server Apps 提高SQL Server应用程序的运行效率 (Part 1)
    How to use CreateChildContorls method inherited from System.Web.UI.Control
    How to quickly access Web Part Management Page
    SQL Script tips for MS SQL Server
    How to enable single signon service on the SPS
    A brief summary of UML & Rational Rose – Use Case Diagram, Part II
    Borland Together for Visual Studio.Net V2.0 安装问题
    Speed Up SQL Server Apps 提高SQL Server应用程序的运行效率 (Part 2)
    体验ReSharper V1.0 for VS.Net 2003 Part I
  • 原文地址:https://www.cnblogs.com/Oude/p/12457202.html
Copyright © 2011-2022 走看看