Linux驱动之poll机制的理解与简单使用

发布时间:2024-08-20  

之前在Linux驱动之按键驱动编写(中断方式)中编写的驱动程序,如果没有按键按下。read函数是永远没有返回值的,现在想要做到即使没有按键按下,在一定时间之后也会有返回值。要做到这种功能,可以使用poll机制。分以下几部来介绍poll机制


1、poll机制的使用,编写测试程序


2、poll机制的调用过程分析


3、poll机制的驱动编写


1、poll机制的使用,编写测试程序。


直接看到测试程序的代码。


#include

#include

#include

#include

#include


/*

  *usage ./buttonstest

  */

int main(int argc, char **argv)

{

    int fd;

    char* filename='dev/buttons';

   unsigned char key_val;

   unsigned long cnt=0;

   int ret;

   struct pollfd *key_fds;//定义一个pollfd结构体key_fds


   

    fd = open(filename, O_RDWR);//打开dev/firstdrv设备文件

    if (fd < 0)//小于0说明没有成功

    {

        printf('error, can't open %sn', filename);

        return 0;

    }

    

    if(argc !=1)

    {

        printf('Usage : %s ',argv[0]);

     return 0;

    }

    key_fds ->fd = fd;//文件

    key_fds->events = POLLIN;//poll直接返回需要的条件

  while(1)

  {

     ret =  poll(key_fds, 1, 5000);//调用sys_poll系统调用,如果5S内没有产生POLLIN事件,那么返回,如果有POLLIN事件,直接返回

    if(!ret)

    {

    printf('time outn');

     }

    else

    {

        if(key_fds->revents==POLLIN)//如果返回的值是POLLIN,说明有数据POLL才返回的

            {

        read(fd, &key_val, 1);           //读取按键值

             printf('key_val: %xn',key_val);//打印

            }

    }

     

  }

    

   return 0;

}


从代码可以看出,相比较第三个测试程序third_test。程序源码见Linux驱动之按键驱动编写(中断方式),多定义了一个pollfd 结构体,它的结构如下:


struct pollfd {

    int fd;          //打开的文件节点

    short events;    //poll直接返回,需要产生的事件

    short revents;   //poll函数返回的事件

};

测试程序调用C库函数的poll函数时会用到这个结构体poll(key_fds, 1, 5000);其中第一个参数就是这个结构体的指针,对于多个目标文件来说是首地址,第二个参数为poll等待的文件个数,第三个参数为超时时间。那么poll是怎么实现的呢?


2、poll机制的调用过程分析


应用层利用C库函数调用poll函数的时候,会通过swi软件中断进入到内核层,然后调用sys_poll系统调用。它位于fsSelect.c中。


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

            long timeout_msecs)

{

    s64 timeout_jiffies;


    if (timeout_msecs > 0) {//超时参数验证以及处理

#if HZ > 1000

        /* We can only overflow if HZ > 1000 */

        if (timeout_msecs / 1000 > (s64)0x7fffffffffffffffULL / (s64)HZ)

            timeout_jiffies = -1;

        else

#endif

            timeout_jiffies = msecs_to_jiffies(timeout_msecs);

    } else {

        /* Infinite (< 0) or no (0) timeout */

        timeout_jiffies = timeout_msecs;

    }


    return do_sys_poll(ufds, nfds, &timeout_jiffies);//调用do_sys_poll

 }


可以看到sys_poll系统调用经过一些参数的验证之后直接调用了do_sys_poll,对于do_sys_poll做一个简单的介绍,它也位于fsSelect.c,它主要调用poll_initwait、do_poll函数


653    int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds, s64 *timeout)

654    {

    ...

    ...

670        poll_initwait(&table);//最终table->pt->qproc = __pollwait

    ...

    ...

709        fdcount = do_poll(nfds, head, &table, timeout);

    ...

    ...

737    }


先看到poll_initwait函数,它的主要功能是将table->pt->qproc = __pollwait,后面会用到


void poll_initwait(struct poll_wqueues *pwq)

{

    init_poll_funcptr(&pwq->pt, __pollwait);//pt->qproc = qproc;即table->pt->qproc = __pollwait

    pwq->error = 0;

    pwq->table = NULL;

    pwq->inline_index = 0;

}


接着看到do_poll(nfds, head, &table, timeout),这里面的主要函数是do_pollfd(pfd, pt)与schedule_timeout(__timeout);下面分别介绍


static int do_poll(unsigned int nfds,  struct poll_list *list,

           struct poll_wqueues *wait, s64 *timeout)

{

    int count = 0;

    poll_table* pt = &wait->pt;


    /* Optimise the no-wait case */

    if (!(*timeout))//处理没有超时的情况

        pt = NULL;

 

