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

时间:2018-12-07 14:14:52

标签: perl memory-mapped-files

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

  

文件被映射到一个变量,该变量可以像其他变量一样读取,并且可以使用标准的Perl技术(例如regexp和substr)写入。

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

  

不建议直接写入内存映射文件

     

将新值截断为内存映射的大小

对这两个警告的解释都不应像使用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

毕竟,如果根本不应该写,为什么文档会明确提到支持它?如果它至少在所有情况下都会导致警告,但我似乎并不支持。

2 个答案:

答案 0 :(得分:8)

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

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

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

  • 在不更改字符串缓冲区大小的情况下进行任何更改都是合适的。

    $ perl -e'print "\0"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 "\0"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 "\0"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时也会发出警告。

答案 1 :(得分:6)

first quote

  

文件被映射到一个变量,该变量可以像读取任何其他变量一样被读取,并且可以使用诸如regexp和substr之类的标准Perl技术将其写入。

在“简单性”标题下。

这是真的:您只需编写可操纵字符串的Perl代码,数据就将最终存储在文件中。

但是,在Warnings部分中,我们有:

  

不建议直接写入内存映射文件

     

由于perl在内部工作的方式,不可能编写允许直接分配但表现良好的映射实现。作为一种折衷,File :: Map仍然可以修复混乱,但是会警告您正在做不应该做的事情。仅在use warnings 'substr'有效时发出此警告。

也就是说,除非可以对字符串缓冲区进行修改(必须先将字符串组装并存储在内存中,然后才将其复制到文件中),否则通过mmap'd变量进行写操作效率不高。 。如果可以,可以使用no warnings 'substr'禁用警告。

  

另外,看一下代码,似乎只有一种用例,其中编写根本不会导致警告,尽管我不知道如何触发该警告。

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

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

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

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