Linux内核:poll机制

在编写驱动程序的过程当中我们可以使用poll机制来非阻塞的打开我们的设备文件,我们知道,在之前我们编写CC1100的驱动程序以及倒车雷达的驱动程序的时候,在read函数中都有用到过wait_event_interruptible_timeout这个函数,这个函数的主要作用就是采用非阻塞的read,因为每一次我们read函数的时候,都会先判断是否有新的数据可以读,如果没有新的数据就会休眠等待有新的数据。同时我们这里也给休眠等待规定了之间限制,即如果在规定的时间里面如果没有新的数据的话,便会自己唤醒自己。当然如果说是我们在上层应用程序只需要打开一个驱动程序的时候,其实这个方式也还比较适用。但是Linux内核针对这种情况呢,自己采用了一种全新的方式,那就是poll机制。其实poll机制的实现原理与我们上面用到的方法也是一样的。

poll机制的作用

poll机制的作用主要是通过在用户空间调用select()和poll()系统调用查询是否可对设备进行无阻塞访问。顾名思义是用来查询该驱动设备是否是无阻塞的。既然要查询,首先poll机制其本身是非阻塞的,那如何实现其本身是非阻塞呢?我们肯定是要用到等待队列机制。即进入驱动程序的自定义poll函数之后,我们首先将该进程加入等待队列(这个等待队列头一定要是该驱动程序的read或者write函数使用的等待队列头),然后就进入休眠等待,这个休眠等待当然也是有timeout限制的(如果没有限制,就成了阻塞调用了),如果在timeout阶段,该等待因为有新的数据而被驱动程序唤醒(能够被唤醒的主要原因就是这个等待队列的队列头是与read和wirte队列头一致的),那么我们就认为该设备是可以非阻塞调用的,反之如果该等待是被其自己通过其timeout机制而唤醒,那么就认为该设备是阻塞访问的。

当应用程序调用poll、select函数的时候,会调用到系统调用do_sys_poll函数,该函数最终调用do_poll函数,do_poll函数中有一个死循环,在里面又会利用do_pollfd函数去调用驱动中的poll函数(fds中每个成员的字符驱动程序都会被扫描到),驱动程序中的Poll函数的工作有两个,一个就是调用poll_wait函数,把进程挂到等待队列中去(这个是必须的,你要睡眠,必须要在一个等待队列上面,否则到哪里去唤醒你呢??),另一个是确定相关的fd是否有内容可读,如果可读,就返回1,否则返回0,如果返回1 ,do_poll函数中的count++,    然后do_poll函数然后判断三个条件(if (count ||!timeout || signal_pending(current)))如果成立就直接跳出,如果不成立,就睡眠timeout个jiffes这么长的时间(调用schedule_timeout实现睡眠),如果在这段时间内没有其他进程去唤醒它,那么第二次执行判断的时候就会跳出死循环。如果在这段时间内有其他进程唤醒它,那么也可以跳出死循环返回(例如我们可以利用中断处理函数去唤醒它,这样的话一有数据可读,就可以让它立即返回)。

