- 浏览: 147093 次
-
文章分类
最新评论
源文件 hello.c 的代码如下:
要运行该程序,需要编译器驱动程序将其翻译成可执行的目标文件 hello,这个过程可分为如下图所示的四个阶段,执行这四个阶段的程序(预处理器、编译器、汇编器和链接器)一起构成了编译系统。
其中各阶段所做的事情如下。
* 预处理阶段:预处理器 cpp 会根据以字符“#”开头的命令来修改 C 程序,比如“#include <stdio.h>”命令会告诉预处理器读取头文件 stdio.h 的内容,并把它直接插入到程序文本中,于是得到一个通常以“.i”作为扩展名的 C 程序。
* 编译阶段:编译器 ccl 将文本文件 hello.i 翻译成汇编语言程序文本文件 hello.s。每条汇编语句都以一种标准的文本格式确切地描述了一条低级机器语言指令。
* 汇编阶段:汇编器 as 将 hello.s 翻译成机器语言指令,并将这些指令打包成一种叫做可重定位目标程序(relocatable object program)的格式,最后将结果保存在二进制的目标文件 hello.o 中。
* 链接阶段:链接器 ld 负责将使用到的其他目标文件(如 hello.c 中调用了 printf 函数,而该函数位于另一个目标文件 printf.o 中)合并到 hello.o 中,之后才能得到可执行的目标文件 hello,它可被系统加载到内存中执行。
翻译后的可执行文件 hello 存放于磁盘上,为理解运行该程序时发生了什么,需要先了解下图所示的一个典型系统的硬件组织。
在此需要明确图中的几个概念。
* 总线:贯穿整个系统的一组电子管道,用于在各个部件间传递字节信息。一般被设计成传送定长的字节块,也就是字。字中的字节数(即字长)是一个基本的系统参数,多为 4 个字节或是 8 个字节。
* I/O 设备:输入/输出(I/O)设备是系统与外界的联系通道,如图中作为用户输入的键盘和鼠标,作为用户输出的显示器,以及用于长期存储数据的磁盘等。每个 I/O 设备都通过一个控制器或适配器与 I/O 总线相连,两者的区别主要在于封装方式的不同。控制器是置于 I/O 设备本身的或者系统的主印制电路板(通常也称主板)上的芯片组,而适配器则是一块插在主板插槽上的卡。但无论如何,它们的功能都是在 I/O 总线和 I/O 设备之间传递信息。
* 主存:处理器执行程序时用来存放程序和数据的一个临时存储设备。从物理上来说,它是由一组动态随机存取存储器(DRAM)芯片组成的。从逻辑上来说,它是一个线性的字节数组,每个字节都有唯一的从零开始的地址(即数组索引)。一般组成程序的每条机器指令都由不同数量的字节所构成。
* 处理器:即中央处理单元(CPU),是解释或执行存储在主存中指令的引擎。它的核心是一个字长的存储设备(或寄存器),称为程序计数器(PC),PC 在任何时刻都指向主存中的某条机器语言指令。
从系统通电开始,直到系统断电,处理器一直在不断地执行 PC 指向的指令,然后再更新 PC,使其指向下一条指令,而这条指令并不一定与刚执行的那条相邻。这样的操作是围绕着主存、寄存器文件和算术/逻辑单元(ALU)进行的。寄存器文件是一个小的存储设备,由一些 1 字长的寄存器组成,每个寄存器都有唯一的名字。ALU 计算新的数据和地址值。
CPU 在指令的要求下可能会执行以下操作:
* 加载:把一个字节或者一个字从主存复制到寄存器。
* 存储:把一个字节或者一个字从寄存器复制到主存的某个位置。
* 操作:把两个寄存器的内容复制到 ALU,以对这两个字做算术操作,并将结果存放到一个寄存器中。
* 跳转:从指令本身中抽取一个字,并将其复制到 PC 中,以覆盖 PC 中原来的值。
了解这些后,再来看一下执行 hello 程序时所发生的事情。
当在 Shell 中使用键盘输入“./hello”时,Shell 会逐一将字符读入寄存器,再把它存放到存储器中。这个过程如下图所示:
Shell 会在用户敲了回车键后结束命令的输入,然后执行一系列指令来加载可执行的 hello 文件,并将其中的代码和数据从磁盘复制到主存。这里的数据包括最终会被输出的字符串“hello, world\n”。利用直接存储器存取(DMA)技术,数据可以不通过处理器而直接从磁盘到达主存。这个过程如下图:
再将目标文件 hello 中的代码和数据加载到主存后,处理器就开始执行 main 函数中的机器语言指令,这些指令将“hello, world\n”从主存复制到寄存器文件,再从寄存器文件中复制到显示设备,最终显示在屏幕上。如下图所示:
从这个例子中还可以看出,系统花了大量的时间把信息从一个地方复制到另一个地方。从程序员的角度来看,这些复制就是开销,减缓了程序“真正”的工作。再加上处理器访问寄存器、主存和磁盘之间存在巨大的速率差异,因此很有必要加快这些复制操作的完成。这也是高速缓存存储器(简称高速缓存)得以发展的原因。
高速缓存一般是用一种叫做静态随机访问存储器(SRAM)的硬件技术实现的。常用的高速缓存一般分为三级:L1、L2 和 L3,它们的大小逐级递增,但处理器访问它们的速度却逐级递减。系统可以获得一个很大的存储器,同时访问速度也很快,就是利用了高速缓存的局部性原理,即程序具有访问局部区域里的数据和代码的趋势。通过在高速缓存里存放经常访问的数据的方法,大部分的存储器操作都能在快速的高速缓存中完成。下图就是一个典型的存储器层次结构。其中,从上到下,设备的访问速度变得越来越慢、容量越来越大,价格也越来越便宜。每一层都可作为低一层的高速缓存,比如,寄存器文件作为 L1 的高速缓存,L1 作为 L2 的高速缓存,依次类推。
参考书籍:
1、《深入理解计算机系统》第一章——计算机系统漫游。
#include <stdio.h> int main(){ printf("hello world!"); return 0; }
要运行该程序,需要编译器驱动程序将其翻译成可执行的目标文件 hello,这个过程可分为如下图所示的四个阶段,执行这四个阶段的程序(预处理器、编译器、汇编器和链接器)一起构成了编译系统。

