上一篇序章我谈了谈 程序员为啥要懂底层计算机结构 ,有人赞同也有人反对也好,这都是博主的个人见解,但是博客还是要坚持学下去。这篇博客以案例驱动的模式,通过跟踪一个简单 Hello World 程序的生命周期开始系统的学习,包括它被程序员创建,到在系统上运行,输出简单的消息,然后终止。LZ 将沿着这个程序的声明周期,先简要的介绍一些逐步出现的关键概念、专业术语以及组成部分。后面将会详细展开。
1、计算机系统
我们知道计算机系统是由硬件和软件组成的。它们共同工作来运行应用程序。虽然系统的实现方式随着时间不断变化,但是系统内在的概念却没有改变。所有计算机系统都有相似的硬件和软件组件,它们执行着相似的功能,我们只有深入了解这些组件是如何工作的,以及这些组件是如何影响程序的正确性和性能的,才能写出高质量的代码。
2、万能程序大法----Hello World
#include <stdio.h> int main()
{
printf("Hello World\n");
return 0;//c标准规定建议main函数返回值为int
}
这段代码不用多说,就是一个C语言的Hello World,程序的执行结果是打印 “Hello World”。
3、信息的表示
我们将上面的 Hello World 程序保存在一个 hello.c 的文件中,那么它是怎么存储在文件中的呢?实际上它是以字节序列的方式存储在文件中。
什么是字节?一个字节由8个位组成,而一个位是由值0和1组成。也就是说 hello.c 源程序是由值0和1组成的位序列。
大部分的现代系统都是用 ASCII 码构成,这种方式实际上就是用一个唯一的单字节大小的整数来表示每个字符。下面我们给出 hello.c 程序的 ASCII 码表示:
左边是文件对应的16进制代码,右边是我们的源程序,例如:第一个字符“#”的 ASCII 值是0x23。需要特别注意一下:每个文本行都以一个看不见的换行符‘\n’结束的。第2行中有2个连续的0x0D 0x0A ,这是windows中特有的“换行符\r\n” ,在linux中的是“换行符\n”。像hello.c文件这样只由 ASCII 码组成的文件叫做“文本文件”,其他所有文件都叫“二进制文件”。
系统中所有的信息都是由位+上下文构成。
包括磁盘文件、存储器中的程序,存储器中存放的用户数据以及网络上传送的数据都是由一串位表示。而区分不同数据对象的唯一方法就是我们读到这些对象时的上下文。比如在不同的上下文中,一个同样的字节序列可能表示一个整数、浮点数、字符串或者机器指令。
作为程序员,我们需要了解数字的机器表示方式,因为它们与实际的整数和实数是不同的。它们是对真值的有限近视值,有时候会有意想不到的行为表现。这个后面我们会详细讲解。
4、程序的编译
hello 程序的生命周期是从一个高级 C 语言程序开始的,因为这种形式能被人读懂。然而,计算机系统是读不懂高级语言的。为了在系统上运行 hello.c 程序,每条 C 语句都必须要被其他程序转化为一系列的低级机器语言指令。
一般来说,要将 hello.c 变成一个可执行的目标程序,必须要经过 预处理器、编译器、汇编器和链接器 的处理。如下:
预处理器、编译器、汇编器和链接器 一起构成了编译系统,下面对每个步骤分别进行解析:
①、预处理阶段:预处理器 cpp 根据以字符 # 开头的命令,修改原始的 C 程序,比如 Hello.c 中第一行 #include<studio.h> 命令告诉预处理器读取系统文件 stdio.h 的内容,并把它直接插入到程序中。结果就得到另一个 C 程序,通常是以 .i 作为文件扩展名。
②、编译阶段:编译器 ccl 将文本文件 hello.i 翻译成文本文件 hello.s,它包含一个汇编语言程序,汇编语言程序中的每条语句都以一种标准的文本格式确切的描述一条低级机器语言指令。汇编语言能为不同高级语言的不同编译器提供通用的输出语言。
③、汇编阶段:汇编器 as 将hello.s 翻译成机器语言指令,把这些指令打包成一种叫做可重定位目标程序的格式,并将结果保存在目标文件 hello.o 中,hello.o 文件是一个二进制文件,它的字节编码是机器预言指令而不是字符。如果我们用文本编辑器打开 hello.o 文件,将会是一堆乱码。
④、链接阶段:在 hello.c 程序中,我们看到程序调用了 printf 函数,它是每个 C 编译器都会提供的标准 C 库中的一个函数。printf 函数存在于一个名为 printf.o 的单独的预编译好了的目标文件中,而这个文件必须以某种方式合并到我们的 hello.o 程序中。链接器 ld 就是负责处理这种合并,结果就得到一个 hello 文件,它是一个可执行目标程序,可以被加载到内存中,由系统运行。
这里我做一下验证,我在 Linux 系统上创建 hello.c 程序,然后依次执行上面的步骤:
预处理:
gcc -E hello.c -o hello.i
然后查看 hello.i
编译阶段:
gcc -S hello.i
然后查看 hello.s
上面截图的是一个汇编程序
5、程序的运行
经过上面程序的编译,hello.c 源程序已经被编译成了可执行目标文件 hello,并存放在磁盘上,那么如何运行呢?
①、系统的硬件组成
为了理解运行 hello 程序时发生了什么,我们先要了解一个典型系统的硬件组织。如下图:
我们现在不需要对这张图有很深入的理解,后面会详细进行介绍。现在先简单的认识一下下面几个主要部件:
一、总线:贯穿整个系统的一组电子管道,通常被设计成用来传送定长的字节块,也就是字。字的大小与系统相关,比如在32位操作系统当中,一个字是4个字节。
二、I/O设备:输入/输出(I/O)设备是系统与外部世界联系通道,上图有4个I/O设备。作为用户输入的键盘和鼠标,作为用户输出的显示器,以及用于长期存储数据和程序的磁盘。每一个I/O设备都通过一个控制器或者适配器与I/O总线相连。控制器是置于I/O设备本身的或者系统的主印刷电路板(通常称为主板)上的芯片组,而适配器则是一块插在主板插槽上的卡。无论如何,它们的功能都是在 I/O 总线和 I/O 设备之间传递信息。
三、主存:它是计算机中的一个临时存储设备,在处理器执行程序的时候,用来存放程序和程序处理的数据。物理上来说,主存是由一组动态随机存取存储器(DRAM)组成的,逻辑上来说,它是一个线性的字节数组,每一个字节都有唯一的地址(即数组索引)。
四、处理器:全称*处理器(CPU),是解释(或执行)存储在主存中指令的引擎。处理器的核心是一个字长的存储设备(或寄存器),简称程序计数器(PC),在任何时刻,它都会指向主存中的某条机器指令(即含有该条指令的地址)。从系统通电到断点,处理器一直在不断的执行程序计数器所指向指令,再更新程序计数器,使其指向下一条指令。处理器所做的操作是围绕主存、寄存器文件以及算术/逻辑单元(ALU)进行的,寄存器文件是一个小的存储设备,由一些1字长的寄存器组成,每个寄存器都有唯一的名字。ALU则计算新的数据和地址值。
CPU 在指令的要求下会做如下操作:
①、加载:把一个字节或者一个字从主存复制到寄存器,以覆盖寄存器原来的内容
②、存储:把一个字节或者一个字从寄存器复制到主存的某个位置,以覆盖这个位置上原来的内容
③、操作:把两个寄存器的内容复制到 ALU,ALU 对这两个字做算术操作,并把结果存放到一个寄存器中,以覆盖寄存器原来的内容
④、跳转:从指令本身中抽取一个字,并将这个字复制到程序计数器(PC)中,以覆盖PC中原来的内容。
处理器当中提到的是指令集结构的简单实现,不过实际上现代处理器使用了非常复杂的机制来加速程序的运行。我们可以这样去区分指令集机构以及微体系结构,指令集结构描述的是每条机器代码指令的效果,而微体系结构描述的是处理器实际上是如何实现的,类似于JAVA虚拟机与JAVA虚拟机实现的关系。
②、运行 Hello World 程序
前面简单的介绍了系统的硬件组成和操作,那么接下来介绍我们运行程序时到底发生了什么。
想要在 Linux 系统中运行该可执行程序,我们要将它的文件名输入到称为外壳(shell)的应用程序中,外壳是一个命令行解释器,它输出一个提示符,等待你输入一个命令,然后执行这个命令。如果该命令行的第一个单词不是一个内置的外壳命令,那么外壳就会假设这是一个可执行文件的名字,它将加载并运行这个文件。
初始时,外壳程序执行它的指令,等待我们输入一个命令。当我们在键盘上输入字符串"./hello"后,外壳程序将字符逐一读入到寄存器中,再把它放入到存储器中,如下图:
PS:为什么要输入“./hello”来执行,对于Linux系统有一定了解的人,可能知道这是运行命令的一种方法。
当我们在键盘上敲回车键的时候,外壳程序知道我们已经结束了命令的输入。然后外壳执行一系列指令来加载可执行的 hello 文件,将 hello 目标文件中的代码和数据从磁盘复制到主存。数据包括最终会被输出的字符串“Hello World\n”,一旦目标文件中的代码和数据被加载到主存,处理器就开始执行 hello 程序的 main 程序中的机器语言指令。这些指令将“Hello World\n” 字符串中的字节从主存复制到寄存器文件,再从寄存器文件中复制到显示设备,最终显示在屏幕上。
6、 本章总结
①、出现的名词解释:
位:"位(bit)"是电子计算机中最小的数据单位。每一位的状态只能是0或1。
字节:8个二进制位构成1个"字节(Byte)",它是存储空间的基本计量单位。1个字节可以储存1个英文字母或者半个汉字,换句话说,1个汉字占据2个字节的存储空间。
字:"字"由若干个字节构成,字的位数叫做字长,不同档次的机器有不同的字长。例如一台8位机,它的1个字就等于1个字节,字长为8位。如果是一台16位机,那么,它的1个字就由2个字节构成,字长为16位。在32位操作系统当中,一个字是4个字节,字是计算机进行数据处理和运算的单位。
ASCII:American Standard Code for Information Interchange,美国信息交换标准代码。注意不是ASCⅡ(罗马数字2),使用指定的7 位或8 位二进制数组合来表示128 或256 种可能的字符。标准ASCII 码也叫基础ASCII码,使用7 位二进制数(剩下的1位二进制为0)来表示所有的大写和小写字母,数字0 到9、标点符号, 以及在美式英语中使用的特殊控制字符。
文本文件和二进制文件:
文本文件是指以ASCII码方式(也称文本方式)存储的文件,后面基于 utf-8 编码的文本文件,utf-8是能够向后兼容ASCII,即相同的ASCII文本文件和UTF-8文本文件完全一致。它是一种典型的顺序文件,其文件的逻辑结构又属于流式文件。
二进制文件:是基于值编码的文件,你可以根据具体应用,指定某个值(可以看作是自定义编码)。
②、内容总结
计算机是由软件与硬件组成的,而硬件又包括了总线、I/O设备、主存以及处理器,其中信息是由位以及上下文表示的,而信息则是从I/O设备以位的形式通过总线进入主存,然后由处理器从主存将信息取出处理。
一个程序的执行,是经历了预处理器、编译器、汇编器以及链接器的处理之后,才最终成为可执行的文件。
PS:有人问我《深入理解计算机系统》这本书的PDF文档,这里给出下载链接:http://pan.baidu.com/s/1boOM3Tl 密码:kfe1
深入理解计算机系统(1.1)------Hello World 是如何运行的的更多相关文章
-
《深入理解计算机系统V2》学习指导
<深入理解计算机系统V2>学习指导 目录 图书简况 学习指导 第一章 计算机系统漫游 第二章 信息的表示和处理 第三章 程序的机器级表示 第四章 处理器体系结构 第五章 优化程序性能 第六 ...
-
深入理解计算机系统(4.1)---X86的孪生兄弟,Y86指令体系结构
引言 各位猿友们好,计算机系统系列很久没更新了,实在是抱歉之极.新的一年,为了给计算机系统系列添加一些新的元素,LZ将其更改为书的原名<深入理解计算机系统>.这本书非常厚,而且难度较高,L ...
-
深入理解计算机系统(1.2)---hello world的程序是如何运行的
在写本章的内容之前,LZ先做个小广告.其实也不算是什么广告,就是LZ为了和各位猿友交流方便,另外也确实有个别猿友留言或者在博客里发短消息给LZ要联系方式.因此LZ斗胆建立了一个有关<深入理解计算 ...
-
《深入理解计算机系统》 Chapter 7 读书笔记
<深入理解计算机系统>Chapter 7 读书笔记 链接是将各种代码和数据部分收集起来并组合成为一个单一文件的过程,这个文件可被加载(货被拷贝)到存储器并执行. 链接的时机 编译时,也就是 ...
-
深入理解计算机系统家庭作业汇总 20135301&;&;20135328
深入理解计算机系统家庭作业 深入理解计算机系统第二章家庭作业 题目2.64 题目要求 判断二进制数偶数位是否有任意一位位为1,有的话返回1,否则返回0 解题过程 int any_even_one(un ...
-
【转】《深入理解计算机系统》C程序中常见的内存操作有关的典型编程错误
原文地址:http://blog.csdn.net/slvher/article/details/9150597 对C/C++程序员来说,内存管理是个不小的挑战,绝对值得慎之又慎,否则让由上万行代码构 ...
-
《深入理解计算机系统》C程序中常见的内存操作有关的典型编程错误
对C/C++程序员来说,内存管理是个不小的挑战,绝对值得慎之又慎,否则让由上万行代码构成的模块跑起来后才出现内存崩溃,是很让人痛苦的.因为崩溃的位置在时间和空间上,通常是在距真正的错误源一段距离之后才 ...
-
CSAPP(深入理解计算机系统)读后感
9月到10月8号,包括国庆七天,大概每天5小时以上的时间,把Computer System: A Programmer Perspective 2rd version(深入理解计算机系统)的英文版啃完 ...
-
深入理解计算机系统(2.5)------C语言中的有符号数和无符号数以及扩展和截断数字
上一篇博客我们讲解了计算机中整数的表示,包括无符号编码和补码编码,以及它们之间的互相转换,个人觉得那是非常重要的知识要点.这篇博客我们将介绍C语言中的有符号数和无符号数以及扩展和截断数字. 1.C语言 ...
随机推荐
-
原生javascript 实现 animate
原生javascript 实现 animate //animate function getstyle(obj,name){ if(obj.currentStyle){ return obj.curr ...
-
json pickle time
多层装饰器: 字符串格式化: 当字符串格式化时 , %% 生成器 使用函数创造的 yield递归模块PY:模块其他:类库 先导入 后使用 自定义模块内置模块第三方模块为什么要有模块 将代码归类模块的内 ...
-
android开发中常见布局的注意点
常见布局的注意点 线性布局: 必须有一个布局方向 水平或者垂直 在垂直布局中 只有左对齐 右对齐 水平居中生效 在水平布局中 只有顶部对齐 底部对齐 垂直居中生效 权重:组件按比例分配屏幕的剩余部分( ...
-
ipconfig 无效
刚刚配置了很多的环境变量后,在命令行下输入ipconfig后无效了 于是在环境变量PATH底下再次加入了;C:\WINDOWS\system32; 从新运行ipconfig,问题解决
-
java编程思想第四版中net.mindview.util包
把 net那个包 放入到你编写的项目同一个文件夹下(与src文件夹平级的那个),然后刷新一下工程即可
-
jquery_ajax
一.调用 <script type="text/javascript" src="jquery-1.11.2.min.js"></script ...
-
【转】Xcode常用快捷键与技巧分享
原文网址:http://www.jianshu.com/p/039954b0cbe0 工欲善其事必先利其器. 虽然Xcode编写objective-c or swift很完美, 但了解其工具的常用快捷 ...
-
asp.net 从客户端中检测到有潜在危险的 Request.Form 值错误解
从客户端(ftbContent="<P><A href="http://l...")中检测到有潜在危险的 Request.Form 值. 说明: 请求验 ...
-
java web 过滤器跟拦截器的区别和使用
注:文章整理自知乎大牛以及百度网友(电脑网络分类达人 吕明),特此感谢! 一.过滤器 1.什么是过滤器? 过滤器是一个程序,它先于与之相关的servlet或JSP页面运行在服务器上.过滤器可附加到一个 ...
-
纸壳CMS可视化建站系统搭建多语言网站
纸壳CMS是可视化建站系统,现已经从架构上支持多语言.但是多语言功能默认是没有开启的.您可以从设置中开启多语言,或者随时关闭它,您可以随时进行切换. 开启多语言 如果您没有在系统设置中看到多语言设置菜 ...