poll机制

Posted wanjianjun777

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了poll机制相关的知识,希望对你有一定的参考价值。

更新记录

version status description date author
V1.0 C Create Document 2019.1.10 John Wan

status:
C―― Create,
A—— Add,
M—— Modify,
D—— Delete。

注:内核版本 3.0.15

1、poll概述

??所有的系统调用,基于都可以在它的名字前加上“sys_”前缀,这就是它在内核中对应的函数。比如系统调用open、read、write、poll,与之对应的内核函数为:sys_open、sys_read、sys_write、sys_poll,那么在驱动程序中,也有与之对应的函数处于驱动程序的数据结构中,例如 file_operation 中的 open、read、write、poll。

??那么poll机制的作用是什么呢?以按键事件为例:

??1、查询方法:一直在查询,不断去查询是否有事件发生,响应的快慢取决于查询的频率,如何对实时性要求高,那么查询频率要非常快,整个过程都占用CPU资源,消耗CPU资源非常大。

??2、中断方式:当有事件发生时,才跳转到相应事件去处理,CPU占用时间少。

??3、poll机制:在中断的基础上,添加超时机制,对事件有两种处理方式:1)事件发生,立即跳转处理;2)超时,立即跳转处理。这样的好处是,即使在固定的时间之内没有事件发生,也能得到反馈。

2、poll机制的内核框架

??对于系统调用 pollselect,它们对应的内核函数是 sys_poll()。分析 sys_poll() 即可理解 poll机制。

2.1 poll()函数

int poll(struct pollfd *fds, nfds_t nfds, int timeout)

??输入参数:

fds 可以传递多个结构体,也就是说可以监测多个驱动设备所产生的事件,只要有一个产生了请求事件,就能立即返回

struct pollfd {
    int fd;             /* 文件描述符 */
    short events;       /* 请求的事件类型,监视驱动文件的事件掩码 */
    short revents;      /* 驱动文件实际返回的事件 */
} ;

nfds    监测驱动文件的个数

timeout  超时时间,单位为ms 

??事件类型events 可以为下列值:

POLLIN          有数据可读
POLLRDNORM      有普通数据可读,等效与POLLIN
POLLPRI         有紧迫数据可读
POLLOUT         写数据不会导致阻塞
POLLER          指定的文件描述符发生错误
POLLHUP         指定的文件描述符挂起事件
POLLNVAL        无效的请求,打不开指定的文件描述符

??返回值:

有事件发生         返回 revents域不为0的文件描述符个数(也就是说事件发生,或者错误报告)
超时              返回 0;
失败              返回 -1,并设置errno为错误类型

2.2 sys_poll()函数

??位于 linux/syscalls.h文件中的:

asmlinkage long sys_poll(struct pollfd __user *ufds, unsigned int nfds, long timeout);

??位于 fs/select.c文件中的:

SYSCALL_DEFINE3(poll, struct pollfd __user *, ufds, unsigned int, nfds, long, timeout_msecs)
{
    struct timespec end_time, *to = NULL;
    int ret;

    if (timeout_msecs >= 0) {
        to = &end_time;
        poll_select_set_timeout(to, timeout_msecs / MSEC_PER_SEC,
            NSEC_PER_MSEC * (timeout_msecs % MSEC_PER_SEC));
    }

    ret = do_sys_poll(ufds, nfds, to);

......
    return ret;
}

注:从 poll/select 到 sys_poll,再到 SYSCALL_DEFINE3 之间是如何实现的,暂时还不清楚。

??作用:1)对超时参数稍作处理;2)调用 do_sys_poll()函数。

2.3 do_sys_poll()函数

??位于fs/select.c文件中:

int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds,
        struct timespec *end_time)
{
    struct poll_wqueues table;
......
    poll_initwait(&table);
    fdcount = do_poll(nfds, head, &table, end_time);
......
}

??作用:

??1) poll_initwait()函数非常简单,它初始化一个 poll_wqueues变量 table”poll_initwait(&table); > init_poll_funcptr(&pwq->pt, __pollwait); > pt->qproc = qproc;”,即table->pt->qproc = __pollwait__pollwait 将在驱动的 poll 函数里用到。

??2)调用 do_poll()函数。

2.4 do_poll()函数

??位于 fs/select.c 文件中:

