首页 > 解决方案 > 为什么 mremap 只访问 4096 字节内存?

问题描述

下面的代码开始 mmap(initlen=10), 后来 remap(nsize=400000) 和访问 remap 内存地址访问 0x7ffff7f8c000 到 0x7ffff7f8cfff 是可以的,但是在 0x7ffff7f8d000 导致访问错误

#define _GNU_SOURCE
#include <stdio.h>
#include <limits.h>
#include <stdint.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>


int main()
{
    int i;
    char *p;
    void *base;
    const int initlen = 10;
    const int nsize = 400000;

    const char *fname = "/tmp/task.0";
    int fd = open(fname, O_CREAT|O_RDWR, 0600);
    if (fd == -1) {
        return 1;
    }

    if (ftruncate(fd, initlen) < 0) {
        return 1;
    }

    base = mmap(NULL, initlen, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    if (base == MAP_FAILED) {
        return 1;
    }

    // now remap big page.
    base = mremap(base, initlen, nsize, MREMAP_MAYMOVE);
    if (base == MAP_FAILED) {
        printf("mremap fail, %s\n", strerror(errno));
        return 1;
    }

    p = base;
    for (i = 0; i < nsize; i++) {
        printf("%p\n", p);
        *p = i % CHAR_MAX;
        ++p;
    }

    return 0;
}

gdb显示“/tmp/task.0”区域为0x7ffff7f8c000-0x7ffff7fee000后的mremap,在0x7ffff7f8d000处访问出错,为什么???

37      base = mremap(base, initlen, nsize, MREMAP_MAYMOVE);
(gdb)
38      if (base == MAP_FAILED) {
(gdb)
43      p = base;
(gdb)
44      for (i = 0; i < nsize; i++) {
(gdb) p base
$1 = (void *) 0x7ffff7f8c000
(gdb) i proc mappings
process 3333
Mapped address spaces:

          Start Addr           End Addr       Size     Offset objfile
            0x400000           0x401000     0x1000        0x0 /tmp/a.out
            0x600000           0x601000     0x1000        0x0 /tmp/a.out
            0x601000           0x602000     0x1000     0x1000 /tmp/a.out
      0x7ffff7a0e000     0x7ffff7bd1000   0x1c3000        0x0 /usr/lib64/libc-2.17.so
      0x7ffff7bd1000     0x7ffff7dd0000   0x1ff000   0x1c3000 /usr/lib64/libc-2.17.so
      0x7ffff7dd0000     0x7ffff7dd4000     0x4000   0x1c2000 /usr/lib64/libc-2.17.so
      0x7ffff7dd4000     0x7ffff7dd6000     0x2000   0x1c6000 /usr/lib64/libc-2.17.so
      0x7ffff7dd6000     0x7ffff7ddb000     0x5000        0x0
      0x7ffff7ddb000     0x7ffff7dfd000    0x22000        0x0 /usr/lib64/ld-2.17.so
      0x7ffff7f8c000     0x7ffff7fee000    0x62000        0x0 /tmp/task.0
      0x7ffff7fee000     0x7ffff7ff1000     0x3000        0x0
      0x7ffff7ff9000     0x7ffff7ffa000     0x1000        0x0
      0x7ffff7ffa000     0x7ffff7ffc000     0x2000        0x0 [vdso]
      0x7ffff7ffc000     0x7ffff7ffd000     0x1000    0x21000 /usr/lib64/ld-2.17.so
      0x7ffff7ffd000     0x7ffff7ffe000     0x1000    0x22000 /usr/lib64/ld-2.17.so
      0x7ffff7ffe000     0x7ffff7fff000     0x1000        0x0
      0x7ffffffde000     0x7ffffffff000    0x21000        0x0 [stack]
  0xffffffffff600000 0xffffffffff601000     0x1000        0x0 [vsyscall]

Program received signal SIGBUS, Bus error.
0x0000000000400831 in main () at ./test.c:46
46          *p = i % CHAR_MAX;
(gdb) p p
$2 = 0x7ffff7f8d000 <Address 0x7ffff7f8d000 out of bounds>

标签: linuxvirtual-memory

解决方案


它与mremap. 这就是文件映射的工作方式。SIGBUS 意味着您试图访问超过其 EOF 的文件区域(请参阅手册页 mmap(3))。

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

这与 SIGSEGV 不同,SIGSEGV 在您尝试访问进程中不存在的虚拟地址或发生保护错误时发送(例如,尝试写入只读地址)。

要回答为什么您可以在不获取 SIGBUS 的情况下访问前 0xfff 字节的问题(即使文件大小只有 10 个字节),这是因为内存管理是根据 4096 字节页面进行的。但是请注意,即使您可以访问字节 10..4095,该区域也不受文件支持。您写入这些字节的所有内容都不会写入文件。


推荐阅读