我试图通过在嵌入式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无法支持以下任一情况:
这就是您在ext3上使用测试用例创建的内容-一个文件,该文件的前1GB-1MB没有分配数据块,最后只有1MB实际提交的,零初始化的块。
在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文件系统允许的限制大小顺序进行时,它才会对您造成伤害。
其他选项...
在某些情况下,手头的任务涉及两个(或更多)步骤:
预填充文件,或者
将回送文件系统镜像放入其中
我处理过的一种情况是将前两种折叠在一起-即在制作(FAT32)文件系统时修改了
mkdosfs
以预分配/预创建文件。这很简单,在编写FAT表时,只需创建分配的簇链,而不是用“free”标记填充的簇。它还具有保证数据块连续的优势,以防您的应用程序从中受益。您可以决定使mkdosfs
不清除数据块的先前内容。例如,如果您知道您的准备步骤之一涉及无论如何都写入整个数据或执行FAT3-in-file-on-FAT(相当常见的事情-linux设备,用于与Windows app/gui进行数据交换的sd卡),则无需将任何内容归零/重复写入(一次为零,一次为其他)。如果您的用例适合此情况(即格式化存储卡仍然是“初始化使用”过程的有用/正常步骤),请尝试一下;适当修改的mkdosfs
是TomTom'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/