牛客网论坛考研计算机组成原理笔记,GitHub已下载量已过百万

**前言:**看过很多书,但总是忘得很快。知识广度越大越容易接纳新东西,但从考察角度来说,自然是对某个方面了解越深越好。那些大而全的著作虽然每本都是经典中的经典,但实际工作中可能只用到其中的一小部分。我现在越发觉得少即是多,看再多东西没有理解透彻都是白搭,把最常用的每天过一遍才是最有效的。

操作系统概念->王道操作系统考研书->操作系统导论->深入理解计算机系统->汇编/保护模式汇编/gcc内嵌汇编->哈工大李志军老师的操作系统慕课及实验->linux内核设计及实现->linux命令行与shell编程->linux内核完全剖析->unix环境高级编程->unix网络编程->Linux/unix系统编程上下册->linux技术内幕(非常重量级,是基于内核3.2的,结合全部源码看最好)

这一套我大概花了2年,比较费劲,但是学完了能将计算机基础课中所有的知识点串起来,会有种悟道的感觉,有空闲时间的同学非常建议去学一学。学完这一套,只要不是面大厂的操作系统相关核心岗位,在操作系统与网络这一部分基本上不会有啥压力。就是比较废时间,加油呀!

img

1、计算机系统概述

主要讲授信息的数字化表示、存储程序与冯诺依曼体制;计算机的诞生和发展;计算机系统的层次结构和硬件系统组织;计算机的主要性能指标。

第一节 计算机系统层次结构

1.计算机系统的基本组成:硬件+软件

img

硬件部分

  • 最底层的微处理机,由中央处理机中算术和逻辑处理单元、寄存器以及操作控制用的逻辑电路组成。
  • 第二层微型计算机,是在微处理机的基础上,设计适当的总线结构以及存储器、外部设备接口等,构成一个硬件计算机。
  • 第三层是机器语言环境,程序员能够通过计算机提供的机器语言环境,编制机器语言程序,控制计算机完成预定的任务。

软件部分

  • 在操作系统层面,系统操作员可以基于操作系统环境,采用一般的运算指令和I/O操作指令,进行编程,使用计算机全部的系统资源。
  • 同理,汇编程序员和高级语言程序员,用各自规定的语言形式提交各自的处理任务,由相应层上的解释或编译程序进行处理。
  • 应用程序层是给最终用户使用的环境,在该层次中,用户使用时,并不需要了解计算机是如何工作的,在他们的视角下,计算机可能就是一个游戏机、图文处理工具等。

在计算机技术中,从某种角度看,把本来客观存在的事务或属性视作不见的现象,称作透明性(Transparency)。

2.计算机硬件的基本组成:运算器+存储器+控制器+输入设备+输出设备

img

  • **运算器:**信息加工部件。对二进制编码数据进行算术运算(按算数规则进行运算,如加减乘除)和逻辑运算(非算术性运算,如比较,位移等)
  • **存储器:**内存储器(主存,大多数是由半导体器件组成)和外存储器(辅助存储器或外存)。CPU只能直接访问内存中数据,计算机不能与外存直接交换数据,而是和内存交换数据。 常见外存如硬盘,光驱,U盘 等,容量大,数据不丢失,存取慢
  • **控制器:**指挥中心。如从存储器中取出指令,控制计算机各部分协调运行 控制器和运算器整合在CPU中
  • **输入设备:**使计算机从外部获得信息的设备如鼠标,键盘,光笔,扫描仪,话筒,数码相机,摄像头, 手写板
  • **输出设备:**把计算机处理信息的结果以人们能够识别的形式表示出来的设备如显示器,打印机,绘图仪,音箱,投影仪

3.系统软件和应用软件

  • **系统软件:**指控制和协调计算机及外部设备,支持应用软件开发和运行的系统,是无需用户干预的各种程序的集合。
  • **应用软件:**是用户可以使用的各种程序设计语言,以及用各种程序设计语言编制的应用程序的集合。

img

计算机软件系统是由**“系统软件”“应用软件”**两部分组成。系统软件是指担负控制和协调计算机及其外部设备、支持应用软件的开发和运行的一类计算机软件;应用软件是指为特定领域开发、并为特定目的服务的一类软件。

**系统软件和应用软件的区别:**系统软件是指控制和协调计算机及外部设备,支持应用软件开发和运行的系统;应用软件是用户可以使用的各种程序设计语言,以及用各种程序语言编制的应用程序的集合。

功能不同

  • 系统软件:主要功能是调度,监控和维护计算机系统;负责管理计算机系统中各种独立的硬件,使得它们可以协调工作。
  • 应用软件:件是为满足用户不同领域、不同问题的应用需求而提供的那部分软件。它可以拓宽计算机系统的应用领域,放大硬件的功能。

特点不同

  • 系统软件:使得计算机使用者和其他软件将计算机当作一个整体而不需要顾及到底层每个硬件是如何工作的。
  • 应用软件:是利用计算机解决某类问题而设计的程序的集合,供多用户使用。

4.(易考)翻译程序:

  • 汇编程序(汇编器)将汇编语言程序翻译成机器语言程序
  • 解释程序(解释器)将源程序翻译成机器指令并立即执行
  • 编译程序(编译器)将高级语言翻译城机器语言或汇编语言

第二节 计算机性能指标

计算机功能的强弱或性能的好坏,不是由某项指标决定的,而是由它的系统结构、指令系统、硬件组成、软件配置等多方面的因素综合决定的。对于大多数普通用户来说,可以从以下几个指标来大体评价计算机的性能

img

  • 1.吞吐量:一台计算机在某一时间间隔内能够处理的信息量。
  • 2.响应时间:用户输入一个作业至输出开始之间的时间。
  • 3.主频:主频f:是描述计算机运算速度最重要的一个指标。通常所说的计算机运算速度是指计算机在每秒钟所能执行的指令条数,即中央处理器在单位时间内平均“运行”的次数,其速度单位为MHz(兆赫兹)或GHz(吉赫兹)。
  • 4.时钟周期:主频的倒数,T=1/f,度量单位μs (微秒),ns(纳秒)。
1μs=10^-6s,1ns=10^-9s
  • 5.CPI:表示每条指令周期数,即执行每条指令所需的平均时钟周期数。
计算公式:CPI=执行某段程序所需的CPU时钟周期数/程序包含的指令条数
  • 6.CPU执行时间:表示CPU执行一段程序所占用的CPU时间。
计算公式:CPU执行时间=CPU时钟周期数*CPU时钟周期
  • 7.MIPS(Million Instructions Per Second):单字长定点指令平均执行速度 Million Instructions Per Second的缩写,每秒处理的百万级的机器语言指令数。这是衡量CPU速度的一个指标。像是一个Intel80386 电脑可以每秒处理3百万到5百万机器语言指令,即我们可以说80386是3到5MIPS的CPU,所以这个是运算速度。

计算公式:MIPS=指令数/(程序执行时间*10^6)

  • 8.FLOPS是Floating-point Operations Per Second每秒所执行的浮点运算次数的英文缩写。它是一个衡量计算机计算能力的量,这个量经常使用在那些需要大量浮点运算的科学运算中。有时也会被记为flop/s。
MIPS=指令数/(程序执行时间*10^6)

各种FLOPS的含义

一个 MFLOPS (megaFLOPS) 等于每秒1百万 (=10^6) 次的浮点运算,
一个 GFLOPS (gigaFLOPS) 等于每秒10亿 (=10^9) 次的浮点运算,
一个 TFLOPS (teraFLOPS) 等于每秒1万亿 (=10^12) 次的浮点运算,
一个 PFLOPS (petaFLOPS) 等于每秒1千万亿 (=10^15) 次的浮点运算。

8.1 MFLOPS(Million Floating-point Operations per Second,每秒百万个浮点操作),衡量计算机系统的技术指标,不能反映整体情况,只能反映浮点运算情况。MFLOPS=操作浮点数/(执行时间*10^6)。

8.2 GFLOPS 就是 Giga Floating-point Operations Per Second,即每秒10亿次的浮点运算数,常作为GPU性能参数但不一定代表GPU的实际表现,因为还要考虑具体如何拆分多边形和像素、以及纹理填充,理论上该数值越高越好。1GFlops = 1,000MFlops。

8.3 TFLOPS (teraFLOPS) 等于每秒1万亿 (=10^12) 次的浮点运算

8.4 PFLOPS (petaFLOPS) 等于每秒1千万亿 (=10^15) 次的浮点运算。

  • 9.处理机字长:一般来说,计算机在同一时间内处理的一组二进制数称为一个计算机的“字”,而这组二进制数的位数就是“字长”,在其他指标相同的情况下,字长越长,计算机处理数据的速度就越快。
  • 10.总线宽度:一般指CPU中运算器与存储器之间进行互连的内部总线二进制位数。总线宽度分为地址总线宽度和数据总线宽度,其中,地址总线宽度决定了CPU能够使用多大容量的 主存储器, 若计算机的地址总线的宽度为32位,则最多允许直接访问2^32= 22*230=4*1GB=4GB 的物理空间。
  • 11.存储器容量: 存储器中所有存储单元的总数,通常用KB,MB,GB,TB来表示。

有内存储器和外存储器之分:内存储器:是CPU可以直接访问的存储器,需要执行的程序与需要处理的数据就是存放在主存中的。内存的性能指标主要包括存储容量和存取速度。

1B=8bit;1KB=1024B;1MB=1024KB;1GB=1024MB;TB=1024GB;1PB=1024TB

外存储器:通常是指硬盘容量。外存储器容量越大,可存储的信息就越多,可安装的应用软件就越丰富。

  • 12.存储器带宽:单位时间内从存储器读出的二进制数信息量,一般用字节数/秒表示。
  • 13.利用率:在给定的时间间隔内系统被实际使用的时间所占的比率,用百分比表示。即在一段时间内被使用的时间(次数)占总时间(总使用次数)的百分比,有硬件利用率、软件利用率、指令利用率等。

题目总结:①对于高级语言程序员来说,浮点数格式、乘法指令、数据如何在运算器中运算时透明的。对于汇编语言程序员,指令格式,机器构造,数据格式则不是透明的。

②在CPU中,IR、MAR、MDR对各类程序员都是透明的。

③机器字长,指令字长,存储字长

机器字长也称字长——是计算机直接处理二进制数据的位数,机器字长一半等于内部寄存器的大小,它决定了计算机的运算精度。

指令字长——一个指令中包含的二进制代码的位数。

存储字长——一个存储单元中二进制代码的长度。

指令字长一般是存储字长的整数倍,若指令字长等于存储字长的2倍,则需要2次访存来取出一条指令,因此取值周期为机器周期的2倍;若指令字长等于存储字长,则取值周期等于机器周期。

2、数据的表示、运算与校验

主要讲授数值型和字符型数据的表示,数据的运算方法,奇偶校验等常用的数据校验方法。

数制与编码

  • 编码:用少量简单的基本符号,对大量复杂多样信号进行一定规律的组合表示

数值数据的编码表示

  • 数值数据的表示方法:直接用二进制表示:定点浮点表示方法; BCD码
  • 数值数据在计算机内部编码表示的数称为机器数。机器数真正的值称为机器数的真值

校验码

  • 校验位:在有效信息数据代码之外,再扩充几位。增加的部分称为冗余位或校验位
  • 码字:若干位二进制代码组成的一个字
  • 码制:包含若干种码字的集合 (ASCII码、BCD码…)
  • 距离:在一种码制中将两个码字逐位比较,具有不同代码的位的个数叫做这两个码字间的“距离” (例如在8421BCD码中,0000和1001的距离为2)
  • 码距:将一种码制中各码字间的最小距离称为“码距”(例如在8421BCD码中,码距为1。如果发生1位传输错误,则无法查出错误)

合理增大码距,能提高发现错误的能力,但表示一定数量的合法码所使用的二进制位数要变多,增加了电子线路的复杂性和数据存储、数据传送的数量。

  • k 位码有 2 k 2^k2 k个编码状态,全用于表示合法码,则任何一位出错 , 均会变成另一个合法码,不具有检错能力
  • 从一个合法码变成另一个合法码,至少要改变几位码的值,称为最小码距 (码距 ),码距和编码方案将决定其检错纠能力

校验过程:

  • 编码过程:对原始数据进行编码,产生一个校验位,把校验位附加到原始数据中
  • 将包含原始数据以及校验信息的码字进行传送
  • 对接收到的码字进行译码,检查收到的码字,发现 / 改正错误

海明校验码:用于并行数据传送中

奇偶校验码:用于并行数据传送中

实现原理:使码距由1增加到2。若编码中有1位二进制数出错了,出错的编码就成为非法编码,就可以知道出现了错误。在原有的编码之上再增加1位校验位,原编码 k kk 位,形成新的编码为 k + 1 k+1k+1 位。

增加的方法有2种:

  • 奇校验:增加位的0或1,保证整个编码中1的个数为奇数
  • 偶校验:增加位的0或1,保证整个编码中1的个数为偶数

img

img

  • **特点:**在奇偶校验码中,若两个码字中有奇数位不同,则它们的校验位就不同;若有偶数位不同,则虽校验位相同,但至少有两位数据位不同。因而任意两个码字之间至少有两位不同,所以码距 d = 2 d=2d=2。所以,只能发现奇数位出错,不能发现偶数位出错,而且不能确定发生错误的位置,不具纠错能力。
  • **优点:**开销小,常被用于存储器读写检查或按字节传输过程中的数据校验。因为一字节长的代码发生错误时,1位出错的概率较大,两位以上出错则很少,所以奇偶校验码用于校验一字节长的代码是有效的。

循环冗余校验码:用于串行数据传送中

CRC(Cyclical Redundancy Check)校验码一般是指 k kk 位信息之后拼接 r rr 位校验码

CRC码的编码方法:

CRC整个编码长度为 n = k + r n=k+rn=k+r 位,故CRC码又叫 ( n , k ) (n,k)(n,k) 码。

其编码方法如下:

img

发送信息时将等式左边生成的 n nn 位CRC码送给对方。当接收方接到 n nn 位编码后,同样除以 G ( x ) G(x)G(x),如果传输正确则余数为0,否则,可以根据余数的数值确定是哪位数据出错。

img

列出CRC码的查错表:

img

img

定点数的表示和运算

定点数的表示

  • 定点数:在计算机中,小数点位置固定不变的数。定点表示即为约定小数点的位置固定在数的最左边或最右边。小数点不占用存储位

定点小数 (原码、反码、补码)

img

img

整数

整数形式:小数点隐含在数的最右边

1 位符号位,n − 1 n-1n−1 位数值位。

img

因此,同一个整数的移码与补码仅符号位相反。

定点数的运算

定点数的移位运算

算术移位:移位的对象是数值型数据,在移位后会发生数值大小的变化

  • 对于二进制数,左移,绝对值扩大;右移,绝对值缩小
  • 算术移位规则:左移与逻辑移位相同,右移带符号位移位

逻辑移位:逻辑左移、逻辑右移、循环左移和循环右移等。逻辑移位只是使数码位置发生变化,没有正、负性质,也没有数值大小问题。

算术移位和逻辑移位的区别:

  • 算术移位:带符号数移位
  • 逻辑移位:无符号数移位

补码定点数的加/减运算

img

img

定点数的乘法运算

原码一位乘法

两个原码数相乘,其乘积的符号为相乘两数的异或值数值为两数绝对值之积

img

如果直接使用手工运算的计算方法的话,硬件电路会变得比较复杂(要实现四个数的相加)。

img

img

定点补码一位乘法

补码一位乘法:乘法直接用补码进行,以减少转换次数.

img

img

img

注意:上面的移位为算术移位

浮点数的表示和运算

浮点数的表示

img

img

浮点数规格化

为了保证数据精度,尾数通常用规格化形式表示:

  • 当 R = 2 R=2R=2,且尾数值不为 0 00 时,其绝对值大于或等于 0.5 0.50.5
  • 对非规格化浮点数,通过将尾数左移或右移,并修改阶码值使之满足规格化要求

增加尾数位数也可以提高精度,但数值范围减小 (阶码位数减少了)

img

浮点数的溢出判断

根据规格化后的阶码判断:

  • 上溢——浮点数阶码大于机器最大阶码—中断
  • 下溢——浮点数阶码小于机器最小阶码—按机器零处理

假如不考虑规格化:

img

若考虑规格化,则

最小正数:2 − 1 × 2 − ( 2 m − 1 ) 2^{-1}\times 2^{-(2^m-1)}2 
−1
 ×2 
−(2 m−1)
 
最大负数:− 2 − 1 × 2 − ( 2 m − 1 ) -2^{-1}\times 2^{-(2^m-1)}−2 
−1
 ×2 
−(2m−1)

IEEE754 标准

  1. 单精度浮点数(32位),阶码8位,尾数24位(内含1位符号位)
  2. 双精度浮点数(64位),阶码11位,尾数53位(内含1位符号位)

img

阶码使用的移码偏移量为什么不用 128 ?
因为若用128,则最大阶127对应的编码127+128=255,而255(全1)要用来表示一些特殊值

IEEE754 标准中的规范化浮点数:

阶码为非全0非全1的数是正常的规格化浮点数。即:阶码范围在 1 11~254 254254 (单精度)和 1 11~2046 20462046 (双精度)的数是一个正常的规格化数

根据IEEE754 的定义,可知其阶码的真值范围:

− 126 -126−126~+ 127 +127+127(单精度)
− 1022 -1022−1022~+ 1023 +1023+1023(双精度)

全0阶码全0尾数:表示 + 0 / − 0 +0\ /-0+0 /−0

img

img

浮点数的加/减运算

  • 两数首先均为规格化数,进行规格化浮点数的加减运算需经过5步完成:
  • 对阶操作:低阶向高阶补齐,使阶码相等
  • 阶码较小的数将阶码增大,尾数减小 (阶码每增大1,尾数就进行1位右移。尾数为原码时,尾数右移时,符号位不动,最高数值位补0;尾数为补码时,尾数右移时,符号也移位,最高位补符号位。)
  • 尾数运算:阶码对齐后直接对尾数运算
  • 结果规格化:对运算结果进行规格化处理。如尾数溢出则需右规(尾数向右移位直到它为规格化的数);如不是,规格化时应左规
  • 舍入操作:在对阶和右规过程中,可能出现尾数末位丢失引起误差,需考虑舍入。丢失位进行0舍1入或恒置1处理。常用“0”舍“1”入法:当移掉的部分最高位为1时,在尾数的末尾加1,如果加1后又使得尾数溢出,则要再进行一次右规
  • 判断溢出:判断阶码是否溢出,下溢则将运算结果置0(机器0),上溢则设置溢出标志

img

img

img

算术逻辑单元 ALU

串行进位加法器

img

img

特点:串行进位(又称行波进位)加法器,逻辑电路较简单,但最高位的加法运算,一定要等到所有低位的加法完成之后才能进行,低位的进位要逐步传递到高位,逐级产生进位,因此运算速度较慢.

并行进位加法器

引进两个函数

img

  • 并行进位加法器的运算速度很快,形成最高进位输出的延迟时间很短,但是以增加硬件逻辑线路为代价
  • 对于长字长的加法器,往往将加法器分成若干组,在组内采用并行进位,组间采用串行进位或并行进位

先行进位加法器

单级先行进位:将 n nn 位字长分为若干组,每组内采用并行进位方式,组与组之间则采用串行进位方式

img

ALU 电路

Arithmetic logical unit

利用集成电路技术可将若干位全加器、并行进位链、输入选择电路等部分集成在一块芯片上,称为多功能算术、逻辑运算部件 ALU

img

4位ALU部件 SN74181

img

用4片74181电路可组成16位ALU。如图所示,片内进位是快速的,但片间进位是逐片传递的,因此总的形成时间还比较长.

img

  • 如果把16位ALU中的每4位作为一组,用类似位间快速进位的方法来实现16位ALU(4片ALU组成),那么就能得到16位快速ALU:

利用16位并行进位链集成电路SN74182,产生芯片连接时所需要的并行进位信号,构成片内、片间均并行进位的ALU。

img

SN74182可以向SN74181提供片间并行进位信号,其芯片本身输出的G、P还可以支持更高一级的并行进位链,从而可构成更长位数的ALU

例如,采用3片SN74182和8片SN74181可级连组成片内、片间均并行进位的32位ALU电路。5片SN74182和16片SN74181可级连组成片内、片间均并行进位的64位ALU电路(如下图所示)

img

---------------------------------------------IT开发人员的加分项----------------------------------------------

内核是IT开发人员的加分项,一个计算机系统是一个硬件和软件的共生体,它们互相依赖,不可分割。计算机的硬件,含有外围设备、处理器、内存、硬盘和其他的电子设备组成计算机的发动机。但是没有软件来操作和控制它,自身是不能工作的。完成这个控制工作的软件就称为操作系统,在Linux的术语中被称为“内核”,也可以称为“核心”。Linux内核的主要模块(或组件)分以下几个部分:存储管理、CPU和进程管理、文件系统、设备管理和驱动、网络通信,以及系统的初始化(引导)、系统调用等。

Linux 内核实现了很多重要的体系结构属性。在或高或低的层次上,内核被划分为多个子系统。Linux 也可以看作是一个整体,因为它会将所有这些基本服务都集成到内核中。这与微内核的体系结构不同,后者会提供一些基本的服务,例如通信、I/O、内存和进程管理,更具体的服务都是插入到微内核层中的。

随着时间的流逝,Linux 内核在内存和 CPU 使用方面具有较高的效率,并且非常稳定。但是对于 Linux 来说,最为有趣的是在这种大小和复杂性的前提下,依然具有良好的可移植性。Linux 编译后可在大量处理器和具有不同体系结构约束和需求的平台上运行。一个例子是 Linux 可以在一个具有内存管理单元(MMU)的处理器上运行,也可以在那些不提供MMU的处理器上运行。Linux 内核的uClinux移植提供了对非 MMU 的支持。

