原文:The Abstraction: Address Spaces

在早期,构建计算机系统是很简单的。为什么?你可能会想。因为用户期望值不高。都是那些『该死』的用户,想要一个"易用"、"高性能"、"可靠"的系统,才引起了一系列头疼的问题。下一次你遇到这些计算机用户们,别忘记感谢一下他们制造出来的问题:)

早期的系统

从内存的角度来说,早期的机器并没有提供太多的抽象给用户。一般而言,机器的物理内存可以用下图表示:

整个 OS(图中的阴影部分,译者注) 不过就是物理内存中的一些routines(本质上来说,一个 library),在这个例子中,从物理地址的 address 0开始。同时,还有一个运行的程序(进程)位于物理地址的 address 64k 处,并且独自占据了整个剩下的内存。用户并没对操作系统寄予太大的期望,操作系统程序员的生活还是很轻松的,不是吗?

多道编程(Multiprogramming)和 分时系统(Time Sharing)

一段时间过后,因为计算机实在是太贵了,人们开始更有效地共享有限的计算资源。于是多道编程 DV66:Programming Semantics for Multiprogrammed Computations 的时代开始了,在一个时刻,多个进程可以同时等待被执行,然后操作系统不断在这些进程中切换(比如当前进程进行 IO 的时候就可以切换到其他进程)。这样很有效地提高了 CPU 的利用效率。在那个计算机动辄几百万美元的时代,这些效率的提高是非常重要的。

然而,人是永远不会满足的,人名开始期待能够从操作系统那获取更多功能,于是分时系统的时代开始了。很多人尤其是程序员们自己意识到了 batch programming 的局限性,厌倦了很长(于是效率低下)的 debug 周期。因为很多用户在同时使用一台计算机,每个人都在等待(甚至可以说是期望)自己的程序返回结果,可交互性(interactivity)的概念开始变得越来越重要。

实现 time sharing 的一种方法是让一个进程运行一小段时间,这段时间内让它占据所有的内存,然后停止它,把其所有的状态保持到硬盘上(包括所有的物理内存!),接口导入另外一个进程,运行一段时间...... 这也算是一种简陋的实现方法。

显然,这种方法有很大的问题:太慢了,尤其是内存不断增大的时候。保存和欢迎寄存器级别的状态(比如 PC,general-purpose registers等等)是很快的,但是要把整个内存的全部内容保存到硬盘,这无疑不是一种高效的方法。所以,我们得想办法在切换的时候把进程的状态保持在内存里面。

在这个图中,有三个进程(A,B,C),每个占据 512KB内存的一小部分。现在对于 CPU,他可以选择任何一个抽象来运行(比如说 A),然后让另外两个(B 和 C)等待。

随着分时系统越来越流行,你肯定猜到了人们又开始提出新的需求了。尤其重要的一点是:让这么多程序同时保存在内存里,保护措施就很重要,你可不希望一个进程可以看到其他进程的内容,更别说可以随意修改其他进程的内容。

地址空间

用户是很难伺候的,操作系统的设计者得对物理内存做一个抽象,提供一个好用的接口给他们。我们把这种抽象就叫做地址空间。对于运行中的程序而言,它对物理内存是无感知的,它知道的只有地址空间(虚拟的)。理解基本的操作系统抽象,是理解内存是如何虚拟化的关键!

进程的地址空间包含了运行进程的所有状态。比如说,代码总得保存在某个地方吧?所以代码就在地址空间里面。进程运行的时候,会用 stack 来保存当前函数调用链的位置,分配空间给局部变量,并且返回值。还有一部分,叫做 heap,被用来保存动态分配、用户管理的内存,比如调用 malloc() 函数,或者 new 一个对象,都是从这里面得到需要的内存。当然,保存在地址空间的内容还有很多,目前而言我们只需要关注于这三点就行了。

上图中,有一个很小的地址空间(16kb)。程序的代码保存在地址空间的最上方(0-1kb)。代码是静态的,不会增加也不会减少,可以直接放在地址空间的最上部。还有两部分大小可变的区域,那就是 heap 和 stack。上图中,stack 和 heap 往两个不同的方向增长。如进行 malloc() 申请内存时,heap 往下增长;当进行一个procedure call时,stack 往上增长。当然这只是一个惯例,并没有物理上的限制要求一定要这样,你可以以其他方法重新组织地址空间。(事实上,当多个 threads 共存的时候,也不存在这么优雅的地址空间划分方法。)

我们谈地址空间的时候,我们谈的是操作系统提供给进程的抽象。上图的进程并不是在物理内存的0-16kb 位置,它可以被存放在物理内存的任意位置。回顾一下图13.2,你可以看到每个进程被加载到内存的不同位置。

当操作系统这么做的时候,我们说操作系统是在虚拟化内存,因为运行中的进程是以为自己占据了所有的内存的。然而现实很不一样。

比如说,图13.2的进程A想要加载地址0处的数据(这里指的是虚拟内存),操作系统,具备特定的硬件支持,会把这个这个地址0映射到物理内存中另外的位置。这个就是内存虚拟化的核心,几乎每一个现代计算机系统都在试用这种方法。

目标

虚拟内存系统的最主要目标是透明化。也就是底层的物理地址对运行的程序不可见。所以,程序根本就不会意识到内存是虚拟化的,而是认为自己有一个完整的私有物理地址。操作系统在幕后,承当着虚拟地址到物理地址的转换工作。这极大地降低了程序开发者地复杂度!