static int do_poll(unsigned int nfds,  struct poll_list *list,
           struct poll_wqueues *wait, struct timespec *end_time)
{
......
    for (;;) {
        struct poll_list *walk;

        for (walk = list; walk != NULL; walk = walk->next) {
            struct pollfd * pfd, * pfd_end;

            pfd = walk->entries;
            pfd_end = pfd + walk->len;
            for (; pfd != pfd_end; pfd++) {
                /*
                 * Fish for events. If we found one, record it
                 * and kill the poll_table, so we don't
                 * needlessly register any other waiters after
                 * this. They'll get immediately deregistered
                 * when we break out and return.
                 */
                if (do_pollfd(pfd, pt)) {
                    count++;
                    pt = NULL;
                }
            }
        }
        /*
         * All waiters have already been registered, so don't provide
         * a poll_table to them on the next loop iteration.
         */
        pt = NULL;
        if (!count) {
            count = wait->error;
            if (signal_pending(current))
                count = -EINTR;
        }
        if (count || timed_out)
            break;

        /*
         * If this is the first loop and we have a timeout
         * given, then we convert to ktime_t and set the to
         * pointer to the expiry value.
         */
        if (end_time && !to) {
            expire = timespec_to_ktime(*end_time);
            to = &expire;
        }

        if (!poll_schedule_timeout(wait, TASK_INTERRUPTIBLE, to, slack))
            timed_out = 1;
    }
    return count;
}

??分析代码,作用如下:

① 这是个循环,它的退出条件为:

? a. 有信号等待处理;

? b. count非0,超时。

② 调用do_pollfd()函数,这是重点,后面分析;

poll_schedule_timeout() 让本进程休眠一段时间,注意:应用程序执行 poll() 调用后,如果 ① ② 的条件不满足,进程就会进入休眠。那么谁来唤醒呢?除了休眠到指定时间被系统唤醒外,还可以被驱动程序唤醒--记住这点,这就是为什么驱动的 poll() 里要调用 poll_wait的原因,后面分析。

2.5 do_pollfd()函数

??位于 fs/select.c 文件中:

static inline unsigned int do_pollfd(struct pollfd *pollfd, poll_table *pwait)
{
......
            if (file->f_op && file->f_op->poll)
......
                mask = file->f_op->poll(file, pwait);
......
}

??作用:这就是调用在驱动程序中注册的 poll函数(file_operation中的poll)。

3、poll机制的驱动程序

??驱动程序里与 poll 相关的地方有两处:

1)是构造 file_operation 结构时,要定义自己的 poll 函数;

2)是通过 poll_wait 来调用上面说到的 __pollwait 函数。

??位于 linux/poll.h 文件中:

static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
{
    if (p && wait_address)
        p->qproc(filp, wait_address, p);
}

??p->qproc 就是 __pollwait() 函数(前面的poll_initwait()函数进行的),从它的代码可知,它只是把当前进程挂入我们驱动程序里定义的一个队列中,函数位于 fs/select.c 中代码如下:

static void __pollwait(struct file *filp, wait_queue_head_t *wait_address,
                poll_table *p)
{
    struct poll_wqueues *pwq = container_of(p, struct poll_wqueues, pt);
    struct poll_table_entry *entry = poll_get_entry(pwq);
    if (!entry)
        return;
    get_file(filp);
    entry->filp = filp;
    entry->wait_address = wait_address;
    entry->key = p->key;
    init_waitqueue_func_entry(&entry->wait, pollwake);
    entry->wait.private = pwq;
    add_wait_queue(wait_address, &entry->wait);
}

??执行到驱动程序的 poll_wait() 函数时,进程并没有休眠,我们的驱动程序里实现的 poll() 函数是不会引起休眠的。让进程进入休眠的是前面分析 do_sys_poll() 函数中的 poll_schedule_timeout()

??poll_wait() 只是把本进程挂入某个队列,应用程序调用顺序 "poll > sys_poll > do_sys_poll > poll_initwait,do_poll > do_pollfd > 我们自己写的poll函数后,再调用 poll_schedule_timeout() 进入休眠"。如果我们的驱动程序发现情况就绪,可以把这个队列上挂着的进程唤醒。可见,poll_wait 的作用,只是为了让驱动程序能找到要唤醒的进程。即使不用 poll_wait,我们的程序也有机会被唤醒:poll_schedule_timeout() ,只是要休眠设定的那段时间。

4、总结

??poll 机制:

  1. poll > sys_poll > do_sys_poll > poll_initwaitpoll_initwait() 函数注册一下回调函数 __pollwait,它就是我们的驱动程序执行poll_wait时,真正被调用的函数。

  2. 接下来执行do_pollfd()中的file->f_op->poll,即我们驱动程序里自己实现的poll函数。

    它会调用poll_wait()把自己挂入某个队列,这个队列驱动程序自己来定义,指明应该挂哪个队列。

    它还判断一下设备是否就绪。

  3. 如何设备未就绪,do_sys_poll里会让进程休眠一定时间。

  4. 进程被唤醒的条件有两个:1)设定的时间到了,即超时;2)被驱动程序唤醒。驱动程序发现条件就绪时,就把"某个队列"上挂着的进程唤醒,这个队列,就是前面通过poll_wait把本进程挂过去的队列。

  5. 如果驱动程序没有去唤醒进程,那么poll_schedule_timeout 超时后,会重复2、3的动作,直到应用程序的 poll 调用传入的时间到达。

