如何在嵌入式Linux中高效创建VFAT分区上的大文件

3

我正在尝试在嵌入式Linux设备上,使用“dd”命令在VFAT分区上创建一个大的空文件:

dd if=/dev/zero of=/mnt/flash/file bs=1M count=1 seek=1023

原本的意图是跳过前1023个块,仅在文件末尾写入1个块,在原生EXT3分区上应该非常快,而事实上确实如此。然而,在VFAT分区上,这个操作却变得相当缓慢,并伴随着以下信息:

lowmem_shrink:: nr_to_scan=128, gfp_mask=d0, other_free=6971, min_adj=16
// ... more `lowmem_shrink' messages

另一种尝试是使用fopen()打开VFAT分区上的文件,然后使用fseek()定位到末尾进行数据写入,但这也被证明很慢,并且会出现来自内核的相同消息。因此,基本上有没有快速的方法在VFAT分区上创建文件(而不必遍历前1023个块)?谢谢。

2
尽管可能有一些变通方法,但VFAT不支持稀疏文件。如果您想要一个特定大小的文件,则必须在磁盘上确切地存在那么多块。 - SingleNegationElimination
谢谢您的评论,但我也想知道是否存在什么解决方法... - Aufheben
不。FAT文件系统格式根本不支持稀疏文件。唯一的分配文件的方式就是完全分配它。 - ephemient
1
@TokenMacGuy:你的评论应该是一个答案,而不是评论。 - Jim Garrison
解决方法是在后台执行操作,而不是等待它完成。理论上,可以更改VFAT驱动程序以使用任务处理程序,并“欺骗”用户空间应用程序,即在中间的零填充完成之前声明写入完成。我不知道是否有这样的工作。 - FrankH.
1个回答

10

为什么VFAT“跳过”写操作如此缓慢?

除非VFAT文件系统驱动程序在这方面进行了“欺骗”,否则在FAT类型的文件系统上创建大文件将始终需要很长时间。驱动程序需要遵守FAT规范,分配所有数据块并对其进行零初始化,即使您“跳过”写操作也是如此。这是由于“簇链接”FAT所做的。

这种行为的原因是FAT无法支持以下任何一种:

  • UNIX风格的文件中的“空洞”(也称为“稀疏文件”)
    这就是您在ext3上使用测试用例创建的内容 - 一个没有分配到前1GB-1MB的数据块的文件,并且实际提交、零初始化块的单个1MB块)在文件末尾。
  • NTFS样式的“有效数据长度”信息。
    在NTFS上,文件可以有未初始化的块分配给它,但是文件的元数据将保留两个大小字段 - 一个用于文件的总大小,另一个用于实际写入到它的字节数(从文件开头开始)。

如果没有支持任何一种技术的规范,则文件系统始终需要分配和填充所有“中间”数据块,即使您跳过了一定范围。

还要记住,在ext3上,您使用的技术实际上并没有分配块给文件(除了最后1MB)。如果您需要预分配块(而不仅仅是设置文件大小),则还需要在那里执行完整的写操作。

如何修改VFAT驱动程序以处理此问题?

目前,驱动程序使用Linux内核函数cont_write_begin()来启动甚至是异步写入文件;该函数如下:

/*
 * For moronic filesystems that do not allow holes in file.
 * We may have to extend the file.
 */
int cont_write_begin(struct file *file, struct address_space *mapping,
                    loff_t pos, unsigned len, unsigned flags,
                    struct page **pagep, void **fsdata,
                    get_block_t *get_block, loff_t *bytes)
{
    struct inode *inode = mapping->host;
    unsigned blocksize = 1 << inode->i_blkbits;
    unsigned zerofrom;
    int err;

    err = cont_expand_zero(file, mapping, pos, bytes);
    if (err)
            return err;

    zerofrom = *bytes & ~PAGE_CACHE_MASK;
    if (pos+len > *bytes && zerofrom & (blocksize-1)) {
            *bytes |= (blocksize-1);
            (*bytes)++;
    }

    return block_write_begin(mapping, pos, len, flags, pagep, get_block);
}

