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

Lon*_*ner 8 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;
}
Run Code Online (Sandbox Code Playgroud)

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

$ > foo.txt && gcc foo.c && ./a.out 
pagesize: 4096
data: 0x7f8d882ab000
Bus error
Run Code Online (Sandbox Code Playgroud)

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

$ printf A > foo.txt && gcc foo.c && ./a.out 
pagesize: 4096
data: 0x7f5f3b679000
65
0
0
48
56
10
Run Code Online (Sandbox Code Playgroud)

mmap(2)提到以下内容.

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

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

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

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

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

And*_*nle 5

SIGBUS当访问过去整个映射页面的末尾时,您会得到,因为POSIX标准指出:

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

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

SIGBUS当你超越你绘制的4kB页面时,你不会得到一个,因为那不在你的映射中.SIGBUS当文件大于零字节时,您无法访问映射,因为整个页面都已映射.

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