Linux中直接I/O原理

在介绍直接 I/O 之前,先来介绍下直接I/O这种机制产生的原因 。毕竟已经有了缓存I/O(Buffered I/O),那肯定能够像到缓存I/O有缺陷吧,就按照这个思路来 。

Linux中直接I/O原理

文章插图
 
 
什么是缓存 I/O (Buffered I/O) 
缓存 I/O 又被称作标准 I/O,大多数文件系统的默认 I/O 操作都是缓存 I/O 。在 linux 的缓存 I/O 机制中,操作系统会将 I/O 的数据缓存在文件系统的页缓存( page cache )中,也就是说,数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间 。写的过程就是数据流反方向 。缓存 I/O 有以下这些优点:
  1. 缓存 I/O 使用了操作系统内核缓冲区,在一定程度上分离了应用程序空间和实际的物理设备 。
  2. 缓存 I/O 可以减少读盘的次数,从而提高性能 。
【Linux中直接I/O原理】对于读操作:当应用程序要去读取某块数据的时候,如果这块数据已经在页缓存中,那就返回之 。而不需要经过硬盘的读取操作了 。如果这块数据不在页缓存中,就需要从硬盘中读取数据到页缓存 。
对于写操作:应用程序会将数据先写到页缓存中,数据是否会被立即写到磁盘,这取决于所采用的写操作机制:
  • 同步机制,数据会立即被写到磁盘中,直到数据写完,写接口才返回;
  • 延迟机制:写接口立即返回,操作系统会定期地将页缓存中的数据刷到硬盘 。所以这个机制会存在丢失数据的风险 。想象下写接口返回的时候,页缓存的数据还没刷到硬盘,正好断电 。对于应用程序来说,认为数据已经在硬盘中 。
 
Linux中直接I/O原理

文章插图
缓存I/O的写操作
 
缓存 I/O 的缺点在缓存I/O的机制中,以写操作为例,数据先从用户态拷贝到内核态中的页缓存中,然后又会从页缓存中写到磁盘中,这些拷贝操作带来的CPU以及内存的开销是非常大的 。
对于某些特殊的应用程序来说,能够绕开内核缓冲区能够获取更好的性能,这就是直接I/O出现的意义 。
 
Linux中直接I/O原理

文章插图
直接I/O写操作
 
 
直接I/O 介绍凡是通过直接I/O方式进行数据传输,数据直接从用户态地址空间写入到磁盘中,直接跳过内核缓冲区 。对于一些应用程序,例如:数据库 。他们更倾向于自己的缓存机制,这样可以提供更好的缓冲机制提高数据库的读写性能 。直接I/O写操作如上图所示 。
直接I/O 设计与实现要在块设备中执行直接 I/O,进程必须在打开文件的时候设置对文件的访问模式为 O_DIRECT,这样就等于告诉操作系统进程在接下来使用 read() 或者 write() 系统调用去读写文件的时候使用的是直接 I/O 方式,所传输的数据均不经过操作系统内核缓存空间 。使用直接 I/O 读写数据必须要注意缓冲区对齐( buffer alignment )以及缓冲区的大小的问题,即对应 read() 以及 write() 系统调用的第二个和第三个参数 。这里边说的对齐指的是文件系统块大小的对齐,缓冲区的大小也必须是该块大小的整数倍 。
下面主要介绍三个函数:open(),read() 以及 write() 。Linux 中访问文件具有多样性,所以这三个函数对于处理不同的文件访问方式定义了不同的处理方法,本文主要介绍其与直接 I/O 方式相关的函数与功能.首先,先来看 open() 系统调用,其函数原型如下所示:
int open(const char *pathname, int oflag, … /*, mode_t mode * / ) ; 
当应用程序需要直接访问文件而不经过操作系统页高速缓冲存储器的时候,它打开文件的时候需要指定 O_DIRECT 标识符 。
操作系统内核中处理 open() 系统调用的内核函数是 sys_open(),sys_open() 会调用 do_sys_open() 去处理主要的打开操作 。它主要做了三件事情:
  1. 调用 getname() 从进程地址空间中读取文件的路径名;
  2. do_sys_open() 调用 get_unused_fd() 从进程的文件表中找到一个空闲的文件表指针,相应的新文件描述符就存放在本地变量 fd 中;
  3. 函数 do_filp_open() 会根据传入的参数去执行相应的打开操作 。
下面列出了操作系统内核中处理 open() 系统调用的一个主要函数关系图 。
sys_open()|-----do_sys_open()|---------getname()|---------get_unused_fd()|---------do_filp_open()|--------nameidata_to_filp()|----------__dentry_open()


推荐阅读