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

标签 linux file-io filesystems embedded-linux

我试图通过在嵌入式Linux框中使用“dd”命令在VFAT分区上创建一个大的空文件:

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

目的是跳过前1023个块,并在文件末尾仅写入1个块,这在 native EXT3分区上应该非常快,的确如此。但是,此操作在VFAT分区上显示的速度非常慢,并显示以下消息:
lowmem_shrink:: nr_to_scan=128, gfp_mask=d0, other_free=6971, min_adj=16
// ... more `lowmem_shrink' messages

另一种尝试是先打开VFAT分区上的文件,然后再打开fseek()直到最后写入数据,事实证明这也很慢,同时还有来自内核的相同消息。

因此,基本上,有没有一种快速的方法可以在VFAT分区上创建文件(无需遍历前1023个块)?

谢谢。

最佳答案

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

除非在这方面使VFAT文件系统驱动程序“作弊”,否则在FAT类型的文件系统上创建大文件将始终花费很长时间。为了遵守FAT规范,即使您“跳过”写入操作,驱动程序也必须分配所有数据块并将其零初始化。这是因为FAT具有“集群链接”功能。

该行为的原因是FAT无法支持以下任一情况:

  • 文件中的UN * X样式“孔”(又名“稀疏文件”)
    这就是您在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文件系统实用程序接口(interface)的情况下,可以通过内部创建两个“虚拟”文件来实现此目的-一个用于待填充区域,另一个用于实际写入数据。只有在后台任务实际完成后,才将真实文件的目录条目和FAT群集链更新,方法是将其最后一个群集与“zerofill文件”的第一个链接,并将该文件的最后一个群集与“零填充文件”的第一个链接。实际写入文件”。为了避免浪费页面缓存,人们还希望进行Directio写以进行零填充。

    注意:虽然从技术上讲所有这一切都可以肯定,但问题是进行这样的更改有多有值(value)?谁一直需要此操作?副作用是什么?
    现有的(简单的)代码对于较小的跳过写入是完全可以接受的,如果您创建一个1MB的文件并在最后写入一个字节,您将不会真正注意到它的存在。仅当文件大小按FAT文件系统允许的限制大小顺序进行时,它才会对您造成伤害。

    其他选项...

    在某些情况下,手头的任务涉及两个(或更多)步骤:
  • 使用FAT
  • 重新格式化(例如)SD卡
  • 将一个或多个大文件放到其上,以“预填充”卡
  • (取决于应用程序,可选)
    预填充文件,或者
    将回送文件系统镜像放入其中

  • 我处理过的一种情况是将前两种折叠在一起-即在制作(FAT32)文件系统时修改了mkdosfs以预分配/预创建文件。这很简单,在编写FAT表时,只需创建分配的簇链,而不是用“free”标记填充的簇。它还具有保证数据块连续的优势,以防您的应用程序从中受益。您可以决定使mkdosfs不清除数据块的先前内容。例如,如果您知道您的准备步骤之一涉及无论如何都写入整个数据或执行FAT3-in-file-on-FAT(相当常见的事情-linux设备,用于与Windows app/gui进行数据交换的sd卡),则无需将任何内容归零/重复写入(一次为零,一次为其他)。如果您的用例适合此情况(即格式化存储卡仍然是“初始化使用”过程的有用/正常步骤),请尝试一下;适当修改的mkdosfsTomTom's dosfsutils sources, see mkdosfs.c search for the -N command line option handling的一部分。

    如前所述,在谈论预分配时,还有 posix_fallocate() 。当前在Linux上使用FAT时,此操作与手动dd ...基本相同,即等待调零。但是函数的规范并不要求它是同步的。块分配(FAT簇链生成)必须同步完成,但是可以对VFAT磁盘差异大小更新和数据块零填充进行后台处理/延迟(即,要么在后台以低优先级完成,要么仅在显式地完成通过fdsync()/sync()请求,以便该应用程序可以例如分配块,使用非零本身写入内容...)。那是技术/设计;我还不知道有人做过内核修改,即使只是为了进行实验。

    关于linux - 如何在嵌入式Linux中有效地在VFAT分区上创建大文件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4395021/

    相关文章:

    python - 查找执行中的python程序路径

    bash - 用 Unix 文件中的另一个列表替换字符串列表的有效方法是什么?

    java - 如何在java中获取url html内容到字符串

    c# - 读取内存中的整个文件 VS 读取 block

    linux - 设备上没有剩余空间

    python - 伪造文件系统/虚拟文件系统

    c++ - 更改应用程序文件的名称(.C 文件)

    linux - 使用 shell 脚本查找出现百分比

    mysql - 是否可以从 CentOS 命令行执行数据库插入?

    node.js - 定期检查和清理服务器中的文件