我有一个非常简单的代码来测试低内存地址上的 mmap。
unsigned long *p = mmap ((void*)(4096*16), 4096, PROT_READ|PROT_WRITE|PROT_EXEC,
MAP_FIXED|MAP_PRIVATE|MAP_ANONYMOUS|MAP_GROWSDOWN, -1, 0);
fprintf (stderr, "p=0x%lx\n", (unsigned long)p);`
*p = 2554;
printf ("p=0x%lx; *p=%ld\n", (unsigned long)p, *p);
当我运行代码并得到以下输出时:
p=0x10000
Segmentation fault (core dumped)
在 dmesg 日志中,我可以看到以下打印:
segfault at 10000 ip 00000000004006cc sp 00007fff5845f4c0 error 6
总的来说,mmap似乎是成功的,但是写操作失败了。我无法解释这两个相互矛盾的观察结果。请帮我。谢谢。
最佳答案
如果您在 mmap
调用的 flags
参数中省略 MAP_GROWSDOWN
,您可能会发现段错误不再发生。
如果在 mmap
调用之后检查 /proc/$PID/maps
文件,您可能会看到一个奇怪的地方(包括 MAP_GROWSDOWN
在 flags
中)。该地址似乎比请求的地址高一页,并且该映射的大小似乎比您请求的小一页。简而言之,该映射的起始地址偏移了 4096 字节。我在 MAP_GROWSDOWN
的文档中没有发现这种奇怪的情况,对我来说它更像是一个错误而不是一个功能。您是否看到这种特殊情况可能取决于您使用的内核版本(我从标签中假设您使用的是 Linux 内核)。在任何情况下,在进程处于事件状态时检查该文件可能是有教育意义的,即使您的代码在没有 MAP_GROWSDOWN
的情况下按预期工作也是如此。
使进程保持事件状态足够长的时间以检查其 maps
文件的一种方法是在 gdb
中设置断点。调用 mmap
的函数中的任何地方都应该足够了,如果你走得足够远(刚刚通过 mmap
调用)。上述路径名中的$PID
旨在表示调用mmap
的进程的进程ID。您可以从合适的 ps
输出或 gdb
中的 info inferior
的输出中获取该进程 ID。
为了解决您的具体问题,mmap
调用的成功反射(reflect)了 maps
文件中列出的映射(即使该映射的大小在您的示例中为零),而失败反射(reflect)了 mmap
的返回值 (0x10000) 与映射开始 (0x11000) 之间的差异。以 4096 作为大小(如在您的示例中),没有地址将允许分配给 *p
但是使用更大的大小将 4096 添加到 mmap
的返回值会给您一个工作地址(假设你的内核和我的一样)。如果映射的开头等于 mmap
返回值(因为没有 MAP_GROWSDOWN
),则不会有差异。
关于linux - mmap 成功但写入失败,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34932551/