前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >重学计算机组成原理(8)-程序是如何被装载的

重学计算机组成原理(8)-程序是如何被装载的

作者头像
JavaEdge
发布2022-11-30 15:22:44
5200
发布2022-11-30 15:22:44
举报
文章被收录于专栏:JavaEdgeJavaEdge

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 = 256MB

讲道理,已有足够的空间再装载个200MB程序。但这256MB内存空间并非连续,而是被分成两段128MB内存。于是实际上我们的程序无法被加载进来。

如何解决这个问题呢?

内存交换(Memory Swapping)

可将Py程序所用256MB内存写到硬盘,再从硬盘读回到内存。读回时,不再把它加载到原来位置,而是紧跟在那已被占用的512MB内存后。

这就有了连续的256MB内存空间,就能去加载个新的200MB程序。 若你安装过Linux,你肯定遇到过分配一个swap硬盘分区抉择,这块分出的磁盘空间,就是给Linux进行内存交换用的。

虚拟内存、分段,再加上内存交换,三驾马车,看起来已完美解决计算机同时装载运行很多个程序的问题。不过仍有性能瓶颈:

  • 硬盘的访问速度比内存慢太多
  • 每次内存交换,都要把一大段连续内存数据写到硬盘

所以,若内存交换时,交换的是个很占内存空间的程序,整个机器会变得卡顿。

3 内存分页

既然问题原因是:

  • 内存碎片
  • 内存交换的空间太大

则解决方案自然就是想着:

  • 怎么少出现内存碎片
  • 当需内存交换时,让需交换写入或从磁盘装载的数据更少一点

于是内存管理给出了 内存分页(Paging) 方案。

和分段这样分配一整段连续的空间给到程序相比,分页是把整个物理内存空间切成一段段固定尺寸的大小。而对应程序所需占用的虚拟内存空间,也会切成一段段固定尺寸的大小。

这一个个连续&&尺寸固定的内存空间,叫页(Page)。 从虚拟内存 =》物理内存的映射,不再是拿整段连续的内存的物理地址,而是按一个个页,一般来说:

页的尺寸 << 整个程序的大小

Linux下,通常设置成4KB,可查看:

  • Linux设置的页大小
  • macOS 的内存页大小

由于内存空间都是预先划分好的,也就没有不能使用的碎片,而只有被释放出来的很多4K的页。 即使内存空间不够,需要让现有的、正在运行的其他程序通过内存交换释放出一些内存页出来,一次性写入磁盘的也只有少数的一个页或几个页,不会花太多时间,让整个机器被内存交换的过程给卡住。

分页的方式使得加载程序的时候,不再需要一次性把程序加载到物理内存中。 可在进行虚拟内存、物理内存页之间的映射后,并不真的把页加载到物理内存,而是只在程序运行中,需要用到对应虚拟内存页里面的指令和数据时,再加载到物理内存。

os也就是这么做的,当要读取特定的页,却发现数据并未加载到物理内存,就会触发来自CPU的缺页错误(Page Fault)。 os会捕捉到这个错误,然后将对应页,从存放在硬盘上的虚拟内存读出,加载到物理内存。 这使得可以运行那些远大于我们实际物理内存的程序。同时,任何程序都无需一次性加载完所有指令和数据,只需加载当前所需的。

通过虚拟内存、内存交换和内存分页技术组合,最终得到了一个让程序无需考虑实际物理内存地址、大小和当前分配空间的解决方案。 这些技术和方法,对于我们程序的编写、编译和链接过程都是透明的。这也是我们在计算机的软硬件开发中常用的一种方法,就是加入一个间接层

通过引入虚拟内存、页映射和内存交换,程序不再需考虑对应真实内存地址、程序加载、内存管理等问题。 任一程序只需把内存当成是一块完整而连续的空间来直接使用即可,其它都是 os 保姆帮你做好的。

4 总结

在虚拟内存、内存交换和内存分页这三者结合之下,要运行一个程序,“必需”的内存是很少的。CPU只需要执行当前的指令,极限情况下,内存也只需要加载一页就好了。 再大程序,也可分成一页。每次,只在需要用到对应的数据和指令的时候,从硬盘上交换到内存里面来就好了。 以现在4K内存一页的大小,640K内存也能放下足足160页呢,也无怪乎在比尔·盖茨会说出“640K ought to be enough for anyone”。

不过硬盘的访问速度比内存慢很多,所以我们现在的计算机没有几G内存都不好意思卖。

除了程序分页装载这种方式之外,还有其他优化内存使用的方式:“动态装载”,请见后文分解。

FAQ

在Java这样使用虚拟机的编程语言里,程序是怎么装载到内存里的?也和本文一样,通过内存分页和内存交换的方式加载到内存里面来的么?

JVM已是上层应用,无需考虑物理分页,一般更直接是考虑对象本身空间大小,物理硬件管理统一由承载JVM的os解决。

参考

  • 深入浅出计算机组成原理
  • 《程序员的自我修养——链接、装载和库》的第1章和第6章
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019-08-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 如何才能实现程序装载?
  • 2 内存分段(Segmentation)
    • 内存碎片(Memory Fragmentation)
      • 内存交换(Memory Swapping)
      • 3 内存分页
      • 4 总结
      • FAQ
      相关产品与服务
      对象存储
      对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档