其中各阶段所做的事情如下。
* 预处理阶段:预处理器 cpp 会根据以字符“#”开头的命令来修改 C 程序,比如“#include <stdio.h>”命令会告诉预处理器读取头文件 stdio.h 的内容,并把它直接插入到程序文本中,于是得到一个通常以“.i”作为扩展名的 C 程序。
* 编译阶段:编译器 ccl 将文本文件 hello.i 翻译成汇编语言程序文本文件 hello.s。每条汇编语句都以一种标准的文本格式确切地描述了一条低级机器语言指令。
* 汇编阶段:汇编器 as 将 hello.s 翻译成机器语言指令,并将这些指令打包成一种叫做可重定位目标程序(relocatable object program)的格式,最后将结果保存在二进制的目标文件 hello.o 中。
* 链接阶段:链接器 ld 负责将使用到的其他目标文件(如 hello.c 中调用了 printf 函数,而该函数位于另一个目标文件 printf.o 中)合并到 hello.o 中,之后才能得到可执行的目标文件 hello,它可被系统加载到内存中执行。
翻译后的可执行文件 hello 存放于磁盘上,为理解运行该程序时发生了什么,需要先了解下图所示的一个典型系统的硬件组织。

在此需要明确图中的几个概念。
* 总线:贯穿整个系统的一组电子管道,用于在各个部件间传递字节信息。一般被设计成传送定长的字节块,也就是字。字中的字节数(即字长)是一个基本的系统参数,多为 4 个字节或是 8 个字节。
* I/O 设备:输入/输出(I/O)设备是系统与外界的联系通道,如图中作为用户输入的键盘和鼠标,作为用户输出的显示器,以及用于长期存储数据的磁盘等。每个 I/O 设备都通过一个控制器或适配器与 I/O 总线相连,两者的区别主要在于封装方式的不同。控制器是置于 I/O 设备本身的或者系统的主印制电路板(通常也称主板)上的芯片组,而适配器则是一块插在主板插槽上的卡。但无论如何,它们的功能都是在 I/O 总线和 I/O 设备之间传递信息。
* 主存:处理器执行程序时用来存放程序和数据的一个临时存储设备。从物理上来说,它是由一组动态随机存取存储器(DRAM)芯片组成的。从逻辑上来说,它是一个线性的字节数组,每个字节都有唯一的从零开始的地址(即数组索引)。一般组成程序的每条机器指令都由不同数量的字节所构成。
* 处理器:即中央处理单元(CPU),是解释或执行存储在主存中指令的引擎。它的核心是一个字长的存储设备(或寄存器),称为程序计数器(PC),PC 在任何时刻都指向主存中的某条机器语言指令。
从系统通电开始,直到系统断电,处理器一直在不断地执行 PC 指向的指令,然后再更新 PC,使其指向下一条指令,而这条指令并不一定与刚执行的那条相邻。这样的操作是围绕着主存、寄存器文件和算术/逻辑单元(ALU)进行的。寄存器文件是一个小的存储设备,由一些 1 字长的寄存器组成,每个寄存器都有唯一的名字。ALU 计算新的数据和地址值。
CPU 在指令的要求下可能会执行以下操作:
* 加载:把一个字节或者一个字从主存复制到寄存器。
* 存储:把一个字节或者一个字从寄存器复制到主存的某个位置。
* 操作:把两个寄存器的内容复制到 ALU,以对这两个字做算术操作,并将结果存放到一个寄存器中。
* 跳转:从指令本身中抽取一个字,并将其复制到 PC 中,以覆盖 PC 中原来的值。
了解这些后,再来看一下执行 hello 程序时所发生的事情。
当在 Shell 中使用键盘输入“./hello”时,Shell 会逐一将字符读入寄存器,再把它存放到存储器中。这个过程如下图所示:

