如何使用 File::Map 正确写入文件?

How to properly write to a file using File::Map?

我经常使用 File::Map 将特别小的文本文件映射到内存中,例如处理那些上的一些只读正则表达式。现在我有一个用例,我也需要替换文件中的一些文本并认为我仍然可以使用 File::Map,因为它记录了以下内容:

Files are mapped into a variable that can be read just like any other variable, and it can be written to using standard Perl techniques such as regexps and substr.

虽然我有兴趣替换的数据已在文件中正确替换,但我正在丢失数据,因为文件保持其原始大小并且数据最终被截断。新数据比旧数据大一点。正如使用以下句子记录的那样,这两件事都会被警告:

Writing directly to a memory mapped file is not recommended

Truncating new value to size of the memory map

这两个警告的解释读起来就像一个人不应该使用 File::Map 写任何东西,但它可能适用于可以忍受截断文件或整体文件大小根本没有改变的情况.但是第一个引用明确提到支持写入,没有任何例外。

那么,是否有一些特殊的方法可以使用 File::Map 安全地写入,例如让基础文件增加等等?第一个警告使用 directly 的措辞,我觉得还有其他更好的支持方式来写?

我目前只是在映射视图上使用 =~ s///,这似乎是错误的方法。我什至找不到任何人尝试使用 File::Map 进行编写,只有官方测试完全按照我的方式进行,并期待我收到警告。此外,查看代码,似乎只有一个用例,其中写入根本不会导致警告,但我不明白我是如何触发它的:

static int mmap_write(pTHX_ SV* var, MAGIC* magic) {
        struct mmap_info* info = (struct mmap_info*) magic->mg_ptr;
        if (!SvOK(var))
                mmap_fixup(aTHX_ var, info, NULL, 0);
        else if (!SvPOK(var)) {
                STRLEN len;
                const char* string = SvPV(var, len);
                mmap_fixup(aTHX_ var, info, string, len);
        }
        else if (SvPVX(var) != info->fake_address)
                mmap_fixup(aTHX_ var, info, SvPVX(var), SvCUR(var));
        else
                SvPOK_only_UTF8(var);
        return 0;
}

https://metacpan.org/source/LEONT/File-Map-0.55/lib/File/Map.xs#L240

毕竟,如果应该完全避免书写,为什么文档明确提到支持它?如果它至少在所有情况下都会导致警告,但我看起来不支持。

first quote,

Files are mapped into a variable that can be read just like any other variable, and it can be written to using standard Perl techniques such as regexps and substr.

在“简单”标题下。

确实如此:您只需编写操作字符串的 Perl 代码,数据就会最终出现在文件中。

但是,在 Warnings 部分我们有:

Writing directly to a memory mapped file is not recommended

Due to the way perl works internally, it's not possible to write a mapping implementation that allows direct assignment yet performs well. As a compromise, File::Map is capable of fixing up the mess if you do it nonetheless, but it will warn you that you're doing something you shouldn't. This warning is only given when use warnings 'substr' is in effect.

也就是说,除非可以就地修改字符串缓冲区,否则通过 mmap 变量写入效率不高(字符串必须先组装并存储在内存中,并且只复制到文件中然后)。如果您对此没问题,可以使用 no warnings 'substr'.

禁用警告

Additionally, looking at the code, there seems to be only one use case in which writing doesn't result in a warning at all, though I don't understand how I'm able to trigger that.

这就是您尝试为自身写入缓冲区的情况。当实际修改标量时会发生这种情况。其他情况是字符串缓冲区被替换时的解决方法(例如,因为它被覆盖:$foo = $bar)。对于真正的 in-place 修改,不需要额外的工作,您也不会收到警告。

但这对您没有帮助,因为无法使用固定大小的映射缓冲区in-place增长字符串。

无法更改文件的大小。这不是因为 File::Map,而是因为底层 mmap system call 适用于固定大小的映射,不提供任何自动调整文件大小的选项。

如果您需要编辑文件(尤其是小文件),我建议改用edit in Path::Tiny

mmap 是文件的一部分到内存的固定大小映射。

各种映射函数将提供的标量的字符串缓冲区设置为映射的内存页。如果需要,OS 会将对该缓冲区的任何更改反映到文件中,反之亦然。

使用 mmap 的正确方法是修改 字符串缓冲区,而不是替换它。

  • 任何改变字符串缓冲区而不改变其大小的东西都是合适的。

    $ perl -e'print "[=10=]"x16' >scratch
    
    $ perl -MFile::Map=map_file -we'
       map_file my $map, "scratch", "+<";
       $map =~ s/\x00/\xFF/g;             # ok
       substr($map, 6, 2, "00");          # ok
       substr($map, 8, 2) = "11";         # ok
       substr($map, 7, 2) =~ s/../22/;    # ok
    '
    
    $ hexdump -C scratch
    00000000  ff ff ff ff ff ff 30 32  32 31 ff ff ff ff ff ff  |......0221......|
    00000010
    
  • 任何替换字符串缓冲区的东西(例如分配给标量)都不行。

    ...有点。该模块注意到您已经替换了标量的缓冲区。它继续将新缓冲区的内容复制到映射内存,然后用指向映射内存的指针替换标量的缓冲区。

    $ perl -e'print "[=11=]"x16' >scratch
    
    $ perl -MFile::Map=map_file -we'
       map_file my $map, "scratch", "+<";
       $map = "4" x 16;  # Effectively: substr($map, 0, 16, "4" x 16)
    '
    Writing directly to a memory mapped file is not recommended at -e line 3.
    
    $ hexdump -C scratch
    00000000  34 34 34 34 34 34 34 34  34 34 34 34 34 34 34 34  |4444444444444444|
    00000010
    

    除了可以使用 no warnings qw( substr );,[1] 消除警告之外,唯一的缺点是这样做需要使用 memcpy 来复制length($map) 字节,而使用 substr($map, $pos, length($repl), $repl) 只需要复制 length($repl) 字节。

  • 任何改变字符串缓冲区大小的东西都不行。

    $ perl -MFile::Map=map_file -we'
       map_file my $map, "scratch", "+<";
       $map = "5" x 32;  # Effectively: substr($map, 0, 16, "5" x 16)
    '
    Writing directly to a memory mapped file is not recommended at -e line 3.
    Truncating new value to size of the memory map at -e line 3.
    
    $ hexdump -C scratch
    00000000  35 35 35 35 35 35 35 35  35 35 35 35 35 35 35 35  |5555555555555555|
    00000010
    

警告:如果您缩小缓冲区,模块不会发出警告,即使这除了用 NUL 破坏一个字节之外没有任何效果。

$ perl -e'print "[=13=]"x16' >scratch

$ perl -MFile::Map=map_file -we'
   map_file my $map, "scratch", "+<";
   substr($map, 0, 16, "6" x 16);
   substr($map, 14, 2, "");
'

$ hexdump -C scratch
00000000  36 36 36 36 36 36 36 36  36 36 36 36 36 36 00 36  |66666666666666.6|
00000010

我已经提交了 ticket


  1. 这有点讽刺,因为它在不使用 substr 时或多或少会发出警告,但我想它在使用 substr "incorrectly".
  2. 时也会发出警告