linux使用mmap和mem在用户空间访问物理地址

Posted by LuckXiang on March 12, 2017

0x00前言

最近要在项目里添加一项功能,需要读取一个寄存器的值,linux用户态使用的是独立的进程地址空间,没有办法直接操作寄存器,一般的做法是在内核里读出寄存器的值,然后添加一个ioctl调用。但是我不想为此增加一个额外的接口,Keep it simple. 还好,linux内核已经给我们提供了解决办法:通过mmap和/dev/mem建立用户直接访问物理地址的通路。

0x01mmap 和/dev/mem

/dev/mem是linux下的一个字符设备,一般需要root权限,源文件是kernel/drivers/char/mem.c,通过这个设备理论上可以访问所有的物理地址,当然,内核会有一些限制,不然就太不安全了。mmap是一个系统调用,可以把文件或其它对象映射到内存,常用于内存共享,通过它直接访问设备内存,效率会比在内核和用户直接拷贝数据高得多。用这两个接口,我们就能够直接把物理地址映射到用户空间的虚拟地址上。

0x02虚拟地址,物理地址

现代操作系统基本上都支持虚拟内存管理机制,这需要MMU的支持,如果处理器没有MMU或者MMU没有启用,CPU执行单元发出的内存地址将直接传到芯片引脚上,被 内存芯片接收,这称为物理地址,如果处理器启用了MMU,CPU执行单元发出的内存地址将被MMU截获,从CPU到MMU的地址称为虚拟地址,而MMU将这个地址翻译成另一个地址发到CPU芯片的外部地址引脚上,也就是将虚拟地址映射成物理地址。在采用mmap+/dev/mem的时候,需要注意的是直接把物理地址映射到了用户空间的虚拟地址上,在操作的时候,我们要传入物理地址来做偏移。

0x03内核地址空间和进程地址空间

32位linux的虚拟地址空间一共有4G,0~3G为用户空间,3~4G为内核空间。每个进程这3G的空间是独享的,而要访问1G的内核空间,则需要通过系统调用和中断。之所以能在内核直接访问寄存器,是因为寄存器的物理地址已经映射到了内核空间中。在使用接口的时候,我们既要弄清楚内核空间和用户空间的区别,也要明白物理地址和虚拟地址的差异。

0x04示例代码

#define REG_BASE 0X4A00000

    int fd;
    unsigned char *base;
    char reg;

    fd = open ("/dev/mem", O_RDONLY);
    base = mmap(NULL, 0xfffff, PROT_READ, MAP_PRIVATE, fd, REG_BASE);
    if(base == NULL)
    {
        printf("mmap error\n");
    }
    reg = base[0x3c000]&0xff;
    printf("reg:0x%x\n", reg);