第二个目标是高效率。操作系统应该更可能让抽象更加高效,包括时间上的和空间上的。比如,操作系统会依赖于一些硬件,比如 TLB 缓存,可以缩短操作系统访问用户内存的时间。

最后一个目标是保护。操作系统需要确保进程间是隔离的,一个进程不能随意读取另一个进程的数据,尤其是不能随意在别人的地盘乱写数据,不然会带来很多很多不可预期的问题。

总结一下

虚拟内存本质上就是一层抽象。操作系统给程序提供了一个简单易用的接口,把虚拟内存映射成底层的物理内存,这中间的复杂度全部交给了操作系统,极大地降低了软件开发的难度,同时降低了软件出错的可能性。

再次深刻地体会到了那句名言:

计算机领域的一切难题都可以通过抽象解决。

我的公众号:全栈不存在的

Linux 内核101:[译]地址空间发展简史相关推荐

  1. Linux内核在中国大发展的黄金十年-写于中国Linux存储、内存管理和文件系统峰会十周年之际...

    Linux阅码场: 国内首屈一指的专注Linux核心技术开发的公众号,扫描下方二维码关注 CLSF: CLSF是中国Linux存储.内存管理和文件系统峰会的简称, 至今已举办十年, 参会成员由组委会根 ...

  2. 编译3.0的linux内核,1-3-编译Linux内核

    1-3-编译Linux内核 1.将Linux源码包拷贝到共享文件夹. 2.进入共享文件夹. 3.解压,命令#tar xvfj Kernel_3.0.8_TQ210_for_Linux_v2.2.tar ...

  3. linux内核1G虚拟地址空间的映射规则以及什么是高端内存?

    前面我们讲了,在32位linux内核里,内核地址空间是0xc0000000~0xffffffff, 大小1G:内核地址空间是0x00000000~0xbfffffff,大小3G.当内核代码访问内存时, ...

  4. Linux 内核101:[译]并发导论

    原文:Operating Systems: Three Easy Pieces:Concurrency: An Introduction 进程和线程 进程和线程在底层的区别 在单线程进程中,只有一个e ...

  5. Linux内核的Nand驱动流程分析

    最近在做Linux内核移植,总体的感觉是这样的,想要彻底的阅读Linux内核代码几乎是不可能的,至少这还不是嵌入式学期初期的重要任务.内核代码解压后有250M左右,据统计,有400多万行,而且涉及到了 ...

  6. Linux内核争抢式并发在SMP多核扩展上的不足

    本文来自:<被神话的Linux, 一文带你看清Linux在多核可扩展性设计上的不足> 我们先来看一段来自猛士王垠的话: 跟有些人聊操作系统是件闹心的事,因为我往往会抛弃一些术语和概念,从零 ...

  7. 一篇长文叙述Linux内核虚拟地址空间的基本概括

    x86-32位虚拟地址空间 就我们所知,Linux内核一般将处理器的虚拟地址空间划分为两个部分.底部比较大的部分用于用户进程,顶部则专用于内核.虽然(在两个用户进程之间的)上下文切换期间会改变下半部分 ...

  8. 【Linux 内核 内存管理】虚拟地址空间布局架构 ① ( 虚拟地址空间布局架构 | 用户虚拟地址空间划分 )

    文章目录 一.虚拟地址空间布局架构 二.用户虚拟地址空间划分 一.虚拟地址空间布局架构 在 646464 位的 Linux 操作系统中 , " ARM64 架构 " 并 不支持 6 ...

  9. 【Linux 内核】进程管理 ( 进程与操作系统 | 进程与程序 | 进程与线程 | 虚拟地址空间 )

    文章目录 一.进程与操作系统 二.进程与程序 三.进程与线程 四.虚拟地址空间 一.进程与操作系统 操作系统与硬件的关系 : 操作系统 使用 硬件 提供的资源 , 如 CPU , 内存 , 磁盘 , ...

最新文章

  1. Java线程池实现原理及其在美团业务中的实践
  2. docker设置网络
  3. pyecharts 应用6 三维曲面图
  4. javascript中变量的判断
  5. centos7上systemd详解
  6. FineUILearning
  7. 缓冲区,粘包,解决粘包的方法,
  8. Struts2中Action的属性接收参数
  9. spring+ibatis+注解开发
  10. DEA模型中的CCR模型
  11. 短视频技术与市场动态
  12. Spring Boot 2.x 基础案例:整合Dubbo 2.7.3 Nacos1.1.3(配置中心)
  13. Qt实现串口调试工具
  14. Cisco switchport trunk encapsulation dot1q 详解
  15. python装饰器带参数函数二阶导数公式_机器学习【二】单变量线性回归
  16. Linux NAS 盘挂载
  17. 编写优雅的JavaScript——前言
  18. 【UOJ#228】 基础数据结构练习题
  19. Web安全工具—Nmap(持续更新)
  20. Java实现智能语音朗读(完整代码+EXE程序制作)

热门文章

  1. ndk android studio万年坑
  2. SQL Server字符串左匹配
  3. 谷歌在线办公法器-Google Docs
  4. Linux sh/bash[精华]
  5. BGP router-id OSPF router-id 路由同步实验
  6. Java ---- baidu评价抽取关键词-商品评论
  7. GC之Minor/Young/Major GC的区别
  8. 华为p20:拍美景,听讲解,旅行更智能
  9. lxde 的安装和卸载以及注意事项,lubuntu
  10. mogodb集群配置笔记