在IT行业 如:嵌入式开发,驱动开发,Android开发,c++开发,Java开发如果接触到底层方面 那么 懂得内核:会使自己的开发工作产生对应的效益。 懂得内核:会让自己更加了解底层的原理与开发源码。 内核是面试的加分项 内核是走向专家的必经之路 不管你是不是做内核开发,内核技术是储备技能,开阔视野,扩展技术面的不二选择。

要转向内核开发,嵌入式开发人员需要掌握以下知识:

  • \1. C语言编程:C语言是内核开发的主要编程语言,需要熟练掌握其语法和编程技巧。
  • \2. 操作系统原理:需要了解操作系统的基本原理,包括进程管理、内存管理、文件系统等。
  • \3. Linux内核:需要深入了解Linux内核的架构、模块、驱动程序等。
  • \4. 设备驱动开发:需要掌握设备驱动的开发流程和技术,包括字符设备、块设备、网络设备等。
  • \5. 调试技能:需要掌握调试技能,包括使用调试工具、分析内核崩溃等。
  • \6. 硬件知识:需要了解硬件的基本原理和操作,包括处理器、内存、外设等。
  • \7. 开源社区:需要了解开源社区的文化和开发流程,以便更好地参与内核开发。
  • 总之,转向内核开发需要广泛的知识储备和实践经验,需要不断学习和探索。

3、CPU子系统

主要讲授CPU的结构和发展历程,指令系统,MIPS32指令集,单周期和多周期MIPS32处理器设计,提升CPU性能的一些高级技术,如多核技术等。

img

Cpu子系统用于控制cgroup中所有进程可以使用的cpu时间片。附加了cpu子系统的hierarchy下面建立的cgroup的目录下都有一个cpu.shares的文件,对其写入整数值可以控制该cgroup获得的时间片。例如:在两个cgroup中都将cpu.shares设定为1的任务将有相同的CPU时间,但在cgroup中将cpu.shares设定为2的任务可使用的CPU时间是在cgroup中将cpu.shares设定为1的任务可使用的CPU时间的两倍。

cpu子系统是通过Linux CFS调度器实现的。所以在介绍cpu子系统之前,先简单说一下CFS调度器。按照作者Ingo Molnar的说法:“CFS百分之八十的工作可以用一句话概括:CFS在真实的硬件上模拟了完全理想的多任务处理器”。在“完全理想的多任务处理器”下,每个进程都能同时获得CPU的执行时间。当系统中有两个进程时,CPU的计算时间被分成两份,每个进程获得50%。然而在实际的硬件上,当一个进程占用CPU时,其他进程就必须等待。所以CFS将惩罚当前进程,使其它进程能够在下次调度时尽可能取代当前进程。最终实现所有进程的公平调度。

CFS调度器将所有状态为RUNABLE的进程都插入红黑树。在每个调度点,CFS调度器都会选择红黑树的最左边的叶子节点作为下一个将获得CPU的进程。那红黑树的键值是怎么计算的呢?红黑树的键值是进程所谓的虚拟运行时间。一个进程的虚拟运行时间是进程时间按整个红黑树中所有的进程数量normalized的结果

每次tick中断,CFS调度器都要更新进程的虚拟运行时间,然后调整当前进程在红黑树中的位置,调整完成后如果发现当前进程不再是最左边的叶子,就标记need_resched标志,中断返回时就会调用scheduler()完成进程的切换。

最后再说一下,进程的优先级和进程虚拟运行时间的关系。前面提到了,每次tick中断,CFS调度器都要更新进程的虚拟运行时间。那这个时间是怎么计算的呢?CFS首先计算出进程的运行时间delta_exec,然后计算normalized后的delta_exec_weighted,最后再将delta_exec_weighted加到进程的虚拟运行时间上。跟进程优先级有关的就是delta_exec_weighted,delta_exec_weighted=delta_exec_weighted * NICE_O_LOAD / se->load,其中NICE_O_LOAD是个常量,而se->load跟进程的nice值成反比,因此进程优先级越高则se->load越大,则计算出来的delta_exec_weighted越小,这样进程优先级高的进程就可以获得更多的cpu时间。

介绍完CFS调度器,我们开始介绍cpu子系统是如何通过CFS调度器实现的。CFS调度器不仅支持基于进程的调度,还支持基于进程组的组调度。CFS中定义了一个task_group的数据结构来管理组调度。

struct task_group {
	struct cgroup_subsys_state css;
 
#ifdef CONFIG_FAIR_GROUP_SCHED
	/* schedulable entities of this group on each cpu */
	struct sched_entity **se;
	/* runqueue "owned" by this group on each cpu */
	struct cfs_rq **cfs_rq;
	unsigned long shares;
 
	atomic_t load_weight;
#endif
 
#ifdef CONFIG_RT_GROUP_SCHED
	struct sched_rt_entity **rt_se;
	struct rt_rq **rt_rq;
 
	struct rt_bandwidth rt_bandwidth;
#endif
 
	struct rcu_head rcu;
	struct list_head list;
 
	struct task_group *parent;
	struct list_head siblings;
	struct list_head children;
 
#ifdef CONFIG_SCHED_AUTOGROUP
	struct autogroup *autogroup;
#endif
 
	struct cfs_bandwidth cfs_bandwidth;
};

task_group中内嵌了一个cgroup_subsys_state,也就是说进程可以通过cgroup_subsys_state来获取它所在的task_group,同样地cgroup也可以通过cgroup_subsys_state来获取它对应的task_group,因此进程和cgroup都存在了一组cgroup_subsys_state指针。

struct sched_entity **se是一个指针数组,存了一组指向该task_group在每个cpu的调度实体。

struct cfs_rq **cfs_rq也是一个指针数组,存在一组指向该task_group在每个cpu上所拥有的一个可调度的进程队列。

parent、sibling和children三个指针负责将task_group连成一棵树,这个跟cgroup树类似。

有了这个数据结构,我们来看CFS在调度的时候是怎么处理进程组的。我们还是从CFS对tick中断的处理开始。

CFS对tick中断的处理在task_tick_fair中进行,在task_tick_fair中有:

for_each_sched_entity(se)
{
    cfs_rq = cfs_rq_of(se);
    entity_tick(cfs_rq, se, queued);
}

首先看下在组调度的情况下,for_each_sched_entity是怎么定义的:

#define for_each_sched_entity(se) \
    for (;se;se = se->parent)

即从当前进程的se开始,沿着task_group树从下到上对se调用entity_tick,即更新各个se的虚拟运行时间。

在非组调度情况下:

#define for_each_sched_entity(se) \
    for (; se; se = NULL)

即只会对当前se做处理。CFS处理完tick中断后,如果有必要就会进行调度,CFS调度时通过pick_next_task_fair函数选择下一个运行的进程的。在pick_next_task_fair中有:

do {
se = pick_next_entity(cfs_rq);
set_next_entity(cfs_rq, se);
cfs_rq = group_cfs_rq(se);
} while (cfs_rq);

在这个循环中,首先从当前的队列选一个se,这个跟非组调度一样的(红黑树最左边的节点),再将se设置成下一个运行的se,再从该se获取该se对应的task_group拥有的cfs_rq(如果该se对应一个进程而非一个task_group的话,cfs_rq会变成NULL),继续这个过程直到cfs_rq为空,即当se对应的是一个进程。

简而言之,同一层的task_group跟进程被当成同样的调度实体来选择,当被选到的是task_group时,则对task_group的孩子节点重复这个过程,直到选到一个运行的进程。因此当设置一个cgroup的shares值时,该cgroup当做一个整体和剩下的进程或其他cgroup分享cpu时间。比如,我在根cgroup下建立cgroup A,将其shares值设为1024,再建立cgroup B,将其shares值设为2048,再将一些进程分别加入到这两个cgroup中,则长期调度的结果应该是A:B:C = 1:2:1(C是系统中未加入到A或B的进程)。

引起CFS调度的除了tick中断外,还有就是有新的进程加入可运行队列这种情况。CFS处理这个情况的函数时enqueue_task_fair,在enqueue_task_fair中有:

for_each_sched_entity(se)
{
    if (se->on_rq)
        break;
    cfs_rq = cfs_rq_of(se);
    enqueue_entity(cfs_rq, se, flags);
    flags = ENQUEUE_WAKEUP;
}

我们前面已经看过for_each_sched_entity在组调度下的定义了,这里是将当前se和se的直系祖先节点都加入到红黑树,而在非组调度情况下,只需要将当前se本身加入即可。造成这种差异的原因,在于在pick_next_task_fair中选择se时,是从上往下的,如果一个se的祖先节点不在红黑树中,它永远都不会被选中。而在非组调度的情况下,se之间并没有父子关系,所有se都是平等独立,在pick_next_task_fair,第一次选中的肯定就是进程,不需要向下迭代。

类似的处理还发生在讲一个se出列(dequeue_task_fair)和put_prev_task_fair中。

以上是cpu系统通过CFS调度器实现以cgroup为单位的cpu时间片分享,下面我们来看一下cpu子系统本身。cpu子系统通过一个cgroup_subsys结构体来管理:

struct cgroup_subsys cpu_cgroup_subsys = {
	.name		= "cpu",
	.create		= cpu_cgroup_create,
	.destroy	= cpu_cgroup_destroy,
	.can_attach	= cpu_cgroup_can_attach,
	.attach		= cpu_cgroup_attach,
	.exit		= cpu_cgroup_exit,
	.populate	= cpu_cgroup_populate,
	.subsys_id	= cpu_cgroup_subsys_id,
	.early_init	= 1,
};

cpu_cgroup_subsys其实是对抽象的cgroup_subsys的实现,其中的函数指针指向了特定于CPU子系统的实现。这里再说一下,cgroups的整体设计。当用户使用cgroup文件系统,创建Cgroup的时候,会调用cgroup目录操作的mkdir指针指向的函数,该函数调用了cgroup_create,而cgroup_create会根据该cgroup关联的子系统,分别调用对应的子系统实现的create指针指向的函数。即做了两次转换,一次从系统调用命令到cgroup文件系统,另一次从cgroup文件系统到特定的子系统实现。

cgroup中除了通用的控制文件外,每个子系统还有自己的控制文件,子系统也是通过cftype来管理这些控制文件。cpu子系统很重要的一个文件就是cpu.shares文件,以为就是通过这个文件的数值来调节Cgroup所占用的cpu时间。shares文件对应的cftype结构为:

#ifdef CONFIG_FAIR_GROUP_SCHED
{
.name = "shares",
.read_u64 = cpu_shares_read_u64,
.write_u64 = cpu_shares_write_u64,
},
#endif

当对cgroup目录下的文件进行操作时,该结构体重定义的函数指针指向的函数就会被调用,下面我们就再看看这两个函数的实现,从而发现shares文件的值时如何起作用的。

static u64 cpu_shares_read_u64(struct cgroup *cgrp, struct cftype *cft)
{
	struct task_group *tg = cgroup_tg(cgrp);
 
	return (u64) scale_load_down(tg->shares);
}

比较简单,简单的读取task_group中存储的shares就行了

static int cpu_shares_write_u64(struct cgroup *cgrp, struct cftype *cftype,
				u64 shareval)
{
	return sched_group_set_shares(cgroup_tg(cgrp), scale_load(shareval));
}

设定cgroup对应的task_group的shares值。

那这个shares值是怎么起作用的呢?在sched_group_set_shares中有:

tg->shares = shares;
for_each_possible_cpu(i)
{
    cfs_rq_set_shares(tg->cfs_rq[i], 0);
    set_se_shares(tg->se[i], shares);
}

cfs_rq_set_shares强制做一次cpu SMP负载均衡。真正起作用的是在set_se_shares中,它调用了__set_se_shares,在__set_se_shares中有:

se->load.weight = shares;
se->load.inv_weight = 0;

根据之前我们分析的CFS的调度原理可以知道,load.weight的值越大,算出来的虚拟运行时间越小,进程能使用的cpu时间越多。这样一来,shares值最终就是通过调度实体的load值来起作用的。

4、存储子系统

主要讲授存储系统的层次和存储器的类型及技术指标,半导体存储原理及存储器,磁表面存储原理及磁盘,光存储原理及器件,存储系统性能的改进措施。

img

第一节 存储器概述

什么是存储器

存储器(Memory)是计算机系统中的记忆设备,用来存放程序和数据。计算机中的全部信息,包括输入的原始数据、计算机程序、中间运行结果和最终运行结果都保存在存储器中。它根据控制器指定的位置存入和取出信息。

存储器的构成

构成存储器的存储介质,目前主要采用半导体器件和磁性材料。存储器中最小的存储单位就是一个双稳态半导体电路或一个CMOS晶体管或磁性材料的存储元,它可存储一个二进制代码。由若干个存储元组成一个存储单元,然后再由许多存储单元组成一个存储器。一个存储器包含许多存储单元,每个存储单元可存放一个字节。每个存储单元的位置都有一个编号,即地址,一般用十六进制表示。一个存储器中所有存储单元可存放数据的总和称为它的存储容量。假设一个存储器的地址码由20位二进制数(即5位十六进制数)组成,则可表示220,即1M个存储单元地址。每个存储单元存放一个字节,则该存储器的存储容量为1KB。

存储器的分类

按存储介质分

  • 半导体存储器:用半导体器件组成的存储器。U盘是半导体存储器,U盘内集成的是Flash芯片,存储介质为半导体。
  • 磁表面存储器:用磁性材料做成的存储器。

按存储方式分

  • 随机存储器:任何存储单元的内容都能被随机存取,且存取时间和存储单元的物理位置无关。
  • 顺序存储器:只能按某种顺序来存取,存取时间和存储单元的物理位置有关。

按存储器的读写功能分

  • 只读存储器(ROM):存储的内容是固定不变的,只能读出而不能写入的半导体存储器。
  • 随机读写存储器(RAM):既能读出又能写入的半导体存储器。

按信息的可保存性分

  • 非永久记忆的存储器:断电后信息即消失的存储器。
  • 永久记忆性存储器:断电后仍能保存信息的存储器。

按在计算机系统中的作用分

根据存储器在计算机系统中所起的作用,可分为主存储器、辅助存储器、高速缓冲存储器、控制存储器等。为了解决对存储器要求容量大,速度快,成本低三者之间的矛盾,目前通常采用多级存储器体系结构,即使用高速缓冲存储器、主存储器和外存储器。

存储器的层次结构

在一个过程与 SPI 管理器联接之前,当前存储器环境是上层执行器环境,所以所有由过程自身通过 palloc/repalloc 或通过 SPI 应用函数在联接到 SPI 管理器之前分配的存储器都在这个环境里.

按照与CPU的接近程度,存储器分为内存储器与外存储器,简称内存与外存。内存储器又常称为主存储器(简称主存),属于主机的组成部分;外存储器又常称为辅助存储器(简称辅存),属于外部设备。CPU不能像访问内存那样,直接访问外存,外存要与CPU或I/O设备进行数据传输,必须通过内存进行。在 80386以上的高档微机中,还配置了高速缓冲存储器(cache),这时内存包括主存与高速缓存两部分。对于低档微机,主存即为内存。

存储器的性能指标

1.存储容量:存储字数×字长

2.单位成本:每位价格=总成本/总容量

3.存储速度:数据传输率=数据的宽带/存储周期

存储周期=存取时间+恢复时间

把存储器分为几个层次主要基于下述原因:

1.合理解决速度与成本的矛盾,以得到较高的性能价格比。半导体存储器速度快,但价格高,容量不宜做得很大,因此仅用作与CPU频繁交流信息的内存储器。磁盘存储器价格较便宜,可以把容量做得很大,但存取速度较慢,因此用作存取次数较少,且需存放大量程序、原始数据(许多程序和数据是暂时不参加运算的)和运行结果的外存储器。计算机在执行某项任务时,仅将与此有关的程序和原始数据从磁盘上调入容量较小的内存,通过CPU与内存进行高速的数据处理,然后将最终结果通过内存再写入磁盘。这样的配置价格适中,综合存取速度则较快。
为解决高速的CPU与速度相对较慢的主存的矛盾,还可使用高速缓存。它采用速度很快、价格更高的半导体静态存储器,甚至与微处理器做在一起,存放当前使用最频繁的指令和数据。当CPU从内存中读取指令与数据时,将同时访问高速缓存与主存。如果所需内容在高速缓存中,就能立即获取;如没有,再从主存中读取。高速缓存中的内容是根据实际情况及时更换的。这样,通过增加少量成本即可获得很高的速度。

2.使用磁盘作为外存,不仅价格便宜,可以把存储容量做得很大,而且在断电时它所存放的信息也不丢失,可以长久保存,且复制、携带都很方便。

存储器管理

服务器在存储器环境按这样的方法分配存储器:在某个环境分配的存储器可以被环境析构器释放而不会影响其他环境中分配的存储器.所有存储器分配(通过 palloc 等)都被当作在当前环境的区域中分配存储器.如果你试图释放(或再分配)不在当前环境的存储器,你将得到不可预料的结果.创建存储器环境和切换存储器环境是 SPI 管理器中存储器管理器的任务.SPI 过程处理两种存储器环境:上层执行器存储器环境和过程存储器环境(如果已联接).在一个过程与 SPI 管理器联接之前,当前存储器环境是上层执行器环境,所以所有由过程自身通过 palloc/repalloc 或通过 SPI 应用函数在联接到 SPI 管理器之前分配的存储器都在这个环境里.在进行 SPI_connect 调用之后,当前环境是过程自身所有的.通过 palloc/repalloc 或通过 SPI 应用函数分配的存储器(除了 SPI_copytuple,SPI_modifytuple,SPI_palloc 和 SPI_repalloc 以外)都在这个环境中分配.当进程与 SPI 管理器断开(通过调用 SPI_finish)后,当前环境恢复为上层执行器环境并且所有在过程存储器环境分配的存储器都被释放,并且不可继续使用!如果你想返回一些东西给上层执行器,那么你必须为此在上层环境分配一片存储器!SPI 不能自动释放在上层执行器环境里分配的存储器!SPI 在查询完成后自动释放查询执行期间的存储器分配!

数码相机存储器

是一张数码存储卡,可以是活动的,也可以是固定的,用于保存图像和视频。

CF闪存卡

一种紧凑型闪存卡,(Compact Flash Card)。像PC卡那样插入数码相机,它可用适配器,(又称转接卡),使之适应标准的PC卡阅读器或其他的PC卡设备。CF存储卡的部分结构采用强化玻璃及金属外壳,CF存储卡采用Standard ATA/IDE接口界面,配备有专门的PCMCIA适配器(转接卡),笔记本电脑的用户可直接在PCMCIA插槽上使用,使数据很容易在数码相机与电脑之间传递。

SD闪存卡

即SecureDigital Card(加密数字卡), 尺寸大小为:32mm×24mm×2.1mm ,存储的速度快,体积小巧,目前市面上较多数数码相机使用这种格式的存储卡,市场占有率较高。

Micro SD卡(TF卡)

Micro SD卡又称TF卡,是更小的SD卡,尺寸大小为:15mm×11mm×1mm,也能以转接器来连接于SD卡插槽中使用。目前市面上较多应用在网络监控摄像头以及一些小型便携设备中。

嵌入式应用中存储器类型的选择技巧

存储器的类型将决定整个嵌入式系统的操作和性能,因此存储器的选择是一个非常重要的决策。无论系统是采用电池供电还是由市电供电,应用需求将决定存储器的类型(易失性或非易失性)以及使用目的(存储代码、数据或者两者兼有)。另外,在选择过程中,存储器的尺寸和成本也是需要考虑的重要因素。对于较小的系统,微控制器自带的存储器就有可能满足系统要求,而较大的系统可能要求增加外部存储器。为嵌入式系统选择存储器类型时,需要考虑一些设计参数,包括微控制器的选择、电压范围、电池寿命、读写速度、存储器尺寸、存储器的特性、擦除/写入的耐久性以及系统总成本。

第二节 主存储器

计算机的主存储器是指ROM和RAM,主存储器是计算机硬件的一个重要部件,其作用是存放指令和数据,可分为只读存储器【ROM】和随机存储器【RAM】两大类。

img

**主存储器(Main memory)**简称主存。是计算机硬件的一个重要部件,其作用是存放指令和数据,并能由中央处理器(CPU)直接随机存取。

img

主存与cpu联系

cpu来读写主存,如果执行读操作,cpu首先将内存单元地址送入MAR地址寄存器,然后由MAR将地址送到地址总线,然后cpu给出读命令,主存接受到读命令,根据地址总线地址取出相应内存单元数据放入数据总线,送至MDR数据寄存器,然后由cpu决定MDR中数据去向。

img

主存中存储单元分配

大端方式与小端方式 大端高位在低地址,小端高位在高地址

img

主存技术指标

  • 存储容量 :主存中存放二进制代码的总位数 存储容量=存储单元数*存储字长
  • 存储速度:存取时间 存储器的访问时间 读出时间 写入时间
  • 存取周期 :连续两次独立的存储器操作所需的最小间隔时间
  • 存储器带宽 :位/秒

**随机存取存储器(RAM)**既可向指定单元存入信息又可从指定单元读出信息。任何RAM中存储的信息在断电后均会丢失,所以RAM是易失性存储器。

静态RAM SRAM

img

静态RAM基本电路

静态RAM使用触发器来存储信息,读出后信息不丢失,但断电会丢失。

读操作

行地址选择,使得T5,T6打开
列地址选择,使得T7,T8打开
读选择有效  VA由T6,T8,读放输出到Dout

img

写操作

行地址选择,使得T5,T6打开
列地址选择,使得T7,T8打开
写选择有效,数据由DIn输入
原数据由T8,T6写入A  原数据非由T7,T5写入A非,完成写入操作

img

动态RAM DRAM

使用电容存储电荷来存储信息,有电荷1,无电荷0,基本单元电路有单体式和三管式。

