IO多路复用的演变历史以及底层原理解释

什么是IO多路复用

IO多路复用的发展历史及底层原理

发展历史

IO多路复用起源于通信领域,特别是在电信和计算机网络中。它最初是一种通过共享介质将多个模拟或数字信号组合成一个信号的方法,旨在共享稀缺的物理传输介质。在电信领域,这项技术可以追溯到1870年代的电报技术,并在后来逐渐发展成为电话载波复用技术,由George Owen Squier在1910年开发。

随着计算机技术的发展,IO多路复用被引入到计算机系统中,特别是在UNIX和类UNIX系统中,用于处理多个I/O操作。在操作系统层面,IO多路复用技术经历了从select、poll到epoll的演进过程,每种技术都在不断解决前一种技术的局限性,以提高系统的性能和资源利用率。

底层原理

IO多路复用的底层原理主要是通过单一的线程(或进程)来管理多个I/O操作,避免了为每个I/O操作创建单独线程或进程的开销。以下是几种主要IO多路复用技术的底层原理:

  1. select

    • 使用fd_set来表示文件描述符集合,但存在文件描述符集合大小的限制(通常为FD_SETSIZE)。
    • 需要遍历整个文件描述符集合来检查是否有I/O事件就绪,这会导致随着文件描述符数量的增加,效率下降。
  2. poll

    • 解决了select中文件描述符集合大小的限制问题,使用一个pollfd结构体数组来存储文件描述符及其关注的事件。
    • 仍然需要遍历整个数组来检查I/O事件,但相较于select,它可以处理更大数量的文件描述符。
  3. epoll

    • 引入了事件通知机制,通过epoll_ctl注册关注的事件,并通过epoll_wait阻塞等待事件的发生。
    • 有效地监控大量的文件描述符,支持零拷贝技术,减少数据拷贝的开销。
    • 支持水平触发和边缘触发两种工作模式,前者在文件描述符就绪时持续通知,后者仅在状态变化时通知。

工作流程

以epoll为例,其工作流程大致如下:

  1. 创建并注册文件描述符

    • 通过epoll_create()创建一个epoll实例(文件描述符)。
    • 使用epoll_ctl()将需要关注的文件描述符(如socket)及其事件(如读、写)注册到epoll实例中。
  2. 等待事件发生

    • 调用epoll_wait()阻塞等待,直到有注册的文件描述符上发生了关注的事件。
  3. 处理事件

    • 当epoll_wait()返回时,会告知哪些文件描述符上发生了事件。
    • 程序根据这些信息进行相应的I/O操作。

优点

  • 资源节约:相较于多线程或多进程模型,IO多路复用可以更高效地利用系统资源。
  • 适用于高并发:在需要处理大量并发连接的情况下,IO多路复用可以减小线程或进程的开销,提高系统性能。
  • 避免死锁:由于只有一个线程(或进程)在处理多个连接,可以避免多线程模型中可能发生的死锁问题。

综上所述,IO多路复用技术通过高效的线程(或进程)管理和事件通知机制,显著提高了系统的并发处理能力和资源利用率。

select工作机制

Select机制是一种I/O多路复用技术,它允许单个进程或线程同时监视多个文件描述符(如套接字、管道等),以等待一个或多个文件描述符上的I/O事件(如可读、可写或异常)。以下是Select机制的详细介绍:

一、Select机制的工作原理

Select机制通过调用select函数来实现对多个文件描述符的监视。当调用select函数时,它会阻塞当前进程或线程,直到一个或多个文件描述符上的I/O事件就绪,或者指定的超时时间到达。Select函数会返回一个就绪的文件描述符集合,然后程序可以根据这个集合进行相应的I/O操作。

二、Select函数的原型

Select函数的原型定义在<sys/select.h>头文件中,其原型如下:

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

参数说明:

  • nfds:需要监视的文件描述符集合中最大文件描述符的值加1。
  • readfds:指向可读文件描述符集合的指针,如果不需要监视读事件,可以设置为NULL。
  • writefds:指向可写文件描述符集合的指针,如果不需要监视写事件,可以设置为NULL。
  • exceptfds:指向异常文件描述符集合的指针,如果不需要监视异常事件,可以设置为NULL。
  • timeout:指向一个timeval结构体,表示select函数的超时时间。如果设置为NULL,则表示select函数将无限期等待,直到有文件描述符就绪。

三、Select机制的关键数据结构

Select机制使用fd_set类型来表示文件描述符集合,它是一个位图(bitmap)结构,通过整型数组模拟而成。每个位对应一个文件描述符,用于表示该文件描述符是否处于就绪状态。