首先系统会调用do_sys_poll函数,该函数的主要作用是去调用do_poll函数。函数源码如下:

  1. int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds,  
  2.         struct timespec *end_time)  
  3. {  
  4.     struct poll_wqueues table;  
  5.     int err = -EFAULT, fdcount, len, size;  
  6.     /* Allocate small arguments on the stack to save memory and be 
  7.        faster - use long to make sure the buffer is aligned properly 
  8.        on 64 bit archs to avoid unaligned access */  
  9.     long stack_pps[POLL_STACK_ALLOC/sizeof(long)];  
  10.     struct poll_list *const head = (struct poll_list *)stack_pps;  
  11.     struct poll_list *walk = head;  
  12.     unsigned long todo = nfds;  
  13.   
  14.     if (nfds > rlimit(RLIMIT_NOFILE))  
  15.         return -EINVAL;  
  16.   
  17.     len = min_t(unsigned int, nfds, N_STACK_PPS);  
  18.     //这个for循环会进行一些简单的判断。通常一般都会跳出该for循环。  
  19.     for (;;) {  
  20.         walk->next = NULL;  
  21.         walk->len = len;  
  22.         if (!len)  
  23.             break;  
  24.         //这段代码很关键,将应用程序中通过open函数得到的fd信息,在这里copy给linux内核变量walk->entries。这个时候walk变量就携带有设备文件的fd信息了。  
  25.         if (copy_from_user(walk->entries, ufds + nfds-todo,  
  26.                     sizeof(struct pollfd) * walk->len))  
  27.             goto out_fds;  
  28.   
  29.         todo -= walk->len;  
  30.         if (!todo)  
  31.             break;  
  32.   
  33.         len = min(todo, POLLFD_PER_PAGE);  
  34.         size = sizeof(struct poll_list) + sizeof(struct pollfd) * len;  
  35.         walk = walk->next = kmalloc(size, GFP_KERNEL);  
  36.         if (!walk) {  
  37.             err = -ENOMEM;  
  38.             goto out_fds;  
  39.         }  
  40.     }  
  41.     //进行一些初始化的动作  
  42.     poll_initwait(&table);  
  43.     //关键调用函数,会去调用do_poll函数,通过其返回值来判断其可读的个数,同时table指针是poll_wqueues类型的指针  
  44.     //该poll_wqueues结构体包含有poll_table、poll_table_page、task_struct等非常重要的结构体和指针,例如后面要用到的等待队列项wait_queue_t就存放在  
  45.     //poll_table_page->poll_table_entry->wait_queue_t中,当然poll_wait函数是可以通过poll_table_struct的地址找到poll_table_entry的。  
  46.     //看我们这里的第二个参数head,其实就是walk的地址(poll_list指针类型)。  
  47.     fdcount = do_poll(nfds, head, &table, end_time);  
  48.     poll_freewait(&table);  
  49.   
  50.     for (walk = head; walk; walk = walk->next) {  
  51.         struct pollfd *fds = walk->entries;  
  52.         int j;  
  53.   
  54.         for (j = 0; j < walk->len; j++, ufds++)  
  55.             if (__put_user(fds[j].revents, &ufds->revents))  
  56.                 goto out_fds;  
  57.     }  
  58.   
  59.     err = fdcount;  
  60. out_fds:  
  61.     walk = head->next;  
  62.     while (walk) {  
  63.         struct poll_list *pos = walk;  
  64.         walk = walk->next;  
  65.         kfree(pos);  
  66.     }  
  67.   
  68.     return err;  
  69. }  
下面继续看我们的do_poll函数的源代码

  1. static int do_poll(unsigned int nfds,  struct poll_list *list,  
  2.            struct poll_wqueues *wait, struct timespec *end_time)  
  3. {  
  4.     poll_table* pt = &wait->pt;  
  5.     ktime_t expire, *to = NULL;  
  6.     int timed_out = 0, count = 0;  
  7.     unsigned long slack = 0;  
  8.   
  9.     /* Optimise the no-wait case */  
  10.     if (end_time && !end_time->tv_sec && !end_time->tv_nsec) {  
  11.         pt = NULL;  
  12.         timed_out = 1;  
  13.     }  
  14.   
  15.     if (end_time && !timed_out)  
  16.         slack = select_estimate_accuracy(end_time);  
  17.   
  18.     for (;;) {  
  19.         struct poll_list *walk;  
  20.         //看这里面的walk首先会指向list。每一个walk都应该代表一个设备文件,因为walk里面的entries数组只有一个元素用来存放fd信息的  
  21.         for (walk = list; walk != NULL; walk = walk->next) {  
  22.             struct pollfd * pfd, * pfd_end;  
  23.   
  24.             pfd = walk->entries;  
  25.             pfd_end = pfd + walk->len;  
  26.             for (; pfd != pfd_end; pfd++) {  
  27.                 /* 
  28.                  * Fish for events. If we found one, record it 
  29.                  * and kill the poll_table, so we don't 
  30.                  * needlessly register any other waiters after 
  31.                  * this. They'll get immediately deregistered 
  32.                  * when we break out and return. 
  33.                  */  
  34.                 //调用do_pollfd函数,该函数会调用我们自己编写驱动程序的file_operation->poll函数指针指向的函数  
  35.                 if (do_pollfd(pfd, pt)) {  
  36.                     count++;  
  37.                     pt = NULL;  
  38.                 }  
  39.             }  
  40.         }  
  41.         /* 
  42.          * All waiters have already been registered, so don't provide 
  43.          * a poll_table to them on the next loop iteration. 
  44.          */  
  45.         pt = NULL;  
  46.         if (!count) {  
  47.             count = wait->error;  
  48.             if (signal_pending(current))  
  49.                 count = -EINTR;  
  50.         }  
  51.         if (count || timed_out)  
  52.             break;  
  53.   
  54.         /* 
  55.          * If this is the first loop and we have a timeout 
  56.          * given, then we convert to ktime_t and set the to 
  57.          * pointer to the expiry value. 
  58.          */  
  59.         if (end_time && !to) {  
  60.             expire = timespec_to_ktime(*end_time);  
  61.             to = &expire;  
  62.         }  
  63.   
  64.         if (!poll_schedule_timeout(wait, TASK_INTERRUPTIBLE, to, slack))  //关键函数,该函数会使进程将当前进程休眠,因为在此前该进程已经被标识在等待队列上了  
  65.             timed_out = 1;  
  66.     }  
  67.     return count;  
  68. }  
