linux IO多路复用POLL机制深入分析
POLL机制的作用这里就不进行介绍,根据linux man手册,解释为在一个文件描述符上等待某个事件。按照抽象一点的理解,当某个事件被触发(条件被满足),文件描述符变为有状态,那么用户空间可以根据此进行操作,结合多个文件描述符,可以实现文件描述符的无阻塞访问。其实个人感觉这里的无阻塞主要是在监听多个文件描述符的情况下,把多个文件描述符放在一起管理,哪个有状态了就处理哪个,这样好像在调用具体的处理函数前比如read调用前,加了一层管理层用于检查是否可以进行操作,当所有的文件描述符都不可用,还是要阻塞,poll函数原型如下:
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
首个参数fds是一个数组指针,指向文件描述符集合,每个文件描述符对应一个pollfd 结构,nfds表示fd的个数,timeout指定阻塞的时间,单位为毫秒。用户空间调用后该函数后,经过poll系统调用进入内核,看下内核的实现在select.c文件中
SYSCALL_DEFINE3(poll, struct pollfd __user *, ufds, unsigned int, nfds,int, timeout_msecs)
主要还是调用了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;
/* Allocate small arguments on the stack to save memory and be
faster - use long to make sure the buffer is aligned properly
on 64 bit archs to avoid unaligned access */
long stack_pps[POLL_STACK_ALLOC/sizeof(long)];
struct poll_list *const head = (struct poll_list *)stack_pps;
struct poll_list *walk = head;
unsigned long todo = nfds; if (nfds > rlimit(RLIMIT_NOFILE))
return -EINVAL; len = min_t(unsigned int, nfds, N_STACK_PPS);
/*fd通过walk管理,多个walk形成链表*/
for (;;) {
walk->next = NULL;
walk->len = len;
if (!len)
break;
/*每次copy len个fd*/
if (copy_from_user(walk->entries, ufds + nfds-todo,
sizeof(struct pollfd) * walk->len))
goto out_fds; todo -= walk->len;
if (!todo)
break; len = min(todo, POLLFD_PER_PAGE);
size = sizeof(struct poll_list) + sizeof(struct pollfd) * len;
walk = walk->next = kmalloc(size, GFP_KERNEL);
if (!walk) {
err = -ENOMEM;
goto out_fds;
}
}
/*通过一次poll提交的fd共用一个table*/
poll_initwait(&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 = ; j < walk->len; j++, ufds++)
/*把实际的结果返回给用户空间*/
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);
}
/*正常情况下返回fd 的数目*/
return err;
}
文件描述符在内核中通过poll_list结构管理,每个poll_list管理一定数目的fd,多个poll_list形成一条链表,首个poll_list是在栈上分配的,用以加速访问。但是如果一个poll_list不够用,则必须要进行再次分配,再次就通过kmalloc进行分配了,看下代码,首先在栈上 申请了POLL_STACK_ALLOC个字节的空间,按照8个字节对齐,然后转化成了poll_list指针。如果参数中指定的nfds大于RLIMIT_NOFILE,则返回错误,否则在N_STACK_PPS和nfds中取小者作为首个poll_list的长度,即fd的个数。下面进入一个死循环,主要目的是分批次拷贝fd.没什么好说的,看下poll_list结构
struct poll_list {
struct poll_list *next;//连接下一个poll_list
int len;//该list中fd的数目
struct pollfd entries[0];//每个entry对应一个fd
};
拷贝完之后,需要进行核心处理了。首先初始化一个poll_wqueues结构
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];
};
pt是一个poll_table_struct结构,该结构内容如下,前者_qproc是一个函数指针,在驱动的poll函数中会对齐进行调用,主要是负责吧当前进程加入到等待队列中,key是请求的事件掩码,默认请求所有可用事件。
typedef struct poll_table_struct {
poll_queue_proc _qproc;//函数指针,一般负责吧当前进程加入到等待队列
unsigned long _key;//请求的事件掩码
} poll_table;
table指向poll_table_page结构,该结构管理poll_table_entry,每一个entry对应一个fd,所有的poll_table_page形成链表,不过在poll_table_page结构之前,优先使用的是poll_wqueues结构中的inline_entries,该数组是伴随poll_wqueues一起申请,inline_index指向其中的最后一个已经使用的元素,用以加速分配,OK结构描述就到这里,看函数代码。对poll_wqueues初始化后就调用了do_poll,该函数的核心功能就是对每一个fd,调用驱动的poll函数,从而获取各个fd的状态。代码简单列举下
for (;;) {
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++) {
/*
* Fish for events. If we found one, record it
* and kill poll_table->_qproc, so we don't
* needlessly register any other waiters after
* this. They'll get immediately deregistered
* when we break out and return.
*/
if (do_pollfd(pfd, pt)) {
count++;
pt->_qproc = NULL;
}
}
}
/*
* All waiters have already been registered, so don't provide
* a poll_table->_qproc to them on the next loop iteration.
*/
pt->_qproc = NULL;
if (!count) {
count = wait->error;
if (signal_pending(current))
count = -EINTR;
}
if (count || timed_out)
break; /*
* If this is the first loop and we have a timeout
* given, then we convert to ktime_t and set the to
* pointer to the expiry value.
*/
if (end_time && !to) {
expire = timespec_to_ktime(*end_time);
to = &expire;
} if (!poll_schedule_timeout(wait, TASK_INTERRUPTIBLE, to, slack))
timed_out = ;
}
核心业务都在这一个for循环中实现,前半部分正是针对每一个pfd调用do_pollfd函数,该函数是一个内敛函数,用以调用具体的poll函数来获取fd状态,并记录到pollfd->revents中。需要注意的是,虽然对各个fd均调用了poll函数,但是并不是所有的调用都会把进程加到等待队列,只要找到任何一个有状态的fd,即do_pollfd返回非0,则之后的poll调用均不会把进程加入到等待队列。因为加入到等待队列的本质目的是让该进程在阻塞的时候可以被正常唤醒。但是现在既然存在fd有状态,那么本次调用就不会阻塞,所以就不用在加入等待队列了。count记录有状态的fd的个数。如果count为0,就有三种可能性
1、如果有信号需要处理,则返回处理信号
2、如果超时了,则返回
3、阻塞,这是会触发调度器,下次调度还是会挨个检查fd状态,不过不会加入等待队列了。
而如果count不为0,则直接返回了。
以马内利
参考资料
linux3.10.1内核源码
linux IO多路复用POLL机制深入分析的更多相关文章
- Linux IO多路复用 poll
Linux IO多路复用 poll 之前曾经提到过 select poll 跟select类似,poll改进了select的一个确定,就是poll没有监听上限 不过poll还是需要遍历以及频繁的把数组 ...
- IO多路复用——poll
1.基本知识 poll是Linux中的字符设备驱动中的一个函数.Linux 2.5.44版本后,poll被epoll取代.和select实现的功能差不多,poll的作用是把当前的文件指针挂到等待队列. ...
- 【知乎网】Linux IO 多路复用 是什么意思?
提问一: Linux IO多路复用有 epoll, poll, select,知道epoll性能比其他几者要好.也在网上查了一下这几者的区别,表示没有弄明白. IO多路复用是什么意思,在实际的应用中是 ...
- Linux IO多路复用
监听文件描述符的状态来进行相应的读写操作,3个函数: 123 selectpollepoll 123456789 int (int nfds, fd_set *readfds, fd_set *wri ...
- Linux IO多路复用 select
Linux IO多路复用 select 之前曾经写过简单的服务器,服务器是用多线程阻塞,客户端每一帧是用非阻塞实现的 后来发现select可以用来多路IO复用,就是说可以把服务器这么多线程放在一个线程 ...
- IO多路复用的机制:select、poll、epoll
select.poll.epoll之间的区别总结[整理] IO多路复用之epoll总结 我读过的最好的epoll讲解--转自”知乎“
- Linux驱动之poll机制的理解与简单使用
之前在Linux驱动之按键驱动编写(中断方式)中编写的驱动程序,如果没有按键按下.read函数是永远没有返回值的,现在想要做到即使没有按键按下,在一定时间之后也会有返回值.要做到这种功能,可以使用po ...
- Linux通信之poll机制分析
poll机制分析 韦东山 2009.12.10 所有的系统调用,基于都可以在它的名字前加上“sys_”前缀,这就是它在内核中对应的函数.比如系统调用open.read.write.poll,与之对应的 ...
- Linux IO多路复用 select/poll/epoll
Select -- synchronius I/O multiplexing select, FS_SET,FD_CLR,FD_ISSET,FD_ZERO #include <sys/time. ...
随机推荐
- jfinal框架的初级学习
1.同strust2,使用jfinal框架首先需要在web.xml配置自己的过滤器(com.jfinal.core.JFinalFilter),并初始化JFinalConfig类. <filte ...
- 安装 oracle [转]
先下载3个东西:链接忘记了,大家自己找一下 1 ORA+11+G+R2+server+64bit+for+windows.iso (Oracle 安装文件) 2 PLSql 3 oracle6 ...
- 判断字符串String是否为空问题
一.判断一个字符串str不为空的方法有: 1.str == null; 2."".equals(str): 3.str.length <= 0; 4.str.isEmpty( ...
- mysql数据库中,flush logs语句的作用是什么呢?
需求描述: 今天在研究mysql数据库的备份和恢复,用到了flush logs这个SQL语句. 所以,在此进行测试,并且记录该SQL语句的作用. 概念描述: 在mysql数据库,如果数据库启动的时候, ...
- android 过USB-IF測试注意事项及改动
USB IF本身是个自愿性的标志(logo)认证. 使用或贴有USB标志的产品都需接受这项測试.其用意是确保全部USB装置的品质都符合要求.产品接受測试的方法有两种:參加USB- IF赞助的兼容性測试 ...
- cocos2d-x游戏引擎核心之七——数据持久化
一.XML与JSON XML 和 JSON 都是当下流行的数据存储格式,它们的共同特点就是数据明文,十分易于阅读.XML 源自于 SGML,是一种标记性数据描述语言,而 JSON 则是一种轻量级数据交 ...
- Solr可视化简单的操作
Solr可视化简单的操作 启动solr服务器;在浏览器输入Tomcat启动: http://192.168.191.142:8080/solr/#/ Ø 添加core,首先在存放home的文件下创建 ...
- Objective-c官方文档翻译 类的定义
类是对象的蓝图. 一个类是描述了对象的行为和属性.例如NSString的一个实例.他的类提供了各种的方法来转化和表示他的内部字符的表示. 每个类的实例都包含了这个类的属性和行为.例如每个NSSt ...
- poj_1743 后缀数组
题目大意 给定一串数字,长度为N.定义数字中的某个连续的子串为一个"theme",只要子串满足: (1)长度 >= 5 (2)和该子串相同或者该子串的“变种串”在整串数字中出 ...
- c++11——右值引用
1. 左值和右值 左值是表达式结束之后仍然存在的持久化对象,而右值是指表达式结束时就不再存在的临时对象. c++11中,右值分为两种类型:将亡值(xvalue, expiring value) ...