技术图片

??1. 驱动模块的加载就已经向 file_operation进行了成员.poll的注册。

??2. 应用程序调用 poll()函数,对应的调用内核的sys_poll()函数。然后执行内核当中的框架。

??3. 内核框架的do_poll()函数中会调用驱动中的.poll成员,而驱动中的poll函数会通过调用poll_wait()函数来讲进程挂载到指定的队列用,poll_wait()的实质就是调用在内核中注册的__pollwait()函数。在驱动程序的poll函数中判断设备是否就绪,并将返回值返回到内核的do_poll()函数中。

??4. do_poll()将进行判断处理,将返回值给到应用层的poll()函数。

5、案例

??驱动代码:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
//#include <asm/arch/regs-gpio.h>
//#include <asm/hardware.h>

/*驱动注册的头文件,包含驱动的结构体和注册和卸载的函数*/
#include <linux/platform_device.h>
/*Linux中申请GPIO的头文件*/
#include <linux/gpio.h>
/*三星平台的GPIO配置函数头文件*/
/*三星平台EXYNOS系列平台,GPIO配置参数宏定义头文件*/
#include <plat/gpio-cfg.h>
#include <mach/gpio.h>
/*三星平台4412平台,GPIO宏定义头文件*/
#include <mach/gpio-exynos4.h>

#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/interrupt.h>
#include <linux/sched.h>
#include <linux/poll.h>


#define DEVICE_NAME "buttons_irq"

static struct class *buttons_irq_class;
static struct device *buttons_irq_class_dev;


struct pin_desc {
    unsigned int pin;
    unsigned int key_val;
};

struct pin_desc pins_desc[5] = {
    {EXYNOS4_GPX1(1), 1},
    {EXYNOS4_GPX1(2), 2},
    {EXYNOS4_GPX3(3), 3},
    {EXYNOS4_GPX2(1), 4},
    {EXYNOS4_GPX2(0), 5},
};

static unsigned char key_val = 0;

static DECLARE_WAIT_QUEUE_HEAD(button_waitq);   //声明一个队列

/* 中断事件标志, 中断服务程序将它置1,third_drv_read将它清0 */
static volatile int ev_press = 0;


static irqreturn_t buttons_irq(int irq, void *dev_id)
{
    struct pin_desc *pindesc = (struct pin_desc *)dev_id;
    unsigned int pinval;

    pinval = gpio_get_value(pindesc->pin);

    if (pinval)
        key_val = 0x80 | pindesc->key_val;
    else
        key_val = pindesc->key_val;

    printk(DEVICE_NAME " key press1");

    ev_press = 1;   //中断发生
    wake_up_interruptible(&button_waitq);   /* 唤醒休眠的进程 */
  

    printk(DEVICE_NAME " key press2");

    return IRQ_RETVAL(IRQ_HANDLED);
}


