虚拟地址/物理地址——virtual address(memory)/physical address: 树莓派 mmap example

8 篇文章 0 订阅
7 篇文章 0 订阅

关于如何访问物理地址:

通常:

If you want to find a way for access physical memory in Linux there are only two solutions. The first is to develop a module running in kernel space with the correct privileges to access physical memory and the second is to use a special devices called “/dev/mem”.

因为,在系统内核中,有一部分虚拟地址是直接映射到物理地址上的。找个这个映射关系(偏移)就能直接读写物理地址了。而在用户空间,不通过特殊方法,通常只能访问到内存(RAM) ,不能访问到其他的物理地址

注意,物理地址是相对于CPU的寻址范围来说的,物理地址不仅包括内存(RAM),而且包括PCI设备,显卡,网卡等其他IO资源。这里附一张x86系统的物理地址分配图

x86物理地址分配图

在用户空间的程序,基于以下两个条件可以访问到整个物理地址:

mmap maps files or devices into memory.

/dev/mem provides access to the system’s physical memory(相当于物理地址的一个映射)

因此,可以在程序中通过 mmap 调用将字符设备 /dev/mem 映射到程序的虚拟地址空间,从而实现通过读写程序虚拟地址(变量地址) 访问系统物理地址(外设地址)的目的。在linux系统中读写 /dev/mem 设备相当于直接读写物理地址。

我们在程序中使用的都是 virtual address(heap stack etc.),每一个进程都有自己的虚拟地址空间。如果不将设备 /dev/mem 映射到程序的虚拟地址空间,那么通过程序访问的任何地址 (包括 new/malloc调用返回的地址)都是操作系统在RAM中映射的地址,也就不可能实现在程序的虚拟地址中读写除RAM以外的外设。

在linux系统中通过 cat /proc/iomem 可以查看系统的物理地址的分配情况,CPU的寻址空间从地址0x00000000开始,每一行都代表一个资源(地址范围和资源名), 而内存(RAM)在其中表示为System RAM.

通过调整位置,读写字符设备 /dev/mem 相当于直接读写系统的物理地址,因此该设备可以很方便的用来编写用户空间的驱动程序,只不过就是用户空间不能响应中断。

如果是将物理地址映射到内核空间,则使用 ioremap。

注意: 能够读写设备 /dev/mem 的前提是你的内核关闭了 CONFIG_STRICT_DEVMEM 配置选项(CONFIG_STRICT_DEVMEM=n),否则即使是root用户也不能够读写该设备。

see link:
http://www.tldp.org/LDP/khg/HyperNews/get/devices/devices.html
http://www.tldp.org/LDP/khg/HyperNews/get/devices/fake.html

基于树莓派物理地址的访问,实现led灯的点亮熄灭:

抛砖引玉,直接上代码:

/**
* guo.wei.1107@163.com
*/
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <errno.h>

// the raspberry pi 2 model B GPIO base address and pin2 registers
// 该地址的是通过树莓派的手册找到的物理地址。
// 如果是写成内核模块,访问虚拟地址,在相当于访问0xf3f20000,见下文分析。
#define GPIO_BASE  0x3f200000

#define GPIO_GPFSEL0  0x00  // offset of pin2 function select register, 32bit
#define GPIO_GPSET0  0x1C // offset of pin2 set register, 32bit
#define GPIO_GPCLR0 0x28  // offset of pin2 clear register, 32bit

int main(int argc, char *argv[])
{
    int fd;
    // map is the memory to process, should run with sudo
    if ((fd = open("/dev/mem", O_RDWR)) == -1)
    {
        perror("open");
        exit(1);
    }

    char *addr; 
    addr = mmap(0, sysconf(_SC_PAGESIZE)/*getpagesize()*/, PROT_READ | PROT_WRITE, MAP_SHARED , fd, GPIO_BASE);
    if(addr == MAP_FAILED)
    {
        perror("mmap");
        exit(1);
    }

    volatile uint32_t * GPFSEL0 = (uint32_t *)(addr+GPIO_GPFSEL0);
    volatile uint32_t * GPSET0 = (uint32_t *)(addr+GPIO_GPSET0);
    volatile uint32_t * GPCLR0 = (uint32_t *)(addr+GPIO_GPCLR0);

    *GPFSEL0 = *GPFSEL0 | 0x40;  // set the pin2 function register as output mode

    for(;;)
    {
        *GPCLR0 = 0x04;    // set to 0
        sleep(1);
        *GPSET0 = 0x04;     // set to 1
        sleep(1);
    }

    return 0;
}

