一、内存简介
内存是计算机中重要的部件之一,它是与CPU进行沟通的桥梁。其作用是用于暂时存放CPU中的运算数据,以及与硬盘等外部存储器交换的数据。只要计算机在运行中,CPU就会把需要运算的数据调到内存中进行运算,当运算完成后CPU再将结果传送出来。计算机中所有程序的运行都是在内存中进行的。
二、内存管理演变史
在早期的计算机中,程序是直接运行在物理内存上的。直接操作物理内存有几个问题:
地址空间不隔离: 程序操作相同地址空间会造成互相影响甚至奔溃,而且安全性也得不到保证。
使用效率低:没有特别好的策略保证多个进程对超过物理内存大小的内存需求的满足。
程序运行地址不能确定: 程序每次需要运行时,都需要在内存中分配一块足够大的空闲区域,而问题是这个空闲的位置是不能确定的,这会带来一些重定位的问题。
内存管理无非就是想办法解决上面三个问题。
这里引用计算机界一句无从考证的名言:“计算机系统里的任何问题都可以靠引入一个中间层来解决。”
那么办法就是在程序和物理内存之间引入了虚拟内存这个概念。虚拟内存位于程序和物理内存之间,程序只能看见虚拟内存,再也不能直接访问物理内存。每个程序都有自己独立的进程地址空间,这样就做到了进程隔离。这里的进程地址空间是指虚拟地址。
三、虚拟内存
物理内存:系统中由RAM芯片控制的可用内存。
虚拟内存:虚拟内存是计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。
四、虚拟内存的划分
从Linux操作系统层次上,可将Linux虚拟内存划分为用户空间内存和内核空间内存。以32位操作系统为例,最大寻址范围是4G,也就是整个虚拟地址空间是4G,Linux简化了分段机制,使得虚拟地址与线性地址总是一致的。Linux一般把这个4G的地址空间划分为两个部分:其中 0~3G为用户程序地址空间,虚地址0x00000000到0xBFFFFFFF,供各个进程使用;3G~4G为内核的地址空间,虚拟地址 0xC0000000到0xFFFFFFFF, 供内核使用。
这里有几点:
用户进程通常情况下只能访问用户空间( 0~3G)的虚拟地址,不能访问内核空间的虚拟地址。例外情况只有用户进程进行系统调用(代表用户进程在内核态执行)等时刻可以访问到内核空间。
每个进程的用户空间(0-3G)完全独立、互不相干,内核空间(3G-4G)则由则由所有进程以及内核共享。
用户空间对应进程,所以每当进程切换,用户空间就会跟着变化;而内核空间是由内核负责映射,它并不会跟着进程变化,是固定的。内核空间地址有自己对应的页表,用户进程各自有不同的页表。
五、虚拟地址与物理地址的映射方案
既然我们在程序和物理地址之间增加了虚拟地址,那么就要解决怎么从虚拟地址映射到物理地址,因为程序最终肯定是运行在物理内存中的,主要有分段和分页两种技术。
分段(Segmentation):将程序所需要的内存地址空间大小的虚拟空间映射到某个物理地址空间。
我们将两块大小相同的虚拟地址空间和实际物理地址空间一一映射,即虚拟地址空间中的每个字节对应于实际地址空间中的每个字节,这个映射过程由软件来设置映射的机制,实际的转换由硬件来完成。
分析:这种做法解决了地址隔离和地址重定位问题,但是并没有解决效率问题。因为这种内存映射机制仍然是以程序为单位,当内存不足时仍然需要将整个程序交换到磁盘,这样内存使用的效率仍然很低。
分页(Page) :分页机制就是把内存地址空间分为若干个很小的固定大小的页。
当然这个粒度不能太大也不能太小。太大浪费,太小又影响分配效率。Linux中一般页的大小是4KB。然后我们把进程的地址空间按页分割,把常用的数据和代码页装载到内存中,不常用的代码和数据保存在磁盘中。
我们可以看到进程1和进程2的虚拟地址空间都被映射到了不连续的物理地址空间内(这个意义很大,如果有一天我们的连续物理地址空间不够,但是不连续的地址空间很多,如果没有这种技术,我们的程序就没有办法运行),甚至他们共用了一部分物理地址空间,这就是共享内存。进程1的虚拟页VP2和VP3被交换到了磁盘中,在程序需要这两页的时候,Linux内核会产生一个缺页异常,然后异常管理程序会将其读到内存中。
分析:分页的内存划分与分段不同,分段是程序需要多少,分配多大。分页是我固定把整个地址空间按4K大小等分,程序需要多少,就给你拼几块。所以它同样也解决了地址隔离和地址重定位问题。另外,它把常用的数据和代码页装载到内存中,不常用的代码和数据保存在磁盘中,这样提升了效率。
另外,分页机制的实现需要硬件的实现,这个硬件名字叫做MMU(Memory Management Unit),他就是专门负责从虚拟地址到物理地址转换的,也就是从虚拟页找到物理页。
六、地址类型
物理地址:CPU通过地址总线的寻址,找到的真实的物理内存对应地址。
逻辑地址:机器语言指令中,用来指定一个操作数或者是一条指令的地址。即程序代码经过编译后出现在 汇编程序中地址。
虚拟地址(线性地址):是CPU所能寻址的空间范围。每个进程所能使用的虚拟地址大小取决于CPU的寻址能力,处理器是多少位的,就有多少位地址总线,以32位系统为例,那么32位总线就一次读写能力是32bit(4Byte).最大寻址范围为2^32 - 1(4G).
转换关系:
七、思考
经过上面的总结,我们知道了所谓的Linux内存管理,实际上是虚拟内存的管理,而虚拟内存又分为内核空间和用户空间。 对应到Android上,也就是Kernel层内存管理 和 用户层 Native + Dalvik 内存管理。
那么我理解的内存管理最终的目的是:将系统内有限的物理内存如何及时有效的分配给多个程序。细分为两点:最小化管理内存所需的时间。最大化程序的可用内存。
参考:
https://blog.csdn.net/h674174380/article/details/75453750
https://blog.csdn.net/acs713/article/details/42836335