TASK01

编译系统的工作流程

image-20211214191226063.png

这个过程虽然是通过一条命令完成的,然而实际上编译系统的处理过程却是非常复 杂的,大致可以分为四个阶段,分别为预处理编译汇编以及链接

image-20211214191313483.png

  • 预处理

    预处理器器会根据以 # 开头的代码,来修改原始程序。例如 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。

硬件架构图

image-20211214191905902.png

CPU架构

中央处理单元(Central Processing Unit , CPU),也称处理器,包含PC ( 程序计数器:Program Count )、寄存器堆(Register file)、ALU(算数/逻辑计算单元:Arithmatic/logic Unit)三个部分.

内存

主存(Main Memory),也称为内存、运行内存,处理器在执行程序时,内存主要存放程序指令以及数据。从物理上讲,内存是由随机动态存储器芯片组成;从逻辑上讲,内存可以看成一个从零开始的大数组,每个字节都有相应地址.

总线

内存和处理器之间通过总线来进行数据传递。实际上,总线贯穿了整个计算机系统,它负责将信息从一个部件传递到另外一个部件。通常总线被设计成传送固定长度的字节块,也就是字(word),至于这个字到底是多少个字节,各个系统中是不一样的,32 位的机器,一个字长是4 个字节;而64 位的机器,一个字长是8 个字节.

输入输出设备

  • 输入输出设备

    除了处理器,内存以及总线,计算机系统还包含了各种输入输出设备,例如键盘、鼠标、显示器以及磁盘等等。每一个输入输出设备都通过一个控制器或者适配器与IO 总线相连.

