2.8 作业
2.8.1 内存
有这样一个算式:taxableIncome = salary - exempts - percent401k / 100 * salary ,其中的变量 salary = 50000,exempts = 7000,percent401k = 4.5。
在配有 Intel Core i7 处理器的系统中,taxableIncome 的值在内存中是怎样表示的?假设该值是从 0x013A32A8h 这一地址开始存放的。
2.8.2 指令执行
假设某程序有 220 条指令。如果把它放在下列三个处理器中运行,那么分别需要多少个时钟周期才能执行完毕?
不支持指令流水线技术的3阶段式处理器
配有 3 层指令流水线的处理器
配有 15 层指令流水线的处理器
2.8.3 输入/输出(挑战题)
如果在 Linux 系统的 /dev/ 目录里执行 ls -la 命令,那么就会输出一份长清单,其中包含一些特殊文件,用以表示连接到该系统的每一个设备,这些文件均带有重要的信息。请通过上网搜索来研究这份清单看看其中涉及几种 I/O 设备,每种设备都是怎样表示的。
第1章与第2章补充材料
与体系结构有关的更多细节
程序加载
第 1 章说过,程序是由一个叫作程序加载器的工具来加载的。加载进来之后,CPU(主要是说它的 eip/rip 寄存器)指向程序的入口点,这个入口点可能叫作 main 或 start 等。下面描述启动程序的一般步骤。当用户开启程序(例如用鼠标双击程序的图标)时:
1. 操作系统把文件的大小以及该文件在磁盘中的物理位置等信息获取出来。
2. 操作系统在内存中寻找合适的地点分配空间,并把必要的信息放在描述符表(descriptor table)中(第 10 章会讲解描述符)。
3. 操作系统开始执行程序的第一条指令(也就是位于入口点的那条指令)。这使程序变为进程,并获得由系统所赋予的 ID。
4. 该进程自行运作,而操作系统则会对进程所发出的资源请求予以响应。
5. 进程结束并让出它所占据的内存。
提高存储器访问速度的措施
第 2 章说过,从主存(也就是 RAM)中读取数据不如从寄存器里读取快,因为后者更接近 ALU,然而程序不太可能把所有数据都放在寄存器里,它还是需要读取主存中的数据,于是,这些年来就产生了下列措施,旨在用更好的办法来执行指令并访问数据,以提升速度。
缓存,可以缓解内存的访问速度较慢这一问题。如果要获取的指令或数据能在缓存中找到,那么就出现了缓存命中现象;若是不能,则称为缓存未命中。此外,指令可以成批地移入缓存或是从缓存里移出。
预取算法(prefetching algorithm)可以根据计算机访问内存的模式把将来有可能需要的指令与数据填充到缓存及预取缓冲中。当前大多数 CPU在流水线里都安排有预解码这一环节。
集成式内存控制器(integrated memory controller)技术把内存控制器整合到 CPU 中,使其与 RAM 之间无须再通过前端总线及北桥来通信。
多核心/多处理器技术使得系统能够把有待执行的指令分布到这些核心及处理器上。
对 CPU 起支持作用的处理器及组件
计算机系统中,有很多处理器及组件负责执行一些特殊的任务以支持 CPU 的工作。下面列出几个值得注意的组件:
浮点运算器(Floating-Point Unit,FPU)。它通常与 CPU 相整合(参见第 6 章)。
时钟发生器:为 CPU 产生时钟信号。
可编程中断控制器(Programmable Interrupt Controller,PIC):处理由键盘、硬盘及其他硬件设备所产生的中断(参见第 10 章)。
可编程间隔计时器:每秒钟把系统中断 18.2 次,用以更新系统时间与时钟、控制系统扬声器,并负责刷新主存。
图形处理器(Graphical Processing Unit,GPU):经过特别设计的处理器,能够高效地处理矩阵及向量并将可视数据显示到屏幕上。
指令流水线技术与多单元的处理器技术
第 2 章说过,指令流水线技术可以同时推进很多条指令处理流程从而提升执行速度。CPU 中通常包含许多单元,例如取指单元、解码单元、执行控制单元、寻址单元、内存控制器、乘法器、移位器、ALU、FPU等。当前的 CPU一般会在每个处理器及内核中配备多个单元。因此,考虑到硬件环境方面的因素,指令流水线技术可以用多种形式来实现。
标量(scalar)处理器每次只处理一份数据(在这里指的是一条指令),因此,执行指令的平均速度大约和时钟的速度一样。
超标量(superscalar)处理器可以同时获取多条指令,并将其派发到多个功能单元上分别执行。这样处理指令的速度要大于时钟的速度。
下面举几个多单元处理器的例子。在处理器的发展过程中,产生了很多种这样的处理器,这里所举的只是其中的几种而已。
范例1:PowerPC 970 有两个 ALU,两个 FPU,两个加载/存储单元,两个 SIMD 单元,而且配有 10 ~ 16 层流水线,各单元的具体层数有所不同。
范例2:Pentium 3处理器有 3 个 ALU,Pentium 4 处理器有两个 ALU(然而每一个的时钟频率都是 P3 ALU 的两倍)。这两种处理器都有 1 个 FPU。
范例3:AMD Athlon 处理器有 3 个 ALU、3 个 AGU(Address-Generation Unit,地址生成单元)及 1 个 FPU。
范例4:Haswell 架构的 Intel Core i7 处理器支持 HyperThreading(超线程)技术,这意味着每个内核在逻辑上相当于两个处理器。4 核 i7 处理器的执行单元共有 8 个“端口”,因此每个时钟周期能够执行 8 条微操作(micro-op)。这种处理器有 4 个 ALU。
输入/输出系统
开发者如果能多了解一些与硬件及设备有关的知识,就可以更好地利用其特性来编写程序并优化程序代码。一般来说,I/O 分为以下三个层级。
应用程序层(Application Level):高级语言的程序库与 API(Application Programming Interface,应用程序编程接口)。
系统层(System Level):用汇编指令来执行系统调用。
UEFI 层(UEFI Level):UEFI(Unified Extensible Firmware Interface,统一可扩展固件接口)是一种底层软件,能够直接与硬件通信并提供启动及运行期服务。UEFI 是新式的解决方案,用来替代 BIOS(Basic Input/Output System,基本输入输出系统),它比后者更加先进。
设备驱动程序(Device Driver)包含一些例程,使得操作系统能够直接与硬件通信。
下面举一个例子来演示信息是怎样在整个 I/O 体系的各个层级之间交换的。
1. C++ 程序里有一条用高级语言写成的语句,它调用库函数以便向标准输出端写入字符串,这条语句可以是: cout << “This is string”;。
2. 库函数调用系统例程并把相关的参数(例如字符串的地址与大小)传过去。
3. 系统在执行该例程的时候反复调用设备驱动程序或 UEFI 子例程,并把每个字符的 ASCII/Unicode 码及其颜色传过去。
a.设备驱动程序或 UEFI 子例程收到字符,将其与字体对应起来并把它发送给与视频控制器相连的硬件端口。
b. 视频控制器通过定时的硬件信号把该字符打印到屏幕上。在此过程中,它要控制每个像素应该怎样显示。
c. 系统调用另一个设备驱动程序或 UEFI 子例程将光标推进到下一个位置。
汇编语言很强大,它能够灵活地游走于各个层级。开发者可以在应用程序层调用库函数以执行控制台式的 I/O 或基于文件的 I/O。也可以在操作系统层中通过调用内核来完成这些 I/O 操作。此外,还能在 UEFI 层/设备驱动层中调用相关的函数,以控制与具体设备有关的特性。开发者需要在效率、结果与可移植性等因素之间权衡,以决定自己究竟应该在哪个层面上执行 I/O 操作。本书第 10 章将会讨论这个话题,还会讲解应用程序层面与系统层面的调用。