为什么对内存映射零字节文件的读操作会导致SIGBUS?

时间:2017-01-01 13:59:09

标签: c mmap sigbus

以下是我编写的示例代码。

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

int main()
{
    int fd;
    long pagesize;
    char *data;

    if ((fd = open("foo.txt", O_RDONLY)) == -1) {
        perror("open");
        return 1;
    }

    pagesize = sysconf(_SC_PAGESIZE);
    printf("pagesize: %ld\n", pagesize);

    data = mmap(NULL, pagesize, PROT_READ, MAP_SHARED, fd, 0);
    printf("data: %p\n", data);
    if (data == (void *) -1) {
        perror("mmap");
        return 1;
    }

    printf("%d\n", data[0]);
    printf("%d\n", data[1]);
    printf("%d\n", data[2]);
    printf("%d\n", data[4096]);
    printf("%d\n", data[4097]);
    printf("%d\n", data[4098]);

    return 0;
}

如果我向该程序提供零字节foo.txt,它将以SIGBUS终止。

$ > foo.txt && gcc foo.c && ./a.out 
pagesize: 4096
data: 0x7f8d882ab000
Bus error

如果我为这个程序提供一个字节的foo.txt,那么就没有这样的问题了。

$ printf A > foo.txt && gcc foo.c && ./a.out 
pagesize: 4096
data: 0x7f5f3b679000
65
0
0
48
56
10

mmap(2)提及以下内容。

  

使用映射区域可能会产生以下信号:

     

SIGSEGV尝试写入映射为只读的区域。

     

SIGBUS尝试访问与文件不对应的缓冲区的一部分(例如,超出文件末尾,包括另一个进程截断文件的情况)。

因此,如果我理解正确,即使是第二个测试用例(1字节文件)也应该导致SIGBUS,因为data[1]data[2]正在尝试访问缓冲区的一部分({{ 1}})与文件不对应。

你能帮助我理解为什么只有一个零字节文件会导致这个程序失败并发生SIGBUS吗?

2 个答案:

答案 0 :(得分:5)

当访问过去整个映射页面的末尾时,您获得SIGBUS,因为the POSIX standard states

  

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

使用零字节文件,您映射的整个页面“超出底层对象的当前末尾”。所以你得到SIGBUS

当你超出你绘制的4kB页面时,你不会得到SIGBUS,因为那不在你的映射中。当文件大于零字节时,你不会得到SIGBUS访问你的映射,因为整个页面都被映射了。

但如果您将其他页面映射到文件末尾,则会得到SIGBUS,例如为一个1字节的文件映射两个 4kB页面。如果您访问第二个4kB页面,则会获得SIGBUS

答案 1 :(得分:3)

1字节文件不会导致崩溃,因为mmap将以页面大小的倍数映射内存,而余数为零。从手册页:

  

文件以页面大小的倍数映射。对于一个文件          不是页面大小的倍数,剩余内存在归零时归零          映射和写入该区域不会写入文件。          更改映射的基础文件大小的效果          在与文件的添加或删除区域对应的页面上          没有具体说明。