img

对于三管式,预充电信号使T4导通,读选择线有效则T2导通,此时如果Cg中有电荷,则T1导通,VDD,T2,T1,Cg,地开始放电,则读数据线此时为低电平;如果Cg没有电荷,则读数据线为高电平。因此Cg中有电荷输出为0,无电荷输出为1;写入时,T3导通,写数据线给Cg充电。对于单管式,根据数据线有无电流来决定01.输出时字线有效,则Cs如果有电荷则数据线显示高电平,如果无电荷则为低电平。

三管动态RAM Intel 1103读

img

给定行地址与列地址,选择指定的存储单元,然后由读写控制电路将数据输出到D。

三管动态RAM Intel 1103写

img

行地址,列地址 数据D 读写控制电路 列单元 数据写入

单管动态RAM 4116 结构

img

4116 读

给出行地址和列地址,某一指定单元电平由度放大器,读写线,IO缓冲 输出驱动输出到Dout.

img

4116 写

给出行列地址,导通某一存储单元,数据由Din,数据输入,IO缓冲,读写数据线,来决定对于指定单元的电容充放电。

img

动态RAM刷新问题

动态RAM使用电容存储电荷来存储信息,电容随着时间推移,电荷会慢慢流逝,因此需要定时刷新。

刷新实质:读出原信息,刷新放大器形成原信息,写入原信息

刷新周期,再生周期:在指定时间内,将动态RAM所有基本单元电路都刷新一次。

刷新方式:集中刷新 分散刷新 异步刷新

刷新是按行进行

集中刷新

在一个刷新周期内,对所有存储单元进行集中按行刷新,此时停止读写操作。

img

分散刷新

每行存储单元的刷新分散到某个存储周期,一个存储周期 前一段负责读写,后一段负责刷新。

img

RAM和DRAM比较

静态RAM速度要快,集成度低,价格高,一般用作缓存;动态RAM一般用作主存。

img

**只读存储器(ROM)**以非破坏性读出方式工作,只能读出无法写入信息。信息一旦写入后就固定下来,即使切断电源,信息也不会丢失,所以又称为固定存储器。ROM所存数据通常是装入整机前写入的,整机工作过程中只能读出,不像随机存储器能快速方便地改写存储内容。ROM所存数据稳定 ,断电后所存数据也不会改变,并且结构较简单,使用方便,因而常用于存储各种固定程序和数据。

MROM 只读

行列选择线交叉处有MOS管为1,无MOS管为0

PROM 一次性编程

img

EPROM

img

**可编程的只读存储器(PROM)**一次性写入后只能读不能修改。

**可擦除可编程的只读存储器(EP ROM )**用紫外线擦除内容的PROM,擦除后可再写入内容。

**可电擦除的可编程只读存储器(E2 PROM)**用电擦除。

按信息的保存性是否长久来分

  • 易失性存储器:断电后,存储信息即消失的存储器。
  • 非易失性存储器:断电后信息仍然保存的存储器。 RAM属于易失性存储器 ROM、PROM、EP ROM、 E2 PROM 属于非易失性存储器。

性能指标:

存储容量:指主存所能容纳的二进制信息总量。

第三节 主存储器与CPU的连接

主存储器(简化结构)

img

img

主存简单模型

img

**连接原理:**数据总线、地址总线、控制总线

img

从存储器中读出一个信息字:

  1. 首先CPU把这个信息字的地址送到MAR
  2. 然后经过地址总线到主存
  3. 在通过控制总线发出读命令
  4. 主存接到读命令后,就知道把这个地址的数据读出
  5. 根据CPU决定将数据送到哪,通过数据总线

写一个数据到主存:

  1. CPU要把信息字所在的主存单元的地址经过MAR送到地址总线
  2. 然后在把这个信息字送到MDR
  3. 然后向主存发送一个写命令
  4. 主存收到写命令后
  5. 把数据总线上的信息写到相应地址线指出的储存单元中

主存地址单元分配(比较重要)

主存地址分配

img

总容量1KB
按字节寻址:1K个单元,每个单元1B,1K=1024,十根地址线
按字寻址:256个单元,每个单元4B,高位看成组号,后面跟两位
按半子寻址:512个单元,每个单元2B
按双字寻址:128个单元,每个单元8B

img

如何存放一个字?如12345678H

img

  • 大端方式:高位字(12)节作为字地址
  • 小端方式:低位字(78)节做为字地址

主存储容量的扩展

由于单个芯片的容量总是有限的。需要在字和位两个方面扩充。位扩展法,字扩展法,字位扩展法。

img

这个是8K*1位,即13根地址线和1位数据线。CPU要求:8位的数据线,16位的地址线

img

如何进行连接?

img

CS是高电平有效,所以要选择到他要给个1,这样显然是不够的,cpu有8位的数据线,而存储芯片只有1位,不满足存储容量的要求。在加一块芯片:

img

8块芯片进行扩展

img

相当于并联在一起。

img

一个8K*8位的存储器。

字扩展

存储芯片是8K*8位的,CPU也是8位的数据线

img

img

会不知道哪一个数据,使用片选线来进行控制。

img

但是还是会遇到同样的问题,如果是11的话,两个的数据都会被操作了。

img

线选法:A14 A13只能为01或10,n条线->n个选片信号

地址:01x xxxx xxxx xxxx、10x xxxx xxxx xxxx

改进:用一个非门

img

地址1x xxxx xxxx xxxx、0x xxxx xxxx xxxx

译码片选法:n条线->2^n个选片信号

译码器。

img

译码器

高电平有效,1

img

低电平有效,0

img

位地址选8块芯片,注意圈圈;使能端、EN、还有可能有多个使能端,使译码器工作。

将译码器利用到字扩展

img

00,01,10,11对比

img

字位同时拓展

img

img

例题:

img

系统程序区用ROM,用户程序区用RAM;确认地址线、数据线、选择存储芯片;数据线:CPU数据线8根 -> 存储器位数应扩展为8位。

img

img

img

第四节 外部存储器*

一、磁盘存储器

优点:存储容量大,价格低,长期保存而不丢失。

缺点:存取速度慢,机械结构复杂,对环境要求高。

磁盘最小的读写单位是一个扇区。

二、固态存储器SSD(新增考点)

优点:读写速度快。若要写的页有数据,则不能写入,需要将块内其他页全部复制到一个新的块中,再写入新的页。

缺点:价格高,一个块被写入多次可能会坏掉(采用平均磨损,对我们来说仍然很耐用)而磁盘不会。

第五节 高速缓冲存储器(重点)*

一、什么是Cache,为什么要引入Cache?

Cache存储器也被称为高速缓冲存储器,位于CPU和主存储器之间。之所以在CPU和主存之间要加cache是因为现代的CPU频率大大提高,内存的发展已经跟不上CPU访存的速度。在2001 – 2005年间,处理器时钟频率以每年55%的速度增长,而主存的增长速度只是7%。在现在的系统中,处理器需要上百个时钟周期才能从主存中取到数据。如果没有cache,处理器在等待数据的大部分时间内将会停滞不动

二、原理

采用了程序访问的时间局部性原理和空间局部性原理

时间局部性:如果一个数据现在被访问了,那么以后很有可能也会被访问

空间局部性:如果一个数据现在被访问了,那么它周围的数据在以后可能也会被访问

三、多级Cache的由来?

cache分为L1,L2,L3甚至L4等多级。为什么不能把L1的容量做大,不要其它的cache了?原因在于性能/功耗/面积(PPA)权衡考虑。L1 cache一般工作在CPU的时钟频率,要求的就是够快,可以在2-4时钟周期内取到数据。L2 cache相对来说是为提供更大的容量而优化的。虽然L1和L2往往都是SRAM,但构成存储单元的晶体管并不一样。L1是为了更快的速度访问而优化过的,它用了更多/更复杂/更大的晶体管,从而更加昂贵和更加耗电;L2相对来说是为提供更大的容量优化的,用了更少/更简单的晶体管,从而相对便宜和省电。在有一些CPU设计中,会用DRAM实现大容量的L3 cache。

四、如何区分Cache和主存的数据块对应关系?

每次被访问的主存块,一定会被立即调入Cache,而且是以块为单位进行调入。

img

那是采用什么方式将主存块号调入到Cache呢?有三种方式

①全相联映射——主存块可以放在Cache的任意位置。

那它是如何来访问主存的呢?

img

对以上图只要能看懂,对于全相联映射就没什么问题了。做几点说明,CPU在访问主存时,会先对比Cache所有块中的标记Tag,Tag就是在主存中的主存块号,占22位。

②直接映射——每个主存块只能放在一个特定的位置。Cache块号=主存块号%Cache块总数

img

做以下几点说明

  • 相对于全相联映射,直接映射对Tag进行了优化,因为主存块号最后三位地址就是Cache中的位置,所以将主存块号其余位作为标记即可。
  • 若Cache总块数= 2n ,则主存块号末尾n位直接反映它在Cache的位置,所以将主存块号其余位作为标志位即可。

③组相联映射——Cache块分为若干组,每个主存块可以放到特定分组中的任意一个位置。组号=主存块号%分组数

img

做以下几点说明

  • 相对于全相联映射,直接映射对Tag进行了优化,因为主存块号最后两位地址就是Cache中的位置,所以将主存块号其余位作为标记即可。
  • 一个组内有几个Cache块就成为几路相联映射

④三种映射方法对比总结

全相联 直接 组相联
特点 任意位置 特定位置 分组中的任意位置
主存地址结构 标记+块内地址 标记+行号+块内地址 标记+组号+块内地址
优点 Cache存储空间利用充分 对任意地址,执行对比一个Tag,速度快 折中办法
缺点 可以会对比所有行的标记,速度慢 Cache空间利用不充分 /

五、Cache很小,而主存很大,如果Cache满了,是利用了什么替换算法?

替换条件:对于全相联映射,需要在全局中选择替换哪一块,对于直接映射,若非空,则直接替换,对于组相联,组内满了,则在组内选择替换哪一块。

Ⅰ、随机算法(RAND)

随机,随便,随意,换哪一个都行。实现简单,但完全没有考虑局部性原理,命中率低,实际效果很不稳定。

可能会导致,换出的块,下一次又需要访问。就会多次访问内存块。导致抖动现象。

Ⅱ、先进先出算法(FIFO)

替换最先进入的块。同样实现简单,但仍然没有考虑到局部性原理,最先被调入Cache块可能是被访问最频繁的。

Ⅲ、近期最少使用(LRU)

为每个Cache块设置一个”计数器“,用于记录每个Cache块多久没有被访问了。然后替换”计数器“值最大的。

  • 计数器的位数=Cache块的总数= 2n ,只需要n位,且Cache装满后所有计数器的值一定不重复。
  • 基于局部性原理,近期被访问的主存块,未来可能仍会被使用,LRU算法实际运行效果优秀。
  • 若频繁访问的主存块数量>Cache行的数量,则有可能发生”抖动“

Ⅳ、最近不经常使用(LFU)

为每个Cache设置一个”计数器“,用于记录Cache被访问过几次,然后替换”计数器“值最小的(访问次数最少的)

曾经被经常访问的主存块不一定在未来会被用到。并没有很好的遵循局部性原理,因此实际运行效果不如LRU。

六、Cache写策略——CPU修改了Cache中的数据副本,如何确保主存中数据母本一致性?

Ⅰ、写命中——写入的时候,在Cache中

①回写法:当CPU对Cache写命中时,只修改Cache的内容,而不立即写入主存,只有当次块被换出时才写回主存。减少了访存次数,但存在数据不一致的隐患。

被换出时,看”脏位“是否知道是否被修改。

②全写法:当CPU对Cache写命中时,必须把数据同时写入Cache和主存,一般使用写缓冲。访存次数增加,速度变慢,但是能保证数据的一致性。无脏位。

Ⅱ、写不命中——写入的时候,不在Cache中

①写分配法——当CPU对Cache不命中时,把主存中的块调入Cache,在Cache中修改。通常搭配回写法使用,改完后要被换出,才在主存中修改。

②非写分配法——当CPU对Cache写不命中时,只写入主存,不调入Cache,搭配全写法使用。

第六节 虚拟存储器*

虚拟存储器:在操作系统的管理下,只把当前需要的部分数据调入主存,暂不需要的部分留在辅存中。在用户看来,似乎获得了一个超大的主存。(虚拟性)

一、页式虚拟存储器

背景:CPU执行的机器指令中,使用的是”逻辑地址“,因此需要通过”页表“将逻辑地址转为物理地址。

一个程序在逻辑上被分为若干个大小相等的”页面“,”页面“大小与”块“的大小相同。每个页面可以离散的存放在不同主存块中。

页表的作用:记录了每个逻辑页面存放在哪个主存块中。

无快表:

img

  • 逻辑地址=逻辑页号+页内地址
  • 物理地址=主存块号+页内地址

增加快表(存放在Cache中,先访问快表,若未命中,则去访问主存中的慢表)

img

  • 快表查询速度很快,若快表中无,则会去慢表中查找,会把相应的内容存入快表中

清楚整个查找流程

img

二、段式虚拟存储(按功能拆分成大小不同的模块)

按照功能模块拆分不同的模块大小。

虚拟地址:段号+段内地址

优点:段的分界与程序的自然分界相对应,因而具有逻辑独立性,使得它易于编译、管理、修改和保护。

缺点:段的长度可变,分配空间不便,容易留下碎片,不好利用,造成浪费。

三、段页式虚拟存储

把程序按逻辑结构分段,每段在分固定大小的页,主存空间也划分为大小相等的页,每个程序对应一个段表,每段对应一个页表。

虚拟地址:段号+段内地址+页内地址

优点是兼具段式和页式的优点缺点是需要查两次表,系统开销较大。

四、虚拟存储器与Cache的比较

Cache 虚拟存储器
解决CPU与主存速度不匹配的问题 解决主存容量的问题
全由硬件组成,对所有程序员透明 由OS和硬件组成,逻辑上存储器对系统程序员不透明
不命中影响小 不命中影响大
不命中时,主存直接与CPU通信 不命中时,不能直接和CPU通信,要先硬盘调入主存

题目总结:

【2015统考真题】假定主存地址为32位,按字节编址,主存和Cache之间采用直接映射方式,主存块大小为4个字,每字32位,采用回写方式,则能存放4K字数据的Cache的总容量的位数至少是()

  • Cache的容量分为两个部分一个是数据存储容量+标记阵列容量
  • 标记阵列中一定包含有效位和标记位,若为回写法,则还存在一位的”脏位“,若为LRU、LFU替换算法,则还存在替换算法位(计数器)位数为 log2n ,n为Cache的个数。
  • 本题按照字节编址,则块内地址占4位,采用直接映射方法中的标志位为32-4-10=18,Tag=18。
  • 采用回写法,有一位脏位,故最终标记项有18+1+1=20
  • 标记阵列容量为 210 ×20=20K,数据储存容量为4K×32=128K,故总的为148K。

5、指令系统

指令系统是计组中的一单元,知识点不少,计划用3篇文章解释清楚指令系统的知识。

第一节 指令格式

指令系统主要分三大内容

img

指令系统:一台计算机中所有指令的集合。

发展:复杂指令系统-精简指令系统

复杂指令系统计算机(CISC):为增强指令系统的功能,设置一些功能复杂的指令,把一些原来由软件实现,常用功能改用硬件的指令系统来实现。目前绝大多数计算机都属于这。

精简指令系统(RISC):尽量简化指令系统,只保留那些功能简单,能在一个节拍内执行完的指令,较复杂的功能用一段子程序来实现。

指令格式

指令格式包括两个方面:操作码和地址码

img

操作码(OP):表示该指令应该进行什么性质的操作,如加减乘除,存数,取数等。

地址码:

img

指令字长度

1.定义:一个指令字包含二进制代码的位数。

2.机器字长:计算机能直接处理的二进制数据的位数。

指令系统

1.指令集体系结构:一个指令器支持的指令和指令的字节级编码。

不同的处理器族支持不同的指令集体系结构,因此,一个程序被编译在一种机器上运行,往往不能在另一种机器上运行。

2.按暂存机制分类,根据在CPU内部存储操作数的区别,可以吧指令集系统分为3类:堆栈(Stack),累加器,和寄存器组。

指令的流水处理

1.指令控制方式:顺序,重叠和流水。

(1)顺序方式:各条机器指令之间顺序串行的执行,执行完一条后才取下一条指令,而且每条指令内部的微操作也是顺序执行的。

(2)重叠方式:在解释第K条指令操作完成之前就开始解释第K+1条指令。但容易冲突。

(3)流水方式:仿流水线作业,并行处理。同时解释多条指令。

img

指令类型

日常使用的 Intel CPU 大概有 2000 多条 CPU 指令。可以分为以下 5 大类型:

  • 算术类指令:加减乘除。
  • 数据传输类指令:变量赋值、读写内存数据。
  • 逻辑类指令:与或非。
  • 条件分支类指令:条件判断语句。
  • 无条件跳转指令:方法、函数的调用跳转。

img

继续细分的话,具有如下指令类型

  • 算术逻辑运算指令

  • 移位操作指令

    • 算术移位
    • 逻辑移位
    • 循环移位
  • 矢量运算指令(矩阵运算)

  • 浮点运算指令

  • 十进制运算指令

  • 字符串处理指令

    • 字符串传送
    • 字符串比较
    • 字符串查询
    • 字符串转换
  • 数据传输指令

    • 寄存器与寄存器传输
    • 寄存器与主存储器单元传输
    • 存储器单元与存储器单元传输
    • 数据交换(源操作数与目的操作下互换)
  • 转移指令

    • 条件转移
    • 无条件转移
    • 过程调用与返回
    • 陷阱
  • 堆栈及堆栈操作指令

  • I/O 指令

  • 特权指令

  • 多处理机指令(在多处理器系统中保证共享数据的一致性等)

  • 控制指令

第二节 寻址方式(重点)

指令寻址

指令寻址,即是根据指令字的地址码来获取到实际的数据,寻址的方式跟硬件关系密切,不同的计算机有不同的寻址方式。有的计算机寻址方式种类少,所以会直接在操作码上表示寻址方式;有些计算机的寻址方式种类多,就会在指令字中添加一个特别用于标记寻址方式的字段,例如:假设该字段具有 3 位,那么就可以表示 8 种寻址方式。

NOTE:寻址方式与 CPU 内的寄存器设计密切相关。

img

直接寻址:指令字的地址码直接给出了操作数在存储器中的地址,是最简单的寻址方式。

间接寻址:指令字的地址码所指向的寄存器或存储器的内容并不是真实的操作数,而是操作数的地址。间接寻址常用于跳转指令,只要修改寄存器或存储器的地址就可以实现跳转到不同的操作数上。

相对寻址:把程序计数器(PC)的内容,即当前执行指令的地址与地址码部分给出的偏移量(Disp)之和作为操作数的地址。这种寻址方式同样常用于跳转(转移)指令,当程序执行到本条指令后,跳转到 PC+Disp。

立即数寻址:即地址码本身就是一个操作数的寻址方式,该方式的特点就是数据块(因为实际上没有寻址),但操作数固定。常用于为某个寄存器或存储器单元赋初值,或提供一个常数。

通用寄存器寻址:CPU 中大概会有几个到几十个通用寄存器用于临时储存操作数、操作数的地址或中间结果,指令字的地址码可以指向这些寄存器。通用寄存器具有地址短,存取速度快的特性,所以地址码指向通用寄存器的指令的长度也会更短,节省存储空间,执行效率更快。常被用于执行速度要求严格的指令中。

基址寄存器寻址:基址,即基础地址,基址寄存器就是存放基址的寄存器,可以是一个专用寄存器,也可以使用通用寄存器来充当基址寄存器。执行指令时,需要将基址与指令字的地址码结合得到完成的地址,此时的地址码充当着偏移量(位移量)的角色。当存储器容量较大时,直接寻址方式是无法存取到所有存储单元的,所以通常会采用 分段分页 的内存管理方式。此时,段或页的首地址就会存放于基址寄存器中,而指令字的地址码就作为段或页的长度,这样只要修改基址寄存器的内容就可以访问到存储器的任意单元了。这种寻址方式常被用于为程序或数据分配存储区,与虚拟地址实现密切相关。基址寄存器寻址方式解决了程序在存储器中的定位存储单元和扩大 CPU 寻址空间的问题。

变址寄存器寻址:变址寄存器内的地址与指令字地址之和得到了实际的有效地址,如果 CPU 中存在基址寄存器,那么就还得加上基址地址。这种寻址方式常用于处理需要循环执行的程序,例如:循环处理数组,此时变址寄存器所改变的就是数组的下标了。

堆栈寻址:堆栈是有若干个连续的存储器单元组成的先进后出(FILO)存储区。堆栈是用于提供操作数和保存运算结果的主要存储区,同时还主要用于暂存中断和子程序调用时的线程数据及返回地址。

通过 MIPS 感受指令字的设计

MIPS(Millions of Instructions Per Second)是一种最简单的精简指令集架构,由 MIPS 科技公司设计。MIPS 指令具有 32 位(最新版本为 64 位),高 6 位为操作码(OPCODE),描述了指令的操作类型。其余 26 位具有 3 种格式:R、I 和 J。不同的指令类型和操作码组合能够完成多种功能实现,如下:

img

加法算数指令add $t0,$s2,$s1的指令字及其对应的机器码如下:

img

  • opcode:0
  • rs:代表第一个寄存器 s1 的地址是 17
  • rt:代表第二个寄存器 s2 的地址是 18
  • rd:代表目标临时寄存器 t0 的地址是 8
  • shamt:0,表示不位移

最终加法算数指令 add $t0,$s2,$s1 的二进制机器码表示为 000000 10001 10010 01000 00000 1000000(0X02324020)。可以看见,机器码中没有保存任何实际的程序数据,而是保存了程序数据的储存的地址,这也算是存储程序计算机指令集设计的一大特点。

