80年代640K内存对哪个人都够用了。那时微软开发的还是DOS os,程序员们还在想如何压榨完有限的640K内存。
而现在,随便一个笔记本都16G内存了,比那时多了一万倍。那当时这种言论是无稽之谈吗?为何觉得这么小内存就够了呢?
1 如何才能实现程序装载?
在运行这些可执行文件时,是通过装载器解析ELF或PE格式的可执行文件。
装载器会将对应指令和数据加载到内存,让CPU去执行。
要实现装载到内存,则装载器需满足:
可执行程序加载后,占用的内存空间是连续的
执行指令时,程序计数器是顺序一条条指令执行。这就要求这一条条指令得连续存储
需同时加载很多个程序 && 不能让程序自己规定在内存中加载的位置
虽编译出的指令里已有对应的各种内存地址,但实际加载时,其实无法确保这个程序一定加载在哪段内存地址。因为现在计算机通常会同时运行很多个程序,可能你想要的内存地址已被其他加载了的程序占用了
如何才能满足如上需求呢?
可在内存里,找到一段连续内存空间,然后分配给装载的程序,然后把这段连续的内存空间地址,和整个程序指令里指定的内存地址做个映射:
- 指令里用到的内存地址叫虚拟内存地址(Virtual Memory Address)
- 实际在内存硬件里的空间地址,叫物理内存地址(Physical Memory Address)
程序里有指令和各种内存地址,但只需关心虚拟内存地址。
任一程序,它看到的都是同样的内存地址。我们维护一个虚拟内存到物理内存的映射表,这样实际程序指令执行时,会通过虚拟内存地址,找到对应物理内存地址,然后执行。
因为是连续内存地址空间,所以只需维护映射关系的:
- 起始地址
- 对应的空间大小
2 内存分段(Segmentation)
找出一段连续的物理内存和虚拟内存地址进行映射的方法。这里的段指系统分配出来的那个连续的内存空间。
这很好,解决了程序本身无需关心具体物理内存地址的问题,但也有不足,如
内存碎片(Memory Fragmentation)
比如电脑有1GB内存,先启动一个 图形渲染,占了512MB内存,接着启动个Chrome,占了128MB内存,再启动一个Py程序,占了256MB内存。
现在关掉Chrome,空闲内存还有:
1024 − 512 − 256 = 256 M B 1024 - 512 - 256 = 256MB1024−512−256=256MB
讲道理,已有足够的空间再装载个200MB程序。但这256MB内存空间并非连续,而是被分成两段128MB内存。于是实际上我们的程序无法被加载进来。
如何解决这个问题呢?
内存交换(Memory Swapping)
可将Py程序所用256MB内存写到硬盘,再从硬盘读回到内存。读回时,不再把它加载到原来位置,而是紧跟在那已被占用的512MB内存后。
这就有了连续的256MB内存空间,就能去加载个新的200MB程序。
若你安装过Linux,你肯定遇到过分配一个swap硬盘分区抉择,这块分出的磁盘空间,就是给Linux进行内存交换用的。
虚拟内存、分段,再加上内存交换,三驾马车,看起来已完美解决计算机同时装载运行很多个程序的问题。不过仍有性能瓶颈:
- 硬盘的访问速度比内存慢太多
- 每次内存交换,都要把一大段连续内存数据写到硬盘
所以,若内存交换时,交换的是个很占内存空间的程序,整个机器会变得卡顿。