fd_set相关的几个宏操作包括:

  • FD_ZERO(fd_set *fdset):将指定的文件描述符集清空,即将fd_set变量的所有位初始化为0。
  • FD_SET(int fd, fd_set *fdset):在文件描述符集合fdset中增加一个新的文件描述符fd。
  • FD_CLR(int fd, fd_set *fdset):在文件描述符集合fdset中删除一个文件描述符fd。
  • FD_ISSET(int fd, fd_set *fdset):测试指定的文件描述符fd是否在该集合fdset中,如果包含文件描述符fd的信息,则返回真(非0值)。

四、Select机制的优缺点

优点:
  1. 跨平台性好:几乎所有的操作系统都支持select机制。
  2. 简单易用:select函数的接口简洁,易于理解和上手。
缺点:
  1. 监听文件描述符数量受限:Select通过位图表示文件描述符集合,而位图的大小通常由内核中FD_SETSIZE常量决定,在32位系统中默认是1024。这意味着,使用select最多只能同时监听1024个socket连接,对于需要高并发的服务端程序来说,这个上限是远远不够的。
  2. 线性扫描效率低下:无论内核还是用户态,都需要遍历整个文件描述符集合才能找到就绪的socket。当监听的socket数量较多时,每次扫描耗时也会线性增长,造成CPU资源的浪费。
  3. 内存拷贝开销大:在调用select时,需要把进程的文件描述符集拷贝到内核里,在返回时,又要把内核更新过的描述符集拷贝回进程中。当监听的socket较多时,这种频繁的内存拷贝会带来可观的性能损耗。
  4. 不能直接定位就绪的socket:Select只返回已就绪的文件描述符总数,具体是哪些socket就绪还需要用户自己去遍历。这种轮询方式效率低下,尤其是在大量连接中只有少量活跃时更加明显。

五、应用场景

尽管Select机制存在上述缺点,但在一些特定场景下仍然有其应用价值。例如,在需要同时监控的文件描述符数量较少,且对实时性要求不是特别高的场景下,Select机制仍然是一个简单有效的选择。此外,由于Select机制的跨平台性,它也被广泛应用于需要跨平台兼容性的应用程序中。

总的来说,Select机制是I/O多路复用技术中的一种重要实现方式,它通过监视多个文件描述符来提高系统的并发处理能力。然而,随着技术的发展和应用场景的变化,更高效的I/O多路复用机制(如poll和epoll)也逐渐被引入并广泛应用。

poll工作机制

Poll的工作机制是一种高效的I/O多路复用技术,它允许单个进程或线程同时监视多个文件描述符(如套接字、文件等),以等待一个或多个文件描述符上的I/O事件(如可读、可写或异常)。以下是Poll工作机制的详细介绍:

一、Poll函数原型

Poll函数的原型定义在<poll.h>头文件中,其原型如下:

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

参数说明:

  • fds:指向pollfd结构体数组的指针,每个pollfd结构体描述一个要监视的文件描述符和关注的事件。
  • nfds:数组中pollfd结构体的数量,即要监视的文件描述符的总数。
  • timeout:指定超时的时间,以毫秒为单位。如果设置为-1,poll将一直阻塞直到有事件发生;如果设置为0,poll将立即返回;否则,poll将在超时时间内等待事件。

二、Pollfd结构体

pollfd结构体用于描述一个文件描述符及其关注的事件,其定义如下:

struct pollfd {
    int fd;           // 文件描述符
    short events;     // 关注的事件,如POLLIN(可读)、POLLOUT(可写)等
    short revents;    // 由内核填充,表示实际发生的事件
};

三、Poll工作机制

Poll的工作机制主要包括以下几个步骤:

  1. 初始化pollfd数组
    程序需要创建一个pollfd结构体数组,并为每个要监视的文件描述符填充相应的fdevents字段。revents字段由内核在调用后填充。

  2. 调用poll函数
    程序调用poll函数,并传入pollfd结构体数组、数组中的元素数量以及超时时间。poll函数会阻塞当前进程或线程,直到以下任一条件发生:

    • 有一个或多个文件描述符上的I/O事件就绪。
    • 超时时间到达。
    • 发生错误。
  3. 处理返回结果
    poll函数返回后,程序可以通过检查pollfd结构体数组中的revents字段来确定哪些文件描述符上的I/O事件已经就绪。revents字段会被内核设置为一个或多个事件的组合(如POLLIN | POLLHUP),表示文件描述符上发生的具体事件。

    • 如果revents字段非零,表示有事件就绪,程序可以根据事件类型进行相应的处理(如读取数据、关闭连接等)。
    • 如果revents字段为零且poll函数的返回值为0,表示超时时间到达,没有事件就绪。
    • 如果poll函数的返回值小于0,表示发生了错误,程序需要根据errno的值来确定具体的错误原因。

