poll系统调用是io复用早期的实现,和select、epoll类似。今天来分析一下他的原理。先看一下poll的声明。
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
再看一下相关的数据结构。
struct pollfd {
int fd;
short events; /* 用户感兴趣的事件 */
short revents; /* 系统触发的事件 */
};
下面我们开始分析sys_poll函数(poll函数对应的系统调用)。
struct poll_wqueues table;
table->pt->qproc = __pollwait;
table->error = 0;
table->table = NULL
首先初始化一个结构体。poll_wqueues 定义如下。
在这里插入图片描述 接着申请内存把用户传递的数据复制到内核。
head = NULL;
walk = NULL;
i = nfds;
err = -ENOMEM;
while(i!=0) {
struct poll_list *pp;
// 申请一页大小的内存,保存一个poll_list结构体和多个pollfd结构体,大于一页的,再循环这个过程
pp = kmalloc(sizeof(struct poll_list)+
sizeof(struct pollfd)*
(i>POLLFD_PER_PAGE?POLLFD_PER_PAGE:i),
GFP_KERNEL);
pp->next=NULL;
// 记录本次复制的pollfd结构体个数
pp->len = (i>POLLFD_PER_PAGE?POLLFD_PER_PAGE:i);
// 构造链表
if (head == NULL)
head = pp;
else
walk->next = pp;
// 执行当前的poll_list,poll_list形成一个链表
walk = pp;
// 复制用户的数据到内核
copy_from_user(pp->entries, ufds + nfds-i, sizeof(struct pollfd)*pp->len);
// 剩下待复制的个数
i -= pp->len;
}
复制完成后结构如下。
在这里插入图片描述 接着开始poll文件描述符,看是否准备好了。
// nfds文件描述符个数,head保存了文件描述符和事件的链表头指针,table用于挂起进程,timeout最多poll多久
fdcount = do_poll(nfds, head, &table, timeout);
下面看一下do_poll的实现(省略部分代码)。
static int do_poll(unsigned int nfds, struct poll_list *list,
struct poll_wqueues *wait, long timeout)
{
int count = 0;
poll_table* pt = &wait->pt;
// timeout为空,说明即使没有就绪事件也不需要阻塞
if (!timeout)
pt = NULL;
for (;;) {
struct poll_list *walk;
walk = list;
while(walk != NULL) {
do_pollfd( walk->len, walk->entries, &pt, &count);
walk = walk->next;
}
// count代表有没有就绪事件,timeout 说明没有设置超时或者已经超时,signal_pending代表有信号需要处理
if (count || !timeout || signal_pending(current))
break;
// 挂起进程,timeout后被唤醒
timeout = schedule_timeout(timeout);
}
return count;
}
就是遍历刚才构造的链表,如果没有就绪的时候,并且设置了超时,也没有信号需要处理,则挂起进程,等待唤醒(有就绪事件或者超时都会被唤醒)。我们看遍历的时候,对每个pollfd结构体做了什么。
static void do_pollfd(unsigned int num, struct pollfd * fdpage,
poll_table ** pwait, int *count)
{
int i;
for (i = 0; i < num; i++) {
int fd;
unsigned int mask;
struct pollfd *fdp;
mask = 0;
// 当前当处理的pollfd结构体
fdp = fdpage+i;
// 待处理文件描述符
fd = fdp->fd;
if (fd >= 0) {
// 获取fd对应的file结构体
struct file * file = fget(fd);
mask = POLLNVAL;
if (file != NULL) {
mask = DEFAULT_POLLMASK;
if (file->f_op && file->f_op->poll)
// mask记录就绪的事件
mask = file->f_op->poll(file, *pwait);
// 过滤掉不感兴趣的
mask &= fdp->events | POLLERR | POLLHUP;
fput(file);
}
// 有就绪事件,记录
if (mask) {
*pwait = NULL;
(*count)++;
}
}
// 记录就绪的事件
fdp->revents = mask;
}
}
就是调用各个功能实现的poll函数。判断是否有事件就绪。我们以pipe为例看一下poll函数的大致实现。
static unsigned int
pipe_poll(struct file *filp, poll_table *wait)
{
...
/* Reading only -- no need for acquiring the semaphore. */
nrbufs = info->nrbufs;
mask = 0;
if (filp->f_mode & FMODE_READ) {
mask = (nrbufs > 0) ? POLLIN | POLLRDNORM : 0;
if (!PIPE_WRITERS(*inode) && filp->f_version != PIPE_WCOUNTER(*inode))
mask |= POLLHUP;
}
if (filp->f_mode & FMODE_WRITE) {
mask |= (nrbufs < PIPE_BUFFERS) ? POLLOUT | POLLWRNORM : 0;
if (!PIPE_READERS(*inode))
mask |= POLLERR;
}
return mask;
}
判断一下是否有事件就绪了。如果没有就绪事件,系统会做两件事情。 1 把进程加入到inode的等待队列。 2 定时挂起进程,等待超时唤醒。 如果在超时之前,就有就绪事件触发,那进程会被唤醒。如果一直没有事件触发,直到超时,进程被唤醒,这时候sys_poll函数返回。sys_poll大致的逻辑就是这样,整个流程比这个复杂,尤其是加入到等待队列的逻辑。