将高级语言翻译成汇编代码

为什么要保留汇编语言

汇编语言是与机器语言最接近的高级编程语言(或称为中级编程语言),汇编语言基本上与机器语言对应,即汇编指令和计算机指令是相对匹配的。虽然汇编语言具有与硬件的关系密切,占用内存小,运行速度快等优点,但也具有可读性低、可重用性差,开发效率低下等问题。高级语言的出现是为了解决这些问题,让软件开发变得更加简单高效,易于协作。但高级语言也存在自己的缺陷,例如:难以编写直接操作硬件设备的程序等。

所以为了权衡上述的问题,最终汇编语言被作为中间的状态保留了下来。一些高级语言(e.g. C 语言)提供了与汇编语言之间的调用接口,汇编程序可作为高级语言的外部过程或函数,利用堆栈在两者之间传递参数或参数的访问地址。两者的源程序通过编译或汇编生成目标文件(OBJ)之后再利用连接程序(linker)把它们连接成为可执行文件便可在计算机上运行了。保留汇编语言还为程序员提供一种调优的手段,无论是 C 程序还是 Python 程序,当我们要进行代码性能优化时,了解程序的汇编代码是一个不错的切入点。

顺序程序流

计算机指令是一种逻辑上的抽象设计,而机器码则是计算机指令的物理表现。机器码(Machine Code),又称为机器语言,本质是由 0 和 1 组成的数字序列。一条机器码就是一条计算机指令。程序由指令组成,但让人类使用机器码来编写程序显然是不人道的,所以逐步发展了对人类更加友好的高级编程语言。这里我们需要了解计算机是如何将高级编程语言编译为机器码的。

Step 1. 编写高级语言程序。

// test.c
int main()
{
  int a = 1;
  int b = 2;
  a = a + b;
}

Step 2. 编译(Compile),将高级语言编译成汇编语言(ASM)程序。

$ gcc -g -c test.c

Step 3. 使用 objdump 命令反汇编目标文件,输出可阅读的二进制信息。下述左侧的一堆数字序列就是一条条机器码,右侧 push、mov、add、pop 一类的就是汇编代码。

$ objdump -d -M intel -S test.o

test.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <main>:
int main()
{
   0:    55                       push   rbp
   1:    48 89 e5                 mov    rbp,rsp
  int a = 1;
   4:    c7 45 fc 01 00 00 00     mov    DWORD PTR [rbp-0x4],0x1
  int b = 2;
   b:    c7 45 f8 02 00 00 00     mov    DWORD PTR [rbp-0x8],0x2
  a = a + b;
  12:    8b 45 f8                 mov    eax,DWORD PTR [rbp-0x8]
  15:    01 45 fc                 add    DWORD PTR [rbp-0x4],eax
}
  18:    5d                       pop    rbp
  19:    c3                       ret

NOTE:这里的程序入口是main()函数,而不是第 0 条汇编代码。

img

条件程序流

值得注意的是,某些特殊的指令,比如跳转指令,会主动修改 PC 的内容,此时下一条地址就不是从存储器中顺序加载的了,而是到特定的位置加载指令内容。这就是 if…else 条件语句,while/for 循环语句的底层支撑原理。

Step 1. 编写高级语言程序。

// test.c


#include <time.h>
#include <stdlib.h>


int main()
{
  srand(time(NULL));
  int r = rand() % 2;
  int a = 10;
  if (r == 0)
  {
    a = 1;
  } else {
    a = 2;
  }
}

Step 2. 编译(Compile),将高级语言编译成汇编语言。

$ gcc -g -c test.c

Step 3. 使用 objdump 命令反汇编目标文件,输出可阅读的二进制信息。我们主要分析 if…else 语句。

if (r == 0)
  33:    83 7d fc 00              cmp    DWORD PTR [rbp-0x4],0x0
  37:    75 09                    jne    42 <main+0x42>
  {
    a = 1;
  39:    c7 45 f8 01 00 00 00     mov    DWORD PTR [rbp-0x8],0x1
  40:    eb 07                    jmp    49 <main+0x49>
  } else {
    a = 2;
  42:    c7 45 f8 02 00 00 00     mov    DWORD PTR [rbp-0x8],0x2
  }

首先进入条件判断,汇编代码为 cmp 比较指令,比较数 1:DWORD PTR [rbp-0x4] 表示变量 r 是一个 32 位整数,数据在寄存器 [rbp-0x4] 中;比较数 2:0x0 表示常量 0 的十六进制。比较的结果会存入到 条件码寄存器,等待被其他指令读取。当判断条件为 True 时,ZF 设置为 1,反正设置为 0。

条件码寄存器(Condition Code)是一种单个位寄存器,它们的值只能为 0 或者 1。当有算术与逻辑操作发生时,这些条件码寄存器当中的值就随之发生变化。后续的指令通过检测这些条件码寄存器来执行条件分支指令。常用的条件码类型如下:

  • CF:进位标志寄存器。最近的操作是最高位产生了进位。它可以记录无符号操作的溢出,当溢出时会被设为 1。
  • ZF:零标志寄存器,最近的操作得出的结果为 0。当计算结果为 0 时将会被设为 1。
  • SF:符号标志寄存器,最近的操作得到的结果为负数。当计算结果为负数时会被设为 1。
  • OF:溢出标志寄存器,最近的操作导致一个补码溢出(正溢出或负溢出)。当计算结果导致了补码溢出时,会被设为 1。

回到正题,PC 继续自增,执行下一条 jnp 指令。jnp(jump if not equal)会查看 ZF 的内容,若为 0 则跳转到地址 42 <main+0x42>(42 表示汇编代码的行号)。前文提到,当 CPU 执行跳转类指令时,PC 就不再通过自增的方式来获得下一条指令的地址,而是直接被设置了 42 行对应的地址。由此,CPU 会继续将 42 对应的指令读取到 IR 中并执行下去。

42 行执行的是 mov 指令,表示将操作数 2:0x2 移入到 操作数 1:DWORD PTR [rbp-0x8] 中。就是一个赋值语句的底层实现支撑。接下来 PC 恢复如常,继续以自增的方式获取下一条指令的地址。

img

循环程序流

  • C 语言代码
// test.c


int main()
{
    int a = 0;
    int i;
    for (i = 0; i < 3; i++)
    {
        a += i;
    }
}
  • 计算机指令与汇编代码
for (i = 0; i < 3; i++)
   b:    c7 45 f8 00 00 00 00     mov    DWORD PTR [rbp-0x8],0x0
  12:    eb 0a                    jmp    1e <main+0x1e>
    {
        a += i;
  14:    8b 45 f8                 mov    eax,DWORD PTR [rbp-0x8]
  17:    01 45 fc                 add    DWORD PTR [rbp-0x4],eax
    for (i = 0; i < 3; i++)
  1a:    83 45 f8 01              add    DWORD PTR [rbp-0x8],0x1
  1e:    83 7d f8 02              cmp    DWORD PTR [rbp-0x8],0x2
  22:    7e f0                    jle    14 <main+0x14>
    }

img

函数调用栈的工作原理

与普通的跳转程序(e.g. if…else、while/for)不同,函数调用的特点在于具有回归(return)的特点,在调用的函数执行完之后会再次回到执行调用的 call 指令的位置,继续往下执行。能够实现这个效果,完全依赖堆栈(Stack)存储区的特性。 首先我们需要了解几个概念。

  • 堆栈(Stack):是有若干个连续的存储器单元组成的先进后出(FILO)存储区,用于提供操作数、保存运算结果、暂存中断和子程序调用时的线程数据及返回地址。通过执行堆栈的 Push(压栈)和 Pop(出栈)操作可以将指定的数据在堆栈中放入和取出。堆栈具有栈顶和栈底之分,栈顶的地址最低,而栈底的地址最高。堆栈的 FILO 的特性非常适用于函数调用的场景:父函数调用子函数,父函数在前,子函数在后;返回时,子函数先返回,父函数后返回。
  • 栈帧(Stack Frame):是堆栈中的逻辑空间,每次函数调用都会在堆栈中生成一个栈帧,对应着一个未运行完的函数。从逻辑上讲,栈帧就是一个函数执行的环境,保存了函数的参数、函数的局部变量以及函数执行完后返回到哪里的返回地址等等。栈帧的本质是两个指针寄存器: EBP(基址指针,又称帧指针)和 ESP(栈指针)。其中 EBP 指向帧底,而 ESP 指向栈顶。当程序运行时,ESP 是可以移动的,大多数信息的访问都通过移动 ESP 来完成,而 EBP 会一直处于帧低。EBP ~ ESP 之间的地址空间,就是当前执行函数的地址空间。

NOTE:EBP 指向当前位于系统栈最上边一个栈帧的底部,而不是指向系统栈的底部。严格说来,“栈帧底部” 和 “系统栈底部” 不是同一个概念,而 ESP 所指的栈帧顶部和系统栈顶部是同一个位置。

img

简单概括一下函数调用的堆栈行为,ESP 随着当前函数的压栈和出栈会不断的移动,但由于 EBP 的存在,所以当前执行函数栈帧的边界是始终清晰的。当一个当前的子函数调用完成之后,EBP 就会跳到父函数栈帧的底部,而 ESP 也会随其自然的来到父函数栈帧的头部。所以,理解函数调用堆栈的运作原理,主要要掌握 EBP 和 ESP 的动向。下面以一个例子来说明。

NOTE:我们习惯将将父函数(调用函数的函数)称为 “调用者(Caller)”,将子函数(被调用的函数)称为 “被调用者(Callee)”。

  • C 程序代码
#include <stdio.h>

int add(int a, int b) {
    int result = 0;

    result = a + b;

    return result;
}

int main(int argc, char *argv[]) {
    int result = 0;

    result = add(1, 2);

    printf("result = %d \r\n", result);

    return 0;
}
  • 使用gcc编译,然后gdb反汇编main函数,看看它是如何调用add函数的
(gdb) disassemble main 
Dump of assembler code for function main:
   0x08048439 <+0>:     push   %ebp
   0x0804843a <+1>:     mov    %esp,%ebp
   0x0804843c <+3>:     and    $0xfffffff0,%esp
   0x0804843f <+6>:     sub    $0x20,%esp
   0x08048442 <+9>:     movl   $0x0,0x1c(%esp)  # 给 result 变量赋 0 值
   0x0804844a <+17>:    movl   $0x2,0x4(%esp)   # 将第 2 个参数 argv 压栈(该参数偏移为esp+0x04)
   0x08048452 <+25>:    movl   $0x1,(%esp)      # 将第 1 个参数 argc 压栈(该参数偏移为esp+0x00)
   0x08048459 <+32>:    call   0x804841c <add>  # 调用 add 函数
   0x0804845e <+37>:    mov    %eax,0x1c(%esp)  # 将 add 函数的返回值地址赋给 result 变量,作为子函数调用完之后的回归点
   0x08048462 <+41>:    mov    0x1c(%esp),%eax
   0x08048466 <+45>:    mov    %eax,0x4(%esp)
   0x0804846a <+49>:    movl   $0x8048510,(%esp)
   0x08048471 <+56>:    call   0x80482f0 <printf@plt>
   0x08048476 <+61>:    mov    $0x0,%eax
   0x0804847b <+66>:    leave  
   0x0804847c <+67>:    ret    
End of assembler dump.

(gdb) disassemble add
Dump of assembler code for function add:
   0x0804841c <+0>:     push   %ebp             # 将 ebp 压栈(保存函数调用者的栈帧基址)
   0x0804841d <+1>:     mov    %esp,%ebp        # 将 ebp 指向栈顶 esp(设置当前被调用函数的栈帧基址)
   0x0804841f <+3>:     sub    $0x10,%esp       # 分配栈空间(栈向低地址方向生长)
   0x08048422 <+6>:     movl   $0x0,-0x4(%ebp)  # 给 result 变量赋 0 值(该变量偏移为ebp-0x04)
   0x08048429 <+13>:    mov    0xc(%ebp),%eax   # 将第 2 个参数的值赋给 eax 寄存器(准备运算)
   0x0804842c <+16>:    mov    0x8(%ebp),%edx   # 将第 1 个参数的值赋给 edx 寄存器(准备运算)
   0x0804842f <+19>:    add    %edx,%eax        # 运算器执行加法运算 (edx+eax),结果保存在 eax 寄存器中
   0x08048431 <+21>:    mov    %eax,-0x4(%ebp)  # 将运算结果 eax 赋给 result 变量
   0x08048434 <+24>:    mov    -0x4(%ebp),%eax  # 将 result 变量的值赋给 eax 寄存器(eax 的地址将作为函数返回值)
   0x08048437 <+27>:    leave                   # 恢复函数调用者的栈帧基址(pop %ebp)
   0x08048438 <+28>:    ret                     # 返回(准备执行下条指令)
End of assembler dump.

img

可见,每一次函数调用,都会对调用者的栈帧基址 EBP 进行压栈操作(为了调用回归),并且由于子函数的栈帧基址 EBP 来自于栈指针 ESP 而来(生成新的子函数的栈帧),所以各层函数的栈帧基址很巧妙的构成了一个链,即当前的栈帧基址指向下一层函数栈帧基址所在的位置。

img

由此当子函数执行完成时,ESP 依旧在栈顶,但 EBP 就跳转到父函数的栈帧底部了,并且堆栈下一个弹出的就是子函数的调用回归点,最终程序流回到调用点并继续往下执行。

通过函数调用堆栈的工作原理我们可以看出,无论程序中具有多少层的函数调用,或递归调用,只需要维护好每个栈帧的 EBP 和 ESP 就可以管理还函数之间的跳转。但堆栈也是由容量限制的,如果函数调用的层级太多就会出现栈溢出的错误(Stack Overflow)。

程序在操作系统中的装载与运行

一个程序在操作系统上运行需要经历以下阶段:

第一阶段:得到可执行文件

  1. 编译(Compile)
  2. 汇编(Assemble)
  3. 链接(Link)

第二阶段:装载运行

  1. 装载器(Loader)将可执行文件载入到内存
  2. CPU 从内存中可执行文件的程序入口开始读取指令和数据,开始真正执行程序。

img

编译和汇编的过程在上文中已经提到了,下面再继续介绍链接的过程。

  • 子程序
// add_lib.c

int add(int a, int b)
{
    return a+b;
}
  • 主函数
// link_example.c

#include <stdio.h>
int main()
{
    int a = 10;
    int b = 5;
    int c = add(a, b);
    printf("c = %d\n", c);
}
  • 编译 C 程序得到 Object 文件
$ gcc -g -c add_lib.c link_example.c
  • 链接上述两个 Object 文件得到一个可执行文件
$ gcc -o link-example add_lib.o link_example.o

$ ./link-example
c = 15

区别于 Object 文件,真正的可执行文件的内容如下:

$ objdump -d -M intel -S link-example

link-example:     file format elf64-x86-64


Disassembly of section .init:

00000000004003c8 <_init>:
  4003c8:    48 83 ec 08              sub    rsp,0x8
  4003cc:    48 8b 05 25 0c 20 00     mov    rax,QWORD PTR [rip+0x200c25]        # 600ff8 <__gmon_start__>
  4003d3:    48 85 c0                 test   rax,rax
  4003d6:    74 05                    je     4003dd <_init+0x15>
  4003d8:    e8 43 00 00 00           call   400420 <.plt.got>
  4003dd:    48 83 c4 08              add    rsp,0x8
  4003e1:    c3                       ret

Disassembly of section .plt:

00000000004003f0 <.plt>:
  4003f0:    ff 35 12 0c 20 00        push   QWORD PTR [rip+0x200c12]        # 601008 <_GLOBAL_OFFSET_TABLE_+0x8>
  4003f6:    ff 25 14 0c 20 00        jmp    QWORD PTR [rip+0x200c14]        # 601010 <_GLOBAL_OFFSET_TABLE_+0x10>
  4003fc:    0f 1f 40 00              nop    DWORD PTR [rax+0x0]

0000000000400400 <printf@plt>:
  400400:    ff 25 12 0c 20 00        jmp    QWORD PTR [rip+0x200c12]        # 601018 <printf@GLIBC_2.2.5>
  400406:    68 00 00 00 00           push   0x0
  40040b:    e9 e0 ff ff ff           jmp    4003f0 <.plt>

0000000000400410 <__libc_start_main@plt>:
  400410:    ff 25 0a 0c 20 00        jmp    QWORD PTR [rip+0x200c0a]        # 601020 <__libc_start_main@GLIBC_2.2.5>
  400416:    68 01 00 00 00           push   0x1
  40041b:    e9 d0 ff ff ff           jmp    4003f0 <.plt>

Disassembly of section .plt.got:

0000000000400420 <.plt.got>:
  400420:    ff 25 d2 0b 20 00        jmp    QWORD PTR [rip+0x200bd2]        # 600ff8 <__gmon_start__>
  400426:    66 90                    xchg   ax,ax

Disassembly of section .text:

0000000000400430 <_start>:
  400430:    31 ed                    xor    ebp,ebp
  400432:    49 89 d1                 mov    r9,rdx
  400435:    5e                       pop    rsi
  400436:    48 89 e2                 mov    rdx,rsp
  400439:    48 83 e4 f0              and    rsp,0xfffffffffffffff0
  40043d:    50                       push   rax
  40043e:    54                       push   rsp
  40043f:    49 c7 c0 f0 05 40 00     mov    r8,0x4005f0
  400446:    48 c7 c1 80 05 40 00     mov    rcx,0x400580
  40044d:    48 c7 c7 31 05 40 00     mov    rdi,0x400531
  400454:    e8 b7 ff ff ff           call   400410 <__libc_start_main@plt>
  400459:    f4                       hlt
  40045a:    66 0f 1f 44 00 00        nop    WORD PTR [rax+rax*1+0x0]

0000000000400460 <deregister_tm_clones>:
  400460:    b8 37 10 60 00           mov    eax,0x601037
  400465:    55                       push   rbp
  400466:    48 2d 30 10 60 00        sub    rax,0x601030
  40046c:    48 83 f8 0e              cmp    rax,0xe
  400470:    48 89 e5                 mov    rbp,rsp
  400473:    77 02                    ja     400477 <deregister_tm_clones+0x17>
  400475:    5d                       pop    rbp
  400476:    c3                       ret
  400477:    b8 00 00 00 00           mov    eax,0x0
  40047c:    48 85 c0                 test   rax,rax
  40047f:    74 f4                    je     400475 <deregister_tm_clones+0x15>
  400481:    5d                       pop    rbp
  400482:    bf 30 10 60 00           mov    edi,0x601030
  400487:    ff e0                    jmp    rax
  400489:    0f 1f 80 00 00 00 00     nop    DWORD PTR [rax+0x0]

0000000000400490 <register_tm_clones>:
  400490:    b8 30 10 60 00           mov    eax,0x601030
  400495:    55                       push   rbp
  400496:    48 2d 30 10 60 00        sub    rax,0x601030
  40049c:    48 c1 f8 03              sar    rax,0x3
  4004a0:    48 89 e5                 mov    rbp,rsp
  4004a3:    48 89 c2                 mov    rdx,rax
  4004a6:    48 c1 ea 3f              shr    rdx,0x3f
  4004aa:    48 01 d0                 add    rax,rdx
  4004ad:    48 d1 f8                 sar    rax,1
  4004b0:    75 02                    jne    4004b4 <register_tm_clones+0x24>
  4004b2:    5d                       pop    rbp
  4004b3:    c3                       ret
  4004b4:    ba 00 00 00 00           mov    edx,0x0
  4004b9:    48 85 d2                 test   rdx,rdx
  4004bc:    74 f4                    je     4004b2 <register_tm_clones+0x22>
  4004be:    5d                       pop    rbp
  4004bf:    48 89 c6                 mov    rsi,rax
  4004c2:    bf 30 10 60 00           mov    edi,0x601030
  4004c7:    ff e2                    jmp    rdx
  4004c9:    0f 1f 80 00 00 00 00     nop    DWORD PTR [rax+0x0]

00000000004004d0 <__do_global_dtors_aux>:
  4004d0:    80 3d 55 0b 20 00 00     cmp    BYTE PTR [rip+0x200b55],0x0        # 60102c <_edata>
  4004d7:    75 11                    jne    4004ea <__do_global_dtors_aux+0x1a>
  4004d9:    55                       push   rbp
  4004da:    48 89 e5                 mov    rbp,rsp
  4004dd:    e8 7e ff ff ff           call   400460 <deregister_tm_clones>
  4004e2:    5d                       pop    rbp
  4004e3:    c6 05 42 0b 20 00 01     mov    BYTE PTR [rip+0x200b42],0x1        # 60102c <_edata>
  4004ea:    f3 c3                    repz ret
  4004ec:    0f 1f 40 00              nop    DWORD PTR [rax+0x0]

00000000004004f0 <frame_dummy>:
  4004f0:    48 83 3d 28 09 20 00     cmp    QWORD PTR [rip+0x200928],0x0        # 600e20 <__JCR_END__>
  4004f7:    00
  4004f8:    74 1e                    je     400518 <frame_dummy+0x28>
  4004fa:    b8 00 00 00 00           mov    eax,0x0
  4004ff:    48 85 c0                 test   rax,rax
  400502:    74 14                    je     400518 <frame_dummy+0x28>
  400504:    55                       push   rbp
  400505:    bf 20 0e 60 00           mov    edi,0x600e20
  40050a:    48 89 e5                 mov    rbp,rsp
  40050d:    ff d0                    call   rax
  40050f:    5d                       pop    rbp
  400510:    e9 7b ff ff ff           jmp    400490 <register_tm_clones>
  400515:    0f 1f 00                 nop    DWORD PTR [rax]
  400518:    e9 73 ff ff ff           jmp    400490 <register_tm_clones>

000000000040051d <add>:
// add_lib.c

