一、编译系统
一个简单的hello.c程序
1 #include <stdio.h>
2
3 int main(){
4 printf(” hello , world ”);
5 return 0;
6 }
编译系统的工作流程主要分为四个阶段:预处理、编译、汇编以及链接。
- 预处理:预处理器会根据以 # 开头的代码,来修改原始程序。例如 hello 程序中引入了头文件 stdio.h,预处理器会读取该头文件中的内容,将其中的内容直接插入到源程序中,结果就得到了另外一个 C 程序。即:hello.c 经过预处理器后得到为文本文件 hello.i。
- 编译:编译器将 hello.i 文件翻译成 hello.s 文件,这一阶段包括词法分析、语法分析、语义分析、中间代码生成以及优化等等一系列的中间操作。
- 汇编:汇编器根据指令集将汇编程序 hello.s 翻译成机器指令,并且把这一系列的机器指令按照固定的规则进行打包,得到可重定位目标文件 hello.o 。此时 hello.o 虽然是一个二进制的文件,但是还不能执行,还要经历最后一个阶段:链接。
- 链接:在 hello 这个程序中,我们调用了 printf 函数,这个函数是标准 C 库中的一个函数,存储在名为 printf.o 的文件中。链接器 (ld)负责把 hello.o 和 printf.o 按照一定规则进行合并。正是因为链接器要对 hello.o 和 printf.o 的进行调整,所以 hello.o 才会被称之为可重定位目标文件。最终经过链接阶段可以得到可执行目标文件 hello。
二、应用与意义
- 1、理解编译系统可以优化程序的性能
- 2、理解编译系统可以帮助我们理解链接过程中出现的错误
- 3、避免安全漏洞:缓冲区溢出(buffer overflow)
三、系统的硬件组成
- CPU:中央处理单元(Central Processing Unit , CPU),也称处理器,包含 PC ( 程序计数器:Program Count )、寄存器堆(Register file)、ALU(算数/逻辑计算单元:Arithmatic/logic Unit)三个部分.
- 程序计数器 PC:是一个 4 字节或是 8 字节的存储空间,里面存放的是某一条
指令的地址。从系统上电的那一瞬间,直到系统断电,处理器就不断地在执行
PC 指向的指令,然后更新 PC,使其指向下一条要执行的指令(注意:这个下
一条指令与刚刚执行的指令不一定是相邻的) - 寄存器:可以理解为一个临时存放数据的空间。例如计算两个变量 a+b 的和,处理器从内存中读取 a 的值暂存在寄存器 X 中,读取 B 的值暂存在寄存器 Y中,这个操作会覆盖寄存器中原来的数值,处理器完成加载的操作后,ALU(Arithmatic/logic Unit)会从复制寄存器 X 和 Y 中保存的数值,然后进行算术运算,得到的结果会保存到寄存器 X 或者寄存器 Y 中,此时寄存器中原来的数值会被新的数值覆盖。
- 算数/逻辑计算单元 ALU:计算速度极快,且专攻算数与逻辑的计算,计算机核心部分
- 内存:主存(Main Memory),也称为内存、运行内存,处理器在执行程序时,内存主要存放程序指令以及数据。从物理上讲,内存是由随机动态存储器芯片组成;从逻辑上讲,内存可以看成一个从零开始的大数组,每个字节都有相应地址。
- 总线:内存和处理器之间通过总线来进行数据传递。实际上,总线贯穿了整个计算机系统,它负责将信息从一个部件传递到另外一个部件。通常总线被设计成传送固定长度的字节块,也就是字(word),至于这个字到底是多少个字节,各个系统中是不一样的,32 位的机器,一个字长是 4 个字节;而 64 位的机器,一个字长是 8 个字节.
- 输入输出设备:除了处理器,内存以及总线,计算机系统还包含了各种输入输出设备,例如键盘、鼠标、显示器以及磁盘等等。每一个输入输出设备都通过一个控制器或者适配器与IO 总线相连.
四、解释内存中的指令
hello.c程序在执行过程中发生了什么?
-
操作过程
• hello.c 经过编译系统得到可执行目标文件 hello,此时可执行目标文件 hello 已经存放在系统的磁盘上,那么,如何运行这个可执行文件呢?
• 在 linux 系统上运行可执行程序:打开一个 shell 程序,然后在 shell 中输入相应可执行程序的文件名:
• linux>./hello -
shell 是什么?
shell 是一个命令解释程序,如果命令行的第一个单词不是内置的 shell 命令,shell就会对这个文件进行加载并运行. 此处,shell 加载并且运行 hello 程序,屏幕上显示hello,world 内容,hello 程序运行结束并退出,shell 继续等待下一个命令的输入.
-
程序执行流程
1 首先我们通过键盘输入”./hello” 的字符串,shell 程序会将输入的字符逐一读入寄存器,处理器会把 hello 这个字符串放入内存中。
2 当我们完成输入,按下回车键时,shell 程序就知道我们已经完成了命令的输入,然后执行一系列的指令来来加载可执行文件 hello。
3 这些指令将 hello 中的数据和代码从磁盘复制到内存。数据就是我们要显示输出的”hello , world\n” ,这个复制的过程将利用 DMA(Direct Memory Access)技术,数据可以不经过处理器,从磁盘直接到达内存。
4 当可执行文件 hello 中的代码和数据被加载到内存中,处理器就开始执行 main函数中的代码,main 函数非常简单,只有一个打印功能。
五、存储
- 设备容量
通常情况下,大容量的存储设备的存取速度要比小容量的慢,运行速度更快的设备的价格相对于低速设备要更贵。例如:在一个系统上,磁盘的容量一般为 TB 级,内存的容量一般为 GB 级,磁盘的容量大概是内存的 1000 倍。 - Cache 至关重要
1、对于处理器而言,从磁盘上读取一个字所花费的时间开销比从内存中读取的开销大1000 万倍。寄存器文件的只能存储几百个字节的信息,而内存的可以存放几十亿的字节信息(GB 级),从寄存器文件读取数据比从内存读取差不多要快 100 倍。
2、随着半导体技术的发展,处理器与内存之间的差距还在持续增大,针对处理器和内存之间的差异,系统设计人员在寄存器文件和内存之间引入了高速缓存(cache),比较新的,处理能力比较强的处理器,一般有三级高速缓存,分别为 L1 cache ,L2cache 以及 L3 cache。
3、L1 cache 的访问速度与访问寄存器文件几乎一样快,容量大小为数万字节(KB 级别);L2 cache 的访问速度是 L1 cache 的五分之一,容量大小为数十万到数百万字节之间;L3 cache 的容量更大,同样访问速度与 L2 cache 相比也更慢。 - 存储设备的层次结构
整个计算机系统的信息存储可以用一个层次结构来表示,通常而言,存储容量越小,速度越快,价格越高,上一层存储设备是下一层存储设备的高速缓存.
六、操作系统管理硬件
1、操作系统的作用
无论是shell 程序还是hello 程序都没有直接访问键盘、显示器、磁盘这些硬件设备,真正操作硬件的是操作系统,我们可以把操作系统看成是应用程序和硬件之间的中间层,所有的应用程序对硬件的操作必须通过操作系统来完成。
这样设计的目的主要有两个:
1 防止硬件被失控的应用程序滥用;
2 操作系统提供统一的机制来控制这些复杂的底层硬件.
为了实现上述的功能,操作系统引入了几个抽象的概念。例如:文件2是对IO设备的抽象;虚拟内存是对内存和磁盘IO的抽象;进程是对处理器、内存以及IO设备的抽象。
2、进程
假设示例场景中只有两个并发的进程:shell 进程和hello 进程
1 最开始的时候,只有shell 进程在运行,即shell 在等待命令行的输入。
2 当我们通过shell 进程加载hello 进程时,shell 进程通过系统调用来执行我们的请求,系统调用会将控制权从shell 进程传递给操作系统,操作系统保存shell进程的上下文,然后创建一个新的hello 进程及其上下文,然后将控制权转交给新的hello 进程。
3 hello 进程执行完之后,操作系统就会恢复shell 进程的上下文,并将控制权交给shell 进程,之后shell 进程继续等待下一个命令的输入。
4 操作系统会跟踪进程运行所需要的所有状态信息,这种状态,称为上下文(Context)。例如当前PC 和寄存器的值,以及内存中的内容等等。
现代操作系统中,一个进程实际上由多个线程组成,每个线程都运行在进程的上下文中,共享代码和数据。由于网络服务器对并行处理的需求,线程成为越来越重要的编程模型。
我们肯定是希望能够同时进行多种线程,而不希望单线程占据全部操作系统. 举个例子:一个微信程序就是一个进程,而我们在微信里一边文字聊天、一边视频聊天、一边传输文件就是三个线程同时进行.
七、虚拟内存
操作系统为每个进程提供了一个假象,就是每个进程都在独自占用整个内存空间,每个进程看到的内存都是一样的,我们称之为虚拟地址空间。
下图为Linux 的虚拟地址空间,从下往上看,地址是增大的。最下面是0 地址。
- 第一个区域是用来存放程序的代码和数据的,这个区域的内容是从可执行目标文件中加载而来的,例如我们多次提到的hello 程序。对所有的进程来讲,代码都是从固定的地址开始。至于这个读写数据区域放的是什么数据呢?例如在C语言中,全局变量就是存放在这个区域.
- 顺着地址增大的方向,继续往上看就是堆(heap),学过C 语言的同学应该用过malloc 函数,程序中malloc 所申请的内存空间就在这个堆中。程序的代码和数据区在程序一开始的时候就被指定了大小,但是堆可以在运行时动态的扩展和收缩.
- 接下来,就是共享库的存放区域。这个区域主要存放像C 语言的标准库和数学库这种共享库的代码和数据,例如hello 程序中的printf 函数就是存放在这里.
- 继续往上看,这个区域称为用户栈(user stack),我们在写程序的时候都使用过函数调用,实际上函数调用的本质就是压栈。这句话的意思是:每一次当程序进行函数调用的时候,栈就会增长,函数执行完毕返回时,栈就会收缩。需要注意的是栈的增长方向是从高地址到低地址.
- 最后,我们看一下地址空间的最顶部的区域,这个区域是为内核保留的区域,应用程序代码不能读写这个区域的数据,也不能直接调用内核中定义的函数,也就是说,这个区域对应用程序是不可见的.
1、文件
Linux 系统的哲学思想是:一切皆为文件。
• 所有的IO 设备,包括键盘,磁盘,显示器,甚至网络,这些都可以看成文件,系统中所有的输入和输出都可以通过读写文件来完成.
• 虽然文件的概念非常简单,但却非常强大。例如︰当程序员需要处理读写磁盘上的文件时,他们不需要了解具体的磁盘技术,同一个程序,可以在不同磁盘技术上的不同系统上运行.
2、系统之间利用网络通信
系统来看,网络也可以视为一个IO 设备。
• 随着互联网的发展,从一台计算机发送消息到另外一台计算机已经成为非常普遍的应用。《深入理解计算机系统》中讲述了如何使用本地计算机上的telnet 客户端连接远程主机上的telnet 服务器。
• 由于telnet 的安全性问题,目前ssh 的连接方式的更加普遍。当我们在ssh 的客户端中输入hello 字符串并且敲下回车之后,客户端的软件就会通过网络将字符串发送到ssh 服务端,ssh 服务端从网络端接收到这个字符串以后,会将这个字符串传递给远程主机上的shell 程序,然后shell 负责hello 程序的加载,运行结果返回给ssh 的服务端,最后ssh 的服务端通过网络将程序的运行结果发送给ssh 的客户端,ssh 客户端在屏幕上显示运行结果。
3、一些约定/假定
• 任务(task):并行计算所处理的对象.
• 工作量(workload):处理某任务所需的各种开销的总和.
• 处理器(processor):并行计算中所使用的最基本的处理器单元.
• 执行率(execution rat):每个处理器单位时间能完成的工作量.
• 执行时间(execution time):处理某任务所需的时间.
• 加速比(scalability):当处理器个数增多时,完成某固定工作量任务所需执行时间的减少倍数.
• 理想加速比(ideal scalability):处理器个数增多的比例.
• 并行效率(parallel efficiency): 加速比÷ 理想加速比×100%.
八、系统加速
九、并发和并行
如何获得更高的计算能力呢?可以通过以下三种途径:
1 线程级并发;
2 指令级并行;
3 单指令多数据并行.
- 线程级并发
• 首先我们看一个多核处理器的组织结构,下图的处理器芯片具有四个CPU 核心,由于篇幅限制,另外两个用省略号代替了。每个CPU 核心都有自己的Llcache 和L2 cache ,四个CPU 核心共享L3 cache,这4 个CPU 核心集成在一颗芯片上.
• 对于许多高性能的服务器芯片,单颗芯片集成的CPU 数量高达几十个,甚至上百个。通过增加CPU 的核心数,可以提高系统的性能.
除此之外,还有一个技术就是超线程(hyperthreading),也称同时多线程。如果每个CPU 核心可以执行两个线程,那么四个核心就可以并行的执行8 个线程,那么,单个CPU 核心是如何实现超线程的呢?
在CPU 内部,像程序计数器和寄存器文件这样的硬件部件有多个备份,而像浮点运算部件这个样的硬件还是只有一份,常规单线程处理器在做线程切换时,大概需要20000 个时钟周期,而超线程处理器可以在单周期的基础上决定执行哪一个线程,
这样一来,CPU 可以更好地利用它的处理资源。当一个线程因为读取数据而进入等待状态时,CPU 可以去执行另外一个线程,其中线程之间的切换只需要极少的时间代价. - 指令级并行
现代处理器可以同时执行多条指令的属性称为指令级并行,每条指令从开始到结束大概需要20 个时钟周期或者更多,但是处理器采用了非常多的技巧可以同时处理多达100 条指命,因此,近几年的处理器可以保持每个周期24 条指令的执行速率。
- 单指令多数据并行
现代处理器拥有特殊的硬件部件,允许一条指令产生多个并行的操作,这种方式称为单指令多数据(Single Instruction Multiple Data)。SIMD 的指令多是为了提高处理视频、以及声音这类数据的执行速度,比较新的Intel 以及AMD 的处理器都是支持SIMD 指令加速。