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

2024-04-21

我正在尝试在嵌入式 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 个块)?

Thanks.


为什么 VFAT“跳过”写入速度如此之慢?

除非 VFAT 文件系统驱动程序在这方面进行了“欺骗”,否则在 FAT 类型文件系统上创建大文件总是需要很长时间。为了遵守 FAT 规范,驱动程序必须分配所有数据块并对它们进行零初始化,即使您“跳过”写入也是如此。这是因为 FAT 具有“簇链接”功能。

造成这种情况的原因是 FAT 无法支持以下任一功能:

  • 文件中的 UN*X 式“漏洞”(又名“稀疏文件”)
    这就是您在 ext3 上使用测试用例创建的内容 - 一个没有数据块分配给其前 1GB-1MB 的文件,最后是一个实际提交的 1MB 零初始化块。
  • NTFS 样式的“有效数据长度”信息。
    在 NTFS 上,文件可以分配未初始化的块,但文件的元数据将保留two大小字段 - 一个表示文件的总大小,另一个表示实际写入文件的字节数(从文件的开头开始)。

如果没有支持这两种技术的规范,如果跳过一个范围,文件系统将始终必须分配和清零所有“中间”数据块。

还要记住,在 ext3 上,您使用的技术实际上并不allocate文件的块(最后 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 簇链仅在后台任务实际完成后才会更新,方法是将其最后一个簇与“zerofill 文件”的第一个簇链接,并将该文件的最后一个簇与“zerofill 文件”的第一个簇链接。实际写入文件”。人们还希望进行直接写入来进行零填充,以避免破坏页面缓存。

注意:虽然这一切在技术上都是可行的,但问题是进行这样的改变有多值得?谁一直需要这个手术?会有什么副作用?
现有的(简单)代码对于较小的跳过写入来说是完全可以接受的,如果您创建一个 1MB 文件并在末尾写入一个字节,您将不会真正注意到它的存在。只有当您选择的文件大小符合 FAT 文件系统允许您执行的操作的限制时,它才会对您产生影响。

其他选项...

在某些情况下,手头的任务涉及两个(或更多)步骤:

  1. 使用 FAT 重新格式化(例如)SD 卡
  2. 将一个或多个大文件放入其中以“预填充”该卡
  3. (取决于应用程序,可选)
    预填充文件,或者
    将环回文件系统映像放入其中

我处理过的一个案例我们折叠了前两个 - 即修改mkdosfs在创建 (FAT32) 文件系统时预分配/预创建文件。这非常简单,在写入 FAT 表时,只需创建分配的簇链,而不是填充“空闲”标记的簇。它还具有保证数据块是连续的优点,以防您的应用程序从中受益。你可以决定让mkdosfs not清除数据块之前的内容。例如,如果您知道准备步骤之一涉及写入整个数据或执行 FAT 上的 ext3-in-file-on-FAT(非常常见的事情 - Linux 设备、用于与 Windows 应用程序/GUI 进行数据交换的 SD 卡),那么就不需要将任何东西清零/双重写入(一次用零,一次用其他任何东西)。如果您的用例符合此要求(即格式化卡是“初始化使用”过程的有用/正常步骤),请尝试一下;经过适当修改的mkdosfs是其一部分TomTom 的 dosfsutils 源代码,请参阅mkdosfs.c搜索-N命令行选项处理 http://www.tomtom.com/gpl/arm11/dosfstools-2.11-tt695638.tar.gz.

正如前面提到的,在谈论预分配时,还有posix_fallocate() http://pubs.opengroup.org/onlinepubs/009696799/functions/posix_fallocate.html。目前在 Linux 上使用 FAT 时,这基本上与手动操作相同dd ...,即等待填零。但函数的规范并不要求它是同步的。块分配(FAT 簇链生成)必须同步完成,但 VFAT 磁盘上的不同大小更新和数据块填零可以后台/延迟(即,要么在后台以低优先级完成,要么仅在明确指定的情况下完成)请求通过fdsync() / sync()以便该应用程序可以例如分配块,写入非零内容本身...)。但这就是技术/设计;我不知道有人做过内核修改,即使只是为了实验。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

随机推荐