eMMC&Android&C&Linux

问题:
Open读取文件,有设置O_DIRECT,但是实际Read的时候,仍然使用了PageCache,而不是直接操作磁盘,这是为何?

背景:
1: 题主用的是安卓手机,采用eMMC内存,我想对手机的eMMC内存进行压力测试(一直读写数据)
2:eMMC内存有两种模式:Normal模式和Command Queue模式,
3:题主对Normal模式的eMMC内存可以实现直接访问磁盘,对Command Queue模式的eMMC内存,无法直接从磁盘读取数据(采用了PageCache)
4:明明有设置O_DIRECT,Normal模式有直接读取磁盘的信息,而Command Queue模式却不会直接访问磁盘,先访问PageCache

代码
fileReadHandle = open(filePath, O_RDONLY | O_DIRECT | O_NOCTTY, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);//获取文件句柄
readLength = read(fileReadHandle,jbArray,length);//Read操作,长度为1024 * 1024 = 1MB

我想要的结果:
如何让我的Read操作直接去访问磁盘设备?为何我的O_DIRECT会失效?

O_DIRECT 标志用于绕过页面缓存并直接访问底层存储设备。然而,在您的情况下,可能有几个原因导致它无法正常工作。

一个原因可能是您的文件系统不支持直接 I/O。一些文件系统(如 ext4)不支持 O_DIRECT,并将忽略该标志。

另一个原因可能是文件系统或设备驱动程序不支持 O_DIRECT 的对齐要求。O_DIRECT 要求传递给 read 或 write 系统调用的缓冲区与存储设备的物理块大小对齐。如果缓冲区不对齐,驱动程序可能会回退到使用页面缓存。

最后,该问题也可能由您的安卓设备使用的特定 eMMC 内存控制器引起。一些控制器可能不支持直接 I/O,或者可能有不同的对齐要求。

可能是你的设备的 eMMC 控制器使用了 Command Queue 模式,该模式可能不支持 O_DIRECT。在这种模式下,eMMC 内存控制器使用队列来调度命令并管理存储设备的访问。该模式可能不支持直接 I/O,因此 O_DIRECT 标志可能会被忽略。

为了确认您的设备的 eMMC 控制器是否支持 O_

首先要确定的是,O_DIRECT是在linux系统中定义的一个文件操作标志,用于告诉内核不要使用page cache来缓存读写操作。然而,并不是所有的文件系统都支持O_DIRECT标志。在某些文件系统中,O_DIRECT标志被忽略,或者只有在文件打开时才有效。另外,在某些文件系统中,O_DIRECT标志只对读操作有效,对写操作无效。

在安卓系统中,内核是基于linux内核的,所以O_DIRECT标志也是可用的。然而,你提到的eMMC内存有两种模式:Normal模式和Command Queue模式,这两种模式对于O_DIRECT标志的支持是不同的。对于Normal模式的eMMC内存,O_DIRECT标志是有效的,可以直接访问磁盘。而对于Command Queue模式的eMMC内存,O_DIRECT标志是无效的,无法直接从磁盘读取数据。

如果你想要在Command Queue模式的eMMC内存上直接访问磁盘,那么你需要查找其它的解决方案。比如,可以使用IOCTL系统调用来控制eMMC内存的读写操作,或者使用特定的驱动来实现磁盘直接访问。

O_DIRECT选项是指在读写文件时避免使用缓存。但是,这并不意味着操作系统不会使用缓存。在某些情况下,操作系统仍然会使用缓存,即使O_DIRECT标志被设置。

在您的情况下,您使用的是安卓手机,其中eMMC内存有两种模式:Normal模式和Command Queue模式。Normal模式允许直接访问磁盘,而Command Queue模式使用了PageCache,这可能是导致O_DIRECT失效的原因。

为了确保读取操作直接访问磁盘,您可以尝试使用其他方法,如使用底层的磁盘访问API或使用专用的压力测试工具来读写磁盘。