Shell 会在用户敲了回车键后结束命令的输入,然后执行一系列指令来加载可执行的 hello 文件,并将其中的代码和数据从磁盘复制到主存。这里的数据包括最终会被输出的字符串“hello, world\n”。利用直接存储器存取(DMA)技术,数据可以不通过处理器而直接从磁盘到达主存。这个过程如下图:

再将目标文件 hello 中的代码和数据加载到主存后,处理器就开始执行 main 函数中的机器语言指令,这些指令将“hello, world\n”从主存复制到寄存器文件,再从寄存器文件中复制到显示设备,最终显示在屏幕上。如下图所示:

从这个例子中还可以看出,系统花了大量的时间把信息从一个地方复制到另一个地方。从程序员的角度来看,这些复制就是开销,减缓了程序“真正”的工作。再加上处理器访问寄存器、主存和磁盘之间存在巨大的速率差异,因此很有必要加快这些复制操作的完成。这也是高速缓存存储器(简称高速缓存)得以发展的原因。
高速缓存一般是用一种叫做静态随机访问存储器(SRAM)的硬件技术实现的。常用的高速缓存一般分为三级:L1、L2 和 L3,它们的大小逐级递增,但处理器访问它们的速度却逐级递减。系统可以获得一个很大的存储器,同时访问速度也很快,就是利用了高速缓存的局部性原理,即程序具有访问局部区域里的数据和代码的趋势。通过在高速缓存里存放经常访问的数据的方法,大部分的存储器操作都能在快速的高速缓存中完成。下图就是一个典型的存储器层次结构。其中,从上到下,设备的访问速度变得越来越慢、容量越来越大,价格也越来越便宜。每一层都可作为低一层的高速缓存,比如,寄存器文件作为 L1 的高速缓存,L1 作为 L2 的高速缓存,依次类推。