哈,亮了:

这里写图片描述


查看树莓派gpio的物理地址:

cat /proc/iomem | grep gpio

树莓派1的外设文档,树莓派2除了基地址不一样其他差不太多:
https://www.raspberrypi.org/wp-content/uploads/2012/02/BCM2835-ARM-Peripherals.pdf

这里写图片描述

其中要区分文档中的三个地址,bus address、physical address、virtual address

Peripherals (at physical address 0x20000000 on) are mapped into the kernel virtual address
space starting at address 0xF2000000. Thus a peripheral advertised
here at bus address 0x7Ennnnnn is available in the ARM kenel at virtual address 0xF2nnnnnn.

这里的 virtual address 和 physical address 是通过 ARM MMU 来实现映射的(先不管cache等其他因素)。主板上外设的实际地址是 physical address,所以需要访问 GPIO 寄存器,也就是访问 physical address 中的 0x20000000 开始的某处的地址, 那么就需要在代码中访问 virtual address 的 0xF2000000 开始的某处的地址,由于该虚拟地址在高地址内存,因此只能在内核的代码中才能够访问到.

具体到代码GPIO相关操作的物理起始地址为0x20200000,然后再经过Linux系统的映射为虚拟地址就变成了 0xF2000000(在kernel的内存中)。

注意:树莓派2 model B与上图有点差别,pi2 model B GPIO 的物理起始地址为 0x3f200000

我一直没提到上文中的 bus address 表示什么意思。其实,在 x86 架构的系统中并不存在下图的 VC CPU Bus Addresses,这个 bus address 应该是只在BCM2835系列的型号才有。根据这个链接 和 这个链接

The VC CPU Bus addresses relate to the Broadcom Video Core CPU. Although the Video Core CPU is what bootloads from the SD Card, execution is handed over to the ARM core by the time our kernel.img code is called. So we’re not interested in the VC CPU Bus addresses.

In the VideoCore architecture the ARM CPU is just a co-processor. The ARM does not have direct access to the physical hardware bus of the system. What it sees as a physical bus is a virtual mapping controlled by the VideoCore MMU.

所以,BCM2835是一个VideoCore的架构,ARM CPU只是其一个协处理器,左边的 VC CPU Bus Addresses 表示的正是在VideoCore下的寻址空间,而在 ARM 下的 Physical Address 是经过 VC/ARM MMU 映射后的地址。如果将这个系统比作x86系统,我们只需要考虑 physical address 和 virtual address 的映射,忽略掉那个 bus address 即可(因为不存在)。

关于 mmap 调用的使用

    char* addr;
    addr= mmap((void*)0x100000, 1000, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED, -1, 0);
    printf("%p\n", addr);
    if(addr== MAP_FAILED)
    {
        perror("mmap");
        return 0;
    }
// 如果不提供文件描述符(-1),则需要加上MAP_ANONYMOUS标志(相当于在你的进程空间malloc的一块内存), 
// 文件 /dev/mem 是一个表示你电脑内存的字符设备, Byte addresses in mem are interpreted as physical memory addresses
// 一般不使用MAP_FIXED,因为有可能地址 0x100000 已经被映射过了;如果是想共享内存,必须设置为 MAP_SHARED,否则改动的只是你自己的copy部分,就跟c++传值一样。
// 映射是以页为单位的 4096 Byte, 所以这里想映射1000个字节,其结果就是映射了一个页
// 因此,如果上述代码映射成功,表示该程序的虚拟地址从 0x1000000~0x1000000+4096 是可用的,
// 读写 addr[4095] 没问题,但是如果是读写 addr[4096] 就会 segment fault 了

关于操作系统如何将虚拟地址映射为物理地址,见如下链接(Must Read!):

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值