3.1说说什么是虚拟内存?
单片机是没有操作系统的,所以每次写完代码,都需要借助工具把程序烧录进去,这样程序才能跑起来。另外,单片机的CPU是直接操作内存的【物理地址】。在这种情况下,要想在内存中同时运行两个程序是不可能的。如果第一个程序在2000的位置写入一个新的值,将会擦掉第二个程序放在相同位置上的所有内容,所以同时运行两个程序是根本行不通的,这个程序会立刻崩溃。
【操作系统是如何在内存中运行多个程序的?】
操作系统为每个进程分配一套独立的【虚拟地址】,每个进程都玩自己的,互不干涉,并且每个进程都不能访问物理地址,至于虚拟地址最终是怎么落在物理内存上的,对进程来说是透明的,操作系统把这些都安排了。
操作系统提供了一种机制,将不同进程的虚拟地址和不同内存的物理地址映射起来,如果程序要访问虚拟地址,由操作系统转换成不同的物理地址,这样不同的进程运行的时候,写入的是不同的物理地址,这样就不用冲突了。所以这就引出了两种地址的概念:
- 虚拟内存地址:我们程序所使用的内存地址;
- 物理内存地址:实际存在硬件里面的空间地址;
操作系统引入了虚拟内存,进程持有的虚拟地址会通过CPU芯片中的内存管理单元(MMU)的映射关系,来转换变成物理地址,然后再通过物理地址访问内存;
3.2说说什么是内存分段?
程序由若干个逻辑分段组成,比如可以分成代码段、数据段、栈段、堆段之类的。不同的段有不同的属性,用分段的形式把这些端分离出来。虚拟地址是通过段表与物理地址进行映射的,分段机制会把程序的虚拟地址分成4个段,每个段在段表中有一个项,在这一项找到段的基地址,再加上偏移量,就能找到物理内存中的地址。
分段机制虽然好,但是也有一些不足之处:
(1)内存碎片的问题;
(2)内存交换的效率低的问题;
【分段为什么会产生内存碎片的问题?】
举个例子,假设有1G的物理内存,用户执行了多个程序,其中:游戏占用了512MB内存、浏览器占用了128MB、音乐占用了256MB内存。这个时候,如果我们关闭了浏览器,空闲内存还剩下1024-512-256=256MB内存,如果这个256MB不是连续的,被分成了两段128MB内存,就会导致没有空间再打开一个200MB的程序。
外部内存碎片:产生了多个不连续的小物理内存,导致新的程序无法被装载进内存;
内部内存碎片:程序所有的内存都被装载到了物理内存,但是这个程序有部分内容可能并不常使用,导致内存的浪费;
【内存碎片的解决方式】
解决外部内存碎片的问题就是内存交换。我们可以把分割成两段的128MB内存写到硬盘上,然后再从硬盘读回到内存中。形成一片连续的内存空间,在Linux系统中,swap分区是从硬盘划分出来的,它就是用于内存和硬盘的空间交换。
【为什么分段会导致内存交换效率低?】
对于多进程的系统来说,用分段的方式是很容易产生内存碎片的,产生了内存碎片那就必须使用swap的方式来进行空间交换,这个过程是会产生性能瓶颈的。因为硬盘的访问速度比内存慢太多了,而且如果交换的是一个占用内存很大的程序,我们就需要把一大段内存数据写到硬盘上,然后再从硬盘上读回内存。
3.3说说什么是内存分页?
分段的好处是能产生连续的内存空间,但是会出现内存碎片和内存交换的空间太大的问题。要解决这个问题,就要尽可能少出现一些内存碎片,另外如果需要进行内存交换,需要让写入磁盘、装载内存的数据更少一点。内存分页就能解决这个问题。
分页是把整个虚拟内存和物理内存切成一段段固定尺寸的大小,连续并且尺寸固定的内存空间就是一页,在Linux系统下,是4KB。
页表是存在内存里的,内存管理单元(MMU)负责虚拟内存地址转换成物理地址的工作。当进程访问的虚拟地址在页表中不存在的时候,系统会产生一个缺页异常,这时由用户态切换到内核态,内核分配物理内存、更新进程页表、最后再切换成用户态恢复进程的运行。
【分页是怎么解决分段的内存碎片、内存交换效率低的问题的?】
采用了分页后,释放的内存都是以页为单位的,不会产生无法给进程使用的小内存。如果用户空间不够,操作系统会把其他正在运行的进程中的【最久未使用】的内存页面给释放掉,暂时写在硬盘上。一旦需要的时候,再加载进来。所有,一次性写入磁盘只有少数几个页,不会花太长时间,内存交换的效率比较高。
另外,分页的方式使我们在加载程序的时候,不再需要一次性都把程序加载到物理内存中。而是在程序运行中,需要用到对应虚拟内存页里面的指令和数据时,再加载到物理内存中去。
【分页机制下,虚拟地址与物理地址的映射】
(1)虚拟机制切分成页号和页内偏移量;
(2)根据页号,从页表里查询对应的物理页号;
(3)直接拿物理页面,加上前面的偏移量, 就得到了物理内存地址。
【简单的分页的缺陷】
操作系统支持同时运行多个进程,那这样页表会非常庞大。假设有100个进程,每个页表项会占用4字节的大小,每页是4KB,大约能存下100万个页,每个进程分摊10000个页,一个进程需要用4*10000字节的内存来维护该进程的页表,有100个进程的话整个页表需要占用400MB内存来维护。
【多级页表】
把一级页表分为1024个二级页表,每个二级页表包含1024个页表项,形成二级分页。
【局部性原理】
如果使用了二级分页,一级页表就可以覆盖整个4GB虚拟地址空间,但如果某个一级页表的页表项没有被用到,也就不需要创建这个页表项对应的二级页表了,就是可以在需要的时候才创建二级页表。
从页表的性能来看,保存在内存中的页表承担的职责是将虚拟地址翻译成物理地址。假如虚拟地址在页表中找不到对应的页表项,计算机系统就不能工作了。所以页表一定要覆盖全部虚拟地址空间,不分级的页表需要有100多万个页表项来映射,而二级分页的话只需要1024个页表项来映射。
【64位操作系统的多级页表】
多级页表虽然解决了空间上的问题,但是虚拟地址到物理地址的转换多了几道转换的工序,这显然就降低了地址转换的速度,带来时间上的开销。在CPU芯片中,加入了一个专门存放程序最常访问的页表项Cache,这个Cache就是TLB,通常称为页表缓存。
在CPU芯片里面,封装了内存管理单元芯片,它用来完成地址转换和TLB的访问与交互。有了TLB后,在CPU寻址时,会先查TLB,如果没找到,才会继续查常规的页表。TLB的命中率其实是很高的,因为程序最常访问的页就那么几个。
3.4说说什么是段页式内存管理?
内存分段和内存分页并不是对立的,它们是可以组合起来在同一个系统中使用的,组合起来的管理方式称为段页式内存管理。
(1)先将程序划分为多个具有逻辑意义的段;
(2)接着再把每个段划分为固定大小的页;
地址结构由段号、段页号和页内偏移量三部分组成;
用于段页式地址变换的数据结构是每个进程一张段表,每个段又建立一张页表,段表的地址是页表的起始地址,页表的地址是某页的物理地址;
【段页式的地址变换】
(1)第一次访问段表,得到页表的起始地址;
(2)第二次访问页表,得到物理页号;
(3)将物理页号和页内偏移组合起来,得到物理地址;