一、前言
对于刚开始接触计算机体系架构的同学,各种地址很难分辨清楚,本文总结在操作系统中常用的各种地址,以及他们之间的转换关系,供以后复习使用
二、各种地址总结
物理地址Physical Addr
物理地址内核空间表示为phy_addr_t
;
- 作用
内存在总线上的实际地址,未开启MMU的CPU,或者MMU可以通过物理地址访问内存。
地址总线设置为物理地址,数据总线便能够访问到物理地址对应的内存空间。
虚拟地址Virtual Addr
内核空间直接用(void *)
表示,是一个unsigned long
的数
用户空间的虚拟地址空间,在内核中表示为struct vm_area_struct
- 作用
开启MMU之后的CPU,使用的均是虚拟地址。
CPU地址总线访问的虚拟地址,将传输到MMU,MMU查找页表,将其转换成为物理地址,间接访问实际的内存空间。 - 32位虚拟地址的位含义
虚拟地址在32位系统中,共32位,从高到底分为四层含义PGD
、PMD
、PTE
、OFFSET
PGD
:占10bit,表示当前页表寄存器(CR3)指向的地址,含有1024个pgd_t
。
PMD
:占0bit,表示pgd_t中仅含有1个pmd_t
。
PTE
:占10bit,表示pmd_t中含有1024个pte_t
。
OFFSET
:占12bit,表示一个pte_t大小为4096 bits。
可以看出,寻址过程总是利用上级目录提供一个表,再利用虚拟地址中的地址,查询到下级目录表的物理地址,并进行递归。 - 64位虚拟地址
64位虚拟地址与32位类似,但是支持更多的位,也就支持更大的内存空间。
并且还有部分bits处于未定义的状态,为未来的系统发展预留空间。
逻辑地址
程序编译出来时,所有的段segement,地址都是从零开始的,这个地址称为逻辑地址
线性地址
程序最终链接时,所有的逻辑地址,都需要加上自己所在的段偏移地址,便得到线性地址,这个线性地址在Linux中也就是虚拟地址
值得注意的是,逻辑地址与线性地址,都是X86-32中独有的说法,其他很多体系架构并没有这两个东西,Linux只关心虚拟地址。
总线地址
总线地址在内核空间表示为dma_addr_t
,相当于DMA设备眼中的物理地址。
在深入理解Linux内核
中,有这样一句话
在PC体系结构中,总线地址和物理地址是一致的,但是在其他的体系结构中,这两种地址可能是不同的。
在DMA
操作中,数据不需要CPU
的参与,I/O
设备与DMAC
直接驱动数据总线,因此内核开始DMA
操作时,必须把所涉及的内存缓冲区总线地址写入DMAC
适当的I/O
端口,或者写入I/O
设备适当的I/O
端口。
在Linux内核中,表示总线地址的就是dma_addr_t
。尽管dma_addr_t很多时候和物理地址相同。
页表Page Table
我们通常意义上的一级页表,就是指数据结构pte_t
。
页表的具体实现,与计算机体系架构有关,这里我们列出一个比较典型的3级页表的例子
/* include/asm-generic/page.h */
/* pte_t --> Page Table Entry,页表项 */
typedef struct {
unsigned long pte;
} pte_t;
/* pmd_t --> Page Middle Entry, 页中间目录项 */
typedef struct {
unsigned long pmd[16];
} pmd_t;
/* pgt_t --> Page Global Entry, 页全局目录项 */
typedef struct {
unsigned long pgd;
} pgd_t;
/* 用于表示页表属性flag的数据结构,仅低12bits有效,对应pte_t的低12位,仅仅是方便编程 */
typedef struct {
unsigned long pgprot;
} pgprot_t;
typedef struct page *pgtable_t;
- 作用
供MMU
读取并查询,以便将虚拟地址,转换为物理地址。
页表由CPU
写,由MMU
读,存放在内存RAM
中。 pte_t
格式
从数据结构可以看出,页表pte_t
也就是一个unsigned long
,在32bits系统中也就是32位;
它的高20bits代表页面号,页面号左移12位,再加上一个偏移值,就可以得到页面的物理地址。
它的低12bits是一些页面标志,如我们常见的读、写、执行权限等。- 缺页异常
pte_t
低12位的标志位中,包括一个bit-PTE_V
,用于指示页表是否存在,当MMU解析虚拟地址时,如果PTE_V
指示页表不存在,则MMU会向CPU发出page_fault
异常。 pgd_t
与pmd_t
格式
与pte_t
类似,pgd_t
与pmd_t
两者的高20bits都是一个物理页面号,低12bits表示标志位。
pgd_t
中的页面号,指向了一个pmd_t
表所在的物理页面。
pmd_t
中的页面号,指向了一个pte_t
表所在的物理页面。- 四级页表
额外增加一层页表PUD
,位于PGD
与PMD
之间,原理类似,不多言。 - 为什么多级页表可以节省空间?
因为一个进程,大多数页面通常是不使用的。
比如1024个pte_t
,就要占1024个字节。
如果采用了pmd_t
,那么1024个不使用的pte_t
,可以归纳为1个不使用的pmd_t
,仅占一个字节。由于pmd_t
是不使用的,因此pmd_t
下面可以暂时不分配pte_t
数据结构,也就起到了节约空间的作用。
值得注意的是,如果1024个pte_t
都是被使用的,那么pmd_t
就无法起到节省空间的作用了,相反,还会增加pmd_t
本身1个字节的开销。
page
/* include/linux/mm_types.h */
struct page {
/* 内容较多,仅列部分 */
};
- 作用
管理每个物理页状态以及其他属性,一页的大小为4096 Bytes。 - 存放位置
存放在内存中。根据体系结构够不同,存放的具体位置也不同。
最简单的FLATMEM
中,所有的page存放在一个全局变量struct page *mem_map
中。
mem_map
数组,存放在内存的线性映射区,只需要加上地址偏移,便可直接得到物理地址。 - 和各种地址的关系
page是用来管理物理的的数据结构,内核使用伙伴系统分配的,就是这个数据结构。与各种地址没有直接关系。
三、总结
本文梳理了各项计算机术语种出现的地址,分析了他们与页表之间的关系。