Linux 开发系列笔记(1.7) - 虚拟内存地址
建站提交历史文章,原文写作时间 2023 年 2 月前后。
虚拟内存地址
整理自:为什么需要虚拟内存?_小林coding的博客-CSDN博客
虚拟地址
为什么需要虚拟地址?
- 在做
单片机
时,因为没有操作系统
的存在,我们通常直接访问物理内存地址
(Physical Memory Address
),程序需要烧录到单片机
中。如果在同一个单片机
中,同时运行两个程序,那么这时就可能会出现各种错误。产生错误的原因就是为两个程序分配了包含相同的物理内存地址
,两者的数据相互干预。 - 现代计算机
应用程序
建立在操作系统
之上,操作系统
为我们完成了这部分冲突的解决,使得多道程序
同时在操作系统
上运行,而使得在应用程序
开发中基本无需关心物理内存地址
冲突的问题。 - 解决这一问题的技术
虚拟内存地址
(Virtual Memory Address
)、以及由此衍生出的内存分段
(Segmentation
)与内存分页
(Paging
)技术,就是本章讲解的内容。需要注意的是,虚拟内存由内核
而无需用户关注,本章讲解的均为原理。
虚拟地址空间
-
虚拟内存映射表
表存储于PCB
中,每个进程都拥有一个虚拟内存映射表
,不同进程的虚拟内存地址
可以相同但物理内存地址
通常不同。 -
在操作文件与内存中得到的所有地址都是
虚拟地址
,物理地址
对于用户是不可见的,也是无需关心的。 -
MMU
:Memory Management Unit
,通过获取来自进程虚拟内存映射表
的信息,用于实现虚拟地址
与物理地址
之间的映射,从而完成CPU
的内存管理请求。 -
虚拟内存映射表
并不是直接给出物理内存地址
,而是通过内存分段
与内存分页
实现,将在后面介绍。
内存分段
内存分段原理
内存分段
是最早的虚拟内存地址
技术,该虚拟地址由段选择因子
、段内偏移量
组成。内存分段
会创建一段连续的长度可变的连续虚拟内存。CPU
中的MMU
获取虚拟地址,并得到段号
与段内偏移量
,段号
将在段表
中查找,获取段基地址
(base
)与段界限
(bound
),通过段基地址
与段内偏移量
获取准确物理地址。。
- 分段机制会将进程的
虚拟地址空间
分为四个段:
内存分段缺陷
- 由于内存分段大小可调节、长连续地址的性质,内存分段往往产生大量
内存碎片
且内存交换效率低。
例如:用户同时执行三个程序:游戏、浏览器、音乐。
- 在此情况下,如果物理内存充足,新进程可以向高地址载入内存,内存碎片一直存在;如果如图内存紧张,会进行
交换
(swap
)操作:将最近最少使用
的段换出
(swap out
)到磁盘,换入
等待使用的段,如果下次需要被换出
的段可以重新换入
。 - 由于内存与磁盘之间的
交换
的IO
操作速度极慢,而段的体积通常很大,内存分段
原理造成的频繁交换
操作很容易成为性能瓶颈。
内存分页
内存分页原理
- 与
内存分段
不同的是,内存分页
(Paging
)具有固定的大小。Linux
中页的大小固定为4KB
。内存分页
下的虚拟地址由页号
与页内偏移量组成
。内存分页
的固定大小减小了交换
体积与内存碎片
。 CPU
中的MMU
获取虚拟地址,并得到页号
与页内偏移量
,页号
将在页表
中查找,通过物理页号
与页内偏移量
计算准确物理地址。。
简单分页缺陷
- 通常每个进程占用内存空间不受限制,直到将内存占满。页表需要为每个进程预留空间足够的大小,对于
32bit
操作系统,地址空间为4GB
,页表一条记录需要占用4B
,每个页指向4KB
空间,因此需要条记录,合计4MB
。如果同时有100
个进程运行在操作系统上,那将占用1/10
的内存资源,这是一个无法接受的内存占用。对于64bit
操作系统,需要支持更多空间,这个问题更加突出。 - 由此产生了
多级页表
,用于解决这个问题。
多级页表
- 以
32bit
系统为例,通常采用二级页表
就能很好的解决上述问题。 一级页表
通过一级页号
指向个二级页号
,由二级页号
再指向物理内存
。指向4GB
的物理内存
需要条二级页号
记录,指向条二级页号
需要条一级页号
记录,可以发现合计4MB+4KB
内存。然而,事实上物理内存不会被完全占用,对于多数进程其占用的内存远小于4GB
,其一级页表
多数是空的,而二级页表
根据一级页表
动态加载。一个二级页表
的大小为4KB
,一个进程通常加载不了几个二级页表
。多个进程不可能同时占用4GB
内存,这样一个进程的均摊页表占用为4KB~8KB
。- 在
64bit
系统中,二级页表
显然不够用,通常使用四级页表
,分别为:- 全局页目录项(
Page Global Directory
) - 上层页目录项(
Page Upper Directory
) - 中间页目录项(
Page Middle Directory
) - 页表项(
Page Table Entry
)
- 全局页目录项(
页表缓存(快表)
- 由于多级页表的存在,页表的层层访问自然减慢了访问效率,因此
页表缓存
(TLB
,Translation Lookaside Buffer
,转址旁路缓存
,后称快表
)被加入。 - 频繁访问的页将被存入
快表
中。TLB
是一个高速缓存(cache
),位于CPU
与MMU
近端。MMU
查找物理内存地址
时首先会在快表
中查找,如未找到再在页表中查找。 - 事实上,
快表
很大程度上提高了物理内存地址
效率,因为常用的页表并不多。
段页式内存管理
内存分段
与内存分页
并不冲突。可以将进程
分为多个有逻辑意义的端(如内存分段
中的四段机制)指向连续的页表,再由页表指向物理内存地址
。
Linux 内存管理
Linux
的内存管理可以认为是分页式存储
,但不可避免的涉及了分段式存储
,这与Intel
处理器的发展有关。- 如今被广泛应用的
X86
处理器构架在Intel 80286
使用的是分段式存储
,由于不能满足需求,Intel 80386
很快使用了分页式存储
。但是Intel 80386
实际使用的是段页式存储
,虚拟内存地址
首先通过段表映射到页表
,再由页表
映射到物理内存地址
。 - 内核的开发需要处理器的支持,
Linux
中主要使用分页式存储
,由于历史原因,Linux
不得不处理段表映射。 - 事实上,在
Linux
中,每个段都是段基地址
为0
,段界限
为4GB
(32bit
系统)的,这样实际上跳过了段机制。在Linux
中,段机制只被用于访问控制与内存保护。
- 栈空间从高地址向低地址占用,堆空间从低地址向高地址占用。
评论