IO

操作系统中的io设备管理(浅谈操作系统IO模式)(1)

IO (Input/Output,输入/输出)即数据的读取(接收)或写入(发送)操作,通常用户进程中的一个完整IO分为两阶段:用户进程空间<-->内核空间、内核空间<-->设备空间(磁盘、网络等)。IO有内存IO、网络IO和磁盘IO三种,通常我们说的IO指的是后两者。

LINUX中进程无法直接操作I/O设备,其必须通过系统调用请求kernel来协助完成I/O动作;内核会为每个I/O设备维护一个缓冲区。

对于一个输入操作来说,进程IO系统调用后,内核会先看缓冲区中有没有相应的缓存数据,没有的话再到设备中读取,因为设备IO一般速度较慢,需要等待;内核缓冲区有数据则直接复制到进程空间。

所以,对于一个网络输入操作通常包括两个不同阶段:

(1)等待网络数据到达网卡→读取到内核缓冲区,数据准备好;

(2)从内核缓冲区复制数据到进程空间。


操作系统中的io设备管理(浅谈操作系统IO模式)(2)

关键概念理解

同步才有阻塞和非阻塞之分;
阻塞与非阻塞关乎如何对待事情产生的结果(阻塞:不等到想要的结果我就不走了)

明确进程状态

理解进程的状态转换

操作系统中的io设备管理(浅谈操作系统IO模式)(3)

从操作系统层面执行应用程序理解 IO 模型

阻塞IO模型:

操作系统中的io设备管理(浅谈操作系统IO模式)(4)

当调用recv()函数时,系统首先查是否有准备好的数据。如果数据没有准备好,那么系统就处于等待状态。当数据准备好后,将数据从系统缓冲区复制到用户空间,然后该函数返回。在套接应用程序中,当调用recv()函数时,未必用户空间就已经存在数据,那么此时recv()函数就会处于等待状态。
阻塞模式给网络编程带来了一个很大的问题,如在调用 send()的同时,线程将被阻塞,在此期间,线程将无法执行任何运算或响应任何的网络请求。这给多客户机、多业务逻辑的网络编程带来了挑战。这时,我们可能会选择多线程的方式来解决这个问题。
应对多客户机的网络应用,最简单的解决方式是在服务器端使用多线程(或多进程)。多线程(或多进程)的目的是让每个连接都拥有独立的线程(或进程),这样任何一个连接的阻塞都不会影响其他的连接。
具体使用多进程还是多线程,并没有一个特定的模式。传统意义上,进程的开销要远远大于线程,所以,如果需要同时为较多的客户机提供服务,则不推荐使用多进程;如果单个服务执行体需要消耗较多的 CPU 资源,譬如需要进行大规模或长时间的数据运算或文件访问,则进程较为安全。

非阻塞IO模型

操作系统中的io设备管理(浅谈操作系统IO模式)(5)

IO复用模型:

操作系统中的io设备管理(浅谈操作系统IO模式)(6)

当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。
所以,I/O 多路复用的特点是通过一种机制一个进程能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入读就绪状态,select()函数就可以返回。

异步IO模型

操作系统中的io设备管理(浅谈操作系统IO模式)(7)


区别IO多路复用中的select poll epoll

select

int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); select 函数监视的文件描述符分3类,分别是writefds、readfds、和exceptfds。调用后select函数会阻塞,直到有描述符就绪(有数据 可读、可写、或者有except),或者超时(timeout指定等待时间,如果立即返回设为null即可),函数返回。当select函数返回后,可以 通过遍历fdset,来找到就绪的描述符

poll

int poll (struct pollfd *fds, unsigned int nfds, int timeout); 不同与select使用三个位图来表示三个fdset的方式,poll使用一个 pollfd的指针实现。pollfd并没有最大数量限制(但是数量过大后性能也是会下降)。 和select函数一样,poll返回后,需要轮询pollfd来获取就绪的描述符。

epoll

epoll是通过事件的就绪通知方式,调用epoll_create创建实例,调用epoll_ctl添加或删除监控的文件描述符,调用epoll_wait阻塞住,直到有就绪的文件描述符,通过epoll_event参数返回就绪状态的文件描述符和事件。

epoll操作过程需要三个接口,分别如下: int epoll_create(int size);//创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大 生成一个 epoll 专用的文件描述符,其实是申请一个内核空间,用来存放想关注的 socket fd 上是否发生以及发生了什么事件。

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
控制某个 epoll 文件描述符上的事件:注册、修改、删除。其中参数 epfd 是 epoll_create() 创建 epoll 专用的文件描述符。

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
等待 I/O 事件的发生;返回发生事件数。参数说明:
epfd: 由 epoll_create() 生成的 Epoll 专用的文件描述符;
epoll_event: 用于回传代处理事件的数组;
maxevents: 每次能处理的事件数;
timeout: 等待 I/O 事件发生的超时值;

区别总结

(1)select,poll实现需要自己不断轮询所有fd集合,直到设备就绪,期间可能要睡眠和唤醒多次交替。而epoll其实也需要调用epoll_wait不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但是它是设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在epoll_wait中进入睡眠的进程。虽然都要睡眠和交替,但是select和poll在“醒着”的时候要遍历整个fd集合,而epoll在“醒着”的时候只要判断一下就绪链表是否为空就行了,这节省了大量的CPU时间。这就是回调机制带来的性能提升。
(2)select,poll每次调用都要把fd集合从用户态往内核态拷贝一次,epoll 通过 mmap 把内核空间和用户空间映射到同一块内存,省去了拷贝的操作。

应用举例

tornado 的 IOLoop 模块 是异步机制的核心,它包含了一系列已经打开的文件描述符和每个描述符的处理器 (handlers)。这些 handlers 就是对 select, poll , epoll等的封装。(所以本质上说是 IO 复用)

Linux后端服务器开发要学关于IO的哪些知识点

网络IO是网络通信的血管,数据是血液。血液的流动是不能离开血管的。

源码实现

服务器IO核心— epoll编程实战

客户端多网络连接机制poll

文件IO管理select实战

框架实战

高性能的时间循环 libev

跨平台异步I/O libuv

跨平台的C 库 Boost.Asio

事件通知库 libevent

理论详解

阻塞型 BIO

异步IO AIO

非阻塞型IO NIO

更多linux知识技术点,后台私信:1,领取学习资料

资源列表:
1:linux零基础入门
2:linux服务器开发
3:架构师开发
4:linux内核
5:webrtc入门到精通
6:音视频FFmpeg
7:神器Git入门到精通

操作系统中的io设备管理(浅谈操作系统IO模式)(8)

专注于服务器后台开发,知识点包括C/C ,Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等等

,