int add(int a, int b)
{
  40051d:    55                       push   rbp
  40051e:    48 89 e5                 mov    rbp,rsp
  400521:    89 7d fc                 mov    DWORD PTR [rbp-0x4],edi
  400524:    89 75 f8                 mov    DWORD PTR [rbp-0x8],esi
    return a+b;
  400527:    8b 45 f8                 mov    eax,DWORD PTR [rbp-0x8]
  40052a:    8b 55 fc                 mov    edx,DWORD PTR [rbp-0x4]
  40052d:    01 d0                    add    eax,edx
}
  40052f:    5d                       pop    rbp
  400530:    c3                       ret

0000000000400531 <main>:
// link_example.c

#include <stdio.h>
int main()
{
  400531:    55                       push   rbp
  400532:    48 89 e5                 mov    rbp,rsp
  400535:    48 83 ec 10              sub    rsp,0x10
    int a = 10;
  400539:    c7 45 fc 0a 00 00 00     mov    DWORD PTR [rbp-0x4],0xa
    int b = 5;
  400540:    c7 45 f8 05 00 00 00     mov    DWORD PTR [rbp-0x8],0x5
    int c = add(a, b);
  400547:    8b 55 f8                 mov    edx,DWORD PTR [rbp-0x8]
  40054a:    8b 45 fc                 mov    eax,DWORD PTR [rbp-0x4]
  40054d:    89 d6                    mov    esi,edx
  40054f:    89 c7                    mov    edi,eax
  400551:    b8 00 00 00 00           mov    eax,0x0
  400556:    e8 c2 ff ff ff           call   40051d <add>
  40055b:    89 45 f4                 mov    DWORD PTR [rbp-0xc],eax
    printf("c = %d\n", c);
  40055e:    8b 45 f4                 mov    eax,DWORD PTR [rbp-0xc]
  400561:    89 c6                    mov    esi,eax
  400563:    bf 10 06 40 00           mov    edi,0x400610
  400568:    b8 00 00 00 00           mov    eax,0x0
  40056d:    e8 8e fe ff ff           call   400400 <printf@plt>
}
  400572:    c9                       leave
  400573:    c3                       ret
  400574:    66 2e 0f 1f 84 00 00     nop    WORD PTR cs:[rax+rax*1+0x0]
  40057b:    00 00 00
  40057e:    66 90                    xchg   ax,ax

0000000000400580 <__libc_csu_init>:
  400580:    41 57                    push   r15
  400582:    41 89 ff                 mov    r15d,edi
  400585:    41 56                    push   r14
  400587:    49 89 f6                 mov    r14,rsi
  40058a:    41 55                    push   r13
  40058c:    49 89 d5                 mov    r13,rdx
  40058f:    41 54                    push   r12
  400591:    4c 8d 25 78 08 20 00     lea    r12,[rip+0x200878]        # 600e10 <__frame_dummy_init_array_entry>
  400598:    55                       push   rbp
  400599:    48 8d 2d 78 08 20 00     lea    rbp,[rip+0x200878]        # 600e18 <__init_array_end>
  4005a0:    53                       push   rbx
  4005a1:    4c 29 e5                 sub    rbp,r12
  4005a4:    31 db                    xor    ebx,ebx
  4005a6:    48 c1 fd 03              sar    rbp,0x3
  4005aa:    48 83 ec 08              sub    rsp,0x8
  4005ae:    e8 15 fe ff ff           call   4003c8 <_init>
  4005b3:    48 85 ed                 test   rbp,rbp
  4005b6:    74 1e                    je     4005d6 <__libc_csu_init+0x56>
  4005b8:    0f 1f 84 00 00 00 00     nop    DWORD PTR [rax+rax*1+0x0]
  4005bf:    00
  4005c0:    4c 89 ea                 mov    rdx,r13
  4005c3:    4c 89 f6                 mov    rsi,r14
  4005c6:    44 89 ff                 mov    edi,r15d
  4005c9:    41 ff 14 dc              call   QWORD PTR [r12+rbx*8]
  4005cd:    48 83 c3 01              add    rbx,0x1
  4005d1:    48 39 eb                 cmp    rbx,rbp
  4005d4:    75 ea                    jne    4005c0 <__libc_csu_init+0x40>
  4005d6:    48 83 c4 08              add    rsp,0x8
  4005da:    5b                       pop    rbx
  4005db:    5d                       pop    rbp
  4005dc:    41 5c                    pop    r12
  4005de:    41 5d                    pop    r13
  4005e0:    41 5e                    pop    r14
  4005e2:    41 5f                    pop    r15
  4005e4:    c3                       ret
  4005e5:    90                       nop
  4005e6:    66 2e 0f 1f 84 00 00     nop    WORD PTR cs:[rax+rax*1+0x0]
  4005ed:    00 00 00

00000000004005f0 <__libc_csu_fini>:
  4005f0:    f3 c3                    repz ret

Disassembly of section .fini:

00000000004005f4 <_fini>:
  4005f4:    48 83 ec 08              sub    rsp,0x8
  4005f8:    48 83 c4 08              add    rsp,0x8
  4005fc:    c3                       ret

可见,链接(Link) 不仅仅是单纯的将多个 Object 文件拼凑起来而已,而是将程序真正的转换为一个可以在操作系统上执行的文件格式,且这个文件中还包含了整个程序所有 Object 文件的内容。在 Linux 上,这个文件格式就是 ELF(Execuatable and Linkable File Format,可执行与可链接文件格式)。

ELF 文件格式:是一种用于二进制文件、可执行文件、目标代码、共享库和核心转储格式文件。ELF 文件由 4 部分组成,分别是 ELF header、程序头表(Program Header Table)、节(Section)和节头表(Section Header Table)。

img

在链接器把程序转换为 ELF 格式的可执行文件之后,装载器再去处理就会容易得多。因为装载器不再需要考虑地址跳转的问题,只需要解析 ELF 文件,把对应的指令和数据加载到内存里面供 CPU 执行就可以了。

同样,Windows 也有自己的可执行文件格式 PE(Portable Executable Format)。因为 Linux 和 Windows 的可执行文件格式不同,所以也就不能够 “一次编译,跨平台执行” 了。那么换句话说:是不是只要在 Linux 上运行可以解析 PE 文件的装载器就可以解决这个问题呢?答案是肯定的,Linux 著名的开源软件 Wine 正是此类装载器,国内很多 Linux Desktop 发行版都是基于 Wine 实现了 Windows 常用软件的移植

第三节 高级语言程序与机器级代码之间的对应*

机器语言:计算机执行的二进制命令,都是0和1表示的

汇编语言:用助记符代替机器指令的操作码(如:ADD表示加法)

高级语言:更简单,符合人们的习惯,也更容易理解和修改。高级语言经过编译器编译之后可以得到目标程序。(如:C++、JAVA)

**机器语言、汇编语言、高级语言的区别:**依次接近人类自然语言的表达方式、代码效率依次变低、语言越来越高级

**机器语言、汇编语言、高级语言的联系:**高级语言要通过编译程序翻译成汇编代码,汇编语言要通过汇编得到机器语言,计算机才能执行。

一、基本概念

  • 对操作数的操作地址只涉及三种:寄存器到寄存器,主存到寄存器,立即数到寄存器。
  • dword 32bit ;word 16bit ;byte 8bit
  • 通用寄存器 eax ebx ecx edx 变址寄存器 esi edi 堆栈寄存器 ebp esp。

二、选择结构语句的机器级表示

je jump when equal,
jne jump when not equal,
jg jump when greater,
jge jump when greater or equal
jl jump when less
jle jump when less or equal

img

例如

cmp eax ,ebx #比较寄存器eax和ebx里的值
jg NEXT #若eax>ebx,则跳转到NEXT

三、循环结构语句的机器级表示

用条件指令实现循环

img

用loop指令实现循环

img

就是将”某些处理“封装到了Looptop内,使得代码更加简洁。

四、CISC和RISC

对比项目 CISC RISC
指令系统 复杂,庞大 简单,精简
指令数目 一般大于200条 一般小于100条
指令字长 不固定 定长
可访存指令 没有限制 只有Load/Store指令
各种指令执行时间 相差较大 绝大多数在一个周期内完成
各指令使用频率 有的常用,有点不常用 一般都常用
通用寄存器的数量 较少
控制方式 绝大多数为微程序控制 绝大多数为组合逻辑控制
指令流水线 可以通过一定方式实现 必须实现

6、中央处理器

第一节 CPU

计算机中的CPU是硬件系统的核心,用于数据的加工处理,能完成各种算术、逻辑运算及控制功能。其中,控制器的作用是控制整个计算机的各个部件有条不紊地工作,它的基本功能就是从内存取指令和执行指令。

img

CPU是计算机的控制中心,主要由运算器、控制器、寄存器组和内部总线等部件组成。控制器由程序计数器、指令寄存器、指令译码器、时序产生器和操作控制器组成,它是发布命令的“决策机构”,即完成协调和指挥整个计算机系统的操作。它的主要功能有:从内存中取出一条指令,并指出下一条指令在内存中的位置;对指令进行译码或测试,并产生相应的操作控制信号,以便启动规定的动作;指挥并控制CPU、内存和输入输出设备之间数据的流动。

程序计数器(PC)是专用寄存器,具有寄存信息和计数两种功能,又称为指令计数器,在程序开始执行前,将程序的起始地址送入PC,该地址在程序加载到内存时确定,因此PC的初始内容即是程序第一条指令的地址。执行指令时,CPU将自动修改PC的内容,以便使其保持的总是将要执行的下一条指令的地址。由于大多数指令都是按顺序执行的,因此修改的过程通常只是简单地对PC加1。当遇到转移指令时,后继指令的地址根据当前指令的地址加上一个向前或向后转移的位移量得到,或者根据转移指令给出 的直接转移的地址得到。

指令寄存器(IR)用来保存当前正在执行的指令。当执行一条指令时,先把它从内存取到数据寄存器(DR)中,然后再传送至IR。为了执行任何给定的指令,必须对操作码进行测试,以便识别所要求的操作。指令译码器(ID)就是做这项工作的。指令寄存器中操作码字段的输出就是指令译码器的输入。操作码一经译码后,即可向操作控制器发出具体操作的特定信号。 地址寄存器(AR)用来保存当前CPU所访问的内存单元的地址。由于在内存和CPU之间存在着操作速度上的差别,所以必须使用地址寄存器来保持地址信息,直到内存的读/写操作完成为止。 为了保证程序指令能够连续地执行下去,CPU必须具有某些手段来确定下一条指令的地址。而程序计数器正起到这种作用,所以通常又称为指令计数器。在程序开始执行前,必须将它的起始地址,即程序的一条指令所在的内存单元地址送入PC,因此程序计数器(PC)的内容即是从内存提取的第一条指令的地址。当执行指令时,CPU将自动修改PC的内容,即每执行一条指令PC增加一个量,这个量等于指令所含的字节数,以便使其保持的总是将要执行的下一条指令的地址。由于大多数指令都是按顺序来执行的,所以修改的过程通常只是简单的对PC加1。

输入输出控制,DMA无需CPU干预

计算机中主机与外设间进行数据传输的输入输出控制方法有程序控制方式、中断方式、DMA等。 在程序控制方式下,由CPU执行程序控制数据的输入输出过程。 在中断方式下,外设准备好输入数据或接收数据时向CPU发出中断请求信号,若CPU决定响应该请求,则暂停正在执行的任务,转而执行中断服务程序进行数据的输入输出处理,之后再回去执行原来被中断的任务。 在DMA方式下,CPU只需向DMA控制器下达指令,让DMA控制器来处理数据的传送,数据传送完毕再把信息反馈给CPU,这样就很大程度上减轻了CPU的负担,可以大大节省系统资源。

虚拟储存器:主存-辅存

在具有层次结构存储器的计算机中,虚拟存储器是为用户提供一个比主存储器大得多的可随机访问的地址空间的技术。虚拟存储技术使辅助存储器和主存储器密切配合, 对用户来说,好像计算机具有一个容量比实际主存大得多的主存可供使用,因此称为虚拟存储器。虚拟存储器的地址称为虚地址或逻辑地址。

中断向量:提供中断服务程序入口地址

计算机在执行程序过程中,当遇到急需处理的事件时,暂停当前正在运行的程序, 转去执行有关服务程序,处理完后自动返回原程序,这个过程称为中断。 中断是一种非常重要的技术,输入输出设备和主机交换数据、分时操作、实时系统、计算机网络和分布式计算机系统中都要用到这种技术。为了提高响应中断的速度,通常把所有中断服务程序的入口地址(或称为中断向量)汇集为中断向量表。

补码表示n^2:n字节

补码本身是带符号位的,补码表示的数字中0是唯一的,不像原码有+0和-0之分,也就意味着n位进制编码可以表示2^n个不同的数。

CPU响应DMA请求:一个总线周期之后

DMA控制器在需要的时候代替CPU作为总线主设备,在不受CPU干预的情况下,控制I/O设备与系统主存之间的直接数据传输。DMA操作占用的资源是系统总线,而CPU并非在整个指令执行期间即指令周期内都会使用总线,故DMA请求的检测点设置在每个机器周期也即总线周期结束时执行,这样使得总线利用率最高。

超长指令字:VLIM

VLIW:(Very Long Instruction Word,超长指令字)一种非常长的指令组合,它把许多条指令连在一起,增加了运算的速度。

CPU区分指令和数据:指令周期的不同阶段

指令周期是执行一条指令所需要的时间,一般由若干个机器周期组成,是从取指令、分析指令到执行完所需的全部时间。CPU执行指令的过程中,根据时序部件发出的时钟信号按部就班进行操作。在取指令阶段读取到的是指令,在分析指令和执行指令时,需要操作数时再去读操作数。

指令寄存器位数:指令字长

指令寄存器是CPU中的关键寄存器,其内容为正在执行的指令,显然其位数取决于指令字长。

防火墙基本特点

防火墙最基本的功能就是控制在计算机网络中,不同信任程度区域间传送的数据流。防火墙对流经它的网络通信进行扫描,这样能够过滤掉一些攻击,以免其在目标计算机上被执行。防火墙还可以关闭不使用的端口,隐蔽内部细节。所有的访问都经过防火墙,防火墙就能记录下这些访问并作出日志记录,同时也能提供网络使用情况的统计数据。

高速缓存区

在CPU内外的高速缓存是用来解决CPU与内存之间速度、容量不匹配的问题,与外存无关,可以提高CPU访问主存数据或指令的效率。Cache不属于主存,与主存容量无关。Cache容量相对于其他存储层次,量级较小,不能扩大存储系统的存量。

分级存储:解决储存容量和速度的矛盾

计算机系统中,高速缓存一般用SRAM,内存一般用DRAM,外存一般采用磁存储器。SRAM的集成度低、速度快、成本高。DRAM的集成度高,但是需要动态刷新。磁存储器速度慢、容量大、价格便宜。因此,不同的存储设备组成分级存储体系,来解决速度、存储容量和成本之间的矛盾。

CPU访问最快:通用寄存器

计算机系统中的CPU内部对通用寄存器的存取操作是速度最快的,其次是Cache,内存的存取速度再次,选项中访问速度最慢的就是作为外存的硬盘。它们共同组成分级存储体系来解决存储容量、成本和速度之间的矛盾。

地址映射

全相联地址映射:主存的任意一块可以映象到Cache中的任意一块。
直接相联映射:主存中一块只能映象到Cache的一个特定的块中。
组相联的映射:各区中的某一块只能存入缓存的同组号的空间内,但组内各块地址之间则可以任意存放。即从主存的组到Cache的组之间采用直接映象方式,在两个对应的组内部采用全相联映象方式。

RISC:适合采用硬布线逻辑执行指令

CISC (Complex Instruction Set Computer,复杂指令集计算机)的基本思想是:进一步增强原有指令的功能,用更为复杂的新指令取代原先由软件子程序完成的功能,实现软件功能的硬件化,导致机器的指令系统越来越庞大而复杂。CISC计算机一般所含的指令数目至少300条以上,有的甚至超过500条。 RISC (Reduced Instruction Set Computer,精简指令集计算机)的基本思想是:通过减少指令总数和简化指令功能,降低硬件设计的复杂度,使指令能单周期执行,并通过优化编译提高指令的执行速度,采用硬布线控制逻辑优化编译程序。在20世纪70年代末开始兴起,导致机器的指令系统进一步精炼而简单。
016.BIOS保存在主板的ROM
BIOS(Basic Input Output System)(基本输入输出系统)是一组固化到计算机内主板上一个ROM芯片上的程序,它保存着计算机最重要的基本输入输出的程序、开机后自检程序和系统自启动程序,它可从CMOS中读写系统设置的具体信息。

DMA控制方式:主存与外设(I/O设备)

直接主存存取(Direct Memory Access,DMA)是指数据在主存与I/O设备间(即主存与外设之间)直接成块传送。

相联存储器:按内容访问

计算机系统的存储器按所处的位置可分为内存和外存。按构成存储器的材料可分为磁存储器、半导体存储器和光存储器。按存储器的工作方式可分为读写存储器和只读存储器。按访问方式可分为按地址访问的存储器和按内容访问的存储器。按寻址方式可分为随机存储器、顺序存储器和直接存储器。 相联存储器是一种按内容访问的存储器。

中断

按照是否可以被屏蔽,可将中断分为两大类:不可屏蔽中断(又叫非屏蔽中断)和可屏蔽中断。不可屏蔽中断源一旦提出请求,CPU必须无条件响应,而对可屏蔽中断源的请求,CPU可以响应,也可以不响应。典型的非屏蔽中断源的例子是电源掉电,一旦出现,必须立即无条件地响应,否则进行其他任何工作都是没有意义的。典型的可屏蔽中断源的例子是打印机中断,CPU对打印机中断请求的响应可以快一些,也可以慢一些, 因为让打印机等待是完全可以的。对于软中断,它不受中断允许标志位(IF位)的影响, 所以属于非屏蔽中断范畴。

中断响应时间:中断开始-开始处理

中断系统是计算机实现中断功能的软硬件总称。一般在CPU中设置中断机构,在外设接口中设置中断控制器,在软件上设置相应的中断服务程序。中断源在需要得到CPU服务时,请求CPU暂停现行工作转向为中断源服务,服务完成后,再让CPU回到原工作状态继续完成被打断的工作。中断的发生起始于中断源发出中断请求,中断处理过程中,中断系统需要解决一系列问题,包括中断响应的条件和时机,断点信息的保护与恢复,中断服务程序入口、中断处理等。中断响应时间,是指从发出中断请求到开始进入中断服务程序所需的时间。

地址总线和数据总线的宽度

内存容量为4GB,即内存单元的地址宽度为32位。字长为32位即要求数据总线的宽度为32位,因此地址总线和数据总线的宽度都为32。 地址总线的宽度就是处理机寻址范围,若地址总线为n位,则可寻址空间为2的n次方字节。所以本题的可寻址空间为:4* 1024* 1024* 1024*位,所以地址总线宽度为32

立即寻址执行:数据包含在指令中

立即寻址是一种特殊的寻址方式,指令中在操作码字段后面的部分不是通常意义上的操作数地址,而是操作数本身,也就是说数据就包含在指令中,只要取出指令,也就取出了可以立即使用的操作数。在直接寻址中,指令中地址码字段给出的地址A就是操作数的有效地址,即形式地址等于有效地址。间接寻址意味着指令中给出的地址A不是操作数的地址,而是存放操作数地址的主存单元的地址,简称操作数地址的地址。寄存器寻址指令的地址码部分给出了某一个通用寄存器的编号Ri,这个指定的寄存器中存放着操作数。

堆栈:保护断点和现场

当系统中有多个中断请求时,中断系统按优先级进行排队。若在处理低级中断过程中又有高级中断申请中断,则高级中断可以打断低级中断处理,转去处理高级中断,等处理完高级中断后再返回去处理原来的低级中断,称为中断嵌套。实现中断嵌套用后进先出的栈来保护断点和现场最有效。

全相联映像:Cache发生模块冲突次数最小

Cache工作时,需要拷贝主存信息到Cache中,就需要建变主存地址和Cache地址的映射关系。Cache的地址映射方法主要有三种,即全相联映像、直接映像和组相联映像。其中全相联方式意味着主存的任意一块可以映像到Cache中的任意一块,其特点是块冲突概率低,Cache空间利用率高,但是相联目录表容量大导致成本高、查表速度慢; 直接映像方式是指主存的每一块只能映像到Cache的一个特定的块中,整个Cache地址与主存地址的低位部分完全相同,其特点是硬件简单,不需要相联存储器,访问速度快 (无须地址变换),但是Cache块冲突概率高导致Cache空间利用率很低;组相联方式是对上述两种方式的折中处理,对Cache分组,实现组间直接映射,组内全相联,从而获得较低的块冲突概率、较高的块利用率,同时得到较快的速度和较低的成本。

不同寻址:扩大寻址空间,提高编程灵活性

寻址方式是指寻找操作数或操作数地址的方式。指令系统中采用不同寻址方式的目的是为了在效率和方便性上找一个平衡。立即寻址和寄存器寻址在效率上是最快的, 但是寄存器数目少,不可能将操作数都存入其中等待使用,立即寻址的使用场合也非常有限,这样就需要将数据保存在内存中,然后使用直接寻址、寄存器间接寻址、寄存器相对寻址、基址加变址寻址、相对基址及变址寻址等寻址方式将内存中的数据移入寄存器中。

计算储存单元

每个地址编号为一个存储单元(容量为1个字节),地址区间OOOOAOOOH〜OOOOBFFFH 共有1FFF+1个地址编号(即213),1K=1024,因此该地址区间的存储单元数也就是8K。

(+0) == (-0):补码和移码

海明校验码:2^k-1 >= n+k

利用多组数位的奇偶性检错和纠错。

浮点数的范围:阶码决定

