IO模型

用户空间和内核空间

一个进程包含用户空间和内核空间,应用程序运行在用户空间,内核空间负责对系统资源的操作,比如:文件的管理,进程的通信,内存的管理.

用户空间通过调用内核空间,来实现系统资源的调用.

IO场景

  1. 网络传输IO

  2. 磁盘读写IO

用户空间发起IO调用后,比如read(),send(),后续的执行都是由内核空间完成的.内核空间主要会执行2步:

  1. 等待IO设备准备好数据.

  2. 系统将数据从内核空间拷贝到用户空间.

针对这2步,linux系统和应用程序都做了增加各种优化,来进行提示系统的吞吐量.咱们来逐个分析:

首先,用户空间在调起系统函数后,针对CPU的是否释放,引出了几种IO模型.

IO模型

同步堵塞IO,同步非堵塞IO,IO多路复用,信号驱动IO,异步IO

Java中的BIO

可以看到,一个线程在调用read后,就要一直等待数据的准备就绪.这种方式无法支持百万连接.

Java 中的NIO

java在1.4版本引入了nio包,提供了channel,selector,Buffer等函数来支持nio或者io多路复用.

一直通过轮询避免堵塞的方式.为了改善线程堵塞,在发起read调用后,立马是否CPU时间片.但是对于数据是否准备好,需要用户不断的是进行轮询.不过轮询也是会消耗一定的CPU资源.

IO多路复用

java的nio包中的selector选择器,在windows上会使用select函数,在linux上使用epoll函数.

java中的selector,nginx的epoll 都提供了多路复用的支持.

select和epoll支持监听多个read的调用,因此只需要一个线程就可以监听多个IO.

相比于轮询有了较大的改善.

零拷贝

对于将数据从内核空间复制到用户空间,linux使用零拷贝技术将其优化.

什么是零拷贝技术?

简单来说,就是通过一些技术,去除用户空间与内核空间的数据复制动作,提升数据传输效率的一种手段.

来看一下从磁盘读取数据,进行网络传输的一种流程:

可以看出,传统的数据传输,数据传输需要经过4次用户态和核心态上下文的切换.

我们只是搬运了一次数据,在系统中却进行了4次cooy.

减少上下文切换和数据的复制,是提升数据传输的关键.

sendfile

linux提供了专门发送文件的系统调用函数,sendfile

#include <sys/socket.h> ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);

它的前两个参数分别是目的端和源端的文件描述符,后面两个参数是源端的偏移量和复制数据的长度,返回值是实际复制数据的长度.

将内核缓存的数据直接发送到socket缓存中,只用了2次数据复制和2个用户态和核心态切换.

使用场景

  • jdk中的nio包下面的transferTo方法,如果linux支持sendfile函数,则会使用零拷贝.

  • nginx的sendfile on 表示使用零拷贝技术,off 表示使用传统的read和write.

内核态的缓存

前文中的内核态缓存,称作pagecache,是磁盘高速缓存.

缓存最新数据

通常刚访问的数据,再次被访问的概率很高,因此将这些数据存在pageCache中,后续再使用的时候,先去查询pageCache.同时,避免pageCache数据过多,也会对过期数据进行删除.

预读机制

读取磁盘数据的时候,需要首先找到磁盘的位置,但是对于机械磁盘来说,就是通过磁头旋转到数据所在的扇区,再开始「顺序」读取数据,但是旋转磁头这个物理动作是非常耗时的,为了降低它的影响,PageCache 使用了预读功能.

当然,pageCache也只用作小数据的缓存,对于GB级别的数据,则无法使用零拷贝技术.

Last updated

Was this helpful?