这是我编写的示例代码。
#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: %ldn", pagesize);
data = mmap(NULL, pagesize, PROT_READ, MAP_SHARED, fd, 0);
printf("data: %pn", data);
if (data == (void *) -1) {
perror("mmap");
return 1;
}
printf("%dn", data[0]);
printf("%dn", data[1]);
printf("%dn", data[2]);
printf("%dn", data[4096]);
printf("%dn", data[4097]);
printf("%dn", 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失败吗?
当访问经过最后一个完整映射页面的末尾时,会得到SIGBUS
,因为POSIX标准规定:
mmap()
函数可用于映射大于对象当前大小的内存区域映射内但超出底层对象当前端的内存访问可能导致SIGBUS
信号被发送到进程。
对于一个零字节文件,您映射的整个页面"超出了底层对象的当前末尾"。所以你得到了SIGBUS
。
当你超出你映射的4kB页面时,你不会得到SIGBUS
,因为它不在你的映射范围内。当你的文件大于零字节时,你不会得到访问映射的SIGBUS
,因为整个页面都被映射了。
但是,如果将其他页面映射到文件末尾之后,则会得到SIGBUS
,例如为一个1字节的文件映射两个4kB页面。如果你访问第二个4kB的页面,你会得到SIGBUS
。
1字节的文件不会导致崩溃,因为mmap
将以页面大小的倍数映射内存,并将剩余部分清零。来自手册页:
文件被映射为页面大小的倍数。对于不是页面大小的倍数,当映射,以及对该区域的写入不会写入到文件中。更改映射的基础文件大小的效果在与文件的添加或删除区域相对应的页面上未指定。