一、应用程序
/* struct pollfd { int fd; //文件描述符 short events; //表示请求检测的事件 short revents; //表示检测之后返回的事件 }; */ int fd; struct pollfd fds[1]; // 只用poll函数来检测一个描述符 fd = open("/dev/tiny6410_button", 0); fds[0].fd = fd; //存放文件描述符 fds[0].events = POLLIN; //有数据可以读 while (1) { ret = poll(fds, 1, 5000); // int poll(struct pollfd fds[], nfds_t nfds, int timeout) if (ret == 0) // fds[]:存放需要被检测状态的描述符 nfds:fds[]数组的个数 timeout:poll函数阻塞调用的时间 { printf("timeout fds[0].revents = %d \n",fds[0].revents); } else if (ret>0) { printf("fds[0].revents = %d\n",fds[0].revents); read(fd, press_cnt, sizeof(press_cnt)); for (i = 0; i < sizeof(press_cnt)/sizeof(press_cnt[0]); i++) { if (press_cnt[i]) printf("K%d has been pressed %d times!\n", i+1, press_cnt[i]); } } else { return 0; } } close(fd); return 0;
poll(...)函数的返回值:>0 表示 fds[]中存放的某些文件描述符的状态发生了变化
=0 表示 fds[]中存放的文件描述符的状态没有变化,并且调用超时了
<0 表示有错误发生
看到实验结果:当5秒没有按键按下时,timeout fds[0].revents = 0
当有按键立即按下时, fds[0].revents = 1
因此可以根据revents的值来判断哪个文件描述符的状态发生了变化
二、 从内核看poll 函数调用
2.1 找sys_poll(...)函数
在应用程序调用poll(...)函数时,内核会调用sys_poll(...)函数,因此在内核中寻找sys_poll(...)函数,在linux-2.6.38中,系统
调用函数名称都是用宏定义实现的。所有先找一找sys_poll(...)在哪里,在select.c中有如下函数:
SYSCALL_DEFINE3(poll, struct pollfd __user *, ufds, unsigned int, nfds, long, timeout_msecs)
这是一个宏定义,需要把这个宏定义展开成如下的形式:
asmlink long sys_poll( struct pollfd __user * ufds, unsigned int nfds, long timeout_msecs)
宏定义的展开过程分析:
在syscalls.h中有一大堆关于系统调用的宏定义
#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)
宏替换后变成了:
SYSCALL_DEFINEx(3, _poll, struct pollfd __user *, ufds, unsigned int, nfds, long, timeout_msecs)
#define SYSCALL_DEFINEx(x, sname, ...) __SYSCALL_DEFINEx(x, sname, __VA_ARGS__)
宏替换后变成了:
__SYSCALL_DEFINEx(3,_poll, struct pollfd __user *, ufds, unsigned int, nfds, long, timeout_msecs)
#define __SYSCALL_DEFINEx(x, name, ...) asmlinkage long sys##name(__SC_DECL##x(__VA_ARGS__))
宏替换后变成了:
asmlinkage long sys_poll(__SC_DECL3(struct pollfd __user *, ufds, unsigned int, nfds, long, timeout_msecs))
对__SC_DECL3(struct pollfd __user *, ufds, unsigned int, nfds, long, timeout_msecs)进行展开
__SC_DECL##x(__VA_ARGS__) 这个也是一个宏定义,仍然需要进行展开:
#define __SC_DECL1(t1, a1) t1 a1
#define __SC_DECL2(t2, a2, ...) t2 a2, __SC_DECL1(__VA_ARGS__)
#define __SC_DECL3(t3, a3, ...) t3 a3, __SC_DECL2(__VA_ARGS__)
#define __SC_DECL4(t4, a4, ...) t4 a4, __SC_DECL3(__VA_ARGS__)
#define __SC_DECL5(t5, a5, ...) t5 a5, __SC_DECL4(__VA_ARGS__)
#define __SC_DECL6(t6, a6, ...) t6 a6, __SC_DECL5(__VA_ARGS__)
就得到了 struct pollfd __user * ufds, unsigned int nfds, long timeout_msecs
故得到了最终的展开函数:
asmlink long sys_poll( struct pollfd __user * ufds, unsigned int nfds, long timeout_msecs)
2.2 函数调用过程分析
SYSCALL_DEFINE3(poll, struct pollfd __user *, ufds, unsigned int, nfds, long, timeout_msecs) { .... //1.设置timeout时间 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)); } // 2.完成poll调用的主要任务 ret = do_sys_poll(ufds, nfds, to); .... }
2.2.1这里的核心函数do_sys_poll(...)
int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds, struct timespec *end_time) { struct poll_wqueues table; int err = -EFAULT, fdcount, len, size; // 从这里开始都是分配内存空间,并将用户空间的fds[]拷贝到内核空间 long stack_pps[POLL_STACK_ALLOC/sizeof(long)];// 在栈上分配一个固定空间的 struct poll_list *const head = (struct poll_list *)stack_pps;//强制将上边分配的空间转换为poll_list struct poll_list *walk = head; unsigned long todo = nfds;//用户空间的fds[]数组的个数 if (nfds > rlimit(RLIMIT_NOFILE)) return -EINVAL; len = min_t(unsigned int, nfds, N_STACK_PPS); for (;;) { walk->next = NULL; //将指针先置为NULL walk->len = len; //长度=len if (!len) break; if (copy_from_user(walk->entries, ufds + nfds-todo, sizeof(struct pollfd) * walk->len))//拷贝的前提应该是不会超过分配内存的大小 goto out_fds; //当用户空间的fds[]超过所分配内存大小时,跳转 todo -= walk->len; //nfds-walk->len 求剩下多少个fds结构体组没有拷 if (!todo) //如果不剩即全部拷完了, 则break break; len = min(todo, POLLFD_PER_PAGE); //若还剩todo个没考, 求下一次需要拷的个数 size = sizeof(struct poll_list) + sizeof(struct pollfd) * len;//求需要分配内存的大小 walk = walk->next = kmalloc(size, GFP_KERNEL);// 在分配size大小的内存,并将这块内存的首地址挂载walk->next上, 这样如果有很多fds[]的话,就可以够成一个一个的poll_list 链表, 链表之间通过poll_list->next 连接 if (!walk) { err = -ENOMEM; goto out_fds; } } // 到这里已经分配完内存空间,并将所有的fds[]从用户空间拷贝到内核空间 poll_initwait(&table); // 这个函数就是初始化poll_wqueues类型的变量 table 初始化了什么,在下边分析 fdcount = do_poll(nfds, head, &table, end_time);// 核心函数 poll_freewait(&table); for (walk = head; walk; walk = walk->next) { struct pollfd *fds = walk->entries; int j; for (j = 0; j < walk->len; j++, ufds++) // 分析到这里应该就很明朗了,这里是把revents从内核空间拷贝到用户空间,方便用户空间来查询哪些文件描述符的状态发生了变化 if (__put_user(fds[j].revents, &ufds->revents)) goto out_fds; } err = fdcount; out_fds: walk = head->next; while (walk) { struct poll_list *pos = walk; walk = walk->next; kfree(pos); } return err; }
在do_sys_poll(...)函数中,首先就是分配内存空间,将用户空间的fds[]拷贝到内核空间,具体的拷贝过程已经在注释中大概分析了
其次初始化table变量 ,在初始化table变量之前有必要看一下table是什么
struct poll_wqueues { poll_table pt; struct poll_table_page *table; struct task_struct *polling_task; int triggered; int error; int inline_index; struct poll_table_entry inline_entries[N_INLINE_POLL_ENTRIES]; }; //这是table是什么 重点是poll_table成员 typedef struct poll_table_struct { poll_queue_proc qproc; unsigned long key; } poll_table; // 这是poll_table 是什么 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); } void poll_initwait(struct poll_wqueues *pwq) { init_poll_funcptr(&pwq->pt, __pollwait); //见下边,这table->pt ->qproc = qproc = _pollwait; pwq->polling_task = current; pwq->triggered = 0; pwq->error = 0; pwq->table = NULL; pwq->inline_index = 0; } static inline void init_poll_funcptr(poll_table *pt, poll_queue_proc qproc) { pt->qproc = qproc; pt->key = ~0UL; /* all events enabled */ }
上边的函数和结构体说明了初始化table变量的全过程
在初始化完成table之后: table->polling_task = current;
table->triggered = 0;
table->error = 0;
table->table = NULL;
table->inline_index = 0;
重点 table->pt->qproc = __pollwait; 这是给table->pt->qproc挂上了一个函数__pollwait,注意之后会用到,这个函数的具体类容已经在上边分析过了
2.2.2在do_sys_poll中核心函数: do_poll(nfds, head, &table, end_time);
for (;;) { //这里三层循环嵌套,虽然复杂,但是就是遍历从用户空间拷贝的fds[]数组,然后执行do_pollfd(pfd,pt) ,这里的pt就是上边分析的wait->pt 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++) { if (do_pollfd(pfd, pt)) { count++; pt = NULL; } } } pt = NULL; if (!count) { count = wait->error; if (signal_pending(current)) count = -EINTR; } if (count || timed_out) break; if (end_time && !to) { expire = timespec_to_ktime(*end_time); to = &expire; } if (!poll_schedule_timeout(wait, TASK_INTERRUPTIBLE, to, slack)) timed_out = 1; }
(1)发循环退出条件的分析:
第一个条件:fds[]文件中某些文件描述符的状态发生了变化,即fd[x]->revents != 0
第二个条件:timeout != 0 也就是超时了
这里先分析第二个条件即timeout != 0的情况
当timeout != 0时大循环退出 注意在这里边timeout看起来像是一个bool量,程序刚进入do_poll(...)时,timeout为0, 假设所有的文件描述符的状态都没有发生变化, 然后执行休眠函数poll_schedule_timeout(wait, TASK_INTERRUPTIBLE, to, slack)后,定时休眠成功后timeout变成1,然后再次循环,此时不管fds[]中文件描述符的状态怎么样,timeout都为1,都会退出大循环。
(2) 第一个退出条件分析:count != 0时
static inline unsigned int do_pollfd(struct pollfd *pollfd, poll_table *pwait) { // 先看传入参数:pollfd :fds[]数组中的某一个注意是拷贝到内核空间的fds[] // pwait : 这个pwait就是之前初始化的table->pt // pt下边挂着一个回调函数,在这里派上用处了 unsigned int mask; int fd; mask = 0; fd = pollfd->fd; // 取出文件描述符 if (fd >= 0) { int fput_needed; struct file * file; file = fget_light(fd, &fput_needed); mask = POLLNVAL; if (file != NULL) { mask = DEFAULT_POLLMASK; if (file->f_op && file->f_op->poll) { // 这里就和文件操作对应的结构体中中的内容就比较相似了, //当我们驱动程序中定义了文件操作结构体,并且定义了poll函数,那么久执行驱动程序的poll函数 if (pwait) pwait->key = pollfd->events | POLLERR | POLLHUP; mask = file->f_op->poll(file, pwait); // 这里执行驱动程序的poll函数, 传入参数有一个回调函数 //这个回调函数就是之前初始化table提到的 // 注意驱动程序poll函数执行完会返回一个值, 正是根据驱动程序中的poll函数的返回值来确定,我们的文件描述符的状态是否发生了变化 } /* Mask out unneeded events. */ mask &= pollfd->events | POLLERR | POLLHUP; fput_light(file, fput_needed); } } pollfd->revents = mask; return mask; }
这里的do_pollfd(...)函数实际上就是执行驱动函数poll,通过驱动函数来判断文件描述符的状态。
这里我们结合按键驱动的poll函数来分析这个是怎么判断状态的
unsigned int tiny6410_button_poll (struct file *file, struct poll_table_struct *wait) { unsigned int mask = 0; poll_wait(file, &button_waitq, wait); if (ev_press) mask |= POLLIN; return mask; }
(1)驱动函数中的poll_wait(file, &button_waitq, wait) , 注意wait是table->pt
poll_wait(file, &button_waitq, wait) ====》table->pt->qproc(file, &button_waitq, table->pt) ===》__pollwait(file,&button_waitq,table->pt)
这个_pollwait(.....)函数之前提过,现在在重复一遍:
static void __pollwait(struct file *filp, wait_queue_head_t *wait_address, poll_table *p) { ... add_wait_queue(wait_address, &entry->wait); // add_wait_queue(&button_waitq, &entry->wait) }
这个函数就是将等待对列挂到我们在驱动程序中定义的等待对列:button_waitq中,在这里并不会进入到休眠。
(2)if (ev_press)
mask |= POLLIN;
return mask;
当有按键按下时,产生中断,在中断服务函数中会将ev_press至为1;
当do_pollfd(...) 中调用驱动程序的poll函数时,会检测ev_press,当ev_press==1 时,说明有中断发生,然后mask |= POLLIN 并返回mask
当ev_press == 0, 没有中断发生, mask=0,并返回
然后在do_pollfd(...)将根据mask设置revents;
最后 do_poll(nfds, head, &table, end_time)中,根据返回do_pollfd()返回的mask值,来判断count是否++ ,即当有中断产生的时候,count++,
从而退出do_poll(...)的死循环。