1

浅析IO模型

 2 years ago
source link: https://dawnki.github.io/2017/08/12/%E6%B5%85%E6%9E%90IO%E6%A8%A1%E5%9E%8B/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

在之前的同步异步文章中埋了个坑,今天就来填一下吧。当CPU发起IO调用,需要读取文件里的东西时,由于IO处理的速度没有CPU那么快,因此,通常都是CPU在等待IO处理完毕返回数据。当IO处理中到CPU拿到文件数据,这里存在几种IO模型,分别是read,select,poll,epoll,kqueue。在此先说一下fd,系统在读写文件的时候,需要文件描述符(file descriptor简称fd),当调用系统内核来进行文件操作时,内核会返回一个文件描述符,若要知道IO是否处理完毕,只需查看fd中的事件状态即可,即程序调用内核时就是查看这个状态,来判断是否完成操作的。下面就简单介绍下这几种IO模型。

这是原始的轮询模式,当CPU发起IO调用时,处于等待状态,然后IO就去处理,然后就重复的检查fd的事件状态,当事件状态为处理完毕后,读出完整数据。这种做法比较原始以及性能较差。

select

select是在read上的改进,有一个数组负责存着一堆文件描述符,然后遍历数组中的文件描述符来检查当中的事件状态,当某个文件已经处理完毕之后,就进行一次read操作来读取完整数据,然后交给应用。

select的缺点有两个。①是存放文件描述符的数组大小有限,一个进程处理的文件描述符(fd)数有限(cat /proc/sys/fs/file-max).32位机默认是1024个。64位机默认是2048.②随着fd的增多,会造成性能线性下降的问题(主因线性遍历)。

poll则是在select的基础上进行改良,采用链表的数据结构来存储文件描述符,以解决select的数组大小限制问题。但是还是需要线性遍历,因此select的第二个缺点还是无法避免。

epoll

epoll只能在Linux使用。epoll是目前性能强劲的io复用模型,epoll的原理是每当操作文件时,都有一个回调函数与fd相对应,当io操作完毕后,就会调用这个回调函数来通知应用获取数据。

epoll中由三个函数,分别是epoll_create,epoll_ctl,epoll_wait。

#include <sys/epoll.h>
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

epoll_create用于创建epoll句柄,然后epoll来监听管理一定量的文件描述符。当然这个句柄本身就占一个fd。

epoll_ctl用于控制管理一个epoll句柄下面N个的fd,比如可以对一个epoll句柄下的某个fd进行增删该事件监听,这里监听的事件指的是epoll观察指定fd的状态,比如某个fd需要读写数据等。

struct epoll_event
__uint32_t events;
epoll_data_t data;
typedef union epoll_data
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;

epoll_wait,根据触发方式(水平触发或者边缘触发),来收集刚刚epoll_ctl中已注册并且触发的事件(就绪链表),比如收集了fd为2,3的已经触发的事件,然后把这些事件赋值给一个数组(参数二中的events数组),如果没有超时(timeout),epoll_wait就会返回 触发事件的数目 ,然后去指定事件数组里拿相应数目的文件描述符(epoll_event储存了许多信息,里面的data就包含文件描述符)即可,这样就可以达到不复制文件描述符省去巨大的开销。同时也epoll只需拿到触发事件的文件描述符及其结果,而不像select和poll一样把不活跃的fd都一同获取了。

虽然说epoll_wait也是像select和poll一样需要轮询,不过它是设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在epoll_wait中进入睡眠的进程。虽然都要睡眠和交替,但是select和poll在“醒着”的时候要遍历整个fd集合,而epoll在“醒着”的时候只要判断一下就绪链表是否为空。另外epoll使用了mmap(内存映射技术),加速与内核与用户空间的消息传递,避免不必要的内存拷贝。

kqueue

kqueue与epoll一样都是基于事件的方式来处理IO复用的问题,只是kqueue是应用在BSD系统上,在此就不在赘述。

水平触发 & 边缘触发

LT:水平触发,支持阻塞和非阻塞io,效率会低于ET触发,尤其在大并发,大流量的情况下。但是LT对代码编写要求比较低,不容易出现问题。LT模式服务编写上的表现是:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll_wait时,会再次响应应用程序并通知此事件。select和poll只支持水平触发。

ET:边缘触发,仅支持非阻塞io,效率非常高,在并发,大流量的情况下,会比LT少很多epoll的系统调用,当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。epoll支持边缘触发。

read属于原始的IO模型,而select,poll,epoll,kqueue都是属于IO复用模型,可以管理一定量的文件描述符。epoll基于事件的处理方式在处理大量的连接时仍保持这不错的性能,有效的提高了IO效率。

http://blog.csdn.net/yusiguyuan/article/details/15027821

http://blog.csdn.net/xiajun07061225/article/details/9250579

http://www.hulkdev.com/posts/epoll-io

http://blog.csdn.net/jay900323/article/details/18141217/

http://blog.csdn.net/tianmohust/article/details/6677985/

http://www.cnblogs.com/Anker/p/3263780.html


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK