在DOS时代,计算机的内存还非常珍贵,容量也小,一般以K字节为单位。程序猿想写好程序,就得利用好每一份资源。
▲微软在DOS下开发的Shell
随着计算机技术的发展,由于桌面应用图形化的需求、视频图像技术的升级、以及互联网的快速普及,随之而来的是内存、磁盘的容量等计算机硬件的升级需求变得迫切;同时代码的业务逻辑也变得非常复杂,程序的体积呈指数级增长。
但早期硬件的迭代速度不如软件快,所以为了应对越来越庞大的程序,程序猿们通过ovelay-覆盖块技术,对程序进行分割,分段在操作系统上运行。
不过,花精力给程序分段,是一件复杂而繁琐的事情。所以,在技术上就要有所改进,来避免这种体力劳动。与此同时,一起推动内存管理技术发展的,还有虚拟化技术的演进。
PRINT(0)
早期虚拟化技术中的硬件虚拟化方向,在狭义上就是对内存、硬盘等硬件做虚拟化处理(软件为主),配合操作系统以达到分时复用的效果。
1964 年,IBM 推出了大名鼎鼎的 System/360。它不仅提供了新型的操作系统,还实现了基于全硬件虚拟化(Full Hardware Virtualization)的虚拟机解决方案。
▲图源网络
其中就包括:页式虚拟内存(4k 分页虚拟存储系统),虚拟磁盘以及 TSS 分时系统。System/360 最多可提供 14 个虚拟机,每个虚拟机具有 256k 固定虚拟内存。
有意思的是,System/360 的开发过程被视为了计算机发展史上最大的一次豪赌,为了研发 System/360,IBM 决定征召六万多名新员工,创建了五座新工厂。但即便如此,当时的出货时间仍被不断顺延。
PRINT(1)
现代操作系统,衍生出了一种更先进的内存管理技术——虚拟内存系统。虚拟内存是对主存的一种抽象概念。它由硬件异常、物理地址翻译、主存、磁盘文件和内核软件相互配合共同组成。
虚拟内存系统主要提供了三种能力:
一、提供主存的高速缓存,加快内存访问速度(高速缓存);
二、为每个进程提供一致的地址空间,从而简化内存管理(内存管理);
三、防止每个进程的地址空间被其他进程所破坏(内存保护)。
这里的映射原理是:CPU会生成一个虚拟地址(Virtual Address)来访问主存。访问之前,需要先将虚拟地址转换为物理地址,这个过程称作为地址转换(地址映射/地址翻译)。
为进行此操作,需要CPU硬件和操作系统合作,通过内存管理单元(Memory Management Unit)上的地址翻译硬件,来完成虚拟地址到物理地址的转换。
MMU利用存储在主存上的查询表(translation table)来翻译虚拟地址,该表的内容由操作系统维护和管理。
PRINT(2)
虚拟内存系统将虚拟内存划分为固定大小的块,这个块我们称作为虚拟页(Virtual Page简称VP),同理将物理内存划分为物理页(Physical Page简称PP),也叫页帧(Page Frame)。
至于为什么叫页,和我们查字典的原理差不多。
我们查找字典里对应的内容,一般要先查目录,根据章节和页码,找到对应的页;再根据页里的小节和标号,找到对应的行,甚至是字。
虚拟页就是“目录”,物理页就是“书页”。目录与书页,建立了一一映射的关系。但实际上,相同的虚拟页是可以映射到不同的物理页上(或者两者反过来)。
如果有一个32位的操作系统,虚拟地址空间可以支持4GB;假设只有1G主存,物理地址空间可以支持1GB。
设置每个页面的大小为4KB,那么根据上图所示,这个操作系统的虚拟地址页号有20位[31:12],物理页号有18位[29:12]。
为了把4G虚拟地址空间,映射到1G主存上面,我们必须有某种规则(页表),并且将它缓存在DRAM中的某一个地方。
将虚拟页映射到物理页是通过查询页表实现的,一个页表是由页表条目(Page Table Entry简称PTE)的数组构成。下图是虚拟地址如何通过页表,来映射到物理地址的流程图。
▲图源自嵌入式客栈
上述还引出了DRAM的概念,除此还有SRAM。一般cache由SRAM构成,主存由DRAM构成。DRAM的速度更快,要比SRAM快很多,但是制作工艺复杂,所以容量比较小,一般以M位单位。
PRINT(3)
现代加快虚拟内存翻译的技术,是通过内存管理单元(Memory Management Unit)简称MMU,代替软件实现查表翻译的行为。
MMU是一种硬件电路单元,负责将虚拟内存地址转换为物理内存地址。所有的内存访问,除非没有使能,都将通过MMU进行转换。
操作系统和MMU共同维护一块内存空间,用来管理需要翻译的地址。这块空间被称作转译后背缓冲器(Translation Lookaside Buffer),简称TLB。其本质是MMU用于虚拟地址到物理地址转换表的缓存。
一般由内核软件负责对页表进行维护,MMU负责查表翻译。这种情况下,MMU直接访问页表,页表格式严格依赖硬件设计。
页表与TLB的关系,就像是主存与cache。TLB又被称为快表。当CPU给MMU传递新的虚拟地址之后,MMU先去问TLB那边有没有,如果有,就直接将物理地址发给内存总线。
TLB容量比较小,难免发生Cache Miss,这时候MMU再去页表中找,找到之后MMU除了把地址发到内存总线,还把这条映射关系给到TLB,让它记录一下刷新缓存。
具体示意图如下:
▲图源自知乎:一个会写诗的程序员
PRINT(4)
实际上,操作系统并不会直接把虚拟地址完全映射到物理地址上面。
以Linux为例,虚拟地址空间被分为内核空间和用户空间两部分。只有那些实际使用的虚拟内存才会被分配物理内存。
当进程访问它的虚拟地址空间中的某一页时,如果这个数据页还不在物理内存中,CPU是不能工作的,此时Linux将产生一个hard page fault中断。
该中断将做这些事情:一、通知系统从慢速设备(如磁盘)将对应的数据页读入物理内存;二、建立物理内存地址与虚拟地址空间页的映射关系;三、告诉进程可以访问这部分虚拟地址空间的内存。
linux的page fault 又分为三种:minor page fault、major page fault、invalid(segment fault)。
一般MMU支持的fault类型有很多,但是操作系统只会支持其中的几种。
minor/soft page fault:
指需要访问的内存不在虚拟地址空间,但是在物理内存中,只需要MMU建立物理内存和虚拟地址空间的映射关系即可。
当一个进程在调用 malloc 获取虚拟空间地址后,首次访问该地址会发生一次soft page fault。
通常是多个进程访问同一个共享内存中的数据,当某些进程还没有建立起映射关系,访问时也会出现soft page fault。
major/hard page fault:
指需要访问的内存不在虚拟地址空间,也不在物理内存中,需要从慢速设备载入。
从磁盘swap in回到物理内存就是hard page fault。
swap out:当物理内存不够时,把一些物理内存page中的内容写入到磁盘,以腾出一些空闲的page;
swap in:当CPU要执行的指令被发现已经swap out到了磁盘中,这时就需要从磁盘把这些指令再swap in到物理内存中。
注意:swap in和swap out的操作都比较耗时。
invalid (segment fault):
指进程需要访问的内存地址不在它的虚拟地址空间范围内,属于越界访问,内核就会报 segment fault 错误。
造成 segment fault 的原因可能有以下几种:
堆栈溢出:内存访问越界,如数组下表错误访问越界;使用strcpy/strcat/sprintf/strcmp等字符串操作函数导致的读写越界;
非法指针:野指针,错误的指针转换等;多线程读写的数据未加锁保护,或使用了线程不安全的函数。
PRINT(5)
SkyEye支持多种体系架构处理器的MMU,并且引入了cache和TLB,配合SkyEye的动态翻译技术,可以大大加快MMU的翻译速度,能够获得与真实硬件相近的机器性能。