在计算机中使用了类似于十进制科学计数法的方法来表示二进制实数,因其表示不同的数时小数点位置的浮动不固定而取名浮点数表示法。浮点数编码由两部分组成:阶码(即指数,为带符号定点整数,常用移码表示,也有用补码的)和尾数(是定点纯小数,常用补码表示,或原码表示)。因此可以知道,浮点数的精度由尾数的位数决定,表示范围的大小则主要由阶码的位数决定。

DMA输入输出控制:用不到CPU

中断方式、程序查询方式和无条件传逵方式都是通过CPU执行程序指令来传送数据的,DMA方式下是由DMA控制器直接控制数据的传送过程,CPU需要让出对总线的控制权,并不需要CPU执行程序指令来传送数据。

程序局限性

程序的局限性表现在时间局部性和空间局部性: 1.时间局部性是指如果程序中的某条指令一旦被执行,则不久的将来该指令可能再次被执行; 2.空间局部性是指一旦程序访问了某个存储单元,则在不久的将来,其附近的存储单元也最有可能被访问。 题干描述的是空间局部性。

保存中断现场:返回正确执行

CPU接收到中断请求,会将自己正在执行的程序A的状态进行保存,即保存现场,然后转去处理提交中断申请的程序B,完成程序B之后,再回到程序A中断的断点接着完成程序A。保存现场的目的是为了能正确返回到被中断的程序A继续执行。

总线广义地讲,任何连接两个以上电子元器件的导线都可以称为总线。通常可分为4类:
①芯片内总线。用于在集成电路芯片内部各部分的连接。
②元件级总线。用于一块电路板内各元器件的连接。
③内总线,又称系统总线。用于构成计算机各组成部分(CPU、内存和接口等)的连接。
④外总线,又称通信总线。用计算机与外设或计算机与计算机的连接或通信。 连接处理机的处理器、存储器及其他部件的总线属于内总线,按总线上所传送的内容分为数据总线、地址总线和控制总线。

总线复用:减少信息

总线是一组能为多个部件分时共享的信息传送线,用来连接多个部件并为之提供信息交换通路,通过总线复用方式可以减少总线中信号线的数量,以较少的信号线传输更多的信息。

相联存储器:不是按照寻址方式划分的一类存储器

存储系统中的存储器,按访问方式可分为按地址访问的存储器和按内容访问的存储器;按寻址方式分类可分为随机存储器、顺序存储器和直接存储器。随机存储器(Random Access Memory,RAM)指可对任何存储单元存入或读取数据, 访问任何一个存储单元所需的时间是相同的。顺序存储器(Sequentially Addressed Memory,SAM)指访问数据所需要的时间与数据所在的存储位置相关,磁带是典型的顺序存储器。直接存储器(Direct Addressed Memory,DAM)是介于随机存取和顺序存取之间的一种寻址方式。磁盘是一种直接存取存储器,它对磁道的寻址是随机的,而在一个磁道内,则是顺序寻址。相联存储器是一种按内容访问的存储器。其工作原理就是把数据或数据的某一部分作为关键字,将该关键字与存储器中的每一单元进行比较,从而找出存储器中所有与关键字相同的数据字。

指令寄存器:指令(包括:操作码、地址码)

程序被加载到内存后开始运行,当CPU执行一条指令时,先把它从内存储器取到缓冲寄存器DR中,再送入IR暂存,指令译码器根据IR的内容产生各种微操作指令,控制其他的组成部件工作,完成所需的功能。 程序计数器(PC)具有寄存信息和计数两种功能,又称为指令计数器。程序的执行分两种情况,一是顺序执行,二是转移执行。在程序开始执行前,将程序的起始地址送入PC,该地址在程序加载到内存时确定,因此PC的内容即是程序第一条指令的地址。执行指令时,CPU将自动修改PC的内容,以便使其保持的总是将要执行的下一条指令的地址。由于大多数指令都是按顺序来执行的,所以修改的过程通常只是简单地对PC加1。当遇到转移指令时,后继指令的地址根据当前指令的地址加上一个向前或向后转移的位移量得到,或者根据转移指令给出的直接转移地址得到。

汇编语言可以访问的:程序计数器PC

指令寄存器(IR)用于暂存从内存取出的、正在运行的指令,这是由系统使用的寄存器,程序员不能访问。 存储器数据寄存器(MDR)和存储器地址寄存器(MAR)用于对内存单元访问时的数据和地址暂存,也是由系统使用的,程序员不能访问。 程序计数器(PC)用于存储指令的地址,CPU根据该寄存器的内容从内存读取待执行的指令,程序员可以访问该寄存器。

指令寄存器:对用户完全透明

寄存器组是CPU中的一个重要组成部分,它是CPU内部的临时存储空间。寄存器既可以用来存放数据和地址,也可以存放控制信息或CPU工作时的状态。在CPU中增加寄存器的数量,可以使CPU把执行程序时所需的数据尽可能地放在寄存器中,从而减少访问内存的次数,提高其运行速度。但是,寄存器的数目也不能太多,除了增加成本外,寄存器地址编码增加还会增加指令的长度。CPU中的寄存器通常分为存放数据的寄存器、存放地址的寄存器、存放控制信息的寄存器、存放状态信息的寄存器和其他寄存器等类型。 程序计数器是存放指令地址的寄存器,其作用是:当程序顺序执行时,每取出一条指令,程序计数器(PC)内容自动增加一个值,指向下一条要取的指令。当程序出现转移时,则将转移地址送入PC,然后由PC指向新的指令地址。 指令寄存器(IR)用于存放正在执行的指令,指令从内存取出后送入指令寄存器。其操作码部分经指令译码器送微操作信号发生器,其地址码部分指明参加运算的操作数的地址形成方式。在指令执行过程中,指令寄存器中的内容保持不变。 状态字寄存器(PSW)用于保存指令执行完成后产生的条件码,例如运算是否有溢出,结果为正还是为负,是否有进位等。此外,PSW还保存中断和系统工作状态等信息。 通用寄存器组是CPU中的一组工作寄存器,运算时用于暂存操作数或地址。在程序中使用通用寄存器可以减少访问内存的次数,提高运算速度。 在汇编语言程序中,程序员可以直接访问通用寄存器以存取数据,可以访问状态字寄存器以获取有关数据处理结果的相关信息,可以通过相对程序计数器进行寻址,但是不能访问指令寄存器。

无需干预数据传送过程:直接存储器存取

中断方式下的数据传送是当I/O接口准备好接收数据或准备好向CPU传送数据时,就发出中断信号通知CPU。对中断信号进行确认后,CPU保存正在执行的程序的现场, 转而执行提前设置好的I/O中断服务程序,完成一次数据传送的处理。这样,CPU就不需要主动查询外设的状态,在等待数据期间可以执行其他程序,从而提高了CPU的利用率。采用中断方式管理I/O设备,CPU和外设可以并行地工作。 程序查询方式下,CPU通过执行程序查询外设的状态,判断外设是否准备好接收数据或准备好了向CPU输入的数据。 直接内存存取(Direct Memory Access,DMA)方式的基本思想是通过硬件控制实现主存与I/O设备间的直接数据传送,数据的传送过程由DMA控制器(DMAC)进行控制,不需要CPU的干预。在DMA方式下,由CPU启动传送过程,即向设备发出“传送一块数据”的命令,在传送过程结束时,DMAC通过中断方式通知CPU进行一些后续处理工作。

RISC特点

  1. 高效流水线操作
  2. 寻址方式少
  3. 硬布线控制

RISC(Reduced Instruction Set Computer,精简指令集计算机)的主要特点是重叠寄存器窗口技术;优化编译技术。RISC使用了大量的寄存器,如何合理分配寄存器、提高寄存器的使用效率及减少访存次数等,都应通过编译技术的优化来实现;超流水及超标量技术。为了进一步提高流水线速度而采用的技术;硬布线逻辑与微程序相结合在微程序技术中。

计算机的主存:DRAM

随机访问存储器(RAM)有两类:静态的(SRAM)和动态的(DRAM),SRAM 比DRAM速度更快,但也贵得多。SRAM用来作为高速缓冲存储器(Cache),DRAM 用来作为主存及图形系统的帧缓冲区。SRAM将每个位存储在一个双稳态的存储器单元中,DRAM将每个位存储为对一个电容的充电,由于电容非常小,在10〜l00ms时间内会失去电荷,所以需要周期性地刷新充电以保持信息。 EEPROM是电可擦除可编程只读存储器。

64位和32位计算机

计算机系统的运算速度受多种因素的影响,64位微处理器可同时对64位数据进行运算,但不能说其速度是32位微处理器的2倍。

译码器的作用:指令译码

CPU中指令译码器的功能是对现行指令进行分析,确定指令类型和指令所要完成的操作以及寻址方式,并将相应的控制命令发往相关部件。

SCSI不是系统总线

系统总线又称内总线或板级总线,在微机系统中用来连接各功能部件而构成一个完整的微机系统。系统总线包含有三种不同功能的总线,即数据总线DB (Data Bus)、地址总线AB (Address Bus)和控制总线CB (Control Bus)。 ISA (Industrial Standard Architecture)总线标准是IBM公司1984年为推出PC/AT机而建立的系统总线标准,所以也叫AT总线。它是对XT总线的扩展,以适应8/16位数据总线要求。 EISA总线是1988年由Compaq等9家公司联合推出的总线标准。它在ISA总线的基础上使用双层插座,在原来ISA总线的98条信号线上又增加了98条信号线,也就是在两条ISA信号线之间添加一条EISA信号线。在实用中,EISA总线完全兼容ISA总线信号。 PCI (Peripheral Component Interconnect)总线是当前最流行的总线之一,它是由Intel公司推出的一种局部总线。它定义了32位数据总线,且可扩展为64位。PCI总线主板插槽的体积比原ISA总线插槽还小,支持突发读写操作,最大传输速率可达132MB/S,可同时支持多组外围设备。PCI局部总线不能兼容现有的ISA、EISA、MCA (Micro Channel Architecture)总线,但它不受制于处理器,是基于奔腾等新一代微处理器而发展的总线。 SCSI (Small Computer System Interface)是一种用于计算机和智能设备之间(硬盘、软驱、光驱、打印机、扫描仪等)系统级接口的独立处理器标准。

MISD没有实际意义

Flynn主要根据指令流和数据流来分类,分为四类: ①单指令流单数据流机器(SISD) SISD机器是一种传统的串行计算机,它的硬件不支持任何形式的并行计算,所有的指令都是串行执行,并且在某个时钟周期内,CPU只能处理一个数据流。因此这种机器被称作单指令流单数据流机器。早期的计算机都是SISD机器。 ②单指令流多数据流机器(SIMD) SIMD是采用一个指令流处理多个数据流。这类机器在数字信号处理、图像处理以及多媒体信息处理等领域非常有效。 Intel处理器实现的MMXTM、SSE (Streaming SIMD Extensions)、SSE2及SSE3扩展指令集,都能在单个时钟周期内处理多个数据单元。也就是说人们现在用的单核计算机基本上都属于SIMD机器。 ③多指令流单数据流机器(MISD) MISD是采用多个指令流来处理单个数据流。在实际情况中,采用多指令流处理多数据流才是更有效的方法。因此MISD只是作为理论模型出现,没有投入实际应用。 ④多指令流多数据流机器(MIMD) M1MD机器可以同时执行多个指令流,这些指令流分别对不同数据流进行操作。例如,intel和AMD的双核处理器就属于MIMD的范畴。

CA的公钥:验证数字证书的真实性

数字证书是由权威机构 CA证书授权(CertificateAuthority)中心发行的,能提供在Internet上进行身份验证的一种权威性电子文档,人们可以在互联网交往中用它来 证明自己的身份和识别对方的身份。 数字证书包含版本、序列号、签名算法标识符、签发人姓名、有效期、主体名、主体公钥信息等并附有CA的签名,用户A获取用户B的数字证书后通过验证CA的签名 来确认数字证书的有效性。验证CA的签名时使用的是CA的公钥。

漏洞扫描:判断服务器是否存在可写入目录

漏洞扫描技术是检测远程或本地系统安全脆弱性的一种安全技术。通过与目标主机TCP/IP端口建立连接并请求某些服务(如TELNET、FTP等),记录目标主机的应答, 搜集目标主机相关信息(如匿名用户是否可以登录等),从而发现目标主机某些内在的安全弱点。

TSL最接近SSL

TLS是安全传输层协议的简称,用于在两个通信应用程序之间提供保密性和数据完整性。SSL是安全套接层协议的简称,它也是一种为网络通信提供安全和数据完整性的协议,它与TLS非常接近,它们都是在传输层对网络连接进行加密。PGP是一个基于RSA公匙加密体系的邮件加密软件,用它可以对邮件保密以防止非授权者阅读。HTTPS即安全版的HTTP (超文本传输协议)的,它是在HTTP下加入SSL层,HTTPS的安全基础就是SSL。IPSec是网络层的安全协议,它通过使用加密的安全服务来确保在网络上进行保密而安全的通讯。

报文摘要算法:保护报文不被篡改

报文摘要是用来保证数据完整性的。传输的数据一旦被修改,摘要就不同了。只要对比两次摘要就可确定数据是否被修改过。

木马客户端运行在攻击者主机上

木马(Trojan),是指通过特定的程序(木马程序)来控制另一台计算机。木马通常有两个可执行程序:一个是控制端,另一个是被控制端。植入对方电脑的是服务端,而黑客正是利用客户端进入运行了服务端的电脑。运行了木马程序的服务端以后;会产生一个有着容易迷惑用户的名称的进程,暗中打开端口,向指定地点发送数据(如网络游戏的密码,即时通信软件密码和用户上网密码等),黑客甚至可以利用这些打开的端口进入电脑系统。Sniffer,中文可以翻译为嗅探器,是一种基于被动侦听原理的网络分析方式。使用这种技术方式,可以监视网络的状态、数据流动情况以及网络上传输的信息。Sniffer不是木马程序。

Web服务器:DMZ区域

DMZ是为了解决安装防火墙后外部网络不能访问内部网络服务器的问题,而设立的一个非安全系统与安全系统之间的缓冲区,这个缓冲区位于企业内部网络和外部网络之间的小网络区域内,在这个小网络区域内可以放置一些必须公开的服务器设施,如企业Web服务器、FTP服务器和论坛等。

SSH在终端与远程建立:安全连接

终端设备与远程站点之间建立安全连接的协议是SSH。SSH为Secure Shell的缩写, 是由IETF制定的建立在应用层和传输层基础上的安全协议。SSH是专为远程登录会话和其他网络服务提供安全性的协议。利用SSH协议可以有效防止远程管理过程中的信息泄露问题。SSH最初是UNIX上的程序,后来又迅速扩展到其他操作平台。

第二节 指令执行过程

一条指令的执行过程按时间顺序可分为以下几个步骤。

  • ① CPU发出指令地址。将指令指针寄存器(IP)的内容——指令地址,经地址总线送入存储器的地址寄存器中。
  • ② 从地址寄存器中读取指令。将读出的指令暂存于存储器的数据寄存器中。
  • ③ 将指令送往指令寄存器。将指令从数据寄存器中取出,经数据总线送入控制器的指令寄存器中。
  • ④ 指令译码。指令寄存器中的操作码部分送指令译码器,经译码器分析产生相应的操作控制信号,送往各个执行部件。
  • ⑤ 按指令操作码执行。
  • ⑥ 修改程序计数器的值,形成下一条要取指令的地址。若执行的是非转移指令,即顺序执行,则指令指针寄存器的内容加1,形成下一条要取指令的地址。指令指针寄存器也称为程序计数器。

在指令周期中,包含了:取指周期,在取指周期后需要判断是否有间址周期,如果没有就进入到执行周期,在执行周期后又需要判断是否有中断程序,如果有就响应中断并保存断点生成中断服务程序入口;如果没有就进入下一个取指周期。

img

四个周期都有CPU访存操作,只是访存目的不同。取值周期是取指令;间址周期是取有效地址;执行周期是为了取操作数;中断周期是为了保存程序断点。

指令周期常常有若干个机器周期,机器周期里面又包含若干个时钟周期。每个指令周期内的机器周期可以不同,机器周期内的时钟周期也可以不同。时钟周期是CPU操作的最基本单位。

img

取值周期

取指周期:取指周期的任务是根据PC中的内容从主存中取出指令代码并存放在IR中。而PC中存放的是指令的地址,根据这个地址从内存单元取出的是指令,并放在指令寄存器IR中,取指令的同时,PC加1。

img

间址周期

间址周期:间址周期的任务是取操作数有效地址,以一次间址为例,将指令中的地址码送到MAR并送至地址总线,此后CU向存储器发读命令,以获取有效地址并存至MDR。

img

执行周期

执行周期:执行周期的任务是根据IR中的指令字的操作码和操作数通过ALU操作产生执行结果。不同指令的执行周期操作不同,因此没有统一的数据流向。

指令执行方案:

单指令周期 所有指令选用相同的执行时间(取决于最长指令执行时间),指令间串行,但原本只需要很短时间完成指令也分配了很长时间,降低整个系统运行速度
多指令周期 对不同指令选用不同的执行时间,需要更复杂的硬件设计,指令间是串行
流水线 在每个时钟周期让多个指令同时运行,指令间是并行

中断周期

中断周期:中断周期的任务是处理中断请求。假设程序断点存入堆栈中,并用SP指示栈顶地址,而且进栈操作是先修改栈顶指针,后存入数据。

img

总览

img

过程详述:几乎所有的冯·诺伊曼型计算机的CPU,其工作都可以分为5个阶段:取指令、指令译码、执行指令、访存取数、结果写回。

1.取指令阶段
取指令(Instruction Fetch,IF)阶段是将一条指令从主存中取到指令寄存器的过程。程序计数器PC中的数值,用来指示当前指令在主存中的位置。当一条指令被取出后,PC中的数值将根据指令字长度而自动递增:若为单字长指令,则(PC)+1àPC;若为双字长指令,则(PC)+2àPC,依此类推。

2.指令译码阶段
取出指令后,计算机立即进入指令译码(Instruction Decode,ID)阶段。在指令译码阶段,指令译码器按照预定的指令格式,对取回的指令进行拆分和解释,识别区分出不同的指令类别以及各种获取操作数的方法。

在组合逻辑控制的计算机中,指令译码器对不同的指令操作码产生不同的控制电位,以形成不同的微操作序列;在微程序控制的计算机中,指令译码器用指令操作码来找到执行该指令的微程序的入口,并从此入口开始执行。
在传统的设计里,CPU中负责指令译码的部分是无法改变的。不过,在众多运用微程序控制技术的新型CPU中,微程序有时是可重写的,可以通过修改成品CPU来改变CPU的译码方式。

3.执行指令阶段
在取指令和指令译码阶段之后,接着进入执行指令(Execute,EX)阶段。此阶段的任务是完成指令所规定的各种操作,具体实现指令的功能。为此,CPU的不同部分被连接起来,以执行所需的操作。

例如,如果要求完成一个加法运算,算术逻辑单元ALU将被连接到一组输入和一组输出,输入端提供需要相加的数值,输出端将含有最后的运算结果。

4.访存取数阶段
根据指令需要,有可能要访问主存,读取操作数,这样就进入了访存取数(Memory,MEM)阶段。
此阶段的任务是:根据指令地址码,得到操作数在主存中的地址,并从主存中读取该操作数用于运算。

5.结果写回阶段
作为最后一个阶段,结果写回(Writeback,WB)阶段把执行指令阶段的运行结果数据“写回”到某种存储形式:结果数据经常被写到CPU的内部寄存器中,以便被后续的指令快速地存取;在有些情况下,结果数据也可被写入相对较慢、但较廉价且容量较大的主存。许多指令还会改变程序状态字寄存器中标志位的状态,这些标志位标识着不同的操作结果,可被用来影响程序的动作。

在指令执行完毕、结果数据写回之后,若无意外事件(如结果溢出等)发生,计算机就接着从程序计数器PC中取得下一条指令地址,开始新一轮的循环,下一个指令周期将顺序取出下一条指令。
许多新型CPU可以同时取出、译码和执行多条指令,体现并行处理的特性。

img

img

第三节 数据通路的功能和基本结构

数据通路的基本结构

Ⅰ:CPU内部单总线方式。将所有寄存器的输入端和输出端都连接在一条公共通路上。易发生冲突。

Ⅱ:双总线/多总线方式。多个总线上传不同的数据,提高效率。

Ⅲ:专用数据通路方式。专门给某些部件设计通路。性能很高但是硬件量大。

说明:

①对于单总线的连接方式来说,ALU只能有一端与总线相连,因为两端相连就必会发生冲突。所以另一段要设计一个暂存器,先把数据放入暂存器,暂存器再与总线相连。

②单周期就是指令在一个周期内完成,这是需要与多总线结构配合。才能使各个部件的数据传递。因为单总线一个周期内只能传递一个数据,所以指令不可能在一个周期内完成。

内部总线 是指同一部件,如CPU内部连接各个寄存器及运算部件之间的总线
系统总线 指同一台计算机的各部件,如CPU,主存,I/O之间连接的总线

第四节 控制器的功能和工作原理

硬布线控制器

多提一嘴,一定要看看王道视频是如何设计出组合逻辑图的,我保证看完一定会有颠覆性的收获。

根据指令操作码、目前的机器周期、节拍信号、机器状态条件,即可确定现在这个节拍应该发出哪些”微命令“

img

硬布线控制器的特点:

  • 指令越多,设计和实现就越复杂(逻辑图很复杂),因此一般使用RISC。
  • 如果扩充一条新的指令,则控制器的设计就需要大改,因此扩充指令较为困难。
  • 由于使用存纯硬件实现控制,因此执行速度很快。

第五节 异常和中断机制

操作系统是由 “中断驱动” 或者 “事件驱动” 的。