程序执行过程

  • hello.c 经过编译系统得到可执行目标文件hello,此时可执行目标文件hello 已经存放在系统的磁盘上,那么,如何运行这个可执行文件呢?

  • 在linux 系统上运行可执行程序:打开一个shell 程序,然后在shell 中输入相应可执行程序的文件名:linux>./hello

    (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 函数非常简单,只有一个打印功能。

Hello 程序执行过程

image-20211214200139605.png

image-20211214200150363.png

image-20211214200157398.png

Cache至关重要

随着半导体技术的发展,处理器与内存之间的差距还在持续增大,针对处理器和内 存之间的差异,系统设计人员在寄存器文件和内存之间引入了高速缓存(cache), 比较新的,处理能力比较强的处理器,一般有三级高速缓存,分别为 L1 cache ,L2 cache 以及 L3 cache。


image-20211214200553455.png

操作系统

操作系统作用

  1. 防止硬件被失控的应用程序滥用。
  2. 操作系统提供统一的机制来控制这些复杂的底层硬件。


    image-20211214201201428.png

操作系统抽象概念

  • 文件是对IO 设备的抽象
  • 虚拟内存是对内存和磁盘IO的抽象
  • 进程是对处理器、内存以及IO设备的抽象


    image-20211214201058226.png

进程

image-20211214203642876.png

现代操作系统中,一个进程实际上由多个线程组成,每个线程都运行在进程的上下文中,共享代码和数据。由于网络服务器对并行处理的需求,线程成为越来越重要的编程模型。

虚拟内存

操作系统为每个进程提供了一个假象,就是每个进程都在独自占用整个内存空间, 每个进程看到的内存都是一样的,我们称之为虚拟地址空间1.

下图为 Linux 的虚拟地址空间,从下往上看,地址是增大的。最下面是 0 地址。


image-20211214203832810.png
  • 第一个区域是用来存放程序的代码和数据的,这个区域的内容是从可执行目标文件中加载而来的,例如我们多次提到的 hello 程序。对所有的进程来讲,代码都是从固定的地址开始。至于这个读写数据区域放的是什么数据呢?例如在 C 语言中,全局变量就是存放在这个区域.

  • 顺着地址增大的方向,继续往上看就是堆(heap),学过 C 语言的同学应该用过malloc 函数,程序中 malloc 所申请的内存空间就在这个堆中。程序的代码和数 据区在程序一开始的时候就被指定了大小,但是堆可以在运行时动态的扩展和 收缩.

  • 接下来,就是共享库的存放区域。这个区域主要存放像 C 语言的标准库和数学库这种共享库的代码和数据,例如 hello 程序中的 printf 函数就是存放在这里.

  • 继续往上看,这个区域称为用户栈(user stack),我们在写程序的时候都使用过函数调用,实际上函数调用的本质就是压栈。这句话的意思是:每一次当程序进行函数调用的时候,栈就会增长,函数执行完毕返回时,栈就会收缩。需要注意的是栈的增长方向是从高地址到低地址.

  • 最后,我们看一下地址空间的最顶部的区域,这个区域是为内核保留的区域,应用程序代码不能读写这个区域的数据,也不能直接调用内核中定义的函数,也就是说,这个区域对应用程序是不可见的.

文件

image-20211214204147180.png

所有的 IO 设备,包括键盘,磁盘,显示器,甚至网络,这些都可以看成文件, 系统中所有的输入和输出都可以通过读写文件来完成。

系统之间利用网络通信

image-20211214204336095.png

使用本地计算机上的telnet客户端连接远程主机上的 telnet服务器

当我们在 ssh 的 客户端中输入 hello 字符串并且敲下回车之后,客户端的软件就会通过网络将 字符串发送到 ssh 服务端,ssh 服务端从网络端接收到这个字符串以后,会将这 个字符串传递给远程主机上的 shell 程序,然后 shell 负责 hello 程序的加载,运 行结果返回给 ssh 的服务端,最后 ssh 的服务端通过网络将程序的运行结果发 送给 ssh 的客户端,ssh 客户端在屏幕上显示运行结果


image-20211214204439554.png

阿姆达尔定律 (Amdahl’s Law, 1967)

α ∈ [0, 1] 是某任务无法并行处理部分所占的比例. 假设该任务的工作量固定,则对任意 n 个处理器,相比于 1 个处理器,能够取得的加速比满足:S(n) < 1 .

古斯塔法森定律 (Gustafson’s Law, 1988)

α ∈ [0, 1] 是某任务无法并行处理部分所占的比例. 假设该任务的工作量可以随着 处理器个数缩放,从而保持处理时间固定. 则对任意 n 个处理器,相比于 1 个处理 器,能够取得的加速比 S (n) 不存在上界.

孙-倪定律 (Sun-Ni’s Law, 1990)

并发并行

如何获得更高的计算能力呢?可以通过以下三种途径:

  1. 线程级并发;

  2. 指令级并行;

  3. 单指令多数据并行

线程级并发

  • 首先我们看一个多核处理器的组织结构,下图的处理器芯片具有四个 CPU 核 心,由于篇幅限制,另外两个用省略号代替了。每个 CPU 核心都有自己的 Ll cache 和 L2 cache ,四个CPU核心共享 L3 cache,这 4 个 CPU 核心集成在一 颗芯片上。

  • 对于许多高性能的服务器芯片,单颗芯片集成的 CPU 数量高达几十个,甚至上百个。通过增加 CPU 的核心数,可以提高系统的性能。


    image-20211214205443764.png

还有一个技术就是超线程(hyperthreading),也称同时多线程。如果每 个 CPU 核心可以执行两个线程,那么四个核心就可以并行的执行 8 个线程。在 CPU 内部,像程序计数器和寄存器文件这样的硬件部件有多个备份,而像浮点 运算部件这个样的硬件还是只有一份,常规单线程处理器在做线程切换时,大概需 要 20000 个时钟周期,而超线程处理器可以在单周期的基础上决定执行哪一个线程, 这样一来,CPU 可以更好地利用它的处理资源。当一个线程因为读取数据而进入等 待状态时,CPU 可以去执行另外一个线程,其中线程之间的切换只需要极少的时间代价。

指令级并行

现代处理器可以同时执行多条指令的属性称为指佘级并行,每条指令从开始到结束大概需要 20 个时钟周期或者更多,但是处理器采用了非常多的技巧可以同时处理多达 100 条指命,因此,近几年的处理器可以保持每个周期24条指令的执行速率。


image-20211214210149840.png

单指令多数据并行

现代处理器拥有特殊的硬件部件,允许一条指令产生多个并行的操作,这种方式称为单指令多数据(Single Instruction Multiple Data)。SIMD 的指令多是为了提高处 理视频、以及声音这类数据的执行速度,比较新的 Intel 以及 AMD 的处理器都是支持 SIMD 指令加速。


image-20211214210234053.png
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容