一. 地址类型
1. 用户虚拟地址(Virtual Memory Address)
用户空间程序所能看到的常规地址,32位或者64位。每个进程都有自己的虚拟地址空间,空间分为若干区域/段,/proc/pid/maps 可查看,通过 VMA 管理。
进程的内存映射至少包含如下区域:
a. text 段,即程序的可执行代码段;
b. data 段,包含初始化数据、非初始化数据和堆栈
c. 与每个活动的内存映射对应的区域,即 mmap 的区域
2. 物理地址
在处理器和系统内存之间使用,也是 32 位或者 64 位的,以页为单位进行管理。
3. 内核逻辑地址
内核逻辑地址组成了内核的常规地址空间,内核使用逻辑地址来引用物理内存中的页。该地址映射了部分或者全部的内存,并经常被视为物理地址。在大多数体系结构上,逻辑地址和与其关联的物理地址的区别,仅仅是它们之间存在一个固定的偏移量。逻辑地址使用硬件内建的指针大小,因此在安装了大量内存的 32 位系统中,它无法寻址全部的物理内存。逻辑地址通常保存在 unsigned long 或者 void * 这样类型的变量中。kmalloc 返回的内存就是内核逻辑地址。
我的理解,就像用户空间无法直接访问物理内存,内核也一样,需经过一层映射,换成官方说法:内核无法直接操作没有映射到内核地址空间的内存。不同的是,用户空间的映射是非线性的,而内核空间的逻辑地址则是线性的。所以,kmalloc 返回的不是直接的物理地址,而是内核逻辑地址。
内核逻辑地址与物理地址的相互转换:
__pa():返回某个逻辑地址对应的物理地址 physics address
__va():将物理地址逆向映射到逻辑地址,但只对低端内存有效 virtual address,其实应该是 logic address
#define __pa(x) ((unsigned long)(x) & 0x7fffffff)
#define __va(x) ((void *)((unsigned long)(x) | 0x80000000))
从这个定义来看,内核逻辑地址在物理地址的基础上增加了一个偏移,所以,内核逻辑地址的低位变成了保留位,难道是给 DMA 使用的?
4. 内核虚拟地址
与前者的相同之处在于,都将内核空间的地址映射到物理地址上。区别是,内核虚拟地址与物理地址的映射不必是线性的和一对一的,而是类似于用户虚拟地址的映射方式。所有的逻辑地址都是内核虚拟地址,但是许多内核虚拟地址不是逻辑地址。例如 vmalloc 分配的内存都是虚拟地址,kmap 也返回一个虚拟地址。虚拟地址通常保存在指针变量中。