目录
一、基本介绍
二、使用方法与Demo
三、O_DIRECT 与 O_SYNC
四、Direct IO 与 缓存 IO 写性能 对比
如上图所示,普通的 IO 读写,会先将内容保存在缓冲区中,文件落盘需要调用 fflush 、fsync 等方法。
而 DirectIO 是无缓冲 IO,,使用无缓冲 IO 对文件进行读写,不会经过 OS Cache,数据直接落盘。
大多数使用场景中,使用缓冲区可以达到预读取、批量刷盘等目的,可以提高一部分性能。但对于一些应用场景,例如数据库,他们内部设计了一套自己的缓存机制,如果不使用 Direct IO 会存在双重缓存机制,影响性能。而且,数据库的缓存机制更适用于数据库本身。
下面介绍如何使用 Direct IO
/*
打开文件并返回文件句柄。
flags 必须包含 O_RDONLY(只读), O_WRONLY(只写), O_RDWR(读写) 中的一种。
除此之外还有如下 flag:
O_APPEND : 追加写入,但在 NFS 场景下如果多进程写入文件,会造成文件损毁(NFS不支持追加写入,内核必须模拟)。
O_ASYNC、O_CLOEXEC、O_CREAT、O_DIRECTORY、O_EXCL、O_LARGEFILE、O_NOATIME、O_NOCTTY、O_NOFOLLOW、O_NONBLOCK、O_NDELAY、O_PATH、O_TRUNC
O_DIRECT : 绕过系统缓冲区直接读写磁盘文件,大多数情况下会降低性能,但对于应用自身携带缓存机制的情况下很有用。但需要注意,O_DIRECT 不等同于 O_SYNC。
O_SYNC : 同步 IO,write 操作在物理落盘之前会一直阻塞。
*/
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
在使用 Direct IO 编码过程中,读写的 buff 需要进行内存对齐,所以不能使用 malloc 进行内存随机分配,需要使用 memalign 、aligned_alloc、 valloc 等函数指定分配的内存起始地址。
#include
#include
/*
在内存中分配 size 长度的空间,该空间的起始位置为 alignment 的整数倍(必须是2的幂)
*/
void *memalign(size_t alignment, size_t size);
/*
与 memalign 类似,唯一的不同在于 aligned_alloc 的 size 也必须是 alignment 的整数倍
*/
void *aligned_alloc(size_t alignment, size_t size);
/*
与 memalign 类似,valloc 分配的内存起始默认为 页 的整数倍,等同于 memalign(sysconf(_SC_PAGESIZE),size)
*/
void * valloc (size_t size)
在 NFS 场景下,一些特殊配置的内核可能不支持使用 Direct IO。NFS 场景下只有 Client 会越过系统缓存,Server端不会进行改变。客户端会要求服务器 I/O 同步,以保持 O_DIRECT 的同步语义。在这些情况下,某些服务器的性能会很差,尤其是在 I/O 很小的情况下。Linux NFS 客户端对 O_DIRECT I/O 没有对齐限制。
下面是一个简单的使用 Demo,读取裸设备中的数据并写入指定的文件中。
#define _GNU_SOURCE
#include
#include
#include
#include
#include
#include
#include
#include
#define SECTOR_SIZE 512
int main(int argc, char *argv[]) {
int offset = 0;
int length = 5;
int rc = -1;
char *sector = aligned_alloc(SECTOR_SIZE, SECTOR_SIZE);
memset(sector, 0, SECTOR_SIZE);
int fd = open("/dev/sda1", O_RDWR | O_DIRECT);
FILE *fp = fopen("/home/tp/test.txt","w");
lseek(fd, offset, SEEK_SET);
for (int i = 0; i < length; i++) {
rc = read(fd, sector, SECTOR_SIZE);
if (rc < 0)
printf("sector read error at offset = %d + %d %s", offset, i, strerror(errno));
printf("Sector: %d", i);
fwrite(sector, 1, SECTOR_SIZE, fp);
}
free(sector);
close(fd);
fclose(fp);
}
O_DIRECT 上一章节已经介绍完成,下面简单介绍 O_SYNC。
O_SYNC 表示打开同步 IO,在进行 write 时,在数据刷入磁盘之前会一直阻塞,O_SYNC的同步是通过 fsync 函数实现的。使用 O_SYNC 后会浪费系统资源,会搜索缓存池内的脏页,强制将所有的脏页最终通过 submit_bio 提交给调度器,并等待完成。由于搜索算法的原因,有可能还会导致多余的IO操作。
而 Direct IO 绕过内存直接将数据写入磁盘,但其只保证所有的 IO 下发到磁盘设备并完成,至于磁盘设备本身是否具备缓存等机制是无法感知的,其实对于 O_SYNC 来说也同样如此。
还有一点就是 Direct IO针对读写两个操作,SYNC 只针对写操作。
直接IO和带缓存的IO在写性能方面有很大的区别:
直接IO是指数据直接从应用程序的缓存区写入到磁盘上,没有经过内核缓存的过程。这种方式可以减少内核缓存的开销,但是需要频繁的访问磁盘,因此会导致更多的磁盘寻道和旋转延迟,从而降低写入性能。
带缓存的IO是指数据先写入内核缓存,然后再由内核缓存写入磁盘。这种方式可以减少频繁访问磁盘的次数,从而提高写入性能。但是带缓存的IO也会带来一些额外的开销,如缓存管理和同步等,可能会影响性能。
总体来说,直接IO适用于需要快速写入大量数据的场景,如数据库的日志写入。而带缓存的IO适用于需要频繁读写小量数据的场景,如文件的读写操作。