在设备驱动程序的开发过程中,我们通常需要跟硬件地址打交道。在不带操作系统的嵌入式平台上(如51单片机,STM32等),我们是可以直接操作的是硬件物理地址。
但是在基于Linux的嵌入式平台上,我们想要直接控制硬件的地址基本是不可能的。操作系统操作的是虚拟地址,因此我们只有将物理地址映射成为虚拟地址才能对硬件进行操作。
我使用的硬件平台是树莓派Raspberry Pi 2 Model B,处理器芯片是BCM2836。我想用树莓派直接控制引脚电平高低(简单的GPIO操作)。官方没有提供BCM2837芯片手册,但是提供了BCM2835芯片手册。BCM2837架构是在BCM2835上做了改变,但是基本不变。BCM2835下载地址
我们从芯片手册中可以看出三个地址,分别是总线地址、ARM虚拟地址、ARM物理地址。我们下面对这三个地址进行一 一介绍。
总线地址,顾名思义,是与总线相关的,就是总线的地址线或在地址周期上产生的信号, 外设使用的是总线地址。
物理地址与总线地址之间的关系是由系统的设计决定的。在x86平台上,物理地址与PCI总线地址是相同的。 在其他平台上,也许会有某种转换,通常是线性的转换。
在存储器里以字节位单位存储信息,为正确地存放或取得信息,每一个字节单元给以一个唯一的存储器地址(Physical Address),又叫实际地址或绝对地址。
我们在对Linux系统中是无法直接对物理地址进行操作的,我们操作的都是虚拟地址,而虚拟地址和物理地址之间存在映射关系,具体如下图所示:
物理内存只有 512MB,虚拟内存有 4GB,那么肯定存在多个虚拟地址映射到同一个物理地址上去,虚拟地址范围比物理地址范围大的问题处理器自会处理。
Linux 内核启动的时候会初始化 MMU
,设置好内存映射,设置好以后 CPU 访问的都是虚拟地址 。
在Linux中,虚拟地址和虚拟地址之间的映射是通过ioremap
和 iounmap
函数实现的。
虚拟地址又称为逻辑地址,基于算法实现的软件层面的地址,物理地址通过MMU(分页管理系统)映射后得到的就是虚拟地址,虚拟地址也是我们在编写程序时使用最多的地址。
我们通过上面的概念可以了解到虚拟地址向物理地址的映射是通过ioremap
和 iounmap
这两个函数实现的。
ioremap 函 数 用 于 获 取 指 定 物 理 地 址 空 间 对 应 的 虚 拟 地 址 空 间 , 定 义 在arch/arm/include/asm/io.h
文件中,定义如下:
#define ioremap(cookie,size) __arm_ioremap((cookie), (size),
MT_DEVICE)
void __iomem * __arm_ioremap(phys_addr_t phys_addr, size_t size,
unsigned int mtype)
{
return arch_ioremap_caller(phys_addr, size, mtype,
__builtin_return_address(0));
}
ioremap 是个宏,有两个参数:cookie
和 size
,真正起作用的是函数__arm_ioremap
,此函数有三个参数和一个返回值,这些参数和返回值的含义如下:
phys_addr:
要映射给的物理起始地址。size:
要映射的内存空间大小。mtype:
ioremap 的类型,可以选择 MT_DEVICE、MT_DEVICE_NONSHARED、MT_DEVICE_CACHED 和 MT_DEVICE_WC,ioremap 函数选择 MT_DEVICE。返回值:
__iomem 类型的指针,指向映射后的虚拟空间首地址。卸载驱动的时候需要使用 iounmap 函数释放掉 ioremap 函数所做的映射,iounmap 函数原型如下:
void iounmap (volatile void __iomem *addr)
iounmap 只有一个参数 addr,此参数就是要取消映射的虚拟地址空间首地址。
关于Linux地址映射的应用大家可以看我另一篇文章:树莓派编写GPIO驱动程序(详细教程)