02.内存管理
# 01.硬件原理
# 1.1 内存分页
CPU 一旦开启MMU(MMU是个硬件分页内存管理单元),负责处理CPU的内存访问请求的计算机硬件
比如CPU就只知道虚拟地址是32位,0x12345670 ,假设每一页的内存分成4K。
0x12345
是页号,作为p
(用0x12345去查一张页表,页表本身在内存)670
是页内偏移,作为d
硬件里有寄存器,记录页表的基地址,每次进程切换时,寄存器就会更新一次,因为每个进程的页表不同
CPU一旦访问虚拟地址,通过页表查到页表项,页表项记录对应的物理地址
虚拟地址
:0x12345 670 --> 1M
物理地址
:1M+670
MMU去访问这个物理地址。
总结
- 一旦开启MMU,CPU只能看到虚拟地址,MMU才能看到物理地址。
- 虚拟地址是指针,物理地址是个整数,内存中的一切均通过虚拟地址来访问。
TLB页表缓存(CPU缓存的一种)
- 去内存里读取页表会比较慢,CPU里有个高速单元TLB,它是页表的高速缓存。
- CPU就不需要在内存里读页表,直接在TLB中读取,从虚拟地址到物理地址的映射。
- 如果TLB中读取不到,才回到内存里读取页表映射,并且在TLB中命中。
# 1.2 页表记录的页权限
cpu虚拟地址,mmu根据cpu请求的虚拟地址,访问页表,查得物理地址。
每个MMU中的页表项,除了有虚拟地址到物理地址的映射之外,还可以标注这个页的
RWX权限
和(用户空间
,内核空间
读取地址的权限)用户一般映射到0~3G,只有当CPU陷入到内核模式,才可以访问3G以上地址。
MMU,会把内核空间3G以上的页表项里的每一行,指定为只有CPU 0环才能访问(特权模式才能访问)
# 1.3 Buddy算法
- linux对空闲内存空间管理采取buddy算法,把内存中所有页面按照2^n划分,其中n=0~5
- 每个内存空间按1个页面、2个页面、4个页面、8个页面、16个页面、32个页面进行六次划分
- 划分后形成了大小不等的存储块,称为页面块,简称页块,包含一个页面的页块称为1页块,包含2个页面的称为2页块,依次类推。
- 每种页块按前后顺序两两结合成一对Buddy“伙伴”,每个页块组用一个双向循环链表进行管理,共有6个链表
- 分别为1、2、4、8、16、32页块链表,分别挂到free_area[] 数组上。
内存分配
- 内存分配时,系统按照Buddy算法,根据请求的页面数在free_area[]对应的空闲页块组中搜索。
- 若请求页面数不是2的整数次幂,则按照稍大于请求数的2的整数次幂的值搜索相应的页面块组。
- 当相应页块组中没有可使用的空闲页面块时就查询更大一些的页块组,在找到可用的页块后分配所需要的页面
- 当某一空闲页面被分配后,若仍有剩余的空闲页面,则根据剩余页面的大小把他们加入到相应页面组中。
内存释放
- 内存页面释放时,系统将其作为空闲页面看待,检查是否存在与这些页面相邻的其他空闲页块
- 若存在,则合为一个连续的空闲区按Buddy算法重新分组
# 02.内存动态申请和释放
# 2.1 Buddy算法问题
分配的粒度太大 buddy算法把空闲页面分成1,2,4页,buddy算法会明确知道哪一页内存空闲还是被占用?
4k,8k,16k 但是无论是在应用还是内核,都需要申请很小的内存
所以需要一种新的机制,将page拆分为更小的单位来管理。
# 2.2 SLAB
slab在最高层是 cache_chain,这是一个 slab 缓存的链接列表
每个缓存都包含了一个 slabs 列表,这是一段连续的内存块(通常都是页面)
- slabs_full:完全分配的 slab
- slabs_partial:部分分配的 slab
- slabs_free:空 slab,或者没有对象被分配
由于对象是从 slab 中进行分配和释放的,因此单个 slab 可以在 slab 列表之间进行移动。
例如,当一个 slab 中的所有对象都被使用完时,就从 slabs_partial 列表中移动到 slabs_full 列表中。
当一个 slab 完全被分配并且有对象被释放后,就从 slabs_full 列表中移动到 slabs_partial 列表中。
当所有对象都被释放之后,就从 slabs_partial 列表移动到 slabs_free 列表中