物理地址、线性地址、虚拟地址和逻辑地址以及页表

物理地址

用于芯片级内存单元寻址,对应着与CPU相连的地址总线。地址总线宽度决定了系统寻址能力,比如32位总线地址的寻址为2^32,即4G。但对于64位总线地址,那就是2^64,在目之所及的将来也是足够的。以系统启动时打印的E820表为例,该系统物理地址范围是0x0000000000000000-0x000000023fffffff,对应的物理内存大概是8G的空间,中间还有一些IO口和PCI设备占据一定空间,具体可以查看/proc/iomem。

BIOS-provided physical RAM map:
BIOS-e820: [mem 0x0000000000000000-0x000000000009fbff] usable   ---630K
BIOS-e820: [mem 0x000000000009fc00-0x000000000009ffff] reserved ---1K
BIOS-e820: [mem 0x00000000000f0000-0x00000000000fffff] reserved ---63K
BIOS-e820: [mem 0x0000000000100000-0x00000000bffd9fff] usable   ---3070M
BIOS-e820: [mem 0x00000000bffda000-0x00000000bfffffff] reserved ---151K
BIOS-e820: [mem 0x00000000feffc000-0x00000000feffffff] reserved ---15K
BIOS-e820: [mem 0x00000000fffc0000-0x00000000ffffffff] reserved ---255K
BIOS-e820: [mem 0x0000000100000000-0x000000023fffffff] usable   ---5119M

但是实际程序并不是使用物理地址来访问,如果使用物理地址,每个进程都可以操作同一个地址,那就无法保证多个进程的隔离性;同时也十分容易把内核搞崩。

线性地址(即虚拟地址)

线性地址,即虚拟地址,对应的是虚拟内存。在虚拟内存下,每个进程都有相同的虚拟地址空间,但是实际对应的物理页面是不同的。有了虚拟内存这一层封装,程序就不需要关注具体物理内存位置,专注自己的逻辑即可,因而程序运行时都是通过虚拟地址来访问指令和数据。

比如符号表/boot/System.map中记录的就是内核函数的虚拟地址,我们调用函数时,最终就是到对应的内存地址上获取指令。

ffffffff81a7b700 d traceon_count_probe_ops
ffffffff81a7b720 d func_opts
ffffffff81a7b740 d func_flags
ffffffff81a7b760 d tracer_flags
ffffffff81a7b780 d trace_opts
ffffffff81a7b7a0 d wakeup_prio

逻辑地址

包含在机器语言指令中,用来指定操作数或者操作指令的地址,每个逻辑地址由一个段和偏移量组成。引入逻辑地址是为了兼容远古时代的段式内存管理方式。那时还是直接使用物理地址,为了支持多进程并发,需要对各个进程重定位。目前的系统已经不需要通过这种方式。

物理内存的管理

面对几G甚至几T的内存,为了便于管理,我们把物理内存按照某个大小(一般是4K)来分割,分而治之,形成一个个页帧。而虚拟内存层面,则用页来表示。那这中间的映射关系维护,就需要页表来完成。

页表

用来将虚拟地址空间映射到物理地址空间的数据结构称为页表。但对虚拟地址空间中的每一页,都分配一个数组项也会有问题,我们以32位系统为例,虚拟地址空间为2^32,即4G,按每页4K大小,需要4*1024*1024/4,约100万,更不用说大得多的64位虚拟地址空间。

由于虚拟地址空间的大部分区域都没有使用,也就不需要关联到页帧,那么就可以使用功能相同,但内存用量少得多的模型:多级分页。

目前,64位Linux系统采用四级分页,

  • 页全局目录(Page Global Directory)
  • 页上级目录(Page Upper Directroy)
  • 页中间目录(Page Middle Directory)
  • 页表(Page Table)

物理地址、线性地址、虚拟地址和逻辑地址以及页表_第1张图片但并不是64位都用来寻址,我们只使用其中的48位,寻址空间为2^48,即256TB。在x86_64平台下,线性地址分级为9+9+9+9+12,对应的bit shift如下:

#define PGDIR_SHIFT		39
#define PUD_SHIFT		30
#define PMD_SHIFT		21
#define PAGE_SHIFT		12

对于进程,每一个进程有它自己的页全局目录和自己的页表集,当发生进程切换时,系统把cr3控制寄存器的内容保存在前一个执行进程的描述符中,然后把下一个要执行的进程的描述符的值装入cr3寄存器中。因此,当新进程重新开始在CPU上执行时,分页单元指向一组正确的页面。

MMU与TLB

通过多级页表我们可以节省大量内存,但是每次访问内存时,必须逐级访问多个数组才能将虚拟地址转换为物理地址。 针对这个问题,CPU引入两种方法来加速该过程,

  • MMU(Memory Management Unit),专门处理地址转换的单元,该单元优化了内存访问操作
  • TLB(Translation Lookaside Buffer),地址转换中出现最频繁的地址,会被保存到CPU高速缓存中,也就是TLB——地址转换后备缓冲器。这样就无需访问内存中的页表即可从高速缓存直接获得地址,从而加速地址转换

你可能感兴趣的:(内存管理,虚拟地址,页表,MMU,TLB,内存管理)