O_DIRECT 标志表示请求系统直接访问磁盘而不经过页面缓存。但是,并不是所有文件系统和驱动程序都支持 O_DIRECT 标志。如果文件系统或驱动程序不支持 O_DIRECT 标志,那么 read() 函数可能会忽略 O_DIRECT 标志并使用页面缓存。

另外,在某些情况下,使用 O_DIRECT 标志可能会降低性能,因为它会增加程序的内存使用量和磁盘 I/O 开销。

Linux 中读取磁盘设备的示例代码如下:

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char* argv[]) {
    // 打开磁盘设备
    int fd = open("/dev/sda", O_RDONLY);
    if (fd == -1) {
        perror("open");
        exit(1);
    }

    // 定义缓存区
    char buf[512];

    // 读取数据
    ssize_t n = read(fd, buf, sizeof(buf));
    if (n == -1) {
        perror("read");
        exit(1);
    }

    // 打印读取的数据
    printf("Read %ld bytes:\n", n);
    for (int i = 0; i < n; i++) {
        printf("%02x ", buf[i]);
    }
    printf("\n");

    // 关闭文件
    close(fd);

    return 0;
}

以上代码用于读取磁盘设备的前 512 字节的数据,并将其打印出来。请注意,这段代码仅作为示例,不建议在生产环境中使用,因为直接读取磁盘设备可能会导致数据丢失。

我们可以使用 C++ 的 Win32 API 函数 ReadFile() 来实现 windows 的 Read 操作。

#include <Windows.h>

int main() {
    HANDLE hDevice = CreateFile("\\\\.\\PhysicalDrive0", GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
    if (hDevice == INVALID_HANDLE_VALUE) {
        printf("CreateFile failed, error = %d\n", GetLastError());
        return 1;
    }

    char buffer[512];
    DWORD bytesRead;
    if (!ReadFile(hDevice, buffer, sizeof(buffer), &bytesRead, NULL)) {
        printf("ReadFile failed, error = %d\n", GetLastError());
        CloseHandle(hDevice);
        return 1;
    }

    printf("Read %d bytes:\n", bytesRead);
    for (int i = 0; i < bytesRead; i++) {
        printf("%02x ", buffer[i]);
    }
    printf("\n");

    CloseHandle(hDevice);
    return 0;
}

这段代码中,我们首先使用 CreateFile() 函数打开磁盘设备 "\.\PhysicalDrive0",该函数返回一个句柄。我们可以使用该句柄作为参数传递给 ReadFile() 函数去读取磁盘上的数据,最后使用 CloseHandle() 函数关闭句柄。

注意:读取磁盘数据时需要管理员权限。

O_DIRECT 选项的行为取决于操作系统的实现,并不一定能保证直接访问磁盘,有可能仍然通过 PageCache。如果您希望确保读取操作直接访问磁盘,您可以试试其他方法,例如使用不同的 I/O 函数或创建基于 Raw 设备的文件系统。请注意,直接访问磁盘可能会影响性能,并且不能使用缓存等优化来提高性能。

O_DIRECT标志可能不起作用的原因是,O_DIRECT标志的行为取决于底层文件系统和硬件。 O_DIRECT标志不保证在所有系统上都可以工作,并且在不同的文件系统或硬件上可能有不同的行为。 可能的是,Android文件系统不支持O_DIRECT标志,或者您的手机上的eMMC内存对O_DIRECT标志有不同的实现。 您可以尝试搜索特定于您的设备和文件系统的信息,以了解在eMMC存储器上使用O_DIRECT的指导。

找到解决方法了,这是文件分区的文件系统导致的。我写入的路径一直使用的是storage/emulated/0。我的Normal模式的手机中,这个路径的文件分区是sdcardfs,而CommandQ模式的手机中,是fuse文件系统,正是这个fuse文件系统让我的O_DIRECT字段失效。但是很奇怪,fuse系统是支持0_DIRECT的,只是设置的地方不同,但我找不到设置的地方。最终我的解决方法是:修改路径到了app的data区中,我这边几台测试设备的data区都不是fuse文件系统,我发现androidbench的文件路径也是data区。所以,暂时的解决方法就是如此,后续我再继续深入研究