8

这是我编写的示例代码。

#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]正在尝试访问data与文件不对应的缓冲区()的一部分。

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

4

2 回答 2

5

由于POSIX 标准规定,您SIGBUS在访问最后一个整个映射页面的末尾时会得到:

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

对于零字节文件,您映射的整个页面“超出了基础对象的当前端”。所以你得到SIGBUS.

SIGBUS当您超出已映射的 4kB 页面时,您不会得到 a ,因为那不在您的映射范围内。SIGBUS当您的文件大于零字节时,您无法访问您的映射,因为整个页面都被映射了。

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

于 2017-01-01T14:36:34.497 回答
3

一个 1 字节的文件不会导致崩溃,因为mmap它将以页面大小的倍数映射内存并将其余部分归零。从手册页:

文件以页面大小的倍数进行映射。对于不是页面大小的倍数的文件,剩余内存在映射时为零,并且对该区域的写入不会写出到文件中。未指定更改与文件的添加或删除区域相对应的页面上映射的基础文件大小的效果。

于 2017-01-01T14:08:11.797 回答