四、Poll机制的优点

  1. 不受文件描述符数量限制:与select相比,poll没有文件描述符数量的限制(尽管实际上受限于系统内存和文件描述符表的限制)。
  2. 避免不必要的内存拷贝:poll通过直接传递pollfd结构体数组来减少内存拷贝的开销。
  3. 灵活的超时控制:poll允许用户设置超时时间,以控制等待I/O事件的最长时间。

五、注意事项

  1. 文件描述符必须是非阻塞的:为了充分发挥poll的优势,通常需要将文件描述符设置为非阻塞模式。
  2. 处理多个事件:如果多个文件描述符同时就绪,poll会一次性返回所有就绪的文件描述符,程序需要遍历pollfd数组来检查和处理每个文件描述符上的事件。
  3. 错误处理:在使用poll时,需要注意处理可能出现的错误情况,如文件描述符被关闭、网络问题等。

总之,Poll机制是一种高效的I/O多路复用技术,它通过监视多个文件描述符上的I/O事件来提高系统的并发处理能力。在需要同时处理多个I/O操作的场景中,Poll机制是一种非常有用的技术。

epoll的工作机制

epoll是Linux内核为处理大批量文件描述符(FD)而改进的poll机制,它是一种高效、可扩展的I/O事件通知机制,特别适用于高并发网络编程场景。epoll的工作机制可以详细阐述如下:

一、核心数据结构

epoll主要依赖于两个核心数据结构来管理文件描述符和事件:

  1. 红黑树:用于存储所有通过epoll_ctl函数注册的文件描述符及其事件。红黑树是一种自平衡二叉查找树,能够确保在插入、删除和查找操作时保持较高的效率。

  2. 就绪链表:用于存储已经就绪(即发生了感兴趣的事件)的文件描述符。当文件描述符上的事件发生时,内核会将其从红黑树中取出并添加到就绪链表中。

二、工作流程

epoll的工作流程主要包括以下几个步骤:

  1. 创建epoll实例

    • 使用epoll_create函数创建一个epoll实例,并返回一个文件描述符(epfd)。这个文件描述符用于后续管理需要监听的文件描述符集合。
  2. 注册事件

    • 使用epoll_ctl函数将需要监听的文件描述符及其事件类型添加到epoll实例中。epoll_ctl支持三种操作:添加(EPOLL_CTL_ADD)、修改(EPOLL_CTL_MOD)和删除(EPOLL_CTL_DEL)事件。
  3. 等待事件发生

    • 调用epoll_wait函数等待事件的发生。该函数会阻塞当前线程,直到有文件描述符上的事件发生或超时。当事件发生时,epoll_wait会返回就绪的文件描述符及其事件类型,供应用程序进一步处理。
  4. 处理事件

    • 应用程序遍历epoll_wait返回的事件列表,根据事件类型对相应的文件描述符进行读写等操作。

三、事件触发模式

epoll支持两种事件触发模式:

  1. 水平触发(Level Triggered, LT)

    • 当文件描述符上的事件发生时,内核会立即通知应用程序。如果应用程序没有及时处理该事件(例如,没有读取完所有数据),内核会继续通知应用程序,直到文件描述符上的事件被完全处理。这种模式下,应用程序需要确保在每次调用epoll_wait后都彻底处理完所有就绪的事件,否则可能会导致事件堆积。
  2. 边缘触发(Edge Triggered, ET)

    • 只在文件描述符上的事件状态发生变化时通知应用程序一次。如果应用程序没有一次性处理完所有就绪的数据(例如,在非阻塞模式下只读取了部分数据),那么内核将不会再次通知应用程序,直到有新的数据到来或文件描述符的状态再次发生变化。这种模式下,应用程序需要采用非阻塞I/O或确保在每次调用epoll_wait后都尽可能多地处理数据。

四、优点

相比传统的select和poll机制,epoll具有以下优点:

  1. 支持大量文件描述符:epoll没有文件描述符数量的限制(实际上受限于系统可以打开的最大文件数),而select和poll则受限于FD_SETSIZE(通常为1024)。

  2. 高效的事件通知机制:epoll通过内核与用户空间共享的事件表来管理文件描述符上的事件,避免了遍历整个文件描述符集合的开销。

  3. 支持多种事件类型:epoll支持多种事件类型,如可读、可写、错误等,使得应用程序可以更加灵活地处理不同类型的I/O事件。

  4. 高并发性能:在高并发场景下,epoll的性能明显优于select和poll,因为它只处理活跃的文件描述符,而不需要遍历整个文件描述符集合。

综上所述,epoll是一种高效、可扩展的I/O事件通知机制,特别适用于高并发网络编程场景。通过合理利用epoll的各种特性和接口函数,可以编写出高性能、高可靠性的网络服务器和应用程序。

在不同操作系统下的实现

在不同的操作系统下,多路复用机制的实现方式和技术会有所不同。这里主要讨论在Linux和Windows系统中常见的多路复用机制,并简要提及其他操作系统可能的情况。

