Linux IO多路复用

Posted xzj8023tp

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux IO多路复用相关的知识,希望对你有一定的参考价值。

1、什么是I/O多路复用??

I/O复用无非就是多个进程共同使用一个I/O输入输出流。一旦发现进程指定的一个或者多个描述符可进行无阻塞IO访问时,它就通知该进程。

2、IO多路复用适用以下场合:

(1) 当客户处理多个描述字时(一般是交互式输入和网络套接口),必须使用I/O复用。

(2) 当一个客户同时处理多个套接口时,而这种情况是可能的,但很少出现。

(3) 如果一个TCP服务器既要处理监听套接口,又要处理已连接套接口,一般也要用到I/O复用。

(4) 如果一个服务器既要处理TCP,又要处理UDP,一般要使用I/O复用。

(5) 如果一个服务器要处理多个服务或多个协议,一般要使用I/O复用。

与多进程和多线程技术相比,I/O多路复用技术的大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减少了系统的开销。

对于应用层来说,使用非阻塞I/O的应用程序通常会使用select()和poll()系统调用查询是否可对设备进行无阻塞的访问。

总的来说,I/O处理的模型有5种:

● 阻塞I/O模型:在这种模型下,若所调用的I/O函数没有完成相关的功能,则会使进程挂起,直到相关数据到达才会返回。如常见对管道设备、终端设备和网络设备进行读写时经常会出现这种情况。
● 非阻塞I/O模型:在这种模型下,当请求的I/O操作不能完成时,则不让进程睡眠,而且立即返回。非阻塞I/O使用户可以调用不会阻塞的I/O操作,如open()、write()和read()。如果该操作不能完成,则会立即返回出错(如打不开文件)或者返回0(如在缓冲区中没有数据可以读取或者没空间可以写入数据)。
● I/O多路转接模型:在这种模型下,如果请求的I/O操作阻塞,且它不是真正阻塞I/O,而是让其中的一个函数等待,在此期间,I/O还能进行其他操作。如本小节要介绍的select()和poll()函数,就是属于这种模型。
● 信号驱动I/O模型:在这种模型下,进程要定义一个信号处理程序,系统可以自动捕获特定信号的到来,从而启动I/O。这是由内核通知用户何时可以启动一个I/O操作决定的。

它是非阻塞的。当有就绪的数据时,内核就向该进程发送SIGIO信号。 无论我们如何处理SIGIO信号,这种模型的好处是当等待数据到达时,可以不阻塞。主程序继续执行,只有收到SIGIO信号时才去处理数据即可。

● 异步I/O模型:在这种模型下,进程先让内核启动I/O操作,并在整个操作完成后通知该进程。这种模型与信号驱动模型的主要区别在于:信号驱动I/O是由内核通知我们何时可以启动一个I/O操作,而异步I/O模型是由内核通知进程I/O操作何时完成的。现在,并不是所有的系统都支持这种模型。

    可以看到,select()和poll()的I/O多路转接模型是处理I/O复用的一个高效的方法。它可以具体设置程序中每一个所关心的文件描述符的条件、希望等待的时间等,从select()和poll()函数返回时,内核会通知用户已准备好的文件描述符的数量、已准备好的条件(或事件)等。通过使用select()和poll()函数的返回结果(可能是检测到某个文件描述符的注册事件或是超时,或是调用出错),就可以调用相应的I/O处理函数了。

select()函数的语法要点

技术图片

select()函数根据希望进行的文件操作对文件描述符进行了分类处理,这里对文件描述符的处理主要涉及4个宏函数,如下表所示。

select()文件描述符处理函数

技术图片

一般来说,在每次使用select()函数之前,首先使用FD_ZERO()和FD_SET()来初始化文件描述符集(在需要重复调用select()函数时,先把一次初始化好的文件描述符集备份下来,每次读取它即可)。在select()函数返回后,可循环使用FD_ISSET()来测试描述符集,在执行完对相关文件描述符的操作后,使用FD_CLR()来清除描述符集。
另外,select()函数中的timeout是一个struct timeval类型的指针,该结构体如下所示:

 struct timeval 
    
        long tv_sec; /* 秒 */
        long tv_unsec; /* 微秒 */
    

当使用select()函数时,存在一系列的问题,例如,内核必须检查多余的文件描述符,每次调用select()之后必须重置被监听的文件描述符集,而且可监听的文件个数受限制(使用FD_SETSIZE宏来表示fd_set结构能够容纳的文件描述符的大数目)等。


poll()函数的语法要点

技术图片

每一个pollfd结构体指定了一个被监视的文件描述符,可以传递多个结构体,指示poll()监视多个文件描述符。每个结构体的events域是监视该文件描述符的事件掩码,由用户来设置这个域。revents域是文件描述符的操作结果事件掩码,内核在调用返回时设置这个域。

events域中请求的任何事件都可能在revents域中返回。合法的事件如下:

标志 解释
POLLIN 有数据可读。
POLLRDNORM 有普通数据可读。
POLLRDBAND 有优先数据可读。
POLLPRI 有紧迫数据可读。
POLLOUT 写数据不会导致阻塞。
POLLWRNORM 写普通数据不会导致阻塞。
POLLWRBAND 写优先数据不会导致阻塞。
POLLMSGSIGPOLL 消息可用。
POLLER 指定的文件描述符发生错误。
POLLHUP 指定的文件描述符挂起事件。
POLLNVAL 指定的文件描述符非法。

这些事件在events域中无意义,因为它们在合适的时候总是会从revents中返回。

使用poll()和select()不一样,你不需要显式地请求异常情况报告。

以上是关于Linux IO多路复用的主要内容,如果未能解决你的问题,请参考以下文章

IO多路复用及ThreadingTCPServer源码阅读

Linux 下网络 IO 的多路复用

Linux select多路复用介绍(转)

IO多路复用深入浅出

Linux IO多路复用 poll

Linux IO多路复用模型