那是一种简单的策略,但也会导致页面缓存垃圾(你的日志信息是调用cont_expand_zero()的结果,它完成了所有工作,而且不是异步的)。如果文件系统将这两个操作分开——一个任务来执行“真正”的写入,另一个任务来执行零填充,它看起来会更加迅速。
在仍然使用默认的Linux文件系统实用程序接口的情况下,可以通过内部创建两个“虚拟”文件来实现这一点——一个用于待填零区域,另一个用于实际要写入的数据。只有在后台任务实际完成时,通过将其最后一个簇与“零填充文件”的第一个簇以及该文件的最后一个簇与“实际写入文件”的第一个簇相连接,才会更新真实文件的目录项和FAT簇链。为了避免页面缓存垃圾,还需要进行直接写入以进行零填充。
注意:虽然所有这些从技术上来说肯定是可行的,但问题是做出这样的改变是否值得?谁一直需要这个操作?会有什么副作用?现有的(简单)代码对于较小的跳过写入是完全可接受的,如果您创建一个1MB的文件并在末尾写入一个字节,您几乎不会注意到它的存在。只有当您需要达到FAT文件系统允许的极限文件大小时,它才会对您造成影响。
其他选择...在某些情况下,手头的任务涉及两个(或多个)步骤:
  1. 使用FAT格式对SD卡进行新的格式化
  2. 将一个或多个大文件放入其中以“预填充”该卡
  3. (与应用程序相关,可选)
    预填充文件,或
    将回环文件系统映像放入其中

我曾经处理过的一个案例中,我们折叠了前两个步骤 - 即修改mkdosfs以在创建(FAT32)文件系统时预分配/预创建文件。这相当简单,在写入FAT表时,只需创建已分配的簇链,而不是填充了“空闲”标记的簇。它还具有数据块保证是连续的优点,以防您的应用从中受益。并且您可以决定使mkdosfs 清除数据块的先前内容。如果您知道,例如,您的准备步骤之一涉及无论如何都要写入整个数据或在FAT上执行ext3-in-file(非常常见的事情-用于与Windows应用程序/ GUI交换数据的Linux设备,sd卡),则不需要清零任何内容/双重写入(一次为零,一次为其他内容)。如果您的用例符合此条件(即格式化卡是“初始化为使用”过程中有用/正常的步骤),则请尝试一下;经过适当修改的mkdosfsTomTom的dosfsutils源代码,查看mkdosfs.c搜索-N命令行选项处理的一部分。

谈到预分配,如前所述,还有posix_fallocate()。目前在Linux上使用FAT时,这将与手动dd ...基本相同,即等待零填充。但是,该函数的规范并不要求它是同步的。块分配(FAT集群链生成)必须同步完成,但VFAT磁盘上的dirent大小更新和数据块零填充可以在后台/延迟执行(即仅在低优先级后台执行或仅在明确请求通过fdsync() / sync()执行以便应用程序可以例如自己分配块,写入非零内容...)。这是技术/设计问题;我不知道是否有人已经对内核进行了修改,即使只是为了实验。


不禁想知道想要这样做的(未说明的)原因是否会被Unix文件系统上的稀疏文件特性悄悄地破坏... - Chris Stratton
对于支持稀疏文件系统的UNIX系统,没有理由_悄悄地_削弱其稀疏性。可以通过posix_fallocate()公开地完成这一点,http://www.kernel.org/doc/man-pages/online/pages/man3/posix_fallocate.3.html它恰好做了包装上所说的事情——分配块(然后读取为零,直到稍后写入)。这是一个有用的功能,例如用于初始化大型数据库表空间或任何其他后续使用的直接I/O。 - FrankH.
以下句子只适用于像ext3这样的老旧文件系统:“如果您需要预分配块(不仅是设置文件大小),则还必须在那里执行完整写入。”现代*nix文件系统使用可标记为未写入的范围,这意味着可以分配块而无需进行零初始化。(未写入的范围读取为全零,而实际上不需要从磁盘读取任何块。) - Matt Whitlock
@MattWhitlock 毫无疑问,比VFAT新得多的文件系统不需要任何“技巧”来支持“清晰初始化”(无需显式零填充)。问题和答案都高度针对VFAT。其他文件系统(任何基于extent的...在Linux上,至少是ext4、xfs、nilfs、zfs,可能还有很多其他的)将支持适当的posix_fallocate()(和/或后端系统调用——简而言之,有效地标记文件的“区域”为“读取零、写入时复制/填充”的方法)。 - FrankH.

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