将文件映射到内存并写入文件末尾

Mapping file into memory and writing beyong end of file

我正在试验 Linux 中的内存映射文件,并且有一个问题,即从不同进程映射同一文件并写入超出文件末尾时实际发生了什么。

我用 vim 手动创建了一个文件,并在其中写入了 2 个字节:

$ cat test_mmap
aa

然后我写了2个很简单的程序。

第一个程序映射文件并修改映射没有msyncmunmap

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:

中的指定归零

A file is mapped in multiples of the page size. For a file that is not a multiple of the page size, the remaining memory is zeroed when mapped, and writes to that region are not written out to the file.

运行 reader 和 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 处理一段时间后(需要等待什么时间?)我观察到

$ ./reader
0
1
2

问题:是否正确并在某处记录了如果一个进程修改了超出文件末尾的映射,则页面被标记为dirty 只要页面是脏的并且另一个进程映射同一个文件的相同区域,进程之前写入的数据就不会被清零并保留一段时间?

这些问题的权威参考是POSIX,它在mmap的基本原理部分必须说:

The mmap() function can be used to map a region of memory that is larger than the current size of the object. [... snip discussion on sending SIGBUS if possible, i.e. when accessing a page beyond the end of the file ...] written data may be lost and read data may not reflect actual data in the object.

所以,POSIX 说这样做会导致数据丢失。此外,可移植性充其量也值得怀疑(想想 no-MMU 系统、与大页面的交互、具有不同页面大小的平台...)