Linux系统

在Linux系统中,多路复用机制主要通过selectpollepoll来实现。

  1. select

    • 是最早的I/O多路复用机制之一,它允许程序同时监视多个文件描述符(FD),以查看它们是否处于可读、可写或异常状态。
    • 缺点包括:文件描述符数量有限制(通常是1024,但可以调整),每次调用时都需要重新设置监视的文件描述符集合,且使用线性扫描方式检查文件描述符状态,效率较低。
  2. poll

    • select的改进版,解决了select中一些固有的缺陷,如文件描述符数量的限制。
    • 它使用链表来存储文件描述符,没有文件描述符数量的限制,可以处理任意数量的文件描述符。
    • 但它同样采用线性扫描方式,随着监控文件描述符数量的增加,性能会显著下降。
  3. epoll

    • 是Linux特有的I/O多路复用机制,设计用于处理大规模文件描述符的场景。
    • 它采用基于事件驱动的方式,内核只通知活跃的文件描述符,避免了线性扫描。
    • 支持边缘触发(ET)和水平触发(LT)两种模式,且内存拷贝开销低,是处理大量并发连接时的优选方案。

Windows系统

在Windows系统中,实现多路复用的主要机制包括select(与Linux中的select类似,但有一些差异)和基于Windows套接字的I/O完成端口(IOCP)。

  1. select

    • Windows下的select与Linux中的select在功能上相似,但也存在一些差异,如文件描述符在Windows中被称为套接字句柄(Socket Handle)。
    • 同样存在文件描述符数量限制和效率问题。
  2. I/O完成端口(IOCP)

    • 是Windows特有的I/O模型,用于高效处理大量并发I/O操作。
    • 它允许应用程序创建一个或多个I/O完成端口,并将套接字与之关联。当I/O操作完成时,系统会向I/O完成端口发送一个通知,应用程序可以通过一个或多个线程从该端口中检索已完成的操作结果。
    • 这种方式可以显著提高应用程序处理大量并发I/O操作的能力。

其他操作系统

在其他操作系统中,多路复用机制的实现方式可能因系统而异。例如,某些Unix-like系统(如BSD、macOS等)可能提供了类似于Linux的epoll的机制,但具体实现和性能特点可能有所不同。另外,一些实时操作系统(RTOS)或嵌入式系统可能提供了特定于平台的I/O多路复用解决方案。

综上所述,多路复用机制在不同操作系统下的实现方式和技术有所不同,选择合适的机制需要根据具体的应用场景和系统环境来决定。

相关名词解释

什么是边缘触发和水平触发

边缘触发(Edge Triggered, ET)和水平触发(Level Triggered, LT)是两种常见的事件触发模式,特别是在处理I/O事件时尤为重要。这两种模式在行为上有所不同,适用于不同的编程场景和需求。

边缘触发(ET)

定义
边缘触发模式关注的是事件状态的变化。当被监听的文件描述符(如套接字)上的状态发生变化时(例如,从不可读变为可读),系统才会通知应用程序。这种变化通常指的是从“无事件”到“有事件”的过渡,即所谓的“边缘”。

特点

  • 高效性:由于只在状态变化时通知,减少了不必要的通知次数,提高了效率。
  • 编程复杂度:对编程模型的要求较高,需要确保在状态变化时能够一次性处理完所有待处理的数据,否则可能会遗漏数据。
  • 非阻塞I/O友好:特别适用于非阻塞I/O和基于事件驱动的编程模型。

应用场景
边缘触发模式适用于高并发、低延迟的网络编程场景,如Web服务器、数据库连接池等。

水平触发(LT)

定义
水平触发模式关注的是事件的存在性,而不是状态的变化。当被监听的文件描述符上有可读或可写事件发生时,每次调用检查事件的函数(如epoll_wait)时,只要该事件存在,就会返回该文件描述符。

特点

  • 简单性:编程模型相对简单,不需要特别关注状态变化,只要事件存在就会被通知。
  • 可能导致忙等待:如果应用程序没有及时处理事件,每次调用检查事件的函数时都会返回该事件,可能导致应用程序频繁被唤醒,造成CPU资源浪费。
  • 阻塞I/O友好:虽然也适用于非阻塞I/O,但更常见于传统的阻塞I/O模型。

应用场景
水平触发模式适用于对实时性要求不高、更注重编程简单性的场景,如简单的文件读写操作、串行通信等。

总结

边缘触发和水平触发各有优缺点,选择哪种模式取决于具体的应用场景和需求。在需要高效处理大量并发连接、对性能要求较高的网络编程中,边缘触发模式通常是更好的选择;而在对实时性要求不高、更注重编程简单性的场景中,水平触发模式则可能更为合适。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

问道飞鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值