内存管理

单片机的 CPU 是直接操作内存的「物理地址」

前提每个进程都不能访问物理地址,至于虚拟地址最终怎么落到物理内存里,对进程来说是透明的

操作系统会提供一种机制,将不同进程的虚拟地址和不同内存的物理地址映射起来。

内存管理单元(MMU

内存分段和内存分页

段选择子和段内偏移量

[插图]

段表里面保存的是这个段的基地址、段的界限和特权等级等

段基地址加上段内偏移量得到物理内存地址

不足之处

内存碎片

内存交换的效率低

外部内存碎片,也就是产生了多个不连续的小物理内存,导致新的程序无法被装载;内部内存碎片,程序所有的内存都被装载到了物理内存,但是这个程序有部分的内存可能并不是很常使用,这也会导致内存的浪费;

解决外部内存碎片的问题就是内存交换。

这个内存交换空间,在 Linux 系统里,也就是我们常看到的 Swap 空间,这块空间是从硬盘划分出来的,用于内存与硬盘的空间交换。

如果内存交换的时候,交换的是一个占内存空间很大的程序,这样整个机器都会显得卡顿

分页是把整个虚拟和物理内存空间切成一段段固定尺寸的大小

在 Linux 下,每一页的大小为 4KB

采用了分页,那么释放的内存都是以页为单位释放的,也就不会产生无法给进程使用的小内存。

也就是暂时写在硬盘上,称为换出(Swap Out)。一旦需要的时候,再加载进来,称为换入(Swap In)。所以,一次性写入磁盘的也只有少数的一个页或者几个页,不会花太多时间,内存交换的效率就相对比较高。

只有在程序运行中,需要用到对应虚拟内存页里面的指令和数据时,再加载到物理内存里面去

把虚拟内存地址,切分成页号和偏移量;根据页号,从页表里面,查询对应的物理页号;直接拿物理页号,加上前面的偏移量,就得到了物理内存地址。

多级页表

局部性原理

如果某个一级页表的页表项没有被用到,也就不需要创建这个页表项对应的二级页表了,即可以在需要时才创建二级页表

页表一定要覆盖全部虚拟地址空间,不分级的页表就需要有 100 多万个页表项来映射,而二级分页则只需要1024 个页表项

TLB

在 CPU 芯片中,加入了一个专门存放程序最常访问的页表项的 Cache,这个 Cache 就是 TLB(Translation Lookaside Buffer) ,通常称为页表缓存、转址旁路缓存、快表等。

有了 TLB 后,那么 CPU 在寻址时,会先查 TLB,如果没找到,才会继续查常规的页表。

TLB 的命中率

段页式内存管理

段页式地址变换中要得到物理地址须经过三次内存访问:第一次访问段表,得到页表起始地址;第二次访问页表,得到物理页号;第三次将物理页号与页内位移组合,得到物理地址。

页式内存管理的作用是在由段式内存管理所映射而成的地址上再加上一层地址映射。

程序所使用的地址,通常是没被段式内存管理映射的地址,称为逻辑地址;通过段式内存管理映射的地址,称为线性地址,也叫虚拟地址;逻辑地址是「段式内存管理」转换前的地址,线性地址则是「页式内存管理」转换前的地址。

Linux 内存主要采用的是页式内存管理,但同时也不可避免地涉及了段机制

Linux 系统中的每个段都是从 0 地址开始的整个 4GB 虚拟空间(32 位环境下),也就是所有的段的起始地址都是一样的。这意味着,Linux 系统中的代码,包括操作系统本身的代码和应用程序代码,所面对的地址空间都是线性地址空间(虚拟地址),这种做法相当于屏蔽了处理器中的逻辑地址概念,段只被用于访问控制和内存保护。

进程在用户态时,只能访问用户空间内存;只有进入内核态后,才可以访问内核空间的内存;

虽然每个进程都各自有独立的虚拟内存,但是每个虚拟内存中的内核地址,其实关联的都是相同的物理内存。这样,进程切换到内核态后,就可以很方便地访问内核空间内存。

多进程环境

为每个进程独立分配一套虚拟地址空间

于是操作系统会通过内存交换技术,把不常使用的内存暂时存放到硬盘(换出),在需要的时候再装载回物理内存(换入)。

根据程序的局部性原理,在 CPU 芯片中加入了 TLB,负责缓存最近常被访问的页表项,大大提高了地址的转换速度