Linux 内核101:[译]地址空间发展简史
原文: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:[译]地址空间发展简史相关推荐
- Linux内核在中国大发展的黄金十年-写于中国Linux存储、内存管理和文件系统峰会十周年之际...
Linux阅码场: 国内首屈一指的专注Linux核心技术开发的公众号,扫描下方二维码关注 CLSF: CLSF是中国Linux存储.内存管理和文件系统峰会的简称, 至今已举办十年, 参会成员由组委会根 ...
- 编译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 ...
- linux内核1G虚拟地址空间的映射规则以及什么是高端内存?
前面我们讲了,在32位linux内核里,内核地址空间是0xc0000000~0xffffffff, 大小1G:内核地址空间是0x00000000~0xbfffffff,大小3G.当内核代码访问内存时, ...
- Linux 内核101:[译]并发导论
原文:Operating Systems: Three Easy Pieces:Concurrency: An Introduction 进程和线程 进程和线程在底层的区别 在单线程进程中,只有一个e ...
- Linux内核的Nand驱动流程分析
最近在做Linux内核移植,总体的感觉是这样的,想要彻底的阅读Linux内核代码几乎是不可能的,至少这还不是嵌入式学期初期的重要任务.内核代码解压后有250M左右,据统计,有400多万行,而且涉及到了 ...
- Linux内核争抢式并发在SMP多核扩展上的不足
本文来自:<被神话的Linux, 一文带你看清Linux在多核可扩展性设计上的不足> 我们先来看一段来自猛士王垠的话: 跟有些人聊操作系统是件闹心的事,因为我往往会抛弃一些术语和概念,从零 ...
- 一篇长文叙述Linux内核虚拟地址空间的基本概括
x86-32位虚拟地址空间 就我们所知,Linux内核一般将处理器的虚拟地址空间划分为两个部分.底部比较大的部分用于用户进程,顶部则专用于内核.虽然(在两个用户进程之间的)上下文切换期间会改变下半部分 ...
- 【Linux 内核 内存管理】虚拟地址空间布局架构 ① ( 虚拟地址空间布局架构 | 用户虚拟地址空间划分 )
文章目录 一.虚拟地址空间布局架构 二.用户虚拟地址空间划分 一.虚拟地址空间布局架构 在 646464 位的 Linux 操作系统中 , " ARM64 架构 " 并 不支持 6 ...
- 【Linux 内核】进程管理 ( 进程与操作系统 | 进程与程序 | 进程与线程 | 虚拟地址空间 )
文章目录 一.进程与操作系统 二.进程与程序 三.进程与线程 四.虚拟地址空间 一.进程与操作系统 操作系统与硬件的关系 : 操作系统 使用 硬件 提供的资源 , 如 CPU , 内存 , 磁盘 , ...
最新文章
- Java线程池实现原理及其在美团业务中的实践
- docker设置网络
- pyecharts 应用6 三维曲面图
- javascript中变量的判断
- centos7上systemd详解
- FineUILearning
- 缓冲区,粘包,解决粘包的方法,
- Struts2中Action的属性接收参数
- spring+ibatis+注解开发
- DEA模型中的CCR模型
- 短视频技术与市场动态
- Spring Boot 2.x 基础案例:整合Dubbo 2.7.3 Nacos1.1.3(配置中心)
- Qt实现串口调试工具
- Cisco switchport trunk encapsulation dot1q 详解
- python装饰器带参数函数二阶导数公式_机器学习【二】单变量线性回归
- Linux NAS 盘挂载
- 编写优雅的JavaScript——前言
- 【UOJ#228】 基础数据结构练习题
- Web安全工具—Nmap(持续更新)
- Java实现智能语音朗读(完整代码+EXE程序制作)