虚拟内存
单片机没有操作系统,单片机的CPU是直接操作内存的物理地址。
要想在内存中同时运行两个程序是不可能的。如果第一个程序在2000的位置写入一个新的值,将会擦掉第二个程序存放在相同位置上的所有内容,所以同时运行两个程序会立刻崩溃。
操作系统如何解决这个问题呢?
关键问题:这两个程序都引用了绝对物理地址,而正式我们需要避免的。
把进程所使用的地址[隔离]开来,让操作系统为每个进程分配独立的一套[虚拟地址],互不干涉。但是前提每个进程都不能访问物理地址,至于虚拟地址最终怎么落到物理内存里,对进程来说是透明的,操作系统已经把这些都安排了。
操作系统会提供一种机制,将不同进程的虚拟地址和不同内存的物理地址映射起来。
程序访问虚拟地址的时候,由操作系统转换成不同的物理地址,不同进程运行的时候,写入的是不同的物理地址。
我们程序所使用的内存地址叫做虚拟内存地址(Virtual Memory Address)
实际存在硬件里面的空间地址叫物理内存地址(Physial Memory Address)
操作系统引入了虚拟内存,进程持有的虚拟地址会通过CPU芯片的内存管理单元(MMU)的映射关系,来转换变成物理地址,然后再通过物理地址访问内存
操作系统是如何管理虚拟地址与物理地址之间的关系?.
内存分段和内存分页
内存分段
程序由若干个逻辑分段组成的,可由代码分段、数据分段、栈段、堆段组成。不同的段有不同的属性,所以用分段(Segmentation)的形式把这些段分离出来。
分段机制下,虚拟地址和物理地址是如何映射的?
段选择因子和段内偏移量
- 段选择因子保存在段寄存器里面。段选择因子里面最重要的是段号,用作段表的索引。段表里面保存的是这个段的基地址、段的界限和特权等级等。
- 虚拟地址中的段内偏移量应该位于0和段界限之间,如果段内偏移量是合法的,就将段基地址加上段内偏移量得到物理内存地址
知道了虚拟地址是通过段表与物理地址进行映射的,分段机制会把程序的虚拟地址分成4个段,每个段在段表中有一个项,在这一项找到段的基地址,加上偏移量,就找到物理内存的地址
分段解决了程序根本不需要关心具体的物理内存地址的问题,不足:
- 内存碎片问题
- 内存交换的效率低的问题
为什么会产生内存碎片?
退出的浏览器,变成两段分来的128内存。导致没有空间打开200MB的程序。
内存分段会出现内存碎片吗?
内部内存碎片+外部内存碎片
内存分段管理可以做到段根据实际需求分配内存,所以有多少需求就分配多大的段,不会出现内部内存碎片
由于每个段的长度不固定,多个段未必恰好使用所有的内存空间,会产生多个不连续的小物理内存,导致新的程序无法被加载,所以会出现外部内存碎片的问题。
解决外部内存碎片:内存交换
将音乐程序写到硬盘,在从硬盘读回到内存里。再读回就紧紧跟着已经被占用的内存。
内存交换空间,Swap空间,从硬盘划分出来的,用于内存与硬盘的空间交换。
分段为什么会导致内存交换效率低?
对于多进程系统,用分段的方式容易产生外部内存碎片,不得不重新Swap内存区域,会产生性能瓶颈。
硬盘访问太慢,每次交换都要大量内存数据写到硬盘上,所以内存交换的时候,如果是一个占内存空间很大的程序,整个机器都会卡顿
内存分页
分段的好处:能产生连续空间。不足,会出现[外部内存碎片和内存交换的空间太大导致卡顿]
少出现一些内存碎片,当需要内存交换的时候,让需要交换写入或者磁盘装载的数据更少一点,就可以解决问题了。
分页是把整个虚拟和物理内存空间切成一段段固定尺寸的大小。
页表储存在内存里,内存管理单元MMU做将虚拟内存地址转换成物理地址的工作。
当进程访问的虚拟地址在页表查不到,系统会缺页异常,进入内核空间物理内存、更新进程页表,最后再返回用户空间,恢复进程的运行。
分页是怎么解决[外部内存碎片和内存交换效率低]的问题?
内存分页由于内存空间是预先划分好的,不会像内存分段产生小的内存间隙。采用分页,页与页之间是紧密排列的,所以不会有外部碎片。
程序不足一页,也要分配一页,产生内存分页机制会有内部内存碎片的现象
如果内存空间不够,操作系统会把其他正在运行的进程中的[最近没使用]的内存页面注释掉,暂时写在硬盘里,成为[换出](Swap out)。需要的时候,再加载进来,称为换入(Swap In)
所以,一次性写入磁盘的也只有少数的一页或者几页,内存交换的效率就会高。
分页不需要一次性把程序都加载到物理内存,用页映射后,并不真的把页加载到物理内存。
只有程序运行中,需要用到对应虚拟内存页里面的指令和数据时,再加载到物理内存里面去。
分页机制,虚拟地址和物理地址如何映射?
页号:页表索引,页表包含物理页每页在物理内存的基地址
业内偏移:
对于一个内存地址转换,三个步骤:
- 把虚拟内存地址,切分成页号和偏移量
- 根据页号,从页表里面,查询对应的物理页号
- 直接拿物理页号,加上前面的偏移量,就得到了物理内存地址。
分页的缺点?
操作系统多进程同时运行,导致页表庞大
32位,虚拟内存4GB,每个页4kb(2^12),需要100万个页(2^20),每个页表项需要4个字节大小存储,4GB空间需要4MB
但是每个进程有自己的虚拟地址空间,100个进程有400MB
多级页表
局部性原理
如果某个一级页表的页表项没有被用到,也就不需要创建这个页表项对应的二级页表了,即可以在需要时才创建二级页表。
页表一定要覆盖全部虚拟地址空间,不分级的页表就需要有 100 多万个页表项来映射,而二级分页则只需要 1024 个页表项(此时一级页表覆盖到了全部虚拟地址空间,二级页表在需要时创建)。
推广到多级页表
64位系统,四级目录
全局页目录项PGD(Page Global Directory)
上层页目录项PUD(Page Upper Directory)
中间页目录项PMD(Page Middle Directory)
页表项PTE(Page Table Entry)
TLB
多级页表解决空间问题,但虚拟地址到物理地址的转换多了几道转换的工序,显然降低了地址转换的速度,也就是带来了时间上的开销。
程序有局部性,一段时间仅执行一部分,执行所访问的存储空间也局限于某个内存区域
利用此,把常访问的几个页表项存到访问速度更快的硬件,在CPU中加入了一个专门存放最常访问页表项的Cache:TLB(Translation Lookaside Buffer),称为:页表缓存,转址旁路缓存、快表
内存管理单元(Memory Management Unit)完成地址转换和TLB访问和交互
CPU寻址先找TLB,没有在继续查常规页表。
段页式内存管理
分页+分段组合。
- 先将程序划分为多个有逻辑意义的段
- 每个段划分多个页
地址结构:段号+段内页号+页内位移
Linux内存布局
Linux采用那种方式管理内存?
Intel 处理器80286-段式内存管理-->80386-页式内存管理
页式内存管理的作用是在由段式内存管理所映射而成的地址上再加一层地址映射。
- 程序所使用的地址,通常是没被没内存管理映射的地址,称为逻辑地址
- 通过段式内存管理映射的地址,称为线性地址,也叫虚拟地址
Linux 内存主要采用的是页式内存管理,但同时也不可避免地涉及了段机制。
Linux 系统中的每个段都是从 0 地址开始的整个 4GB 虚拟空间(32 位环境下),也就是所有的段的起始地址都是一样的。这意味着,Linux 系统中的代码,包括操作系统本身的代码和应用程序代码,所面对的地址空间都是线性地址空间(虚拟地址),这种做法相当于屏蔽了处理器中的逻辑地址概念,段只被用于访问控制和内存保护。
Linux的虚拟地址空间如何分布?
内核空间+用户空间
每个进程都有独立的虚拟内存,但是每个虚拟内存中的内核地址,其实关联的都是相同的物理内存。
总结
为了在多进程环境下,使得进程之间的内存地址不受影响,相互隔离,于是操作系统就为每个进程独立分配一套虚拟地址空间,每个程序只关心自己的虚拟地址就可以,实际上大家的虚拟地址都是一样的,但分布到物理地址内存是不一样的。作为程序,也不用关心物理地址的事情。
每个进程都有自己的虚拟空间,而物理内存只有一个,所以当启用了大量的进程,物理内存必然会很紧张,于是操作系统会通过内存交换技术,把不常使用的内存暂时存放到硬盘(换出),在需要的时候再装载回物理内存(换入)。
那既然有了虚拟地址空间,那必然要把虚拟地址「映射」到物理地址,这个事情通常由操作系统来维护。
那么对于虚拟地址与物理地址的映射关系,可以有分段和分页的方式,同时两者结合都是可以的。
内存分段是根据程序的逻辑角度,分成了栈段、堆段、数据段、代码段等,这样可以分离出不同属性的段,同时是一块连续的空间。但是每个段的大小都不是统一的,这就会导致外部内存碎片和内存交换效率低的问题。
于是,就出现了内存分页,把虚拟空间和物理空间分成大小固定的页,如在 Linux 系统中,每一页的大小为 4KB
。由于分了页后,就不会产生细小的内存碎片,解决了内存分段的外部内存碎片问题。同时在内存交换的时候,写入硬盘也就一个页或几个页,这就大大提高了内存交换的效率。
再来,为了解决简单分页产生的页表过大的问题,就有了多级页表,它解决了空间上的问题,但这就会导致 CPU 在寻址的过程中,需要有很多层表参与,加大了时间上的开销。于是根据程序的局部性原理,在 CPU 芯片中加入了 TLB,负责缓存最近常被访问的页表项,大大提高了地址的转换速度。
Linux 系统主要采用了分页管理,但是由于 Intel 处理器的发展史,Linux 系统无法避免分段管理。于是 Linux 就把所有段的基地址设为 0
,也就意味着所有程序的地址空间都是线性地址空间(虚拟地址),相当于屏蔽了 CPU 逻辑地址的概念,所以段只被用于访问控制和内存保护。
另外,Linux 系统中虚拟空间分布可分为用户态和内核态两部分,其中用户态的分布:代码段、全局变量、BSS、函数栈、堆内存、映射区。
最后,说下虚拟内存有什么作用?
- 第一,虚拟内存可以使得进程对运行内存超过物理内存大小,因为程序运行符合局部性原理,CPU 访问内存会有很明显的重复访问的倾向性,对于那些没有被经常使用到的内存,我们可以把它换出到物理内存之外,比如硬盘上的 swap 区域。
- 第二,由于每个进程都有自己的页表,所以每个进程的虚拟内存空间就是相互独立的。进程也没有办法访问其他进程的页表,所以这些页表是私有的,这就解决了多进程之间地址冲突的问题。
- 第三,页表里的页表项中除了物理地址之外,还有一些标记属性的比特,比如控制一个页的读写权限,标记该页是否存在等。在内存访问方面,操作系统提供了更好的安全性。