【发布时间】:2019-06-02 20:40:57
【问题描述】:
我正在 Linux 中试验内存映射文件,并且有一个问题,当从不同进程映射同一个文件并在文件末尾之外写入时实际发生了什么。
我用 vim 手动创建了一个文件,并在其中写入了 2 个字节:
$ cat test_mmap
aa
然后我写了两个非常简单的程序。
第一个程序映射文件,修改映射不带msync和munmap。
writer.c:
int main(void){
int fd = open("/tmp/test_mmap", O_CREAT | O_RDWR, S_IRWXU);
char *mapped_region = mmap(NULL, sysconf(_SC_PAGESIZE), PROT_WRITE, MAP_SHARED, fd, 0);
mapped_region[0] = '0';
mapped_region[1] = '1';
mapped_region[2] = '2';
mapped_region[3] = '3';
mapped_region[4] = '4';
mapped_region[5] = '5';
}
第二个是读取映射。
reader.c:
int main(void){
int fd = open("/tmp/test_mmap", O_CREAT | O_RDWR, S_IRWXU);
char *mapped_region = mmap(NULL, sysconf(_SC_PAGESIZE), PROT_WRITE, MAP_SHARED, fd, 0);
printf("%c\n", mapped_region[0]);
printf("%c\n", mapped_region[1]);
printf("%c\n", mapped_region[2]);
printf("%c\n", mapped_region[3]);
printf("%c\n", mapped_region[4]);
printf("%c\n", mapped_region[5]);
}
所以我跑了
$ ./writer && ./reader && cat /tmp/test/test_mmap
0
1
2
3
4
5
012
这意味着任何超出文件末尾写入的数据都会在映射中保留一段时间(尽管它没有写出到文件中),并且如果另一个进程因此映射相同的区域,则写入超出的数据不会归零,因为在man-page中指定:
文件以页面大小的倍数进行映射。对于一个不是 页面大小的倍数,剩余内存清零时 映射,并且对该区域的写入不会写入文件。
使用perf -e major-faults ./reader 运行阅读器表明
0 major-faults
意味着没有从磁盘读取任何页面。还查看了/proc/<pid_writer>/smaps,我观察到该页面被标记为脏和私有(即使映射是使用MAP_SHARED 标志创建的):
7fc80f279000-7fc80f27a000 -w-s 00000000 fd:00 6057290 /tmp/test_mmap
Shared_Clean: 0 kB
Shared_Dirty: 0 kB
Private_Clean: 0 kB
Private_Dirty: 4 kB
如果我在一段时间后运行阅读器进程(需要等待什么时间?)我观察到
$ ./reader
0
1
2
问题:是否正确并在某处记录了如果一个进程修改了超出文件末尾的映射,则页面被标记为脏并且只要页面是脏了和另一个进程映射同一个文件的相同区域之前该进程写入的数据没有归零并按原样保留一段时间?
【问题讨论】:
-
猜测一下,如果文件的一部分已经映射到物理页面,任何同时映射该文件的进程都会将相同的物理页面映射到其地址空间。因此,对该页面的任何更改(无论是文件的一部分还是文件末尾)都将对已映射该文件的所有进程可见。
-
@1201ProgramAlarm 是的,这是可以观察到的。但这是否与man-page 中指定的内容相矛盾。 对于不是页面大小倍数的文件,映射时剩余内存归零
-
@wildplasser 能否请您稍微扩展一下 您所映射的内容 的含义。我猜想即使页面很脏,它也会被视为文件的一部分。并且当它被清理时,超出 EOF 的数据被归零。对吗?
-
并非如此。文件页面已经被映射(即使映射到不同的进程),所以额外的字节不需要不归零。
-
Page==dirty 在这里完全无关紧要。您得到映射(在映射时达到当前大小)
标签: c linux file-io linux-kernel memory-mapped-files