操作系统-小林coding-内存管理
本系列笔记为作者在跟随小林coding学习的时候做的笔记。感谢小林大大。
为什么要有虚拟内存
操作系统为每个进程分配独立的「虚拟地址」。
操作系统会提供一种机制,将不同进程的虚拟地址和不同内存的物理地址映射起来。
- 程序所使用的内存地址叫做虚拟内存地址(Virtual Memory Address)
- 实际存在硬件里面的空间地址叫物理内存地址(Physical Memory Address)
映射过程主要有两种方式,分别是内存分段
和内存分页
内存分段
分段机制下的虚拟地址由两部分组成,段选择因子和段内偏移量
虚拟地址是通过段表与物理地址进行映射的
不足:内存碎片,内存交换的效率低(需要将整段写入换出)
内存分页
分页是把整个虚拟和物理内存空间切成一段段固定尺寸的大小(页)
虚拟地址与物理地址之间通过页表来映射
内存管理单元 (MMU)将虚拟内存地址转换成物理地址
当进程访问的虚拟地址在页表中查不到时,系统会产生一个缺页异常,进入系统内核空间分配物理内存、更新进程页表,最后再返回用户空间,恢复进程的运行。
采用了分页,页与页之间是紧密排列的,所以不会有外部碎片。内存分页机制会有内部内存碎片。
内存空间不够操作系统将正在运行的进程中「最近没被使用」的内存页面暂时写在硬盘上,称为换出(Swap Out)。需要的时候,再加载进来,称为换入(Swap In)。
一次性写入磁盘只有少数的一个页或者几个页,内存交换的效率相对段式较高。只有在程序运行中,需要用到对应虚拟内存页里面的指令和数据时,再加载到物理内存里面去。
在分页机制下,虚拟地址分为两部分,页号和页内偏移
内存地址转换三个步骤:
- 虚拟内存地址,切分成页号和偏移量
- 根据页号,从页表里面,查询对应的物理页号
- 拿物理页号,加上前面的偏移量,就得到了物理内存地址
简单的分页进程的页表空间开销会很大
多级页表
局部性原理
对于 64 位的系统,两级分页肯定不够了,就变成了四级目录
TLB
把最常访问的几个页表项存储到访问速度更快的硬件 TLB(Translation Lookaside Buffer) ,通常称为页表缓存、转址旁路缓存、快表等
段页式内存管理
- 先将程序划分为多个有逻辑意义的段
- 再把每个段划分为多个页
逻辑地址结构就由段号、段内页号和页内位移三部分组成
Linux 内存布局
Linux 内存主要采用的是页式内存管理,但同时也不可避免地涉及了段机制
Linux 系统中操作系统本身和应用程序每个段都是从 0 地址开始的整个 4GB 虚拟空间(32 位环境下)。所以,地址空间都是线性地址空间(虚拟地址),这相当于屏蔽了处理器中的逻辑地址概念,段只被用于访问控制和内存保护。
虚拟地址空间内部被分为内核空间和用户空间两部分,不同位数的系统,地址空间的范围也不同
内核空间与用户空间的区别:
- 用户态,只能访问用户空间内存
- 内核态才可以访问内核空间的内存
每个虚拟内存中的内核地址,其实关联的都是相同的物理内存
用户空间分布的情况
- 代码段,包括二进制可执行代码
- 数据段,包括已初始化的静态常量和全局变量
- BSS 段,包括未初始化的静态变量和全局变量
- 堆段,包括动态分配的内存,从低地址开始向上增长
- 文件映射段,包括动态库、共享内存等,从低地址开始向上增长(跟硬件和内核版本有关 (opens new window))
- 栈段,包括局部变量和函数调用的上下文等。栈的大小是固定的,一般是 8 MB。系统也提供了参数,以便我们自定义大小
代码段下面还有一段内存空间的(灰色部分),这一块区域是「保留区」。较小数值的地址不是一个合法地址,例如,我们通常在 C 的代码里会将无效的指针赋值为 NULL。因此,这里会出现一段不可访问的内存保留区,防止程序因为出现 bug,导致读或写了一些小内存地址的数据,而使得程序跑飞。
虚拟内存有什么作用
- 运行超过物理内存大小的程序
- 进程隔离
- 标记属性的比特控制内存访问