图解Linux的IO模型和相关技术

阻塞IO模型(Blocking I/O)

图解Linux的IO模型和相关技术

文章插图
 
linux 内核一开始提供了 read 与 write 阻塞式操作 。
  • 当客户端连接时,会在对应进程的文件描述符目录(/proc/进程号/fd)生成对应的文件描述符(0 标准输入;1 标准输出;2 标准错误输出;),比如 fd 8 , fd 9;
  • 应用程序需要读取的时候,通过系统调用 read (fd8)读取,如果数据还没到来,此应用程序的进程或线程会阻塞等待 。man 2 read 概述 #include <unistd.h> ssize_t read(int fd, void *buf, size_t count); 描述 read() 从文件描述符 fd 中读取 count 字节的数据并放入从 buf 开始的缓冲区中. 如果 count 为零,read()返回0,不执行其他任何操作. 如果 count 大于SSIZE_MAX,那么结果将不可预料. 返回值 成功时返回读取到的字节数(为零表示读到文件描述符), 此返回值受文件剩余字节数限制.当返回值小于指定的字节数时 并不意味着错误;这可能是因为当前可读取的字节数小于指定的 字节数(比如已经接近文件结尾,或 者正在从管道或者终端读取数 据,或者 read()被信号中断). 发生错误时返回-1,并置 errno 为相应值.在这种情况下无法得知文件偏移位置是否有变化.
问题如果出现了很多的客户端连接,比如1000个,那么应用程序就会启用1000个进程或线程阻塞等待 。此时会出现性能问题:
  • CPU 会不停的切换,造成进程或线程上下文切换开销,实际读取IO的时间占比会下降,造成CPU算力浪费 。因此,推动了 non-blocking I/O 的诞生 。
非阻塞IO模型(non-blocking I/O)
图解Linux的IO模型和相关技术

文章插图
 
此时,Linux 内核一开始提供了 read 与 write 非阻塞式操作,可以通过socket设置SOCK_NONBLOCK标记。
  • 此时应用程序就不需要每一个文件描述符一个线程去处理,可以只有一个线程不停轮询去读取read,如果没有数据到来,也会直接返回 。
  • 如果有数据,则可以调度去处理业务逻辑 。man 2 socket
SinceLinux2.6.27, the type argument serves a second purpose: in addition to specifying a socket type, it may include the bitwise OR of any of the following values, to modify the behavior ofsocket():SOCK_NONBLOCKSet the O_NONBLOCK file status flag on the open file description (see open(2)) referred to by the new file descriptor.Using this flag saves extra calls to fcntl(2) toachievethe same result.从这里可以看出来 socket Linux 2.6.27内核开始支持非阻塞模式 。
问题同理,当出现了很多的客户端连接,比如1000个,那就会触发1000次系统调用 。(1000次系统调用开销也很客观)
因此,有了 select 。
IO复用模型(I/O multiplexing) - select
图解Linux的IO模型和相关技术

文章插图
 
此时,Linux 内核一开始提供了 select 操作,可以把1000次的系统调用,简化为一次系统调用,轮询发生在内核空间 。
  • select系统调用会返回可用的 fd集合,应用程序此时只需要遍历可用的 fd 集合,去读取数据进行业务处理即可 。man 2 select
SYNOPSIS#include <sys/select.h>int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);DESCRIPTIONselect() allows a program to monitor multiple file descriptors, waiting until one or more of the file descriptors become "ready" for some class of I/O operation (e.g., input possible). A filedescriptor is considered ready if it is possible to perform a corresponding I/O operation (e.g., read(2), or a sufficiently small write(2)) without blocking.select() can monitor only file descriptors numbers that are less than FD_SETSIZE; poll(2) and epoll(7) do not have this limitation. See BUGS.可以看到支持传输多个文件描述符交由内核轮询 。
问题虽然从1000次系统调用,降为一次系统调用的开销,但是系统调用开销中需要传参1000个文件描述符 。这也会造成一定的内存开销 。
因此,有了 epoll 。
select() can monitor only file descriptors numbers that are less than FD_SETSIZE; poll(2) and epoll(7) do not have this limitation. See BUGS.IO复用模型(I/O multiplexing) - epoll
图解Linux的IO模型和相关技术

文章插图
 
man epollman 2 epoll_createman 2 epoll_ctlman 2 epoll_wait
  • epoll:
SYNOPSIS#include <sys/epoll.h>DESCRIPTIONTheepollAPIperformsasimilar task to poll(2): monitoring multiple file descriptors to see if I/O is possible on any of them.The epoll API can be used either as an edge-triggered or alevel-triggered interface and scales well to large numbers of watched file descriptors.The central concept of the epoll API is the epoll instance, an in-kernel data structure which, from a user-space perspective, can be considered as a container for two lists:• The interest list (sometimes also called the epoll set): the set of file descriptors that the process has registered an interest in monitoring.• The ready list: the set of file descriptors that are "ready" for I/O.The ready list is a subset of (or, more precisely, a set of references to) the file descriptors intheinterestlist.The ready list is dynamically populated by the kernel as a result of I/O activity on those file descriptors.


推荐阅读