【问题标题】:Write error: Invalid argument, when file is opened with O_DIRECT写入错误:使用 O_DIRECT 打开文件时参数无效
【发布时间】:2015-12-09 15:24:42
【问题描述】:

写入带有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 不是直接的,这是我需要的)。

有人知道为什么会这样吗?如果O_DIRECT 没有在Android 中实现,那么它应该在open() 失败,而不是在write();所以我一定是做错了什么!

谢谢 -LD

【问题讨论】:

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

标签: android c android-ndk java-native-interface invalid-argument


【解决方案1】:

我解决了它(在您的指导下)-并想发布我的解决方案,以防将来有人遇到类似问题。

诀窍在于,使用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 的位创建,即blksize-1),瞧——你的缓冲区是blksize-aligned。

还请注意,您写入的数量也必须与块大小对齐(至少在我的情况下)。

-LD

【讨论】:

  • 如何使用这种方法编写任意大小的文件?尝试写入块的最后一部分并且文件被截断时,我总是得到一个无效的参数
  • 在写入最后一个块后通过截断文件以正确大小来修复它。
【解决方案2】:

在这种情况下,Calloc 不能很好地对齐内存。分配比您需要的更多的内存,并将其向上舍入到下一个 4k 左右的页面。另请阅读手册页中使用 O_DIRECT 的 open() 的以下注释。

【讨论】:

  • 您可以使用posix_memalign()valloc() 来获取页面对齐的内存。见man7.org/linux/man-pages/man3/posix_memalign.3.html
  • 我明白了。使用 mmap 分配内存也可以。
  • 关于valloc(),根据手册页:“过时的函数 valloc() 分配 size 字节并返回指向已分配内存的指针。内存地址将是页面大小的倍数。它相当于 memalign(sysconf(_SC_PAGE-SIZE),size)。"
  • 关于 posix_memalign(),根据手册页:“函数 posix_memalign() 分配 size 字节并将分配的内存地址放在 *mempr 中。分配的内存地址将是对齐的倍数,它必须是 2 的幂和 sizeof(void *) 的倍数。如果 size 为 0,则放在 *memptr 中的值要么是 NULL,要么是稍后可以成功传递给 free 的唯一指针值(3)。”但没有说明页面边界上的内存对齐。
  • 我在使用 O_DIRECT 时遇到的一个问题是,一旦分配了对齐的内存,就必须写入整个块。因此,如果您的缓冲区大小为 4096 字节,则必须写入所有 4096 字节。
猜你喜欢
  • 2015-09-21
  • 2013-05-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多