    for (;;) {//大循环,一直等待超时时间到或者有相应的事件触发唤醒进程

        struct poll_list *walk;

        long __timeout;


        set_current_state(TASK_INTERRUPTIBLE);//设置当前进程为可中断状态

        for (walk = list; walk != NULL; walk = walk->next) {//循环查找poll_fd列表

            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)) {//pwait = table->pt。调用驱动的poll函数获取mask值,另外将进程放入等待队列

                    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 || !*timeout || signal_pending(current))//如果超时时间到了或者没有poll_fd或者事件发生了,直接退出

            break;

        count = wait->error;

        if (count)

            break;


        if (*timeout < 0) {

            /* Wait indefinitely */

            __timeout = MAX_SCHEDULE_TIMEOUT;

        } else if (unlikely(*timeout >= (s64)MAX_SCHEDULE_TIMEOUT-1)) {

            /*

             * Wait for longer than MAX_SCHEDULE_TIMEOUT. Do it in

             * a loop

             */

            __timeout = MAX_SCHEDULE_TIMEOUT - 1;

            *timeout -= __timeout;

        } else {

            __timeout = *timeout;

            *timeout = 0;

        }


        __timeout = schedule_timeout(__timeout);//设置超时时间,进程休眠

        if (*timeout >= 0)

            *timeout += __timeout;

    }

    __set_current_state(TASK_RUNNING);//重新运行调用sys_poll的进程

    return count;

}


现在看到do_pollfd(pfd, pt)函数,它最终会调用驱动层的poll函数file->f_op->poll(file, pwait),这就跟驱动扯上关系了, __pollwait在这里就被用到了,它将当前进程放入驱动层的等待列表,但是这时候当前进程还未休眠。


static inline unsigned int do_pollfd(struct pollfd *pollfd, poll_table *pwait)

{

    unsigned int mask;

    int fd;


    mask = 0;

    fd = pollfd->fd;//根据pollfd找到文件节点

    if (fd >= 0) {

        int fput_needed;

        struct file * file;


        file = fget_light(fd, &fput_needed);//根据文件节点fd找到文件的file结构

        mask = POLLNVAL;

        if (file != NULL) {

            mask = DEFAULT_POLLMASK;

            if (file->f_op && file->f_op->poll)

                mask = file->f_op->poll(file, pwait);//根据file结构找到驱动的f_op结构,然后调用它的poll函数,并且返回mask

                                                     //这个函数就跟驱动相关了,猜测调用poll_wati将当前进程放到驱动的等待列表。如果有数据的话,那么设置mask = POLLIN

            /* Mask out unneeded events. */

            mask &= pollfd->events | POLLERR | POLLHUP;

            fput_light(file, fput_needed);

        }

    }

    pollfd->revents = mask;


    return mask;

}


继续看到schedule_timeout(__timeout)函数,它位于kernelTimer.c,它的主要作用就是设置一个定时器,当超时时间到的时候利用定时器的函数将进程唤醒。最后它还调用schedule(),进行进程的切换,因为在do_poll中已经被设置为TASK_INTERRUPTIBLE状态了。


fastcall signed long __sched schedule_timeout(signed long timeout)

{

    struct timer_list timer;

    unsigned long expire;


    switch (timeout)

    {

    case MAX_SCHEDULE_TIMEOUT:

        /*

         * These two special cases are useful to be comfortable

         * in the caller. Nothing more. We could take

文章来源于:电子工程世界    原文链接
本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。

我们与500+贴片厂合作,完美满足客户的定制需求。为品牌提供定制化的推广方案、专属产品特色页,多渠道推广,SEM/SEO精准营销以及与公众号的联合推广...详细>>

利用葫芦芯平台的卓越技术服务和新产品推广能力,原厂代理能轻松打入消费物联网(IOT)、信息与通信(ICT)、汽车及新能源汽车、工业自动化及工业物联网、装备及功率电子...详细>>

充分利用其强大的电子元器件采购流量,创新性地为这些物料提供了一个全新的窗口。我们的高效数字营销技术,不仅可以助你轻松识别与连接到需求方,更能够极大地提高“闲置物料”的处理能力,通过葫芦芯平台...详细>>

我们的目标很明确:构建一个全方位的半导体产业生态系统。成为一家全球领先的半导体互联网生态公司。目前,我们已成功打造了智能汽车、智能家居、大健康医疗、机器人和材料等五大生态领域。更为重要的是...详细>>

我们深知加工与定制类服务商的价值和重要性,因此,我们倾力为您提供最顶尖的营销资源。在我们的平台上,您可以直接接触到100万的研发工程师和采购工程师,以及10万的活跃客户群体...详细>>

凭借我们强大的专业流量和尖端的互联网数字营销技术,我们承诺为原厂提供免费的产品资料推广服务。无论是最新的资讯、技术动态还是创新产品,都可以通过我们的平台迅速传达给目标客户...详细>>

我们不止于将线索转化为潜在客户。葫芦芯平台致力于形成业务闭环,从引流、宣传到最终销售,全程跟进,确保每一个potential lead都得到妥善处理,从而大幅提高转化率。不仅如此...详细>>