打开文件时出现写入错误: 无效参数,使用O_DIRECT选项。

7

对我来说,使用O_DIRECT标志向文件写入非常重要。

这是我打开文件的方式:

//Open the file
int fd;
if((fd = open(inFilepath, O_WRONLY | O_CREAT |O_SYNC |O_DIRECT,S_IRUSR|S_IWUSR))<0) {
    //Error handling
    return;
}

我知道 O_DIRECT 有对齐限制。这就是为什么我使用 calloc 初始化缓冲区的原因:

char *buff = (char *) calloc((size_t) 1,sizeof(char));

if(write(fd,buff,(size_t)1)<1) {
    //Error logging
    free(buff);
    return -1;
}

我收到了“write: Invalid argument”错误。 我甚至尝试使用更极端的措施,比如memalign和posix_memalign,但是遇到了问题(memalign卡住了,而ARM处理器缺少posix_memalign)。
当我注释掉“O_DIRECT”标志时,一切都像应该的那样工作(但I/O不是直接的,这正是我需要的)。
有人能解释一下为什么会发生这种情况吗?如果Android没有实现“O_DIRECT”,那么它应该在“open()”阶段失败,而不是在“write()”阶段失败;所以我肯定是做错了什么!
谢谢 -LD

1
使用直接IO进行IO操作时,还可能存在大小限制。一个字节可能无法工作。 - Andrew Henle
1
使用 O_DIRECT 时,I/O 大小需要与磁盘文件块大小相同(通常为 4096 字节),并且在页面上对齐(地址可被 4096 整除)。您可能希望查看 setbuf() 函数作为可行的替代方案。calloc() 唯一特殊的地方是将分配的内存设置为全部 0x00。malloc、calloc 和 realloc 都返回指向内存块的指针,其地址可被底层硬件总线大小(32 或 64 位)均匀地整除。 - user3629249
@user3629249 - Linux上直接IO的限制比那复杂得多。例如,ext4文件系统似乎允许不对齐的直接IO。请参见http://lxr.free-electrons.com/source/fs/ext4/file.c#L102。我还注意到,在最近的Linux版本中放宽了页面或块大小的IO操作要求,这是有道理的,因为很少有文件恰好是这些值的倍数。在Linux上使用直接IO有点不可预测,尽管存在明显的用例(例如,流式传输大量永远不需要在任何页面缓存中的数据)。 - Andrew Henle
2个回答

11

在您的指导下,我解决了这个问题,并希望发布我的解决方案,以防未来有类似问题的人。

诀窍是使用O_DIRECT标志时,需要将内存地址和缓冲区都与文件系统的块大小对齐(至少对我而言,块大小有效;扇区大小无效)。

struct stat fstat;
stat(filepath, &fstat); 
int blksize = (int)fstat.st_blksize;
int align = blksize-1;

const char *buff = (char *) malloc((int)blksize+align);
buff = (char *)(((uintptr_t)buff+align)&~((uintptr_t)align));

if(write(fd,buff,(size_t)blksize)<1) { 
        //Error handling
        free((char *)buff);
        return -1;
}

我做了两件主要的事情:

  1. 使用 stat() 函数和访问 st_blksize 属性找到存储我的文件的块大小的文件系统。
  2. 分配比我需要的多 align 字节。然后将这些额外的 align 字节添加到指针地址中,这样掩码位于较低的块大小对齐位置就不会让我分配的内存少于我想要的数量。然后当然你需要用与掩码相与(由翻转 align 的位并减去 1 得到,即 blksize-1), 然后乘上一个值,从而使缓冲区对齐于 blksize

此外需要注意的是,在我的情况下写入的数量也必须对齐到块大小。

-LD


1
如何使用这种方法编写任意大小的文件?当尝试写入块的最后一部分并且文件被截断时,我总是收到无效参数的错误提示。 - Arne
1
在写入最后一个块后,通过截断文件到正确的大小来解决了这个问题。 - Arne

2

Calloc在这种情况下无法很好地对齐内存。分配比您需要的更多的内存,并将其舍入到下一个4k左右的页面。还要阅读open() with O_DIRECT的manpage下面的注意事项。


你可以使用 posix_memalign()valloc() 来获取页面对齐的内存。请参阅 http://man7.org/linux/man-pages/man3/posix_memalign.3.html。 - Andrew Henle
1
我明白了。使用 mmap 来分配内存也可以起作用。 - ctrl-d
关于valloc(),根据man页面的解释:“这个废弃函数valloc()分配size字节的内存,并返回指向分配内存的指针。该内存地址将是页大小的倍数。它等效于memalign(sysconf(_SC_PAGE‐SIZE),size)。” - user3629249
关于 posix_memalign(),根据 man 手册:“函数 posix_memalign() 分配 size 字节并将分配的内存地址放入 *memptr 中。分配的内存地址将是对齐的,对齐方式必须是 2 的幂且为 sizeof(void *) 的倍数。如果 size 为 0,则在 *memptr 中放置的值可以是 NULL,也可以是一个唯一的指针值,稍后可以成功地传递给 free(3)。”但没有提到在页面边界上的内存对齐。 - user3629249
使用O_DIRECT时遇到的一个问题是,一旦您分配了对齐的内存,您必须写入整个块。因此,如果您的缓冲区大小为4096字节,则必须写入所有4096字节。 - schuess

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接