【架构—基本功】操做系统

缓冲I/O和直接I/O

下表列出了缓冲I/O和直接I/O的API接口列表,缓冲I/O是C语言提供的库函数,均以f打头;直接I/O是Linux的系统API,也是C语言编写的,但在原理上二者差别很大。缓存

类型 对应API接口
缓冲I/O fopen,fclose,fseek,fflush,fread,fwrite,fprintf,fscanf...
直接I/O open,close,lseek,fsync,read,write,pread,pwrite...

在阐述两者的原理对比以前,先解释几个关键概念:网络

  • 应用程序内存:是一般写代码用malloc、new等分配出来的内存。
  • 用户缓冲区:C语言的FILE结构体里面的buffer。
  • 内核缓冲区:Linux操做系统的Page Cache。为了加快磁盘的I/O,Linux系统会把磁盘上的数据以Page为单位缓存在操做系统的内存里。

对于缓冲I/O,每一个读写操做会有3次数据拷贝。例如读:磁盘->内核缓冲区->用户缓冲区->应用程序内存。
对于直接I/O,每一个读写操做会有2次数据拷贝,跳过了用户级的缓冲。多线程

关于缓冲I/O和直接I/O,有几点须要特别说明:并发

  • fflush和fsync的区别。fflush只是把数据从用户缓冲区刷到内核缓冲区而已,fsync则是把数据从内核缓冲刷到磁盘里。这意味着不管缓冲I/O,仍是直接I/O,若是在写数据以后不调用fsync,此时系统断点重启,最新的部分数据会丢失。
  • 对于直接I/O,也有read/write和pread/pwrite两组不一样的API。pread/pwrite在多线程读写同一个文件时颇有用。

内存映射文件与零拷贝

1.内存映射文件

用户空间不分配物理内存,直接将应用程序的逻辑内存地址映射到Linux操做系统的内核缓冲区,应用程序读写的时候实际读写的是内核缓冲区。此时数据的拷贝次数减小为了1次。socket

2.零拷贝

当用户须要把文件中的数据发送到网络的时候,使用直接I/O的话会有4次数据拷贝,读进来2次,写回去2次。使用内存映射文件的话会有3次数据拷贝,再也不通过用户程序内存,直接在内核空间中从内核缓冲区拷贝到socket缓冲区。
但若是使用零拷贝,在内核缓冲区和socket缓冲区之间不会使用数据拷贝,只是一个地址的映射,底层的网卡驱动程序要读取数据并发送到网络的时候,看似读的是socket缓冲区的数据,实际上直接读的是内核缓冲区中的数据。
在Linux系统中,零拷贝的系统API为:函数

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

网络I/O模型