要分析poll机制就得分析系统调用的sys_poll
内核调用poll流程:
进入sys_poll():
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;
}
/*调用do_sys_poll()*/
return do_sys_poll(ufds, nfds, &timeout_jiffies);
}
其中超时时间就是我们在调用poll时传入的,sys_poll主要调用了do_sys_poll(),我们进行分析:
int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds, s64 *timeout)
{
最主要的:
poll_initwait(&table);
fdcount = do_poll(nfds, head, &table, timeout);
}
进入poll_initwait:
void poll_initwait(struct poll_wqueues *pwq)
{
init_poll_funcptr(&pwq->pt, __pollwait);
pwq->error = 0;
pwq->table = NULL;
pwq->inline_index = 0;
}
进入init_poll_funcptr:
static inline void init_poll_funcptr(poll_table *pt, poll_queue_proc qproc)
{
pt->qproc = qproc;
}
可以看出来进入poll_initwait的作用就是: 填充结构体变量struct poll_wqueues table的成员
table->qproc = __pollwait; // __pollwait将在驱动的poll函数里用到
table->error = 0;
table->table = NULL;
table->inline_index = 0;
进入 __pollwait:
static void __pollwait(struct file *filp, wait_queue_head_t *wait_address,
poll_table *p)
{
struct poll_table_entry *entry = poll_get_entry(p);
if (!entry)
return;
get_file(filp);
entry->filp = filp;
entry->wait_address = wait_address;
init_waitqueue_entry(&entry->wait, current);//初始化entry->wait
add_wait_queue(wait_address, &entry->wait);//添加到链表中
}
可以看出 __pollwait就是将entry->wait挂入到wait_address队列中去
回到do_sys_poll():
进入do_poll,这是poll主要实现部分:
static int do_poll(unsigned int nfds, struct poll_list *list,
struct poll_wqueues *wait, s64 *timeout)
{
……
for (;;)
{
/*遍历poll_list链表*/
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 || !*timeout || signal_pending(current))
break;
/*没有错误时,wait->error为0,有错误时,wait->error非0*/
count = wait->error;
if (count)
break;
__timeout = schedule_timeout(__timeout);
}
__set_current_state(TASK_RUNNING);
……
}
进入do_pollfd:
static inline unsigned int do_pollfd(struct pollfd *pollfd, poll_table *pwait)
{
……
file = fget_light(fd, &fput_needed);
if (file != NULL)
{
mask = DEFAULT_POLLMASK;
if (file->f_op && file->f_op->poll)
mask = file->f_op->poll(file, pwait);//调用驱动程序里的poll函数
/* Mask out unneeded events. */
mask &= pollfd->events | POLLERR | POLLHUP;//根据驱动函数的返回值进行判断
fput_light(file, fput_needed);
}
……
}
可以看出来,do_poll(),遍历poll_list链表,然后取出里面的
可以看出do_pollfd的作用就是调用我们的poll处理函数,而do_poll在循环中在链表中查找并且调用do_pollfd,直到
符合四个条件的时候退出循环:
1. count非0 (有调用do_pollfd,也就是驱动程序中有poll处理函数)
2. 超时
3. 信号在等待
4. 发生错误
如果处于循环当中,没有退出的话,那么就会让本进程休眠一段时间,注意:应用程序执行poll调用后,如果四个条件不满足,进程就会进入休眠。那么,谁唤醒呢?所谓唤醒就是退出do_poll的死循环,除了休眠到指定时间被系统唤醒外,还可以被驱动程序唤醒,当达到指定的休眠时间后,会在循环中在执行一次,然后发现满足超时这个条件的时候退出循环,──记住这点,这就是为什么驱动的poll里要调用poll_wait的原因,后面分析。
现在来总结一下内核调用poll机制的流程:
sys_poll
do_sys_poll
poll_initwait(&table);
table->qproc = __pollwait; // __pollwait将在驱动的poll函数里用到
init_waitqueue_entry(&entry->wait, current); //初始化waitqueue队列
add_wait_queue(wait_address, &entry->wait); //将进程加入到waitqueue队列
do_poll(nfds, head, &table, timeout);
for (;;)
{
if (do_pollfd(pfd, pt))
do_pollfd的主要代码:
if (file->f_op && file->f_op->poll)
mask = file->f_op->poll(file, pwait);
mask &= pollfd->events | POLLERR | POLLHUP;
fput_light(file, fput_needed);
return mask;
{
count++;
pt = NULL;
}
/*退出循环的条件*/
if (count || !*timeout || signal_pending(current))
break;
/*没有错误时,wait->error为0,有错误时,wait->error非0*/
count = wait->error;
if (count)
break;
/*不退出的话就进入休眠*/
__timeout = schedule_timeout(__timeout);
}
从内核调用poll机制的过程中我们可以发现最主要的就是两个函数:__pollwait,和do_pollfd,这两个函数__pollwait的作用就是
将我们驱动程序锁运行的进程加入到waitqueue队列从而使得内核可以找驱动程序所在的进程。do_pollfd的作用是调用驱动程序中的
poll处理函数。
因此我们可以得出结论:在驱动程序中我们实现poll机制,需要填充两个函数:__pollwait和poll处理函数
驱动程序:
内核提供给了我们调用__pollwait的函数为:poll_wait()
进入poll_wait:
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就是在poll_initwait中填充的__pollwait,三个参数中的wait_address就是需要驱动程序传入的进程当调用了poll_wait()函数之后,进程并不会立即休眠,它的作用只是用来填充调用__pollwait函数,进程进入休眠是在do_poll函数中实现的,当不满足四个退出循环的条件的时候,进程将会进入休眠状态,直到到达休眠时间,或者被驱动程序中的poll函数所唤醒
那么驱动函数怎么唤醒进程呢?
在do_pollfd函数中,调用了我们的poll处理函数,它返回了mask给poll,并且do_pollfd将其作为返回值
那么想要将进程唤醒的话,就得满足四个退出循环的条件,而poll处理函数所满足的就是count非0的情况,
步骤:
1. do_pollfd调用了poll处理函数,获得它的返回值,并且进行一些处理后作为自己的返回值
2. 在do_poll函数中的if (do_pollfd(pfd, pt))条件满足
3. count非0,退出循环,唤醒进程
应用程序:
那么在应用程序中需要做什么呢:
__timeout = schedule_timeout(__timeout)中的休眠时间,就是在应用程序中设置的,当休眠时间到了之后,驱动程序还没有唤醒
进程的话,系统将唤醒进程。
比如在我们的第四个应用程序中,设置休眠时间为5s
在5s内,如果驱动程序发生中断,唤醒进程,打印出按键信息,
如果没有其他程序唤醒进行的话,超过5s,打印出超时。
由于一直处在while(1)死循环中,将会重复做以上事件
而在应用程序中,调用poll函数,还有相应的处理:
加头文件 #include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
三个参数:
struct pollfd *fds : struct pollfd结构体指针
struct pollfd {
int fd; /*文件描述符*/
short events; /*请求的事件类型*/
short revents; /*返回的时间类型*/
};
nfds, : 能将进程唤醒的程序个数
timeout : 超时时间(ms为单位)
三个参数中,需要填充struct pollfd 结构体,然后将其传给poll
struct pollfd fds={
.fd=fd;
.events=POLLIN; /*POLLIN 表示进程时在做数据的读取. 更多的events取值,可以查询man手册*/
}
poll机制使用总结:
1、 在驱动程序实现的poll函数里面调用_pollwait(file结构,队列,休眠时间),_pollwait并不会让进程立即休眠,而是当此poll函数返回0时,会使得do_sys_poll中的变量count为0,使得不进去if(count ||!*timeout || signal_pending(current)) 语句,不跳出循环,执行下面的休眠语句。
2、在应用程序中,实现poll(struct pollfd结构,唤醒程序个数,休眠时间ms),如果休眠poll函数返回非0,否则返回0。struct pollfd 结构的成员fd需要对应到所需要执行的程序 fds[0].fd = fd; fds[0].events = POLLIN;