《操作系统 - 清华大学》3 -2:地址空间和地址生成
文章目录
- 0.内容概述
- 1. 地址空间定义
- 2. 逻辑地址生成
- 3. 物理地址生成
- 4. 地址安全检查
0.内容概述
运行程序所用到的地址空间以及这个地址空间是如何生成的?这里面会涉及到几点:
- 第一,什么是地址空间,逻辑空间和物理空间到底有什么样的区别?
- 第二,地址空间是怎么生成?什么时候生成逻辑空间?什么时候生成物理空间?
- 第三,操作系统去保护不同进程之间的访问,使它们之间相互不干扰,这有一个安全检查机制,这是关于地址空间与地址生成这部分的内容。
1. 地址空间定义
两种地址空间:
- 一种是物理地址空间,物理地址空间是硬件直接对应的,比如内存条所代表主存以及硬盘所代表的另一种存储空间,那么这两种存储空间就是物理内存。物理内存的管理和控制是由硬件来完成的。
- 另外一种是逻辑地址空间,逻辑地址空间就是指一个运行的程序所看到的内存空间,相对而言它看到内存空间更加简单,它是一个一维的线性的地址空间,那么有这种一维地址空间之后,应用程序就很容易去访问,很容易去做相关的控制和数据访问操作。
但是需要理解,这两者之间是如何建立对应关系,因为所有运行的程序所访问的逻辑地址空间最终都是落实在物理地址空间中存在的。
比如说指令 movl %eax , $oxfffa620e
~
这条指令的地址在什么地方?那么根据逻辑地址空间,它肯定是位于这个箭头指向程序所处的位置,但它最终应该是会放置在主存中,也有可能放置在硬盘中,那么到底放置在主存还是硬盘由操作系统来协调,可以明确说明,这个映射关系实际是需要操作系统去有效进行管理,这是逻辑地址空间和物理地址空间二者的简单介绍。
2. 逻辑地址生成
这个逻辑地址空间怎么生成的?下图左侧所示一个简单的 C 程序,通过编译可以把 C 程序变成汇编程序,这两者之间的区别,它的地址在哪?什么叫地址?地址怎么体现?
在 C 程序里面,函数的位置和变量的名字就是地址,这个是逻辑地址,当然它是一种人更容易理解的方式存在。另一方面还有汇编程序,汇编程序已经是更加贴近机器语言,更便于机器去理解, 但是它依然是用符号代表变量和函数的名字,相对而言,它跟机器语言相比,它也能够更好地被人阅读,当这种汇编语言通过汇编器可以转换成机器语言。
.o 程序的起始地址都是从 0 开始,然后它会把里面的变量符号名和函数符号名转换成相应的地址,地址是相对从0开始连续的地址空间,这是一种逻辑地址。
当然一个大的程序可能有很多个小程序组成,小程序组成的过程有可能会使得不同程序之间的地址相互进行访问,可能形成一个很复杂的依赖关系,这过程怎么来处理的呢?
通过 Linker 工具完成多个 .o 程序最终变成单一的执行程序,可以在内存中执行,放在硬盘中的程序。这程序可以看到,它的地址已经做了全局的分布,不同的.O程序所访问的地址已经能够在单一程序中有相应的定义。但这个定义还不在内存中的一个位置,所以说放在硬盘中的执行程序还需要通过一个应用程序 loader,它会把放在硬盘中的这个执行程序放到内存中去运行,这一步会需要完成把放到内存中的逻辑地址完成相应分配,使得应用程序在内存中可以正常地跑。相对执行程序而言,这个地址有一定的偏移量,偏移量可以为0,也可以为特定的一个值,有偏移量之后,所有程序可以依照这个偏移量来进行正确的数据访问和指令操作,这个过程是一个逻辑地址的生成过程。
从最开始符号的逻辑地址到最终可以在内存中运行的具体逻辑地址。它们经过了很多转换过程,这转换过程基本上不需要操作系统做任何帮助,通过应用程序、编译器、 loader 就可完成这个过程的建立,这是逻辑地址的生成过程。
3. 物理地址生成
另一方面需要注意,即使把执行程序放到内存中去了,放进去后,它其实还是一个逻辑地址,并没有说它是一个物理地址,因为放到内存中去之后,这是运行的程序看到的地址,它和物理地址空间有什么区别呢?
应用程序在访问一个指令时候,这个指令所处的逻辑地址是如何对应到具体的物理的内存空间中去?
这条指令它有自己的逻辑地址,那 CPU 执行这条指令需要把这个指令取出来,从内存中取出来,那么放在内存中什么地方?放在物理内存中什么地方?这个其实 CPU 一开始是不知道的,它只知道这个指令有一个逻辑地址,它会去查找这逻辑地址所对应的物理地址在什么地方。
那么对应关系其实是在硬件中有一个 MMU,需要注意 CPU 里面有 MMU,MMU 中有一块区域表示了这个映射关系,那么这映射关系也会在内存中有相应位置(比如上图中蓝色区域所示),称之为映射关系完成了逻辑地址到物理地址的映射。那么查这个表就可以知道,一个具体的逻辑地址它所对应的物理地在什么地方,一旦知道物理地址在什么地方,就可以让硬件从相应的物理内存中把这个地址给取过来。
那其实可以看出来,整个流程可以简单地描述成如下步骤:
- 首先,当 CPU 要执行某条指令的时候,首先是由 CPU 的控制单元(CU)根据程序计数器(PC)中提供的下一条指令的逻辑地址,向内存管理单元(MMU)发送请求,以获取该指令内容。
- 接下来,CPU 里面 MMU 会去查找这个逻辑地址的映射表中是否存在对应的这个物理地址。如果有,OK,那可以找到。
- 如果没有的话,那么它就会产生一个处理过程去内存中 map 去找,如果找着了, CPU 控制器会给主存发出一个请求,需要某一个物理地址的内容,这个内容实际就是那条指令的内容。
- 然后,主存会把这个内存的内容通过这个总线传给 CPU,那么 CPU 拿到指令内容之后就可以开始对这条指令进行执行了。
在这里面,操作系统起到什么作用?
操作系统很重要的作用在于,它在这四步之前,需要把这个映射关系建好,映射关系就是逻辑地址到物理地址的映射关系,这个关系可以是放在内存中由 CPU 来进行缓存,加快这个访问过程。
可以看出来,逻辑地址和物理地址的映射关系是通过操作系统来完成的,至于怎么完成,那么后面会进一步展开讲解。
4. 地址安全检查
另一方面,操作系统很重要的目标是确保放在内存中的程序相之间不能够相互干扰,那为此需要去确保每一个程序访问地址空间是合法的,或者说是限制在它的一约束范围之内的,那这个限制和约束其实也是靠操作系统来完成。
看上图,操作系统首先要确保每一个程序它可以有效访问的地址空间,那么其实这地址空间包含两部分,第一部分是它的起始地址,第二部分是它的长度,就相当于是可以通过起始地址长度知道有一块区域是属于程序可以合理访问的,一旦长度超出这个区域,那么这个访问就是一个不合法的访问,那么这个表也是操作系统来建立和维护的。
那一旦 CPU 要去执行某条指令的时候,它就会查这个 map ,这个 map 会指出来它访问的某一个逻辑地址是否满足这个区域的一个限制。如果能满足这区域的限制,它可以正常地根据映射关系找到对应的物理地址的位置,然后把它数据给取回来。但一旦说不满足,那么 CPU 就会产生 memory 异常,就内存访问异常,从而可以让操作系统进一个处理,这实际就是地址安全检查,安全检测的过程。