主要作用:

  1. 及时处理设备发来的中断请求
  2. 可使 OS 捕获用户程序提出的服务请求
  3. 防止用户程序执行过程中的破坏性工作
  4. 。。。

特点:

  1. 随即发生的
  2. 自动处理的(硬件来完成)
  3. 可恢复的

为何引入中断与异常?

中断的引入:为了支持CPU和设备之间的并行操作

当 CPU 启动设 备进行输入/输出后,设备便可以独立工作,CPU 转去处理与此次输入/输出不相关的事情;当设备完成输入/输出后,通过向 CPU 发中断报郜此次输入/输出的结果,让 CPU 决定如何处理以后的事情

异常的引入:表示CPU执行指令时本身出现的问题

如算术溢出、除零、取数时的奇偶错,访存地址时越界或执行了 “陷入指令” 等,这时硬件改变了 CPU 当前的执行流程,转到相应的错误处理程序或异常处理程序或执行系统调用

早期两者都是中断,后来有了区分,区分的标准是主要是产生的原因

事件分类

中断(外中断)

    1. IO 中断。比如键盘上按 ctrl+c、网卡接收到数据包、打印机结束、读盘结束
    2. 时钟中断。比如设置的定时器到时间了、cpu 上运行的时间片时间到了
    3. 硬件故障。比如笔记本电脑电池耗尽报警、读内存奇偶检验错误

异常(内中断)

    1. 系统调用
    2. 页故障/页错误
    3. 保护性异常
    4. 断点异常
    5. 其他程序的异常,如算术溢出等

中断:外部事件,正在运行的程序所不期望的
异常:正在执行的指令引发的

img

中断异常机制工作原理

中断异常机制是现代计算机系统的核心机制之一。硬件和软件相互配合而使计算机系统的已充分发挥能力。硬件主要工作是响应。捕获中断源发出的中断/异常请求,以一定方式响应,将处理器控制权交给特定的处理程序。

软件的主要工作是处理。识别中断/异常类型并完成相应的处理。

硬件部分

img

中断响应示意图

img

  1. 设备发送中断信号
  2. 中断硬件部件保存现场,把内存保存到了系统堆栈内,主要是 PSW(程序状态字) + PC
  3. 中断硬件根据中断码查中断向量表,得到对应的处理程序
  4. 把中断处理程序入口地址等信息推送到相应的寄存器
  5. 执行中断处理程序(从中断断点开始执行,完事之后继续执行)

软件部分

中断处理程序

设计操作系统时,为每一类中断/异常时间都编好相应的处理程序,并设置好中断向量表

系统运行时若响应中断,中断硬件部件将 CPU 控制权转给中断处理程序:

  1. 保存相关寄存器信息
  2. 分析中断/异常的具体原因
  3. 执行对应的处理功能
  4. 恢复现场,返回被事件打断的程序

小结

以设备输入输出中断为例:

  • 打印机给 CPU 发中断信号
  • CPU 处理完当前指令后检测到终端,判断出中断来源并向相关设备发确认信号

CPU 开始为软件处理终端做准备:

  • CPU 状态被切换为内核态
  • 在系统栈中保存被中断程序的重要上下文环境,主要是程序计数器 PC,程序状态字 PSW

CPU 根据中断码查中断向量表,获得与该中断相关的处理程序的入口地址,并将 PC 设置成该地址,新的指令周期开始时,CPU 控制转移到中断处理程序

中断处理程序开始工作,在系统栈中保存现场信息

检查 I/O 设备的状态信息,操纵 I/O 设备或者在设备和内存之间传送数据等等

中断处理结束后,CPU 检测到中断返回指令,从系统栈中恢复被中断程序的上下文环境,CPU 状态恢复成原来的状态,PSW 和 PC 恢复成中断前的值,CPU 开始一个新的指令周期

第六节 指令流水线

讲在前面,为什么要引入指令流水线。相信都听说过华强北的流水线运作方式吧。最明显的优点就是相较于顺序执行的吞吐量更大(单位时间内)运行相同数量的指令也更快。效率也更高。

这里就可以知道指令流水线的概念:把指令执行过程划分为不同的阶段,占用不同的资源,就能使多条指令同时执行

①在流水执行的过程中,会经常遇到冲突,包括结构冲突,数据冲突,控制冲突。

结构相关/冲突/冒险 数据相关/冲突/冒险 控制相关/冲突/冒险
概念 多条指令在同一时刻争用同一资源 下一条指令会用到当前指令计算的结果 遇到执行转移、调用、返回导致PC中断
处理办法 1.单独设置数据存储器和指令存储器,使取数和取值操作在不同的存储器中进行 2.暂停时钟周期 1.暂停时钟周期 2.数据旁路技术 3.编译优化 1.早判断,早生成 2.预取转移成功和不成功两个控制流方向的目标指令 3.加快和提前形成条件码 4.提高转移方向的猜准率

五段式指令流水线(超重要)

img

顾名思义,五段分为IF(取值),ID(译码&取数),EX(执行),M(访存),WB(写回寄存器)

只有上一条指令进入ID段后,下一条指令才能开始IF段,否则会覆盖IF段锁存器的内容

考试中常见的五类指令:

Ⅰ、运算类指令的执行过程

  • IF:根据PC从指令Cache取指令至IF段的锁存器
  • ID:取出操作数至ID段的锁存器
  • EX:运算,将结果存入EX段锁存器
  • M:空段
  • WB:将运算结果写回指定的寄存器

Ⅱ、LOAD指令执行过程

作用:load指令可以完成将数据从存储器中复制到目的寄存器中,会访存

  • IF:根据PC从指令Cache取指令至IF段的锁存器
  • ID:将基址寄存器的值放到锁存器A,将偏移量的值放到lmm
  • EX:运算,得到有效地址
  • M:从数据Cache中取数并放入锁存器
  • WB:将运算结果写回指定的寄存器

Ⅲ、STORE指令执行过程

**作用:**将数据从寄存器中,复制到存储器中,会访存

  • IF:根据PC从指令Cache取指令至IF段的锁存器
  • ID:将基址寄存器的值放到锁存器A,将偏移量的值放到lmm。将要存的数放到B
  • EX:运算,得到有效地址。并将锁存器B的内容放到锁存器Store
  • M:写入数据Cache
  • WB:空段

Ⅳ、条件转移指令执行过程

  • IF:根据PC从指令Cache取指令至IF段的锁存器
  • ID:进行比较的两个数放入锁存器A,B;偏移量放入lmm
  • EX:运算,比较两个数
  • M:将目标PC值写回PC
  • WB:空段

Ⅴ、无条件转移指令的执行过程

  • IF:根据PC从指令Cache取指令至IF段的锁存器
  • ID:偏移量放入lmm
  • EX:将目标PC值写回PC
  • M:空段
  • WB:空段

针对条件转移指令和无条件转移指令做以下说明:写入PC的好事比EX更短,可以安排在EX段时间内完成。越早完成就越能避免控制冲突。当然也有在WB段修改PC值的

题目总结:

①流水CPU是由一系列叫做“段”的处理线路组成的。一个m段流水线稳定时的CPU的吞吐能力,与m个并行部件的CPU的吞吐能力相比具有相同的吞吐能力

原因是当流水线稳定后,说明已经进行了一条指令,往后每多一个时钟周期就多一条指令执行成功。

m个并行平均下来也是一个时针周期就多条指令

故具有相同的吞吐能力,但是流水线的方式,结构实现较为简单。

第七节 多处理器的基本概念(选择题)

这节的要求就是明白基本概念

①SISD(单指令流单数据流)

特点:只能并发,不能并行,每条指令处理一个指令

不是数据级并行技术

SISD(单指令流单数据流) SIMD(单指令多数据流) MIMD(多指令多数据流) 多处理器系统 多计算机系统
特点 不是数据级并行技术 是一种数据级并行技术 是一种线程级并行技术 多个处理器共享单一物理地址空间 每台计算机拥有私有存储器,相互独立
特征 一条指令处理一个数据 一条指令处理多个数据 多条指令处理多个数据 多个处理器+一个主存储器 多个处理器+多个主存储器
  • 并发(concurrency):把任务在不同的时间点交给处理器进行处理。在同一时间点,任务并不会同时运行。
  • 并行(parallelism):把每一个任务分配给每一个处理器独立完成。在同一时间点,任务一定是同时运行。

并发不是并行。并行是让不同的代码片段同时在不同的物理处理器上执行。并行的关键是同时做很多事情,而并发是指同时管理很多事情,这些事情可能只做了一半就被暂停去做别的事情了。(你学废了吗?)

7、总线与I/O子系统

主要讲授总线与接口,以及直接程序传送模式PIO、中断、DMA、IOP与PPU等。

第一节 总线的概述

总线的基本概念

总线是连接各个部件的信息传输线,是各个部件共享的传输介质。 总线结构的计算机举例: 单总线结构框图; 面向 CPU 的双总线结构框图; 以存储器为中心的双总线结构框图;

总线的分类

片内总线: 芯片内部 的总线;
系统总线: 计算机各部件之间 的信息传输线;

包括:

  • 数据总线:双向 与机器字长、存储字长有关;
  • 地址总线:单向 与存储地址、 I/O地址有关;
  • 控制总线:有出 有入;

通信总线: 用于 计算机系统之间 或 计算机系统与其他系统(如控制仪表、移动通信等)之间的通信。

传输方式:

  1. 串行通信总线;
  2. 并行通信总线;

总线特性:

  1. 机械特性:尺寸、形状、管脚数 及 排列顺序;
  2. 电气特性:传输方向 和有效的 电平 范围;
  3. 功能特性:每根传输线的 功能(地址、数据、控制);
  4. 时间特性:信号的 时序 关系;

总线的性能指标:

  1. 总线宽度:数据线的根数;
  2. 标准传输率:每秒传输的最大字节数(MBps);
  3. 时钟同步/异步:同步、不同步;
  4. 总线复用:地址线 与 数据线 复用;
  5. 信号线数:地址线、数据线和控制线的 总和;
  6. 总线控制方式:突发、自动、仲裁、逻辑、计数;
  7. 负载能力;

第二节 性能指标

CPU:吞吐量,响应时间,CPU时钟周期,主频,CPI,CPU执行时间,MIPS,MFLOPS,GFLOPS,TFLOPS,PFLOPS,利用率,处理机字长;

吞吐量

一个系统的吞度量(承压能力)与一个请求request对CPU的消耗、外部接口、IO等密切关联。单个reqeust 对CPU消耗越高,外部系统接口、IO影响速度越慢,系统吞吐能力越低,反之越高。

一般系统吞吐量由几个重要关键要素组成:

  • QPS: Queries Per Second 每秒内的查询率。它是指一台服务器每秒能够相应的查询次数,是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准。QPS统计方式,一般使用http_load 进行统计。
  • TPS: TransactionsPerSecond 每秒内的事务数。一个事务是指一个客户机向服务器发送请求然后等待服务器做出反应的过程。客户机在发送请求时开始计时,收到服务器响应后结束计时,以此来计算使用的时间和完成的事务个数,客户机使用加权协函数平均方法来计算得分,最终利用这些信息得出服务器端的整体TPS得分。
  • 并发数: 系统同时处理的request/事务数。
  • 响应时间: 系统平均响应时间。

计算关系

  • QPS = 并发量 / 平均响应时间
  • 并发量 = QPS * 平均响应时间

响应时间(RT)

响应时间是指系统对请求作出响应的时间。直观上看,这个指标与人对软件性能的主观感受是非常一致的,因为它完整地记录了整个计算机系统处理请求的时间。由于一个系统通常会提供许多功能,而不同功能的处理逻辑也千差万别,因而不同功能的响应时间也不尽相同,甚至同一功能在不同输入数据的情况下响应时间也不相同。所以,在讨论一个系统的响应时间时,人们通常是指该系统所有功能的平均时间或者所有功能的最大响应时间。当然,往往也需要对每个或每组功能讨论其平均响应时间和最大响应时间。

对于单机的没有并发操作的应用系统而言,人们普遍认为响应时间是一个合理且准确的性能指标。需要指出的是,响应时间的绝对值并不能直接反映软件的性能的高低,软件性能的高低实际上取决于用户对该响应时间的接受程度。对于一个游戏软件来说,响应时间小于100毫秒应该是不错的,响应时间在1秒左右可能属于勉强可以接受,如果响应时间达到3秒就完全难以接受了。而对于编译系统来说,完整编译一个较大规模软件的源代码可能需要几十分钟甚至更长时间,但这些响应时间对于用户来说都是可以接受的。

CPU时钟周期和主频

CPU时钟周期

时钟发生器发出的脉冲信号做出周期变化的最短时间称之为震荡周期,也称为 CPU 时钟周期。它是计算机中最基本的、最小的时间单位。每一次脉冲(即一个震荡周期)到来,芯片内的晶体管就改变一次状态,让整个芯片完成一定任务。一个震荡周期内,晶体管只会改变一次状态。由此,更小的时钟周期就意味着更高的工作频率。

主频

一秒(1 s)内,震荡周期的个数称为时钟频率,俗称主频。

主频和时钟周期的关系:

时钟频率(主频)=1CPU时钟周期时钟频率(主频)= \frac{1}{CPU 时钟周期}时钟频率(主频)=CPU时钟周期1。由上面的关系不难推出,主频越高,CPU的运算速度就越快。

CPI (每条指令执行平均时间)

CPI(Clock cycle Per Instruction)表示执行某个程序的指令平均时钟周期数,可以用来衡量计算机运行速度。

CPI=TCICCPI=\frac{TC }{IC}CPI=ICTC(IC[instruction counter]表示某个程序的所有指令的条数;TC表示执行某个程序所花费的时钟周期)

CPU执行时间

img

MIPS 每秒处理多少(百万级的指令条数)

MIPS(Million Instructions Per Second):单字长定点指令平均执行速度 Million Instructions Per Second的缩写,每秒处理的百万级的机器语言指令数。这是衡量CPU速度的一个指标。

FLOPS/MFLOPS/GFLOPS/TFLOPS/PFLOPS/EFLOPS

FLOPS是Floating-point Operations Per Second每秒所执行的浮点运算次数的英文缩写。它是一个衡量计算机计算能力的量,这个量经常使用在那些需要大量浮点运算的科学运算中。有时也会被记为flop/s。

MFLOPS(Million Floating-point Operations per Second,每秒百万个浮点操作),衡量计算机系统的技术指标,不能反映整体情况,只能反映浮点运算情况。

GFLOPS 就是 Giga Floating-point Operations Per Second,即每秒10亿次的浮点运算数,常作为GPU性能参数但不一定代表GPU的实际表现,因为还要考虑具体如何拆分多边形和像素、以及纹理填充,理论上该数值越高越好。1GFlops = 1,000MFlops。

MFLOPS(megaFLOPS)= 每秒一佰万(=106)次的浮点运算GFLOPS(gigaFLOPS) = 每秒拾亿(=109)次的浮点运算TFLOPS(teraFLOPS) = 每秒万亿(=1012)次的浮点运算PFLOPS(petaFLOPS) =每秒千万亿(=1015)次的浮点运算EFLOPS(exaFLOPS)=每秒百亿亿(=1018)次的浮点运算。\ MFLOPS(megaFLOPS)\quad=\ 每秒一佰万(=10^6)次的浮点运算\ GFLOPS(gigaFLOPS) \ \ \ \quad= \ 每秒拾亿(=10^9)次的浮点运算\ TFLOPS(teraFLOPS)\ \quad=\ 每秒万亿(=10^{12})次的浮点运算\ PFLOPS(petaFLOPS) \ \ \ \quad=每秒千万亿(=10^{15})次的浮点运算\ EFLOPS(exaFLOPS)\qquad=每秒百亿亿(=10^{18})次的浮点运算。MFLOPS(megaFLOPS)= 每秒一佰万(=106)次的浮点运算GFLOPS(gigaFLOPS) = 每秒拾亿(=109)次的浮点运算TFLOPS(teraFLOPS) = 每秒万亿(=1012)次的浮点运算PFLOPS(petaFLOPS) =每秒千万亿(=1015)次的浮点运算EFLOPS(exaFLOPS)=每秒百亿亿(=1018)次的浮点运算。

CPU利用率

CPU利用率,是对一个时间段内CPU使用状况的统计,通过这个指标可以看出在某一个时间段内CPU被占用的情况,如果CPU被占用时间很高,那么就需要考虑CPU是否已经处于超负荷运作,长期超负荷运作对于机器本身来说是一种损害,因此必须将CPU的利用率控制在一定的比例下,以保证机器的正常运作。

Load Average是 CPU的Load,它所包含的信息不是CPU的使用率状况,而是在一段时间内CPU正在处理以及等待CPU处理的进程数之和的统计信息,也就是CPU使用队列的长度的统计信息。

处理机字长

理机字长是指处理机能同时处理(或运算)的位数,即同时处理多少位(bit)数据。

第三节 总线定时方式

总线的一次信息传送过程,大致可分为如下五个阶段:请求总线,总线仲裁,寻址(目的地址),信息传送,状态返回(或错误报告)。为了同步主方、从方的操作,必须制订定时修定。所谓定时,是指事件出现在总线上的时序关系。下面介绍数据传送过程中采用的几种定时协定:同步定时协定、异步定时协定、半同步定时协定和周期分裂式总线协定。

