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请求时,内核态会阻塞该线程,直到内核态准备好数据将数据移到程序的缓冲区阻塞才会解除
而非阻塞I/O是在请求后立即返回,如果没有得到结果则反复请求的过程
如果设置了 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的过程是这样的
当接收到程序发送的 I/O 请求时,CPU会直接阻塞住,处理数据,等待I/O处理完成后才能继续执行任务
DMA则是在这之中起到了一个中间价的作用,当CPU接收到请求时,会把任务扔给DMA就去做别的事去了,DMA将处理请求给磁盘,磁盘将缓冲区数据拷贝到内核缓冲区,当DMA读取到了足够的数据会给CPU发送中断信号,CPU接收到信号后将内核缓冲区数据返回给用户态,返回数据
文件传输
socket传输
Socket是一种可以远程可以本地传输的套接字,可以理解为一个接口,他的应用场景非常多,可以连接应用程序,可以连接网络层,websocket也正是借鉴了socket的特性开发出的一种协议
实际应用中,我们也是使用socket作为文件传输工具,也可以说,很多I/O操作也都是使用了Socket来操作的,而我们文件传输则需要socket来作为连接网卡的接口来使用
mmap
文件拷贝时,相当于 先 read
再 write
应用发起read
操作时,应用调用 mmap()
可以将内核缓冲区的数据映射到用户缓冲区里面
当应用调用 mmap()
时,DMA会将磁盘的数据拷贝到内核缓冲区,然后将应用进程及内核共享此内容,然后调用write
,操作系统将内核缓冲区的内容拷贝到Socket缓冲区,最后把Socket缓冲区的内容拷贝到网卡缓冲区,整个过程都是由DMA操作的
sendfile(零拷贝)
在 Linux 内核版本 2.1 中,提供了一个专⻔发送文件的系统调用函数 sendfile()
,函数形式如下
1 |
|
它的前两个参数分别是目的端和源端的文件描述符,后面两个参数是源端的偏移量和复制数据的⻓度,返回值是实际复制数据的⻓度,通过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 缓冲区中,这样就减少了一次数据拷⻉;
这就是所谓的零拷⻉(Zero-copy)技术,因为我们没有在内存层面去拷⻉数据,也就是说全程没有通过 CPU 来搬运数据,所有的数据都是通过 DMA 来进行传输的,总体来看,零拷⻉技术可以把文件传输的性能提高至少一倍以上
类似零拷贝技术的应用场景有很多,例如 :
kafka内部使用的transferTo()
实际上就是调用 sendfile
nginx通过配置可以选择是否开启sendfile
1 | http { |
大文件传输
大文件传输很少使用同步传输,由于文件的不可确定性,小文件更适合使用同步传输,大文件由于占用时间过长,如果一直等待的情况下,会让CPU一直阻塞,因此大文件通常使用 异步 I/O + 直接 I/O
来替代零拷贝技术
nginx中则可以使用 如下配置
1 |
|