当底层文件的大小发生变化时,映射区域仍然有效?

时间:2015-06-29 13:26:51

标签: linux posix mmap

让我们看看几个场景:

A)

file size    : |---------|
mapped region: |---------|
region access: |XXXXXXXXX|
--> file grows
file size    : |----------------|
mapped region: |---------|
region access: |XXXXXXXXX|

是否仍然可以很好地定义/移植/安全访问(读/写)完整的映射区域?

(假设文件通过正常写入或通过截断来增长;文件只映射一次,文件大小更改后没有额外的重新映射)

b)中

file size    : |---------|
mapped region: |-----------------------|
access       : |XXXXXXXXX|
--> file grows
file size    : |-----------------------|
mapped region: |-----------------------|
access       : |XXXXXXXXXXXXXXXXXXXXXXX|

比如说,在文件扩展之前,程序只是访问了文件大小和映射区域的交集。这应该没问题。

文件增长后 - 使映射和文件的大小匹配 - 现在是否已定义为访问区域/文件的每个部分?

如果是这种情况,在开头创建较大的映射区域可能是一种优化,以避免某些mremap(或munmap / mmap)来电 - 至少在某些情况下使用 - 例。

c)中

file size    : |---------|
mapped region: |---------|
access       : |XXXXXXXXX|
--> file is truncated
file size    : |---|
mapped region: |---------|
access       : |XXX|

只要程序访问区域仍然重叠的部分 - 那是明确定义的行为吗?

2 个答案:

答案 0 :(得分:2)

通常,如果更改了映射文件的大小,则可以安全地访问不受大小更改影响的页面,并且未指定受大小更改影响的页面会发生什么。

来自mmap(2)

1

  

如果映射文件的大小在调用mmap()后由于映射文件上的某些其他操作而发生更改,则引用映射区域的部分对应于添加或删除的部分的效果该文件未指定

2

  

mmap()函数可用于映射大于对象当前大小的内存区域。映射中的内存访问但超出基础对象的当前可能会导致SIGBUS信号被发送到进程。

因此,在所有这三种情况下,似乎可以安全地访问低于当前文件大小的所有原始映射页面,并且访问当前文件大小以上的页面是不安全的。

我不完全确定案例(b),但它似乎是一个有效的案例,至少在Linux中有效。

请注意,SIGBUS生成无法保证,并且未指定在访问大小超过映射大小或超过文件大小的数据时实际发生的情况。例如,实现可能允许您从页面末尾读取有效数据。

还有两个与问题相关的优化技巧(避免mremap()):

  • 您可以使用mmap()标志在堆中分配大内存区域,然后分配所需的MAP_FIXED页面。
  • 您可以使用特定于Linux的remap_file_pages(2)来电。

测试计划

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>

#define max(a, b) (a > b ? a : b)

int main(int argc, char** argv) {
    int mmap_size = atoi(argv[1]);
    int file_size1 = atoi(argv[2]);
    int file_size2 = atoi(argv[3]);

    char* data = malloc(max(file_size1, file_size2));
    memset(data, 7, max(file_size1, file_size2));

    int fd = open("foo", O_RDWR | O_TRUNC | O_CREAT, 0777);
    write(fd, data, file_size1);

    char* addr = mmap(NULL, mmap_size, PROT_READ, MAP_SHARED, fd, 0);

    if (file_size2 <= file_size1)
        ftruncate(fd, file_size2);
    else
        write(fd, data, file_size2 - file_size1);

    printf("%d\n", addr[0]);
    printf("%d\n", addr[file_size1 - 1]);
    printf("%d\n", addr[file_size2 - 1]);

    return 0;
}

Linux上的示例输出:

$ ./a.out 4096 4096 $(( 4096 * 2))
7
7
0
$ ./a.out $(( 4096 * 2 )) 4096 $(( 4096 * 2))
7
7
7
$ ./a.out $(( 4096 * 2 )) $(( 4096 * 2)) 4096
7
Bus error

答案 1 :(得分:0)

1)文件在映射后增长

如果您知道该文件将增长,您将使用匹配标志映射它,以便映射随文件一起增长。 如果你不知道文件会增长,你也不会在映射区域后面访问。

2)文件在映射后收缩

如果您知道文件已缩小,则没有理由在文件结束后访问该区域,因为您将收到信号。 如果您不知道文件已缩小,请参阅我对此问题的其他答案。