总线传输的四个阶段:

  • 申请分配阶段。由需要使用总线的主模块(或主设备〉提出申请,经总线仲裁机构决定将下一传输周期的总线使用权授予某一申请者。也可将此阶段细分为传输请求和总线仲裁两个阶段。
  • 申请分配阶段。由需要使用总线的主模块(或主设备〉提出申请,经总线仲裁机构决定将下一传输周期的总线使用权授予某一申请者。也可将此阶段细分为传输请求和总线仲裁两个阶段。
  • 传输阶段。主模块和从模块进行数据交换,可单向或双向进行数据传送。结束阶段。主模块的有关信息均从系统总线上撤除,让出总线使用权。

同步定时方式

在同步定时协议中,事件出现在总线上的时刻由总线时钟信号来确定,所以总线中包含时钟信号线。一次VVO 传送被称为时钟周期或总线周期。读数据的同步时序例子,所有事件都出现在时钟信号的前沿,大多数事件只占据单一时钟周期。例如,在总线读周期,CPU 首先将存储器地址放到地址线上,它亦可发出一个启动信号,指明控制信息和地址信息已出现在总线上。第2个时钟周期发出一个读命令。存储器模块识别地址码,经一个时钟周期延迟(存取时间)后,将数据和认可信息放到总线上,被 CPU 读取。如果是总线写周期,CPU在第2个时钟周期开始将数据放到数据线上,待数据稳定后CPU发出一个写命令,存储器模块在第3 时钟周期存入数据。

  • 统一时钟
  • 传送速度快,总线控制逻辑简单
  • 不能及时进行数据通信的有效性检验,可靠性差

img

由于采用了公共时钟,每个功能模块什么时候发送或接收信息都由统一时钟规定,因此,同步定时具有较高的传输频率。同步定时适用于总线长度较短、各功能模块存取时间比较接近的情况。这是因为同步方式对任何两个功能模块的通信都给予同样的时间安排。由于同步总线必须按最慢的模块来设计公共时钟,当各功能模块存取时间相差很大时,会大大损失总线效率。

异步定时方式

在异步定时协议中,后一事件出现在总线上的时刻取决于前一事件的出现时刻,即建立在应答式或互锁机制基础上。在这种系统中,不需要统一的公共时钟信号。总线周期的长度是可变的。图 6.13(a)表示系统总线读周期时序图。CPU 发送地址信号和读状态信号到总线上。待这些信号稳定后,它发出读命令,指示有效地址和控制信号的出现。存储器模块进行地址译码并将数据放到数据线上。一旦数据线上的信号稳定,则存储器模块使确认线有效,通知CPU 数据可用。CPU 由数据线上读取数据后,立即撤销读状态信号,从而引起存储器模块撤销数据和确认信号。最后,确认信号的撤销又使 CPU撤销地址信息。

img

  • 完全依靠传送双方相互制约的”握手“信号来实现定时控制
  • 总线周期长度可变,保证两个工作速度相差较大的部件和设备之间可靠地进行信息交换
  • 复杂,慢

异步定时方式分为

  1. 不互锁方式:请求不回就撤回,回了请求自己撤
  2. 半互锁方式:请求回了才撤回,回了请求自己撤
  3. 全互锁方式:请求回了才撤回,回在请求之后撤

8、I/O设备及接口

主要讲授I/O设备的功能、类型及与主机之间的信息交互,键盘工作原理,显示器件,打印设备等。

第一节 I/O接口

I/O接口全称(Input/Output Interface),指输入/输出设备接口 。I/O接口的作用主机与外界交换信息称为输入/输出(I/O)。主机与外界的信息交换是通过输入/输出设备进行的。一般的输入/输出设备都是机械的或机电相结合的产物,比如常规的外设有键盘、显示器、打印机、扫描仪、磁盘机、鼠标器等,它们相对于高速的中央处理器来说,速度要慢得多。此外,不同外设的信号形式、数据格式也各不相同。

因此,外部设备不能与CPU直接相连,需要通过相应的电路来完成它们之间的速度匹配、信号转换,并完成某些控制功能。通常把介于主机和外设之间的一种缓冲电路称为I/O接口电路,简称I/O接口(Input/Output Interface)。

接口(Interface)

接口可以看做是两个系统或两个部件之间的交接部分,它既可以是两种硬设备之间的连接电路,也可以是两个软件之间的共同逻辑边界。I/O接口通常指主机与I/O设备之间设置的一个硬件电路及其相应的软件控制。不同的I/O设备都有与其对应的设备控制器,而它们往往都是通过I/O接口与主机取得联系的。

端口(Port)

端口是指接口电路中的一些寄存器,这些寄存器分别用来存放数据信息、控制信息和状态信息,相应的端口分别称为数据端口、控制端口和状态端口。若干个端口加上相应的控制逻辑才能组成接口。CPU通过输入指令,从端口读入信息,通过输出指令,可将信息写入到端口中。

I/O接口芯片

这些芯片大都是集成电路,通过CPU输入不同的命令和参数,并控制相关的I/O电路和简单的外设作相应的操作,常见的接口芯片如定时/计数器、中断控制器、DMA控制器、并行接口等。

I/O接口控制卡

有若干个集成电路按一定的逻辑组成为一个部件,或者直接与CPU同在主板上,或是一个插件插在系统总线插槽上。按照接口的连接对象来分,又可以将他们分为串行接口、并行接口、键盘接口和磁盘接口等。

接口的功能

由于计算机的外围设备品种繁多,几乎都采用了机电传动设备,因此,CPU在与I/O设备进行数据交换时存在以下问题:

  1. 速度不匹配:I/O设备的工作速度要比CPU慢许多,而且由于种类的不 同,他们之间的速度差异也很大,例如硬盘的传输速度就要比打印机快出很多。
  2. 时序不匹配:各个I/O设备都有自己的定时控制电路,以自己的速度传 输数据,无法与CPU的时序取得统一。
  3. 信息格式不匹配:不同的I/O设备存储和处理信息的格式不同,例如可以分为串行和并行两种;也可以分为二进制格式、ACSII编码和BCD编码等。
  4. 信息类型不匹配:不同I/O设备采用的信号类型不同,有些是数字信号,而 有些是模拟信号,因此所采用的处理方式也不同。

基于以上原因,CPU与外设之间的数据交换必须通过接口来完成,通常接口有以下一些功能:

  • (1)设置数据的寄存、缓冲逻辑,以适应CPU与外设之间的速度差异,接口通常由一些寄存器或RAM芯片组成,如果芯片足够大还可以实现批量数据的传输;
  • (2)能够进行信息格式的转换,例如串行和并行的转换;
  • (3)能够协调CPU和外设两者在信息的类型和电平的差异,如电平转换驱动器、数/模或模/数转换器等;
  • (4)协调时序差异;
  • (5)地址译码和设备选择功能;
  • (6)设置中断和DMA控制逻辑,以保证在中断和DMA允许的情况下产生中断和DMA请求信号,并在接受到中断和DMA应答之后完成中断处理和DMA传输。

接口的控制方式

CPU通过接口对外设进行控制的方式有以下几种:

(1)程序查询方式:这种方式下,CPU通过I/O指令询问指定外设当前的状态,如果外设准备就绪,则进行数据的输入或输出,否则CPU等待,循环查询。 这种方式的优点是结构简单,只需要少量的硬件电路即可,缺点是由于CPU的速度远远高于外设,因此通常处于等待状态,工作效率很低

(2)中断处理方式 :在这种方式下,CPU不再被动等待,而是可以执行其他程序,一旦外设为数据交换准备就绪,可以向CPU提出服务请求,CPU如果响应该请求,便暂时停止当前程序的执行,转去执行与该请求对应的服务程序,完成后,再继续执行原来被中断的程序。 中断处理方式的优点是显而易见的,它不但为CPU省去了查询外设状态和等待外设就绪所花费的时间,提高了CPU的工作效率,还满足了外设的实时要求。但需要为每个I/O设备分配一个中断请求号和相应的中断服务程序,此外还需要一个中断控制器(I/O接口芯片)管理I/O设备提出的中断请求,例如设置中断屏蔽、中断请求优先级等。

此外,中断处理方式的缺点是每传送一个字符都要进行中断,启动中断控制器,还要保留和恢复现场以便能继续原程序的执行,花费的工作量很大,这样如果需要大量数据交换,系统的性能会很低。

(3)DMA(直接存储器存取)传送方式:DMA最明显的一个特点是它不是用软件而是采用一个专门的控制器来控制内存与外设之间的数据交流,无须CPU介入,大大提高CPU的工作效率。

在进行DMA数据传送之前,DMA控制器会向CPU申请总线控制 权,CPU如果允许,则将控制权交出,因此,在数据交换时,总线控制权由DMA控制器掌握,在传输结束后,DMA控制器将总线控制权交还给CPU

每个接口芯片有一个或者几个端口,每个端口对应芯片内部的一个或者一组寄存器,计算机为每个端口分配唯一的访问地址。

常见接口

**并行接口:**目前,计算机中的并行接口主要作为打印机端口,接口使用的不再是36针接头而是25针D形接头。所谓“并行”,是指8位数据同时通过并行线进行传送,这样数据传送速度大大提高,但并行传送的线路长度受到限制,因为长度增加,干扰就会增加,容易出错。

现在有五种常见的并口:4位、8位、半8位、EPP和ECP,大多数PC机配有4位或8位的并口,许多利用Intel386芯片组的便携机配有EPP口,支持全部IEEE1284并口规格的计算机配有ECP并口。

标准并行口4位、8位、半8位:4位口一次只能输入4位数据,但可以输出8位数据;8位口可以一次输入和输出8位数据;半8位也可以。EPP口(增强并行口):由Intel等公司开发,允许8位双向数据传送,可以连接各种非打印机设备,如扫描仪、LAN适配器、磁盘驱动器和CDROM 驱动器等。ECP口(扩展并行口):由Microsoft、HP公司开发,能支持命令周期、数据周期和多个逻辑设备寻址,在多任务环境下可以使用DMA(直接存储器 访问)。

目前几乎所有的586机的主板都集成了并行口插座,标注为 Paralle1或LPT1,是一个26针的双排针插座。

**串行接口:**计算机的另一种标准接口是串行口,现在的PC机一般至少有两个串行口COM1和COM2。串行口不同于并行口之处在于它的数据和控制信息是一位接一位串行地传送下去。这样,虽然速度会慢一些,但传送距离较并行口更长,因此长距离的通信应使用串行口。通常COM1使用的是9针D形连接器,而COM2有些使 用的是老式的DB25针连接器。

磁盘接口

IDE接口:IDE接口也叫做ATA端口,只可以接两个容量不超过528M的硬盘驱动器,接口的成本很低,因此在386、486时期非常流行。但大多数IDE接口不支持DMA数据传送,只能使用标准的PCI/O端口指令来传送所有的命令、状态、数据。几乎所有的586主板上都集成了两个40针的双排针IDE接口插座,分别标注为IDE1和IDE2。

EIDE接口:EIDE接口较IDE接口有了很大改进,是目前最流行的接口。

首先,它所支持的外设不再是2个而是4个了,所支持的设备除了硬盘,还包括CD-ROM驱动器磁盘备份设备等。

其次,EIDE标准取消了528MB的限制,代之以8GP限制。

第三,EIDE有更高的数据传送速率,支持PIO模式3和模式4标准。

**SCSI接口:**SCSI(SmallComputerSystemInterface)小计算机系统接口,在做图形处理和网络服务的计算机中被广泛采用SCSI接口的硬盘。除了硬盘以外,SCSI接口还可以连接CD-ROM驱动器、扫描仪和打印机等。

它具有以下特点:

  • 可同时连接7个外设;
  • 总线配置为并行8位、16位或32位;
  • 允许最大硬盘空间为8.4GB(有些已达到9.09GB);
  • 更高的数据传输速率,IDE是2MB每秒,SCSI通常可以达到5MB每秒,FASTSCSI(SCSI-2)能达到10MB每秒,最新的SCSI-3甚至能够达到40MB每秒,而EIDE最高只能达到16.6MB每秒;
  • 成本较IDE和EIDE接口高很多,而且,SCSI接口硬盘必须和SCSI接口卡配合使用,SCSI接口卡也比IED和EIDE接口贵很多。
  • SCSI接口是智能化的,可以彼此通信而不增加CPU的负担。在IDE和EIDE设备之间传输数据时,CPU必须介入,而SCSI设备在数据传输过程中起主动作用,并能在SCSI总线内部具体执行,直至完成再通知CPU。

**USB接口:**最新的USB串行接口标准是由Microsoft、Intel、Compaq、IBM等大公司共同推出,它提供机箱外的热即插即用连接,用户在连接外设时不用再打开机箱、关闭电源,而是采用“级联”方式,每个USB设备用一个USB插头连接到一个外设的USB插座上,而其本身又提供一个USB插座给下一个USB设备使用,通过 这种方式的连接,一个USB控制器可以连接多达127个外设,而每个外设间的距离可达5米。USB统一的4针圆形插头将取代机箱后的众多的串/并口(鼠标、MODEM)键盘等插头。USB能智能识别USB链上外围设备的插入或拆卸。除了能够连接键盘、鼠标等,USB还可以连接ISDN、电话系统、数字音响、打印机以及扫描仪等低速外设。

I/O扩展槽

I/O扩展槽即I/O信号传输的路径,是系统总线的延伸,可以插入任意的标准选件,如显示卡、解压卡、MODEM卡和声卡等。通过I/O扩展槽,CPU可对连接到该通道的所有I/O接口芯片和控制卡寻址访问,进行读写。

根据总线的类型不同,主板上的扩展槽可分为ISA、EISA、MAC、VESA和PCI几种。

  • ISA插槽:黑色,分为8位、16位两种。16位的扩展槽可以插8位和16位的控制卡,但8位的扩展槽只能插8位卡。
  • EISA插槽:棕色,外型、长度与16位的ISA卡一样,但深度较大,可插入ISA与EISA控制卡。
  • VESA插槽:棕色,位于16位ISA扩展插槽的下方,与ISA插槽配合使用。
  • PCI插槽:白色,与VESA插槽一样长,与ISA插槽平行,不需要与ISA插槽配合使用,而且只能插入PCI控制卡。由于主板的空间有限,PCI插槽要占用ISA插槽的位置

第二节 主机访问I/O设备的控制

备管理的主要任务之一是控制设备和内存或处理机之间的数据传送,外围设备和内存之间的输入/输出控制方式有四种,下面分别介绍。

程序直接控制方式

在早期的计算机中,由于*无中断机构*,处理机对I/O设备的控制采用程序直接控制方式,或称为忙-等待方式。
如图(a)所示,计算机从外部设备读取数据到存储器,每次读一个字的数据。对读入的每个字,CPU需要对外设状态进行循环检查,直到确定该字已经在I/O控制器的数据寄存器中。由于CPU的高速性和I/O设备的低速性,致使CPU的绝大部分时间都处于等待I/O设备完成数据I/O的循环测试中,造成了 CPU资源的极大浪费。
程序直接控制方式虽然简单易于实现,但是其缺点也是显而易见的,由于CPU和I/O设备只能串行工作,导致CPU的利用率相当低。

中断驱动方式

中断驱动方式的思想是,*允许I/O设备主动打断CPU的运行并请求服务*,从而“解放”CPU,使得其向I/O控制器发送读命令后可以继续做其他有用的工作,CPU与I/O可以并行操作。

如图(b)所示:从I/O控制器的角度来看,I/O控制器从CPU接收一个读命令,然后从外围设备读数据。一旦数据读入到该I/O控制器的数据寄存器,便通过控制线给CPU发出一个中断信号,表示数据已准备好,然后等待CPU请求该数据。I/O控制器收到CPU发出的取数据请求后,将数据放到数据总线上,传到CPU的寄存器中。至此,本次I/O操作完成,I/O控制器又可幵始下一次I/O操作。

从CPU的角度来看,CPU发出读命令,然后保存当前运行程序的上下文(现场,包括程序计数器及处理机寄存器),转去执行其他程序。*在每个指令周期的末尾,CPU检查中断*。当有来自I/O控制器的中断时,CPU保存当前正在运行程序的上下文,转去执行中断处理程序处理该中断。这时,CPU从I/O控制器读一个字的数据传送到寄存器,并存入主存。接着, CPU恢复发出I/O命令的程序(或其他程序)的上下文,然后继续运行。
中断驱动方式比程序直接控制方式有效,但由于数据中的每个字在存储器与I/O控制器之间的传输都必须经过CPU,这就导致了中断驱动方式仍然会消耗较多的CPU时间。

img

DMA方式

在中断驱动方式中,I/O设备与内存之间的数据交换必须要经过CPU中的寄存器,所以速度还是受限,而DMA(直接存储器存取)方式的基本思想是在*I/O设备和内存之间开辟直接的数据交换通路*,彻底“解放” CPU。

DMA方式的特点是:

  • 基本单位是数据块。
  • 所传送的数据,是从设备直接送入内存的,或者相反。
  • 仅在传送一个或多个数据块的开始和结束时,才需CPU干预,整块数据的传送是在 DMA控制器的控制下完成的。

下图列出了DMA控制器的组成:

img

为了实现在主机与控制器之间成块数据的直接交换,必须在DMA控制器中设置如下四类寄存器:

  • **命令/状态寄存器(CR):**用于接收从CPU发来的I/O命令或有关控制信息,或设备的状态。
  • **内存地址寄存器(MAR:**在输入时,它存放把数据从设备传送到内存的起始目标地址;在输出时,它存放由内存到设备的内存源地址。
  • **数据寄存器(DR):**用于暂存从设备到内存,或从内存到设备的数据。
  • **数据计数器(DC):**存放本次CPU要读或写的字(节)数。

如图©所示,DMA方式的工作过程是:CPU读写数据时,它给I/O控制器发出一条命令,启动DMA控制器,然后继续其他工作。之后CPU就把控制操作委托给DMA控制器,由该控制器负责处理。DMA控制器直接与存储器交互,传送整个数据块,每次传送一个字,这个过程不需要CPU参与。当传送完成后,DMA控制器发送一个中断信号给处理器。因此只有在传送开始和结束时才需要CPU的参与。

DMA控制方式与中断驱动方式的主要区别是中断驱动方式在每个数据需要传输时中断CPU,而DMA控制方式则是在所要求传送的一批数据全部传送结束时才中断CPU;此外,中断驱动方式数据传送是在中断处理时由CPU控制完成的,而DMA控制方式则是在DMA 控制器的控制下完成的。

通道控制方式

I/O通道是指专门负责输入/输出的处理机。I/O通道方式是DMA方式的发展,它可以进一步减少CPU的干预,即把对一个数据块的读(或写)为单位的干预,减少为对一组数据块的读(或写)及有关的控制和管理为单位的干预。同时,又可以实现CPU、通道和I/O设备三者的并行操作,从而更有效地提高整个系统的资源利用率。
例如,当CPU要完成一组相关的读(或写)操作及有关控制时,只需向I/O通道发送一条I/O指令,以给出其所要执行的通道程序的首地址和要访问的I/O设备,通道接到该指令后,通过执行通道程序便可完成CPU指定的I/O任务,数据传送结束时向CPU发中断请求。I/O通道与一般处理机的区别是:通道指令的类型单一,没有自己的内存,通道所执行的通道程序是放在主机的内存中的,也就是说通道与CPU共享内存。

I/O通道与DMA方式的区别是:

  • DMA方式需要CPU来控制传输的数据块大小、传输的内存位置,而通道方式中这些信息是由通道控制的。*
  • 每个DMA控制器对应一台设备与内存传递数据,而一个通道可以控制多台设备与内存的数据交换。

版权声明:本文为知乎博主「玩转Linux内核」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
I/O扩展槽

I/O扩展槽即I/O信号传输的路径,是系统总线的延伸,可以插入任意的标准选件,如显示卡、解压卡、MODEM卡和声卡等。通过I/O扩展槽,CPU可对连接到该通道的所有I/O接口芯片和控制卡寻址访问,进行读写。

根据总线的类型不同,主板上的扩展槽可分为ISA、EISA、MAC、VESA和PCI几种。

  • ISA插槽:黑色,分为8位、16位两种。16位的扩展槽可以插8位和16位的控制卡,但8位的扩展槽只能插8位卡。
  • EISA插槽:棕色,外型、长度与16位的ISA卡一样,但深度较大,可插入ISA与EISA控制卡。
  • VESA插槽:棕色,位于16位ISA扩展插槽的下方,与ISA插槽配合使用。
  • PCI插槽:白色,与VESA插槽一样长,与ISA插槽平行,不需要与ISA插槽配合使用,而且只能插入PCI控制卡。由于主板的空间有限,PCI插槽要占用ISA插槽的位置

第二节 主机访问I/O设备的控制

备管理的主要任务之一是控制设备和内存或处理机之间的数据传送,外围设备和内存之间的输入/输出控制方式有四种,下面分别介绍。

程序直接控制方式

在早期的计算机中,由于*无中断机构*,处理机对I/O设备的控制采用程序直接控制方式,或称为忙-等待方式。
如图(a)所示,计算机从外部设备读取数据到存储器,每次读一个字的数据。对读入的每个字,CPU需要对外设状态进行循环检查,直到确定该字已经在I/O控制器的数据寄存器中。由于CPU的高速性和I/O设备的低速性,致使CPU的绝大部分时间都处于等待I/O设备完成数据I/O的循环测试中,造成了 CPU资源的极大浪费。
程序直接控制方式虽然简单易于实现,但是其缺点也是显而易见的,由于CPU和I/O设备只能串行工作,导致CPU的利用率相当低。

中断驱动方式

中断驱动方式的思想是,*允许I/O设备主动打断CPU的运行并请求服务*,从而“解放”CPU,使得其向I/O控制器发送读命令后可以继续做其他有用的工作,CPU与I/O可以并行操作。

如图(b)所示:从I/O控制器的角度来看,I/O控制器从CPU接收一个读命令,然后从外围设备读数据。一旦数据读入到该I/O控制器的数据寄存器,便通过控制线给CPU发出一个中断信号,表示数据已准备好,然后等待CPU请求该数据。I/O控制器收到CPU发出的取数据请求后,将数据放到数据总线上,传到CPU的寄存器中。至此,本次I/O操作完成,I/O控制器又可幵始下一次I/O操作。

从CPU的角度来看,CPU发出读命令,然后保存当前运行程序的上下文(现场,包括程序计数器及处理机寄存器),转去执行其他程序。*在每个指令周期的末尾,CPU检查中断*。当有来自I/O控制器的中断时,CPU保存当前正在运行程序的上下文,转去执行中断处理程序处理该中断。这时,CPU从I/O控制器读一个字的数据传送到寄存器,并存入主存。接着, CPU恢复发出I/O命令的程序(或其他程序)的上下文,然后继续运行。
中断驱动方式比程序直接控制方式有效,但由于数据中的每个字在存储器与I/O控制器之间的传输都必须经过CPU,这就导致了中断驱动方式仍然会消耗较多的CPU时间。

[外链图片转存中…(img-gNxAKoeR-1688132657907)]

DMA方式

在中断驱动方式中,I/O设备与内存之间的数据交换必须要经过CPU中的寄存器,所以速度还是受限,而DMA(直接存储器存取)方式的基本思想是在*I/O设备和内存之间开辟直接的数据交换通路*,彻底“解放” CPU。

DMA方式的特点是:

  • 基本单位是数据块。
  • 所传送的数据,是从设备直接送入内存的,或者相反。
  • 仅在传送一个或多个数据块的开始和结束时,才需CPU干预,整块数据的传送是在 DMA控制器的控制下完成的。

下图列出了DMA控制器的组成:

[外链图片转存中…(img-54yx1mNg-1688132657907)]

为了实现在主机与控制器之间成块数据的直接交换,必须在DMA控制器中设置如下四类寄存器:

  • **命令/状态寄存器(CR):**用于接收从CPU发来的I/O命令或有关控制信息,或设备的状态。
  • **内存地址寄存器(MAR:**在输入时,它存放把数据从设备传送到内存的起始目标地址;在输出时,它存放由内存到设备的内存源地址。
  • **数据寄存器(DR):**用于暂存从设备到内存,或从内存到设备的数据。
  • **数据计数器(DC):**存放本次CPU要读或写的字(节)数。

如图©所示,DMA方式的工作过程是:CPU读写数据时,它给I/O控制器发出一条命令,启动DMA控制器,然后继续其他工作。之后CPU就把控制操作委托给DMA控制器,由该控制器负责处理。DMA控制器直接与存储器交互,传送整个数据块,每次传送一个字,这个过程不需要CPU参与。当传送完成后,DMA控制器发送一个中断信号给处理器。因此只有在传送开始和结束时才需要CPU的参与。

DMA控制方式与中断驱动方式的主要区别是中断驱动方式在每个数据需要传输时中断CPU,而DMA控制方式则是在所要求传送的一批数据全部传送结束时才中断CPU;此外,中断驱动方式数据传送是在中断处理时由CPU控制完成的,而DMA控制方式则是在DMA 控制器的控制下完成的。

通道控制方式

I/O通道是指专门负责输入/输出的处理机。I/O通道方式是DMA方式的发展,它可以进一步减少CPU的干预,即把对一个数据块的读(或写)为单位的干预,减少为对一组数据块的读(或写)及有关的控制和管理为单位的干预。同时,又可以实现CPU、通道和I/O设备三者的并行操作,从而更有效地提高整个系统的资源利用率。
例如,当CPU要完成一组相关的读(或写)操作及有关控制时,只需向I/O通道发送一条I/O指令,以给出其所要执行的通道程序的首地址和要访问的I/O设备,通道接到该指令后,通过执行通道程序便可完成CPU指定的I/O任务,数据传送结束时向CPU发中断请求。I/O通道与一般处理机的区别是:通道指令的类型单一,没有自己的内存,通道所执行的通道程序是放在主机的内存中的,也就是说通道与CPU共享内存。

I/O通道与DMA方式的区别是:

  • DMA方式需要CPU来控制传输的数据块大小、传输的内存位置,而通道方式中这些信息是由通道控制的。*
  • 每个DMA控制器对应一台设备与内存传递数据,而一个通道可以控制多台设备与内存的数据交换。

版权声明:本文为知乎博主「玩转Linux内核」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://zhuanlan.zhihu.com/p/636123406

猜你喜欢

转载自blog.csdn.net/m0_50662680/article/details/131484162
今日推荐