您现在的位置是:网站首页>技术百科技术百科

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在性能上非常接近。

阅读完毕,很棒哦!

文章评论

站点信息

  • 网站地址:www.xiaodahan.com
  • 我的QQ: 3306916637