您现在的位置是:网站首页>技术百科技术百科
IO模型浅谈
小大寒2024-01-01[技术百科]博学多闻
IO模型浅谈IO操作包括内存IO、网络IO和磁盘IO,其中内存IO最快,后两者常成性能瓶颈。为协调CPU与IO设备,各种IO模型被开发,包括同步阻塞IO、非阻塞IO、多路IO就绪通知(如select、poll、epoll等)及其他先进模型。epoll是Linux内核支持的高效实现,结合了水平触发和边缘触发的优点,通过事件通知机制避免复制开销,性能卓越。此外,类似kqueue等模型在其他平台提供功能支持,但兼容性有限。
IO模型浅谈
IO操作通常根据设备类型分为内存IO、网络IO和磁盘IO。其中,内存IO的速度远远快于后两者,因此计算机的性能瓶颈通常不在于内存IO。尽管可以通过购买独享带宽、高速网卡来提升网络IO速度,或者使用RAID磁盘阵列来增强磁盘IO性能,但IO操作是由系统内核调用完成的,而这些调用需要CPU进行调度。由于CPU的处理速度远高于IO操作,等待缓慢的IO会浪费宝贵的CPU时间。为了解决这一问题,使CPU与慢速IO设备更好地协调工作,减少CPU在IO调用上的消耗,各种IO模型逐渐被开发出来。
同步阻塞I/O
阻塞是指发起IO操作的进程被暂停执行。同步阻塞IO意味着,当进程调用某些IO操作的系统调用或库函数(如 accept()
、send()
、recv()
)时,进程会暂停并等待IO操作完成后再继续运行。
这种模型简单易用,且可以结合多进程实现对CPU资源的有效利用,但代价是每个进程需要占用额外的内存空间。
同步非阻塞I/O
在同步阻塞IO中,进程的等待时间通常包含两个部分:等待数据就绪(如数据可读或可写)和等待数据复制完成。而同步非阻塞IO则不同,当数据不可读或不可写时,相关系统调用会立即返回,并通知进程数据尚未就绪。例如,使用非阻塞方式调用 recv()
接收网络数据时,函数会立即返回并告知进程没有数据可读。
这种模型的优势在于,结合反复轮询可以在单个进程中同时处理多个IO操作。然而,这需要进程主动轮询数据是否就绪,导致进程处于忙等待状态。
需要注意的是,非阻塞IO通常仅对网络IO有效;对磁盘IO而言,非阻塞方式并无效果。
多路I/O就绪通知
在多路IO就绪通知模型中,进程可以通过某种机制同时监视多个文件描述符,并快速获取所有就绪的描述符,从而针对这些描述符进行数据操作。这种模型提供了一种高效处理大量文件描述符就绪检测的方法。
需要注意的是,多路IO就绪通知只解决了快速获取就绪文件描述符的问题。实际的数据访问过程仍需选择阻塞或非阻塞的方式。
由于平台和历史原因,多路IO就绪通知的实现方式多种多样,性能也有所差异。
select
select
最早出现于1983年的 4.2BSD 中。通过调用 select()
系统函数,可以监视包含多个文件描述符的数组。当 select()
返回时,内核会标记数组中所有就绪的文件描述符,以便进程进行后续读写操作。
这一机制的缺点包括:
- 单个进程能监视的文件描述符数量有限(在Linux系统中通常为1024)。尽管可以通过修改宏定义或重新编译内核提高限制,但这会带来额外的复杂性。
select()
维护的描述符数组会随文件描述符数量增加而线性增长,导致复制开销增加。- 由于网络延迟,许多TCP连接可能处于非活跃状态,但
select()
仍会对所有socket进行线性扫描,从而造成性能浪费。
poll
poll诞生于1986年的System V Release 3。显然,UNIX系统并未直接沿用BSD的select,而是重新实现了一种方法。尽管poll在本质上与select并无太多区别,但poll没有文件描述符数量的上限限制。
然而,poll和select有一个共同的缺点:包含大量文件描述符的数组在用户态和内核空间之间会被整体复制,而无论这些文件描述符是否就绪,其开销会随着文件描述符数量的增加而线性增长。
此外,poll和select在通知进程哪些文件描述符就绪后,如果进程未对其进行IO操作,那么下次调用时依然会重复报告这些文件描述符。这种行为称为水平触发(Level Triggered)。
SIGIO
在Linux 2.4中引入的SIGIO使用实时信号(Real Time Signal)实现了select/poll的通知功能。与select和poll不同的是,SIGIO仅会通知哪些文件描述符刚刚变为就绪状态,并且仅通知一次。如果未及时处理,SIGIO不会再次告知。这种方式被称为边缘触发(Edge Triggered)。
SIGIO具有较高的性能,但也有缺点:事件信号由内核事件队列维护,可能出现信号到达时描述的文件描述符已过期或被关闭的情况。此外,事件队列的长度有限,可能因装满事件而导致事件丢失,因此需要其他方法弥补。
/dev/poll
Sun公司在Solaris系统中提供了新的实现方法,称为/dev/poll。它通过虚拟设备支持IO操作:我们可以将需要监视的文件描述符数组写入设备,然后调用ioctl()等待事件通知。当事件就绪时,从/dev/poll读取所有就绪的文件描述符。这种方法类似于SIGIO,但减少了扫描所有文件描述符的开销。
在Linux中虽然可以实现类似/dev/poll的功能,但缺乏直接的内核支持,这导致性能在高负载情况下表现不够稳定。
/dev/epoll
/dev/epoll以补丁形式出现在Linux 2.4中,提供了类似/dev/poll的功能,同时引入了内存映射(mmap)技术,提高了性能。然而,/dev/epoll仍是补丁,未整合进Linux 2.4内核。
epoll
epoll在Linux 2.6中作为内核直接支持的实现方法出现,被认为是Linux 2.6下性能最优的多路IO就绪通知方式。epoll几乎集合了之前方法的所有优点,并支持水平触发和边缘触发两种模式。尽管边缘触发性能更高,但代码实现复杂且容易因事件丢失导致请求处理错误。默认情况下,epoll采用水平触发模式,使用边缘触发时需在事件注册时设置EPOLLET选项。
在epoll中,只有就绪的文件描述符会被通知。调用epoll_wait()时,不是直接返回文件描述符,而是返回一个值,指示就绪文件描述符的数量。然后从指定的数组中依次取出相应数量的文件描述符。epoll还利用内存映射技术,避免了系统调用时文件描述符的复制开销。
epoll的本质改进在于其基于事件通知的机制。通过epoll_ctl()事先注册文件描述符后,当描述符就绪,内核通过类似回调机制激活。进程调用epoll_wait()时即可获得通知。
kqueue
kqueue在FreeBSD中实现,与epoll类似支持水平触发和边缘触发,还可监视磁盘文件和目录。然而,由于API兼容性问题和文档匮乏,kqueue在许多平台上不支持。尽管如此,kqueue和epoll在性能上非常接近。
阅读完毕,很棒哦!
上一篇:C语言趣谈:令人抓狂的全局变量
下一篇:聊聊系统优化