linux学习笔记 poll

概述

在没有poll机制的情况下,大部分时间程序都处在read中休眠的那个位置。如果我们不想让程序停在这个位置,而是希望当有按键按下时,我们再去read,因此我们编写poll函数,测试程序调用poll函数根据返回值,来决定是否执行read函数。

本文参考博文:https://www.cnblogs.com/lifexy/p/7508633.html

poll机制作用:相当于定时器,设置一定时间使进程等待资源,如果时间到了中断还处于睡眠状态(等待队列),poll机制就会唤醒中断,获取一次资源

驱动和应用中如何使用这个机制

首先提取驱动中与poll有关的地方
#include <linux/poll.h>


static unsigned forth_drv_poll(struct file *file, poll_table *wait)
{
	unsigned int mask = 0;
	poll_wait(file, &button_waitq, wait); // 不会立即休眠

	if (ev_press)
		mask |= POLLIN | POLLRDNORM;

	return mask;
}


//在file_operations加入.poll
static struct file_operations sencod_drv_fops = {
    .owner   =  THIS_MODULE,  
	.poll    =  forth_drv_poll,
};

再看应用中如何使用poll
int main(int argc, char **argv)
{
	int fd;
	unsigned char key_val;
	int ret;

	struct pollfd fds[1];
	
	fd = open("/dev/buttons", O_RDWR);
	if (fd < 0)
	{
		printf("can't open!\n");
	}

	fds[0].fd     = fd;
	fds[0].events = POLLIN;
	while (1)
	{
		ret = poll(fds, 1, 5000);
		if (ret == 0)
		{
			printf("time out\n");
		}
		else
		{
			read(fd, &key_val, 1);
			printf("key_val = 0x%x\n", key_val);
		}
	}
	
	return 0;
}
使用poll的分析
  • 可以知道用户使用poll函数的时候,最终就会调用驱动的forth_drv_poll函数
  • 用户函数一句ret = poll(fds, 1, 5000)可以实现按键触发或者时间达到5000ms就返回ret
  • 驱动函数的poll函数(forth_drv_poll)需要实现按键等待队列与poll的关联,如果中断发生了,就要返回值来通知poll

poll大概流程

  • 应用调用poll函数,即调用了sys_poll

  • 在sys_poll中,根据传递的参数和系统时钟参数结合,计算出具体的延时参数,然后调用do_sys_poll

  • 在do_sys_poll中调用了2个函数,一个是poll_initwait,一个是do_poll

  • poll_initwait中初始化一个poll_wqueues变量table,作用是用来指定一个函数(__pollwait),让驱动程序内的poll_wait函数来调用

  • do_poll十分关键,在do_poll中有一个 for (;;)死循环

  • do_poll在for循环中通过do_pollfd(pfd, pt)调用驱动函数里定义的file_operations的poll函数

  • 直到do_pollfd返回大于0的值、或者超时、或者有信号在等待,才退出for循环,继续执行程序

  • do_poll函数还调用schedule_timeout,使进程休眠一定时间,直到超时或者按键按下才唤醒进程。

  • 驱动程序file_operations的poll函数中,有一个poll_wait函数

  • poll_wait本质上是调用了__pollwait函数,(__pollwait即do_sys_poll中poll_initwait指定的table ->pt-> qproc)

  • __pollwait的作用是设置中断触发唤醒队列时,poll机制也同时被唤醒

使用函数总结:

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

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

static int do_poll(unsigned int nfds,  struct poll_list *list, struct poll_wqueues *wait,  s64 *timeout)

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

poll流程代码详解,看注释

从用户应用说起
	while (1)
	{
	    //fds包含设备文件fd和事件,等待5000ms超时返回或者触发信号返回
		ret = poll(fds, 1, 5000);
		if (ret == 0)
		{
			printf("time out\n");
		}
		else
		{
			read(fd, &key_val, 1);
			printf("key_val = 0x%x\n", key_val);
		}
	}
再看sys_poll中的函数
static int do_poll(unsigned int nfds,  struct poll_list *list, struct poll_wqueues *wait,  s64 *timeout)
{
  ……
       for (;;)
   {
    ……
    set_current_state(TASK_INTERRUPTIBLE);       //设置为等待队列状态
    ......
       for (; pfd != pfd_end; pfd++) {             //for循环运行多个poll机制
                   /*将pfd和pt参数代入我们驱动程序里注册的poll函数*/
                        if (do_pollfd(pfd, pt))     //若返回非0,count++,后面并退出
              {  count++;
                           pt = NULL; } }

    ……

    /*count非0(.poll函数返回非0),timeout超时计数到0,有信号在等待*/

       if (count || !*timeout || signal_pending(current))
                            break;
    ……
  
    /*进入休眠状态,只有当timeout超时计数到0,或者被中断唤醒才退出,*/
         __timeout = schedule_timeout(__timeout);

    ……

   }

__set_current_state(TASK_RUNNING);  //开始运行
return count;

}
//设置当前等待队列为可中断模式
set_current_state(TASK_INTERRUPTIBLE); 

//会找到驱动内的poll函数来执行
do_pollfd(pfd, pt)

//检查当前进程是否有信号处理,返回不为0表示有信号需要处理
signal_pending(current)

/*
返回0意味着进程因为时间timeout而被唤醒,一个正数意味着进程在时间尚没有timeout的情况下被唤醒
实现方式是创建一个timer,并将进程从CPU的run queue中移除,然后在timer到期的时候唤醒进程
*/
__timeout = schedule_timeout(__timeout)

猜你喜欢

转载自blog.csdn.net/tiantangmoke/article/details/103081059