O_DIRECT + lseek(), read() 报错
问题描述
使用lseek()函数设置所需要的偏移量,使用read() O_DIRECT之后,read返回值是-1。
解决方案
O_DIRECT之后,读取就只能是以页大小(我这里页大小是4K)为单位读取,也必须以页大小对齐(因此注意设置buf为memalign方式分配内存空间)。lseek()的偏移量,也必须以4K对齐,才能运行,否则int ret = read(); 则有ret = -1。
具体背景 + 代码
程序十分简单,就是测试read 和 write使用 O_DIRECT(不经过缓冲区,直接读写磁盘)的时间。
以下C++代码粘贴到test_direct_io.cpp中,用如下命令运行:
g++ -o exec_test_direct_io test_direct_io.cpp
C++代码。
// g++ -o exec_test_direct_io test_direct_io.cpp
#include <errno.h>
#include <fcntl.h>
#include <malloc.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/io.h>
#include <sys/time.h>
#include <sys/unistd.h>
#include <iostream>
#define _GNU_SOURCE //测试宏
using namespace std;
void rawIO() {
int fd;
size_t length;
char *buf;
length = getpagesize() * 1024 * 16 * 15;
cout << "page size:" << getpagesize() << endl;
cout << "length:" << (float)length / 1024 / 1024 << "MB" << endl;
string file_path = "/data/helo.txt";
fd = open(file_path.c_str(), O_RDWR | O_DIRECT);
if (fd == -1) {
printf("failed to open file %s\n", file_path.c_str());
exit(-1);
}
//函数:void * memalign (size_t boundary, size_t size)
//函数memalign将分配一个由size指定大小,地址是boundary的倍数的内存块。参数boundary必须是2的幂!
// 函数memalign可以分配较大的内存块,并且可以为返回的地址指定粒度。
buf = (char *)memalign(getpagesize(), length);
// buf = (char *)malloc(sizeof(uint8_t) * length);
if (buf == NULL) {
printf("buf = null\n");
exit(-1);
}
// for (int i = 0; i < length; i++) {
// buf[i] = 'a';
// }
// int ret = write(fd, (void *)buf, length);
// lseek(fd, 2, SEEK_SET);
int ret = read(fd, buf, length);
if (ret == -1) {
std::cout << "write fail" << std::endl;
printf("err: %s \n", strerror(errno));
exit(0);
} else {
cout << "write over!" << endl;
}
free(buf);
}
// void buffered_IO() {}
int main() {
struct timeval t_s, t_e;
gettimeofday(&t_s, 0);
rawIO();
gettimeofday(&t_e, 0);
float total_t = 0;
total_t +=
t_e.tv_sec - t_s.tv_sec + (float)(t_e.tv_usec - t_s.tv_usec) / 1000000;
cout << "total time = " << total_t << endl;
}
其中的这个部分是用来生成数据集(并测试写入速度)的,使用的时候可以将read部分注释,这部分↓取消注释。
// for (int i = 0; i < length; i++) {
// buf[i] = 'a';
// }
// int ret = write(fd, (void *)buf, length);
其中lseek(fd, 2, SEEK_SET);报错的原因是,使用O_DIRECT后必须使用页大小(4KB)对齐,所以**“文件头 + 2”的偏移肯定不行,只有“文件头 + 4KB整数倍”的偏移才能运行**。
…被这个问题困扰了一晚上,难顶…
蔚天灿雨的Debug实录 2022-05-07 HITSZ LEVI
后记
原本主要是为了解决系统主动缓存带来的HDD速度过快的问题,后面发现使用direct_io运行速度过慢,改用运行时清除磁盘缓存,每读一个文件之前,都清理一次缓存,这样保证缓存中总有(且仅有)一个文件存在。具体内容可以看我的下一篇博客Linux用代码清理磁盘缓存(运行时清理磁盘缓存)
蔚天灿雨 HITSZ LEVI 2022-05-09