参考书籍:
1、《深入理解计算机系统》第一章——计算机系统漫游。
发表评论
-
浮点运算指令
2019-05-22 23:13 1607上一节介绍了浮点数与各种数值类型之间的相互转换 ... -
浮点数类型转换指令
2019-05-15 22:37 1764在浮点寄存 ... -
浮点寄存器概述
2019-05-14 22:31 2640本文介绍的浮点寄存器是基于 AVX2(Adva ... -
汇编指令之跳转指令
2019-04-15 00:21 4856正常执行的情况下,指令会按照顺序一条条地执行, ... -
汇编指令之条件码
2019-04-08 21:05 2388在系统底层,除了整数寄存器,CPU 还维护着一 ... -
汇编指令之算术和逻辑操作指令
2019-03-28 22:16 1449下表是 x86-64 ... -
汇编指令之数据传送指令
2019-03-25 21:28 1323在x86-64 中的 ... -
x86-64 中的寄存器与汇编操作数杂述
2019-03-20 21:45 1018Intel 中常用 ... -
linux启动服务概述
2017-04-08 02:43 416传统的linux中定义了七个运行级,分别如下: ... -
unix限制
2017-04-04 16:08 599UNIX系统实现定义了很多幻数和常量,其中有很 ... -
linux引导加载程序--GRUB
2017-04-04 04:22 646linux世界里有两种 ... -
存储器映射
2016-06-13 00:12 574注:本文摘自《深入理解计算机操作系统》第九章--虚拟存 ... -
虚拟存储器对存储器管理的作用
2016-06-10 16:00 713注:本文中的大部分内容均是摘录自《深入理解计算机系统》一书,权 ... -
信号处理问题
2016-06-03 08:31 580注:本文摘自《深入理解计算机系统》第8章 --- 异常控制流。 ... -
僵尸进程
2016-05-23 23:57 381在解释僵尸进程的概念之前,我们得先了解这样的一个事实: 一个进 ... -
程序优化之存储器别名使用
2016-05-20 08:55 826说明:本文示例摘自《深入理解计算机系统》第五章----优化程序 ... -
条件变量基本概念与原理(转载)
2016-05-20 08:54 1598对于条件变量,我一直感到很困惑,搞不清其与互斥锁到底有啥区别, ... -
CPU与磁盘的交互过程
2016-05-19 09:05 1873对于计算机系统底层技术,想必很多人都和我一样不太了解,最近在学 ... -
存储器层次结构中基本的缓存原理
2016-05-19 09:00 692对于操作系统,我们知道,越靠近CPU的存储器,其存储速度就会越 ... -
异常处理
2016-05-19 00:29 442我知道很多人都知道异常处理,但可能对其底层并不太了解,现在我们 ...
相关推荐
为了演示编译的整个过程,可以在Linux环境下创建一个工作目录,例如test0,然后用文本编辑器编写一个简单的C语言程序Hello.c。编译过程可以分为几个步骤: 1. 预处理:预处理过程包括宏定义的展开、条件预编译指令...
### CMake:代码开发背后的故事 #### 概述与动机 CMake作为一种现代构建系统工具,在软件开发领域扮演着越来越重要的角色。它以其强大的跨平台能力、易用性以及高度可扩展性受到开发者们的青睐。这份由Rodolfo ...
例如,一个简单的“Hello World”程序可能伴随着对标准输入输出函数的解释,而更复杂的排序算法实例则可能涉及到算法效率的分析与优化策略。 这些实例的编写考虑到了学习者的实际需求,不仅覆盖了C语言的核心概念,...
通过这本书,读者不仅能够学习到Swift编程语言的使用,还能够了解到编写和校对这样一本书背后的团队合作和共同努力的故事。这本书是技术和人文精神的结合,是对Swift语言和团队合作精神的一次致敬。
我相信,如果你也对神奇的计算机世界充满好奇,并且希望通过自己编写操作系统的方式来了解背后发生的故事,那么你一定可以在这本书中得到一些帮助。而假如你真的因为我的书而重新燃起实践的热情,从而开始一段操作...
我相信,如果你也对神奇的计算机世界充满好奇,并且希望通过自己编写操作系统的方式来了解背后发生的故事,那么你一定可以在这本书中得到一些帮助。而假如你真的因为我的书而重新燃起实践的热情,从而开始一段操作...