这里要注意的是poll_list是一个单向链表,这个链表应该表示不同的fd设备文件信息,所以这也是为什么poll有轮询的机制,它会轮询
所有fd设备驱动程序的poll函数。
下面我们再继续看我们有一个关键函数do_pollfd,这个函数的作用在于可以去调用我们自己在驱动程序里面即file_operation->poll函数指针指向的函数

  1. static inline unsigned int do_pollfd(struct pollfd *pollfd, poll_table *pwait)  
  2. {  
  3.     unsigned int mask;  
  4.     int fd;  
  5.   
  6.     mask = 0;  
  7.     fd = pollfd->fd;  
  8.     if (fd >= 0) {  
  9.         int fput_needed;  
  10.         struct file * file;  
  11.   
  12.         file = fget_light(fd, &fput_needed);  
  13.         mask = POLLNVAL;  
  14.         if (file != NULL) {  
  15.             mask = DEFAULT_POLLMASK;  
  16.             if (file->f_op && file->f_op->poll) {  
  17.                 if (pwait)  
  18.                     pwait->key = pollfd->events |  
  19.                             POLLERR | POLLHUP;  
  20.                 mask = file->f_op->poll(file, pwait);  //这句代码很关键,在这里就直接调用驱动程序的poll函数了  
  21.             }  
  22.             /* Mask out unneeded events. */  
  23.             mask &= pollfd->events | POLLERR | POLLHUP;  
  24.             fput_light(file, fput_needed);  
  25.         }  
  26.     }  
  27.     pollfd->revents = mask;  
  28.   
  29.     return mask;  
  30. }  


通常我们自己在驱动程序中编写poll函数时都会是这样的模式

  1. static unsigned int button_poll(struct file *file, struct poll_table_struct *wait)  
  2. {  
  3.     unsigned int mask = 0//定义一个返回变量  
  4.     poll_wait(file,&button_waitq,wait)  //将当前进程加入以button_waitq(通常是自己定义)为队列头的等待队列  
  5.       
  6.     if(flag)  //看是否有新的数据可读,如果有则将标志位maks |=POLLIN|POLLRDNORM,并返回。  
  7.         maks |=POLLIN|POLLRDNORM;  
  8.     return mask;  
  9.    
  10. }  
这里我们使用了poll机制一个非常重要的函数poll_wait函数,这个函数的主要作用就是将当前进程加入等待队列,并且将进程的状态标志位置为interruptable
下面我们来看看poll_wait函数

  1. static void __pollwait(struct file *filp, wait_queue_head_t *wait_address,  
  2.                 poll_table *p)  
  3. {  
  4.     struct poll_wqueues *pwq = container_of(p, struct poll_wqueues, pt);  
  5.     struct poll_table_entry *entry = poll_get_entry(pwq);  
  6.     if (!entry)  
  7.         return;  
  8.     get_file(filp);  
  9.     entry->filp = filp;  
  10.     entry->wait_address = wait_address;  
  11.     entry->key = p->key;  
  12.     init_waitqueue_func_entry(&entry->wait, pollwake);  
  13.     entry->wait.private = pwq;  
  14.     add_wait_queue(wait_address, &entry->wait);  
  15. }  
注意这里面有一个 poll_get_entry函数,该函数是取得一个poll_table_entry的地址,我们只要关注poll_get_entry的最后一个代码就可

  1. static struct poll_table_entry *poll_get_entry(struct poll_wqueues *p)  
  2. {  
  3.     struct poll_table_page *table = p->table;  
  4.   
  5.     if (p->inline_index < N_INLINE_POLL_ENTRIES)  
  6.         return p->inline_entries + p->inline_index++;  
  7.   
  8.     if (!table || POLL_TABLE_FULL(table)) {  
  9.         struct poll_table_page *new_table;  
  10.   
  11.         new_table = (struct poll_table_page *) __get_free_page(GFP_KERNEL);  
  12.         if (!new_table) {  
  13.             p->error = -ENOMEM;  
  14.             return NULL;  
  15.         }  
  16.         new_table->entry = new_table->entries;  
  17.         new_table->next = table;  
  18.         p->table = new_table;  
  19.         table = new_table;  
  20.     }  
  21.   
  22.     return table->entry++;  //这个代码很关键,entry++,这样就保证了每一个我们调用poll_wait函数的时候,都会有一个新的wait_queue_t项  

猜你喜欢

转载自blog.csdn.net/qq_27977257/article/details/67638879