一、计算机漫游

一、为了写出更好的代码

计算机系统是由硬件和软件组成的。它们共同工作来运行应用程序。虽然系统的实现方式随着时间不断变化,但是系统内在的概念却没有改变。所有计算机系统都有相似的硬件和软件组件,它们执行着相似的功能,我们只有深入了解这些组件是如何工作的,以及这些组件是如何影响程序的正确性和性能的,以此来提高自身技能。

二、hello world

通过跟踪程序的生命周期了解系统发生什么以及为什么会这样

三、系统中所有的信息都是由位+上下文构成。

包括磁盘文件、存储器中的程序,存储器中存放的用户数据以及网络上传送的数据都是由一串位表示。而区分不同数据对象的唯一方法就是我们读到这些对象时的上下文。比如在不同的上下文中,一个同样的字节序列可能表示一个整数、浮点数、字符串或者机器指令。

举个例子,在硬盘中有一段二进制数据: 10110101

如果有个程序读了出来,把他当成了整数来处理,那就是十进制的181 (不考虑补码);

如果Java虚拟机把他当成字节码把它读了出来,那就是putfield (B5) ,意思是设置某个字段的值,然后虚拟机需要读取接下来的16位,确定是java class的哪个field。 如果接下里的16位不正确,那就报错了。

所以单单数据(bit)是没有意义的,必须得在某个程序,某个环境中(即上下文/Context)才有意义

四、编译

hello 程序的生命周期是从一个高级 C 语言程序开始的,因为这种形式能被人读懂。然而,计算机系统是读不懂高级语言的。为了在系统上运行 hello.c 程序,每条 C 语句都必须要被其他程序转化为一系列的低级机器语言指令。一般来说,要将 hello.c 变成一个可执行的目标程序,必须要经过 预处理器、编译器、汇编器和链接器 的处理。如下:


  预处理器、编译器、汇编器和链接器 一起构成了编译系统,下面对每个步骤分别进行解析:

  ①、预处理阶段:预处理器cpp 根据以字符 # 开头的命令,修改原始的 C 程序,比如 Hello.c 中第一行 #include命令告诉预处理器读取系统文件 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 文件,它是一个可执行目标程序,可以被加载到内存中,由系统运行。

一个小栗子:

gcc -E hello.c -o hello.i

然后查看 hello.i


然后执行 gcc -S hello.i ,查看 hello.s

五、运行

要想知道程序是怎样运行的,至少应该它待在什么样的环境,首先来了解下硬件:



一、总线:贯穿整个系统的一组电子管道,通常被设计成用来传送定长的字节块,也就是字。字的大小与系统相关,比如在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"后,外壳程序将字符逐一读入到寄存器中,再把它放入到存储器中,如下图:



当我们在键盘上敲回车键的时候,shell程序知道我们已经结束了命令的输入。然后外壳执行一系列指令来加载可执行的 hello 文件,将 hello 目标文件中的代码和数据从磁盘复制到主存。数据包括最终会被输出的字符串“HelloWorld\n”,一旦目标文件中的代码和数据被加载到主存,处理器就开始执行 hello 程序的 main程序中的机器语言指令。这些指令将“Hello World\n”字符串中的字节从主存复制到寄存器文件,再从寄存器文件中复制到显示设备,最终显示在屏幕上。


六、存储架构

七、硬件架构


八、总结

名词解释:

位:描述寄存器存储大小的单位,是计算机最小粒度的存储单位描述,每一位的状态只能是 0 或 1

字节:存储空间的基本计量单位,八个位表示一个字节,例:00000001

字:字是计算机进行数据处理和运算的单位。跟多少位系统有关系,例:32位系统的字就是 4 个字节

内容概括:

计算机是由软件与硬件组成的,而硬件又包括了总线、I/O设备、主存以及处理器,其中信息是由位以及上下文表示的,而信息则是从I/O设备以位的形式通过总线进入主存,然后由处理器从主存将信息取出处理。一个程序的执行,是经历了预处理器、编译器、汇编器以及链接器的处理之后,才最终成为可执行的文件。

缓存和存储结构相辅相成,相邻层次的存储结构,下级为上级的高速缓存层,这也是数据使用中缓存优化的点。

你可能感兴趣的:(一、计算机漫游)