io复用之poll源码分析(基于linux2.6.13.1)

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大致的逻辑就是这样,整个流程比这个复杂,尤其是加入到等待队列的逻辑。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章