static int buttons_irq_open(struct inode *pinode, struct file *pfile)
{
    /* 配置各按键引脚为外部中断 */
    request_irq(IRQ_EINT(9), buttons_irq, IRQ_TYPE_EDGE_BOTH, "S1_Home", &pins_desc[0]);
    request_irq(IRQ_EINT(10), buttons_irq, IRQ_TYPE_EDGE_BOTH, "S2_Back", &pins_desc[1]);
    request_irq(IRQ_EINT(27), buttons_irq, IRQ_TYPE_EDGE_BOTH, "S3_Sleep", &pins_desc[2]);
    request_irq(IRQ_EINT(17), buttons_irq, IRQ_TYPE_EDGE_BOTH, "S4_Vol+", &pins_desc[3]);
    request_irq(IRQ_EINT(16), buttons_irq, IRQ_TYPE_EDGE_BOTH, "S5_Vol-", &pins_desc[4]);


    printk(DEVICE_NAME " I'm open!
");

    return 0;
}

static ssize_t buttons_irq_read(struct file *pfile, char __user *pbuf,
                                    size_t count, loff_t *ploff)
{
    if (count != 1)
        return -EINVAL;

    //如果没有按键动作,休眠
    wait_event_interruptible(button_waitq, ev_press);

    //如果有按键动作,返回键值
    copy_to_user(pbuf, &key_val, 1);
    ev_press = 0;

    printk(DEVICE_NAME " I'm read key_val %d!
", key_val);

    return 1;
}


static int buttons_irq_release(struct inode *pinode, struct file *pfile)
{
    free_irq(IRQ_EINT(9), &pins_desc[0]);
    free_irq(IRQ_EINT(10), &pins_desc[1]);
    free_irq(IRQ_EINT(27), &pins_desc[2]);
    free_irq(IRQ_EINT(17), &pins_desc[3]);
    free_irq(IRQ_EINT(16), &pins_desc[4]);

    printk(DEVICE_NAME " I'm release
");

    return 0;
}

static unsigned int buttons_irq_poll(struct file *pfile, struct poll_table_struct *ptable)
{
    unsigned int mask = 0;
    poll_wait(pfile, &button_waitq, ptable); // 不会立即休眠

    if (ev_press)
        mask |= POLLIN | POLLRDNORM;

    return mask;
}


static struct file_operations buttons_irq_fpos = {
    .owner = THIS_MODULE,
    .open = buttons_irq_open,
    .read = buttons_irq_read,
    .release = buttons_irq_release,
    .poll = buttons_irq_poll,
};

int major;
static int __init buttons_irq_init(void)
{
    /*注册主设备号*/
    major = register_chrdev(0, "buttons_irq", &buttons_irq_fpos);

    /*注册次设备号*/
    buttons_irq_class = class_create(THIS_MODULE, "buttons_irq");
    if (IS_ERR(buttons_irq_class))
        return PTR_ERR(buttons_irq_class);

    buttons_irq_class_dev = device_create(buttons_irq_class, NULL,
                                MKDEV(major, 0), NULL, "buttons_irq_minor");

    printk(DEVICE_NAME " initialized
");

    return 0;
}

static void __exit buttons_irq_exit(void)
{
    unregister_chrdev(major, "buttons_irq");

    device_unregister(buttons_irq_class_dev);

    class_destroy(buttons_irq_class);

    //return 0;
}

module_init(buttons_irq_init);
module_exit(buttons_irq_exit);

MODULE_LICENSE("GPL");

??测试代码:

#include <stdio.h>

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


int main(int argc, char **argv)
{
    int fd;
    unsigned char key_val;
    int ret;

    struct pollfd fds[1];

    fd = open("/dev/buttons_irq_minor", O_RDWR);
    if (fd < 0)
        printf("can't open is!
");

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

    while (1) {

        ret = poll(fds, 1, 5000);
        if (ret == 0) {
            printf("time out
");
        } else {
            read(fd, &key_val, sizeof(key_val));
            printf("key_val = 0x%x
", key_val);
        }
    }

    return 0;
}

测试:

[[email protected]]# insmod buttons_poll.ko
[ 1936.603014] buttons_irq initialized
[[email protected]]# lsmod
buttons_poll 2655 0 - Live 0xbf004000
[[email protected]]# ./buttons_poll_test
[ 1948.384684] buttons_irq I'm open!
time out
time out
[ 1961.125175] buttons_irq key press1buttons_irq key press2buttons_irq I'm read key_val 1!
key_val = 0x1
[ 1961.336777] buttons_irq key press1buttons_irq key press2buttons_irq I'm read key_val 129!
key_val = 0x81
[ 1963.807090] buttons_irq key press1buttons_irq key press2buttons_irq I'm read key_val 2!
key_val = 0x2
[ 1964.015423] buttons_irq key press1buttons_irq key press2buttons_irq I'm read key_val 130!
key_val = 0x82
[ 1965.903509] buttons_irq key press1buttons_irq key press2buttons_irq I'm read key_val 3!
key_val = 0x3
[ 1966.075094] buttons_irq key press1buttons_irq key press2buttons_irq I'm read key_val 131!
key_val = 0x83
[ 1967.500492] buttons_irq key press1buttons_irq key press2buttons_irq I'm read key_val 4!
key_val = 0x4
[ 1967.677299] buttons_irq key press1buttons_irq key press2buttons_irq I'm read key_val 132!
key_val = 0x84
[ 1969.124986] buttons_irq key press1buttons_irq key press2buttons_irq I'm read key_val 5!
key_val = 0x5
[ 1969.299227] buttons_irq key press1buttons_irq key press2buttons_irq I'm read key_val 133!
key_val = 0x85
time out

??超过设定时间没有检测到触发,也会唤醒进程打印 "time out"。

参考

  1. 韦东山第一期视频,第十二课
  2. 迅为iTop4412资料

以上是关于poll机制的主要内容,如果未能解决你的问题,请参考以下文章

linux poll机制分析

Linux-应用程序中的poll机制

poll机制实例参考

Linux poll机制

poll机制分析[转]

013_Linux驱动之_poll机制