Linux IO 模式及 select、poll、epoll 详解

一、概念说明

1、内核态(内核空间)和用户态(用户空间)的区别和联系?

用户空间是用户进程所在的内存区域,系统空间是操作系统所在的内存区域。

为了保证内核的安全,处于用户态的程序只能访问用户空间,而处于内核态的程序可以访问用户空间和内核空间。

2、文件描述符 fd

Linux 将所有设备都当做文件来处理,文件描述符来标识每个文件对象。

当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。

3、缓存 IO

Linux 的缓存 IO 机制中,操作系统会将 IO 的数据缓存在文件系统的页缓存中,也就是说,数据会先被拷贝到操作系统内核的缓冲区,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间


二、IO 模式

对于一次 IO 访问(以 read 为例),数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。所以说,当一个 read 操作发生时,会经历两个阶段:

1、等待数据准备(Waiting for the data to be ready)

2、将数据从内核拷贝到进程中(Copy the data from kernel to the process)

扫描二维码关注公众号,回复: 9656660 查看本文章

linux 系统产生了下面五种网络模式的方案:

1、阻塞 IO(blocking IO)

2、非阻塞 IO(nonblocking IO)

3、IO 多路复用(IO multiplexing)

4、信号驱动 IO(signal driven IO)不常用

5、异步 IO (asynchronous IO)


(1)阻塞 IO

在这里插入图片描述

当用户进程调用了 recvfrom 这个系统调用,kernel 就开始了 IO 的第一个阶段:准备数据(对于网络 IO 来说,很多时候数据在一开始还没有到达。比如,还没有收到一个完整的 UDP 包。这个时候 kernel 就要等待足够的数据到来)。这个过程需要等待,也就是说数据被拷贝到操作系统内核的缓冲区中是需要一个过程的。而在用户进程这边,整个进程会被阻塞(当然,是进程自己选择的阻塞)。当 kernel 一直等到数据准备好了,它就会将数据从 kernel 中拷贝到用户内存,然后 kernel 返回结果,用户进程才解除 block 的状态,重新运行起来。

所以,blocking IO 的特点就是在 IO 执行的两个阶段(等待数据和拷贝数据)都被 block 了。


(2)非阻塞 IO

Linux 下,可以通过设置 socket 使其变为 non-blocking。当对一个 non-blocking socket 执行读操作时:
    在这里插入图片描述

当用户进程发出 read 操作时,如果 kernel 中的数据还没有准备好,那么它并不会 block 用户进程,而是立刻返回一个 error。从用户进程角度讲 ,它发起一个 read 操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个 error 时,它就知道数据还没有准备好,于是它可以再次发送 read 操作。一旦 kernel 中的数据准备好了,并且又再次收到了用户进程的 system call,那么它马上就将数据拷贝到了用户内存,然后返回。

所以,nonblocking IO 的特点是用户进程需要不断的主动询问 kernel 数据好了没有


(3)IO 多路复用

IO multiplexing 就是我们说的 select,poll,epoll,有些地方也称这种 IO 方式为 event driven IO。select/epoll 的好处就在于单个 process 就可以同时处理多个网络连接的 IO。它的基本原理就是 select,poll,epoll 这个 function 会不断的轮询所负责的所有 socket,当某个 socket 有数据到达了,就通知用户进程。
    在这里插入图片描述

当用户进程调用了 select,那么整个进程就会被 block,而同时,kernel 会 “监视” 所有 select 负责的 socket,当任何一个 socket 中的数据准备好了,select 就会返回。这个时候用户进程再调用 read 操作,将数据从 kernel 拷贝到用户进程。

所以,IO 多路复用的特点是通过一种机制一个进程能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入就绪状态,select () 函数就可以返回

这里需要使用两个 system call(select 和 recvfrom),而 blocking IO 只调用了一个 system call(recvfrom)。但是,用 select 的优势在于它可以同时处理多个 connection。

如果处理的连接数不是很高的话,使用 select/epoll 的 web server 不一定比使用 mutil-threading + blocking IO 的 web server 性能更好,可能延迟还更大。select/epoll 的优势并不是对于单个连接能处理得更好,而是在于处理更多的连接。


(4)异步 IO
在这里插入图片描述
用户进程发起 read 操作之后,立刻就可以开始去做其它的事。而另一方面,从 kernel 的角度,当它受到一个 asynchronous read 之后,首先它会立刻返回,所以不会对用户进程产生任何 block。然后,kernel 会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel 会给用户进程发送一个 signal,告诉它 read 操作完成了。


三、IO 多路复用之 select、poll、epoll 详解

select,poll,epoll 都是 IO 多路复用的机制。I/O 多路复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但 select,poll,epoll 本质上都是同步 I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步 I/O 则无需自己负责进行读写,异步 I/O 的实现会负责把数据从内核拷贝到用户空间。

1、select

select 函数监视文件描述符,调用后 select 函数会阻塞,直到有描述符就绪,或者超时,函数返回,当 select 函数返回后,就可以遍历描述符,找到就绪的描述符。

select 的一个缺点在于单个进程能够监视的文件描述符的数量也存在最大限制,在 Linux 上一般为 1024,可以通过修改宏定义甚至重新编译内核的方式提升这一限制。但是这样也会造成效率的降低。

2、poll

没有最大限制(但是数量过大后性能也是会下降)。和 select 函数一样,poll 返回后,需要轮询来获取就绪的描述符

select 和 poll 都需要在返回后,通过遍历文件描述符来获取已经就绪的 socket。事实上,同时连接的大量客户端在同一时刻可能只有很少的就绪状态,因此随着监视的描述符数量的增长,其效率也会线性下降。

3、epoll

相对于 select 和 poll 来说,epoll 更加灵活,没有描述符限制。epoll 使用一个文件描述符管理多个描述符。epoll可以理解为event poll,不同于忙轮询和无差别轮询,epoll只会把哪个流发生了怎样的I/O事件通知我们。此时我们对这些流的操作都是有意义的。(复杂度降低到了O(1))

发布了196 篇原创文章 · 获赞 878 · 访问量 30万+

猜你喜欢

转载自blog.csdn.net/qq_33945246/article/details/104359061