内存管理 #
一、虚拟内存思想 #
我们可以把进程所使用的地址「隔离」开来,即让操作系统为每个进程分配独立的一套虚拟地址,人人都有,大家自己玩自己的地址就行,互不干涉。虚拟地址最终怎么落到物理内存里,对进程来说是透明的,操作系统已经把这些都安排的明明白白了(MMU)。
二、内存分段 #
1、机制 #
虚拟地址和物理地址之间通过段表(段基地址+段大小)来映射。
分段机制会把程序的虚拟地址分成 4 个段:代码分段、数据分段、堆段、栈段。
2、不足 #
内存碎片问题(不连续的小物理内存,浪费) -> 并因此有内存交换的效率低的问题。
三、内存分页 #
1、分页机制 #
分页是把整个虚拟和物理内存空间切分成一页一页,固定尺寸,Linux下一页是4KB。
虚拟地址与物理地址之间通过页表来映射。
2、多级页表 #
页表一定要覆盖全部虚拟地址空间,不分级的页表就需要有 100 多万个页表项来映射,而二级分页则只需要 1024 个页表项。(此时一级页表覆盖到了全部虚拟地址空间,二级页表在需要时创建)。
3、缺页异常 #
而当进程访问的虚拟地址在页表中查不到时,系统会产生一个缺页异常,进入系统内核空间分配物理内存、更新进程页表,最后再返回用户空间,恢复进程的运行。
- 最近最久未使用 LRU
- 先进先出
- 第二次机会算法
4、快表 #
局部性原理。
- 根据逻辑地址,得到页号+页内偏移,去快表里面,看是否命中。
- 如果命中,取出对应的内存块号,和页内偏移拼接得到物理地址。即:只需一次访存。
- 如果没命中,访问内存中的页表,找到对应表项,最后得到物理地址,访问内存。即:两次访存。同时,还要存入快表,如果满了,就进行替换。
5、好处和优点 #
- 解决了内存碎片问题
- 交换效率也更高
- 不需要一次性全部加载到物理内存
四、段页式内存管理 #
先将程序划分为多个有逻辑意义的段,也就是前面提到的分段机制;
接着再把每个段划分为多个页。
这样,地址结构就由段号、段内页号和页内位移三部分组成。
段页式地址变换中要得到物理地址须经过三次内存访问:
第一次访问段表,得到页表起始地址;
第二次访问页表,得到物理页号;
第三次将物理页号与页内位移组合,得到物理地址。
五、Linux 内存管理 #
内核空间 和 用户空间两部分:
虽然每个进程都各自有独立的虚拟内存,但是每个虚拟内存中的内核地址,其实关联的都是相同的物理内存。这样,进程切换到内核态后,就可以很方便地访问内核空间内存。且所有段的起始地址都一样。(共享和保护)
通过这张图你可以看到,用户空间内存,从低到高分别是 7 种不同的内存段:
程序文件段,包括二进制可执行代码;
已初始化数据段,包括静态常量;
未初始化数据段,包括未初始化的静态变量;
堆段,包括动态分配的内存,从低地址开始向上增长;
文件映射段,包括动态库、共享内存等,从低地址开始向上增长(跟硬件和内核版本有关)
栈段,包括局部变量和函数调用的上下文等。栈的大小是固定的,一般是 8 MB。当然系统也提供了参数,以便我们自定义大小;
在这 7 个内存段中,堆和文件映射段的内存是动态分配的。比如说,使用 C 标准库的 malloc() 或者 mmap() ,就可以分别在堆和文件映射段动态分配内存。
- 比较
- 对程序员的透明姓
- 地址空间的维度
- 大小是否可改变
- 作用:虚拟内存更大空间;逻辑独立共享保护