如何访问mmaped /dev/mem而不会导致Linux内核崩溃?

16

我有一个简单的程序,试图在用户空间中访问物理内存,其中内核存储了第一个struct page。 在64位机器上,此地址为:

  • 内核虚拟地址:ffffea0000000000
  • 物理地址:0000620000000000

我正在尝试通过在用户空间中使用mmap来访问此物理地址。 但是以下代码会使内核崩溃。

int *addr;
if ((fd = open("/dev/mem", O_RDWR|O_SYNC)) < 0 ) {
    printf("Error opening file. \n");
    close(fd);
    return (-1);
}
/* mmap.  address of first struct page for 64 bit architectures 
 * is 0x0000620000000000.
 */
addr = (int *)mmap(0, num*STRUCT_PAGE_SIZE, PROT_READ, MAP_PRIVATE,
            fd, 0x0000620000000000);
printf("addr: %p \n",addr);
printf("addr: %d \n",*addr); /* CRASH. */

1
mmap()函数在addr参数中返回什么值? - BjoernD
1
@BjoernD:我在32位x86上尝试了上述方法(将mmap偏移量替换为0x01000000); addr = 0xffffffff。当然,在解除引用时会崩溃。有什么解决办法吗? - kaiwan
3
0xffffffff等于-1 -> mmap()返回错误。根据man页,错误原因在'errno'变量中给出。你可能想要检查一下。 - BjoernD
2个回答

21
我认为我已经找到了问题所在——与x86上的/dev/mem内存映射保护有关。
请参考此LWN文章: "x86: 引入具有配置选项的/dev/mem限制" http://lwn.net/Articles/267427/ CONFIG_NONPROMISC_DEVMEM
现在(我在最近的3.2.21内核上测试过),该配置选项似乎被称为CONFIG_STRICT_DEVMEM。
我更改了我的内核配置:
$ grep DEVMEM .config
# CONFIG_STRICT_DEVMEM is not set
$ 

当运行上述程序时,使用了之前的内核,并设置了CONFIG_STRICT_DEVMEM:

dmesg显示:

[29537.565599] Program a.out tried to access /dev/mem between 1000000->1001000.
[29537.565663] a.out[13575]: segfault at ffffffff ip 080485bd sp bfb8d640 error 4 in a.out[8048000+1000]

这是因为内核保护机制所致。

当内核重新构建时(使用 CONFIG_STRICT_DEVMEM 未设置),并运行以上程序:

# ./a.out 
mmap failed: Invalid argument
# 

这是因为“offset”参数大于1 MB(在x86上无效)(它是16MB)。

将mmap的偏移量限制在1 MB以内后:

# ./a.out 
addr: 0xb7758000
*addr: 138293760 
# 

它有效!请查看上面的LWN文章以获取详细信息。

在支持PAT(页面属性表)的x86体系结构上,内核仍然防止映射DRAM区域。如内核源代码中所提到的原因是:

This check is nedded to avoid cache aliasing when PAT is enabled

这个检查会导致类似于上面提到的错误。例如:

Program a.out tried to access /dev/mem between [mem 68200000-68201000].

通过在启动时将“nopat”参数添加到内核命令行中,可以禁用PAT并移除此限制。


嗨Kaiwan,感谢指出有趣的配置变量。在我的情况下,“CONFIG_STRICT_DEVMEM未设置”。内核(3.4.6和3.1.0)。更改偏移后,程序可以正常工作。但是我想访问该地址,因为它保存了第一个struct页面。这可能吗? - Vinay
我将偏移量设置为0x0000000000000000,可以获得有效的返回地址。但是,如果我将偏移量设置为一个随机地址,比如0x00000000000000ff,我就无法获得有效的地址。我需要将地址设置在页面边界上吗? - Vinay
1
ARM需要在mmap()中使用页面边界。从对IA32的一些测试来看,似乎在那里也是这样...(&我想你已经在x86_64上尝试过了)。 此外,关于访问第一个struct页面的评论,如果CONFIG_STRICT_DEVMEM关闭,我认为它应该可以工作(在页面边界上)...不确定这一点... - kaiwan
2
@trblnc 谢谢!你的实现看起来很简单-映射一个特定的硬编码va。我维护了一个更通用的软件,可以用于从用户模式获得对任何内存(MMIO/寄存器/RAM)的读写访问权限;请查看:https://github.com/kaiwan/device-memory-readwrite - kaiwan
@kaiwan,哇!我必须承认你的代码更完整:P,非常感谢你的分享。 - trblnc
显示剩余3条评论

4
在支持PAT(页面属性表)的x86架构上,内核可以防止DRAM区域的映射(即使未设置CONFIG_NONPROMISC_DEVMEM也可以编译)。如内核源码中所述,其原因是:
This check is nedded to avoid cache aliasing when PAT is enabled

这个检查会导致类似于kaiwan上面回答中提到的错误在中出现。例如:
Program a.out tried to access /dev/mem between [mem 68200000-68201000].

这个限制可以通过禁用PAT来解除。
在启动时,在内核命令行中添加“nopat”参数即可禁用PAT。

这个(正确的!)答案似乎是由一个匿名用户在你回答后8分钟复制粘贴到上面的答案中的。如果你想要它被删除,让我知道,我会将其编辑掉,因为如果没有你的同意,这样做是不公平的。 - Ciro Santilli OurBigBook.com
是我。我是那个将它添加到之前的答案中,然后又将其作为单独的答案添加的人,因为(显然)我不知道如何使用这个工具:D。 - Safayet Ahmed
好的!如果您希望将其从上面的回答中删除,请告诉我。干杯。 - Ciro Santilli OurBigBook.com

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接