I/O多路复用技术
系统内核缓冲I/O数据,当某个I/O准备好后,系统通知应用程序该I/O可读或可写,这样应用程序可以马上完成相应的I/O操作,而不需要等待系统完成相应I/O操作,从而应用程序不必因等待I/O操作而阻塞。
select
网上很多讲解select函数的,这里围绕下图讲解一下select函数。
函数构造:
int select(int maxfd, fd_set readset, fd_set writeset, fd_set exceptset, timeval tv);
1. fd: (file descriptor)文件描述符,是内核用来描述一个文件的索引值(非负整型)。
2. fdset: 是一个关于fd的一个bitmap,上面有3个fdset变量,readset:可读集合,writeset:可写集合,exceptset:异常集合。分别对应文件的可读,可写,和异常。
3. maxfd: 就是最大的fd的值+1。估计代码里面是<而非<=,所以必须+1。
4. timevl: 是一个结构体,其中包含两个属性,tv_sec:秒,tv_usec:微妙,表示select超时时间。如图所示:
1. 程序先调用了select函数对fd=1和fd=2的文件进行了可读监听,超时时间为5s。
2. 在接下来的5s内,被监听的两个文件没有全部就绪,直到超时时间到返回一个数值,表示有多少个就绪,这里只有一个所以return的是1,然后重新看readset,发现只有fd=1的位置是1,说明只有fd=1的文件就绪。
3. 接着可以去读取你要读取的文件(fd=1)了。
缺点:
1. 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大。
2. 同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大。
3. select支持的文件描述符数量太小了,默认是1024。
poll
poll的实现和select非常相似,只是描述fd集合的方式不同,poll使用pollfd数组而不是select的fd_set结构,所以poll克服了select文件描述符数量的限制。
epoll
epoll是一种reactor模式,提供了三个函数,epoll_create,epoll_ctl和epoll_wait。
1. epoll_create是创建一个epoll句柄。
2. epoll_ctl是注册要监听的事件类型。
3. epoll_wait则是等待事件的产生。优化select缺点:
1. 对于第一个缺点,epoll的解决方案在epoll_ctl函数中。每次注册新的事件到epoll句柄中时(在epoll_ctl中指定EPOLL_CTL_ADD),会把所有的fd拷贝进内核,而不是在epoll_wait的时候重复拷贝。epoll保证了每个fd在整个过程中只会拷贝一次。
2. 对于第二个缺点,epoll的解决方案不像select或poll一样每次都把fdset里面的fd轮流加入fd对应的设备等待队列中,而只在epoll_ctl时把要监控的fd挂一遍,并为每个fd指定一个回调函数,当设备就绪,唤醒等待队列上的等待者时,就会调用这个回调函数,而这个回调函数会把就绪的fd加入一个就绪链表)。epoll_wait的工作实际上就是在这个就绪链表中查看有没有就绪的fd(利用schedule_timeout()实现睡一会,判断一会的效果)。
3. 对于第三个缺点,epoll没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个和系统限制有关,linux里面可以用ulimit查看文件打开数限制。