Linux中的I/O操作

缓冲与非缓冲 I/O

  • 缓冲 I/O,利用的是标准库的缓存实现文件的加速访问,而标准库再通过系统调用访问文件。

  • 非缓冲 I/O,直接通过系统调用访问文件,不经过标准库缓存。

程序在读写操作时系统并不会立刻返回数据或者处理数据,系统会监听操作并存储在缓冲区,例如C中的 Scanf 操作,只有在用户敲下了回车时才能真正将输入的字符写入,而输入的过程中可能有删除有追加,他们都是存储在缓冲区的

而非缓冲则是使程序直接访问文件系统,直接读取文件内容

直接与非直接I/O

「是否利用操作系统的缓存」,可以把文件 I/O 分为直接 I/O 与非直接 I/O

在操作文件时 可以使用参数O_DIRECT来指定是否需要直接I/O,如果有此参数则说明不需要走操作系统缓存IO

他与缓冲I/O不同在于,一个是操作系统缓存,一个是标准库缓存

例如在Go语言中,利用Go的StringBuffer,可以先将待写入的文件存入系统缓存中,待到调用 write 时才真正的写入到文件中

阻塞I/O与异步I/O

用户在发起I/O请求时,内核态会阻塞该线程,直到内核态准备好数据将数据移到程序的缓冲区阻塞才会解除

ojr1Fe.png
ojr1Fe.png

而非阻塞I/O是在请求后立即返回,如果没有得到结果则反复请求的过程

ojsFnP.png
ojsFnP.png

如果设置了 O_NONBLOCK 标志,那么就表示使用的是非阻塞 I/O 的 方式访问,而不做任何设置的话,默认是阻塞 I/O。

这种I/O轮询的方式会拖慢整个线程的速度,因此I/O多路复用就出现了

而I/O多路复用也是基于非阻塞I/O的衍生,实际上IO多路复用也是从内核态阻塞线程变更到内核态阻塞,当数据准备好后再通知线程,这样做的好处是可以将CPU的利用率达到更高

而实际上上面所说的阻塞 非阻塞 基于非阻塞的I/O多路复用实际上本质都是同步I/O

而异步I/O则是在内核态阻塞后立即返回,当数据处理好后再通知线程

文件

DMA

DMA技术没有应用时,I/O的过程是这样的

ovkTdH.png
ovkTdH.png

当接收到程序发送的 I/O 请求时,CPU会直接阻塞住,处理数据,等待I/O处理完成后才能继续执行任务

DMA则是在这之中起到了一个中间价的作用,当CPU接收到请求时,会把任务扔给DMA就去做别的事去了,DMA将处理请求给磁盘,磁盘将缓冲区数据拷贝到内核缓冲区,当DMA读取到了足够的数据会给CPU发送中断信号,CPU接收到信号后将内核缓冲区数据返回给用户态,返回数据

文件传输

socket传输

Socket是一种可以远程可以本地传输的套接字,可以理解为一个接口,他的应用场景非常多,可以连接应用程序,可以连接网络层,websocket也正是借鉴了socket的特性开发出的一种协议

实际应用中,我们也是使用socket作为文件传输工具,也可以说,很多I/O操作也都是使用了Socket来操作的,而我们文件传输则需要socket来作为连接网卡的接口来使用

mmap

文件拷贝时,相当于 先 readwrite

应用发起read操作时,应用调用 mmap() 可以将内核缓冲区的数据映射到用户缓冲区里面

当应用调用 mmap()时,DMA会将磁盘的数据拷贝到内核缓冲区,然后将应用进程及内核共享此内容,然后调用write,操作系统将内核缓冲区的内容拷贝到Socket缓冲区,最后把Socket缓冲区的内容拷贝到网卡缓冲区,整个过程都是由DMA操作的

sendfile(零拷贝)

在 Linux 内核版本 2.1 中,提供了一个专⻔发送文件的系统调用函数 sendfile() ,函数形式如下

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

它的前两个参数分别是目的端和源端的文件描述符,后面两个参数是源端的偏移量和复制数据的⻓度,返回值是实际复制数据的⻓度,通过sendfile调用来发送数据,则可以替代 read + write 的操作,这样就只调用一次文件拷贝,减少了上下文开销且该方法类似mmap可以直接跳过用户态将内核态数据复制到 socket缓冲区

而网卡如果支持SG-DMA技术的话则可以在此基础上更近一步,跳过socket缓冲区直接将数据发送到网卡缓冲区

通过以下命令查看是否支持

1
ethtool -k eth0 | grep scatter-gather

在 Linux 2.4之后的版本,通过调用sendfile()来实现复制,如果网卡支持SG-DMA则复制文件则会产生如下操作

第一步,通过 DMA 将磁盘上的数据拷⻉到内核缓冲区里;

第二步,缓冲区描述符和数据⻓度传到 socket 缓冲区,这样网卡的 SG-DMA 控制器就可以直接将内 核缓存中的数据拷⻉到网卡的缓冲区里,此过程不需要将数据从操作系统内核缓冲区拷⻉到 socket 缓冲区中,这样就减少了一次数据拷⻉;

ovu7GQ.png
ovu7GQ.png

这就是所谓的零拷⻉(Zero-copy)技术,因为我们没有在内存层面去拷⻉数据,也就是说全程没有通过 CPU 来搬运数据,所有的数据都是通过 DMA 来进行传输的,总体来看,零拷⻉技术可以把文件传输的性能提高至少一倍以上

类似零拷贝技术的应用场景有很多,例如 :

kafka内部使用的transferTo()实际上就是调用 sendfile

nginx通过配置可以选择是否开启sendfile

1
2
3
4
5
http { 
...
sendfile on
...
}

大文件传输

大文件传输很少使用同步传输,由于文件的不可确定性,小文件更适合使用同步传输,大文件由于占用时间过长,如果一直等待的情况下,会让CPU一直阻塞,因此大文件通常使用 异步 I/O + 直接 I/O来替代零拷贝技术

nginx中则可以使用 如下配置

1
2
3
4
5
6

location /sbcoder.cn/ {
sendfile on;
aio on;
directio 1024m;
}