读书笔记–深入理解计算机系统chap1-4

第一章 计算机系统漫游

1.信息就是位+上下文
源程序(0、1位序列;8位一组称为字节)–每个字节表示程序中某个文本字符
大部分的现代系统都是用ASCII标准来表示文本字符,这种方式实际上就是用一个唯一的单字节大小的整数值来表示每个字符
每个文本行都以一个不可见的换行符’\n’来结束的,对应的整数值为10
系统中所有的信息——包括磁盘文件、存储器重的程序、存储器中存放的用户数据以及网络上传送的数据,都是由一串位表示的。区分不同数据对象的唯一方法是我们读到这些数据对象时的上下文。

2.程序被其他程序翻译成不同的格式
例如,gcc编译器驱动程序的翻译过程可以分为4个过程:
-hello.c源程序(文本)–>预处理器(cpp)-hello.i被修改的源程序(文本)–>编译器(ccl)-hello.s汇编程序(文本)–>汇编器(as)-hello.o可重定位目标程序(二进制)+外部printf.o–>链接器(ld)-hello可执行目标程序(二进制)–>
汇编语言非常有用,它为不同高级语言的不同编译器提供了通用的输出语言。

3.了解编译系统如何工作是大有益处的
4.处理器读并解释存储在存储器中的指令
外壳(shell)是一个命令行解释器,输出一个提示符,等待你输入一个命令,然后执行这个命令。
通常总线被设计成传送定长的字节快,就是字(word)。
每个IO设备都通过一个控制器适配器与IO总线相连。控制器和适配器之间的区别主要在于它们的封装方式。控制器是置于IO设备本身的或者系统的主印制电路板(主板)上的芯片组,而适配器则是一块插在主板插槽上的卡。
主存是一个临时存储设备,在处理器执行程序时,用来存放程序和程序处理的数据。从物理上来说,主存是一组动态随机存储器(DRAM)芯片组成的;从逻辑上来说,存储器是一个线性的字节数组,每个字节都有其唯一的地址(即数组索引),这些地址是从零开始的。
中央处理单元(CPU),简称处理器,是解释在主存中指令的引擎。处理器的核心是一个字长的存储设备(或寄存器),成为程序计数器(PC)。在任何时刻,PC都指向贮存中的某条机器语言指令(即含有该条指令的地址)。
CPU在指令的要求下可能会执行以下操作:
加载:把一个字节或者一个字从主存复制到寄存器,以覆盖寄存器原来的内容
存储:把一个字节或者一个字复制到主存的某个位置,以覆盖这个位置上原来的内容
操作:把两个寄存器的内容复制到ALU,ALU对这两个字做算数操作,并将结果存放到一个寄存器中,以覆盖该寄存器原来的内容
跳转:从指令本身中抽取一个字,并将这个字复制到PC中,以覆盖PC中原来的值
Q:寄存器多大?
一个典型的寄存器文件只存储几百字节的信息,而主存里可以存放几十亿字节。然而,处理器从寄存器文件中读数据比从主存中读取几乎快100倍,差距还在持续增大。

5.高速缓存至关重要
L1、L2高速缓存用一种叫做静态随机访问存储器(SRAM)的硬件技术实现。利用了局部性原理,即程序具有访问局部区域里的数据和代码的趋势。

6.存储设备形成层次结构

7.操作系统管理硬件
(1)进程–是操作系统对一个正在运行的程序的一种抽象。在一个系统上可以同时运行多个进程,而每个进程都好像在独占地使用硬件。而并发运行,则是说一个进程的指令和另一个进程的指令是交错执行的。无论是在单核还是多核系统中,一个CPU看上去都像是在并发地执行多个程序,这是通过处理器在进程间切换来实现的。OS实现这种交错执行的机制成为上下文切换
(2)线程–尽管通常我们认为一个进程只有单一的控制流,但是在现代系统中,一个进程实际上可以由多个称为线程的执行单元组成,每个线程都运行在进程的上下文中,并共享同样的代码和全局数据。
由于网络服务器对并行处理的需求,线程称为越来越重要的编程模型,因为多线程之间比多进程之间更容易共享数据,也因为线程一般来说都比进程更高效。当有多处理器可用的时候,多线程也是一种使程序可以更快运行的方法。
多处理器VS多核。一个cpu上多个核心,多处理器多个cpu。
(3)虚拟存储器–抽象概念,为每个进程提供了一个假象,即每个进程都在独占地使用主存。每个进程看到的是一致的存储器,称为虚拟地址空间。
地址从低到高:
程序代码和数据
共享库
内核虚拟存储器
(4)文件–字节序列。每个IO设备,包括磁盘、键盘、显示器、甚至网络,都可以视为文件。

8.系统之间利用网络通信

9.重要主题
线程级并发:
多核处理器是将多个CPU(称为“核”)集成到一个集成电路芯片上。
超线程,有时候称为同时多线程,是一项允许一个CPU执行多个控制流的技术。
指令级并行:
在较低的抽象层次上,现代处理器可以同时执行多条指令的属性称为指令级并行。
单指令、多数据并行:
SIMD并行,在最低层次上,许多现代处理器拥有特殊的硬件,允许一条指令产生多个可以并行执行的操作。
计算机系统中抽象的重要性:
文件是对IO的抽象,虚拟存储器是对程序存储器的抽象(主存+IO),进程是对一个正在运行的程序的抽象(处理器(指令级结构)+虚拟存储器),虚拟机是对整个计算机的抽象(操作系统、处理器和程序)。

第二章 信息的表示和处理

1.信息存储

大多数计算机使用8位的块,或者字节,作为最小的可寻址的存储器单元,而不是在存储器中访问单独的位。
程序级程序将存储器视为一个非常大的字节数组,称为虚拟存储器(virtual memory)
存储器的每个字节都由一个唯一的数字来标识,称为它的的地址(address),所有可能地址的集合称为虚拟地址空间(virtual address space)这个虚拟地址空间只是一个展现给机器级程序的概念性映像。实际的实现是将随机访问存储器(RAM)、磁盘存储器、特殊硬件和操作系统软件结合起来,为程序提供一个看上去统一的字节数组。
C中的指针有两个方面:值和类型。它的值表示某个对象的位置,而它的类型表示那个位置上所存储对象的类型。
十六进制:在C中,以0x或者0X开头的数字常量被认为是十六进制的值。用十六机制书写,一个字节的值域为00~FF。字符A-F既可以大写,也可以小写,甚至大小写混合。
A-10  B-11  C-12  D-13  E-14  F-15
字:指明整数和指针数据的标称大小。因为虚拟地址是以这样的一个字来编码的,所以字长决定的最重要的系统参数就是虚拟地址空间的最大大小。也就是说,对已个字长为w位的机器而言,虚拟地址的范围为0-2w-1,程序最多方位2w个字节。
大多数计算机字长32位,这就限定了虚拟地址空间为4千兆字节(4GB),也就是刚刚超过4X10(9)字节。
寻址和字节顺序:对于跨越多字节的程序对象,我们必须建立两个规则:这个对象的地址是什么?以及在存储器重如何排列这些字节?
在几乎所有的机器上,多字节对象都被存储为连续的字节序列,对象的地址为所使用字节中最小的地址。eg.int类型的变量x的地址为0x100,则&x的值就为0x100,那么x的4个字节将被存储在存储器的0x100、0x101、0x102和0x103。
某些机器选择在存储器中按照从最低有效字节到最高有效字节的顺序存储对象,而另一些机器则按照从最高有效字节到最低有效字节的顺序存储。最低有效字节在最前的方式叫做小端法(little endian),大多数Intel兼容机采用这种规则;最高有效字节在最前面的方式叫做大端法(big endian),大多数IBM和Sun Microsystems的机器采用。也有混合的双端法,可以进行配置。
反汇编是一种确定可执行程序文件所表示的指令序列的工具。
尽管浮点数和整型数据都是对数值12345编码,但是它们有非常不同的字节模式:整型为0x00003039,浮点数为0x4640E400。一般而言,这两种格式使用不同的编码方法。
表示字符串:C语言中字符串被编码为一个以null(其值为0)字符结尾的字符数组。
C语言中的移位运算:对于无符号数据,右移必须是逻辑的,对于有符号数据,算数的或者逻辑的右移都可以。算数右移时,若最高位是1,填充的就是1。

第三章 程序的机器级表示 

用高级语言编写的程序可以在很多不同的机器上编译和执行,而汇编代码则是与特定机器密切相关的。
2.程序编码
unix>gcc -o1 -o p p1.c p2.c
-o1:告诉编译器使用第一级优化;通常,提高优化级别会使最终程序运行得更快,但是编译时间可能会变长,用调试工具对代码进行调试会更困难。正如我们会看到的,使用更高级别的优化产生的代码会严重改变形式,以至于产生的机器代码和出事源代码之间的关系非常难以理解。因此我们会使用第一级优化作为学习工具,然后当我们增加优化级别时,在看会发生什么。实际中,从得到的程序性能方面考虑,第二级优化被认为是较好的选择。
机器级代码:
对于机器级编程来说,其中两种抽象很重要,第一种是机器级程序的格式和行为,定义为指令集体系结构(Instruction set architecture,ISA),它定义了处理器状态、指令的格式,以及每条指令对状态的影响。第二种是机器级程序使用的存储器地址是虚拟地址,提供的存储器模型看上去是一个非常大的字节数组。存储器系统的实际实现是将多个硬件存储器和操作系统软件组合起来。
一些通常对C语言程序员隐藏的处理器状态是可见的:
程序计数器指示将要执行的下一条指令在存储器中的地址;
整数寄存器文件包含8个命名的位置,分别存储32位的值,这些存储器可以存储地址(对应于C的指针)或整数数据。有的寄存器被用来记录某些重要的程序状态,而其他则用来保存临时数据,例如过程的局部变量和函数的返回值;
条件码寄存器保存着最近执行的算术或逻辑指令的状态信息。它们用来实现控制或数据流中的条件变化,比如用来实现if和while语句;
一组浮点寄存器存放浮点数据。
程序存储器(program memory)包含:程序的可执行机器代码,操作系统需要的一些信息,用来管理过程调用和返回的运行时栈,以及用户分配的存储器快(比如用malloc库函数分配的)。程序存储器用虚拟地址来寻址,操作系统负责管理虚拟地址空间,将虚拟地址翻译成实际处理器存储器中的物理地址。
int accum = 0;
2
int sum(int x, int y)
{
int t = x + y;
accum += t;
return t;
}
unix> gcc -O2 -S code.c 
C语言编译器产生的汇编代码
sum:
pushl %ebp
movl %esp,%ebp
movl 12(%ebp),%eax
addl 8(%ebp),%eax
addl %eax,accum
movl %ebp,%esp
popl %ebp
ret
unix> gcc -O2 -c code.c–GCC会编译并汇编该代码产生目标代码文件code.o,二进制形式,无法直接查看。选17个字节序列,十六进制表示:
……55 89 e5 8b 45 0c 03 45 08 01 05 00 00 00 00 89 ec 5d c3……
(gdb) x/19xb sum
如何找到程序的字节表示?
要查看目标代码文件的内容,最优价值的是反汇编器。这些程序根据目标代码产生一种类似汇编代码的格式。
反汇编只是基于机器代码文件中的字节序列来确定汇编代码,不需要访问程序的源代码或汇编代码;
设计指令格式的方式是,从某个给定位置开始,可以将字节唯一地解码成机器指令。例如,只有指令pushl %ebp是以字节值55开头的。
生成实际可执行的代码需要对一组目标代码文件运行链接器,链接器将代码的地址移到了一段不同的地址范围中,并确定了存储全局变量的地址。P109
3.数据格式
4.访问信息
一个IA32中央处理单元(CPU)包含一组8个存储32位值的寄存器。
如图,字节操作指令可以独立地读或者写前四个寄存器的2个低位字节。
操作数指示符
源数据值可以以常数的形式给出,或是从寄存器或存储器中读出,结果可以存放在寄存器或存储器中。
因此,各种不同的操作数的可能性被分为三种类型:
1)立即数(immediate),也就是常数值。用$后面跟一个标准C表示法表示的整数,例如$-377或$0x1F。任何能放入32位的字里的数值都可以用作立即数,不过汇编器在可能时会使用一个或两个字节的编码。(会拆开存在不同的寄存器中?)
2)寄存器(regiser),表示某个寄存器的内容。Ea表示任意寄存器a,Reg[Ea]表示它的值,这是将寄存器集合看成一个数组Reg,用寄存器标识符作为索引。
3)存储器(memory)引用,它会根据计算出来的地址(通常称为有效地址)访问某个存储器位置。Mb[Addr]表示对存储在存储器中从地址Addr开始的b个字节值的引用。
寻址模式
The scaling factor s must be either 1, 2, 4, or 8.

Assume initially that %dh = 8D, %eax = 98765432
1 movb %dh,%al %eax = 9876548D
2 movsbl %dh,%eax %eax = FFFFFF8D
3 movzbl %dh,%eax %eax = 0000008D
例子中都是将寄存器%eax的低位字节设置成%edx的第二个字节。
movb指令不改变其他三个字节,movsbl指令将其他三个字节设为全1或全0,movzbl指令无论如何都是将其他三个字节设为全0.

栈操作说明,根据惯例,我们的栈是倒过来画的,因而栈“顶”在底部,IA32的栈向低地址方向增长,所以压栈是减小栈指针(%esp)的值,并将数据存放到存储器中,而出战是从存储器中读,并增加栈指针的值。
程序栈存放在存储器中的某个区域。栈指针%esp保存着栈顶元素的地址。
int exchange(int *xp,int y)
{
   int x = *xp;
   *xp = y;
   return x;
}
xp at %ebp+8,y at %ebp+12
1. movl 8(%ebp),%edx     Get xp
By copying to %eax below,x becomes the return value
2. movl (%edx),%eax      Get x at xp
3. movl 12(%ebp),%ecx    Get y
4. movl %ecx,(%edx)      Store y at xp
5.算术和逻辑操作
1)加载有效地址(load effective address)
指令leal实际上是movl指令的变形。它的指令形式是从存储器读数据到寄存器,但实际上它根本就没有引用存储器。
它的第一个操作数看上去是一个存储器引用,但该指令并不是从指定的位置读入数据,而是将有效地址写入到目的操作数。
leal S, D   D <–&S Load Effective Address
For example, if register %edx contains value x, then the instruction leal 7(%edx,%edx,4), %eax will set register %eax to 5x + 7. The destination operand must be a register.
AT&T语法,base(offset, index, i),就是 base+offset+index*i
2)一元操作和二元操作
一元操作只有一个操作数,既是源又是目的;
二元操作,源操作数是第一个,目的操作数是第二个,subl %eax,%edx是使寄存器%edx的值减去%eax中的值。第一个操作数可以是立即数、寄存器或是存储器位置,第二个操作数可以是寄存器或是存储器位置,不过同movl指令一样,两个操作数不能同时是存储器位置。
3)移位操作
先给出移位量,然后第二项给出的是要移位的数值。它可以进行算术和逻辑右移。移位量用单个字节编码,因为只允许进行0到31位的移位(只考虑移位量的低5位)。移位量可以是一个立即数,或者存放在单字节寄存器元素%cl中。(这些指令很特别,因为只允许以这个特定的寄存器作为操作数)。
左移指令:SAL和SHL,两者效果是一样的,都是将右边填上0.
右移指令:SAR执行算术移位(填上符号位)>>A,SHR执行逻辑移位>>L(填上0)。
6.控制
条件码(condition code)寄存器描述了最近的算术或逻辑操作的属性,可以检测这些寄存器来执行条件分支指令。
最常用的条件码有:
CF:进位标志。最近的操作使最高位产生了进位。可以用来检查无符号操作数的溢出。
ZF:零标志。最近的操作得出的结果为0.
SF:符号标志。最近的操作得到的结果为负数。
OF:溢出标志。最近的操作导致一个补码溢出——正溢出或负溢出。
比较和测试指令CMP和TEST不修改任何寄存器的值,只设置条件码。
jmp指令是无条件跳转,它可以是直接跳转,即跳转目标是作为指令的一部分编码的;也可以是间接跳转,即跳转目标是从寄存器或存储器位置中读出的。汇编语言中,直接跳转是给出一个标号作为跳转目标的,例如”jmp .L1″。间接跳转的写法是”*”后面跟一个操作数指示符,例如jmp *%eax用寄存器%eax中的值作为跳转目标,而指令jmp *(%eax)以%eax中的值作为读地址,从存储器中读出跳转目标。
其他跳转指令都是有条件的——根据条件码的某个组合,或者跳转,或者继续执行代码序列中的下一条指令。
循环
C语言中提供了多种循环结构,即do-while、while和for,汇编中没有相应的指令存在,可以用条件测试和跳转组合起来实现循环的效果。
do-while循环:
do
     body-statement
     while(test-expr)
=============
loop:
     body-statement
     t=test-expr;
     if(t)
          goto loop;
while循环:
while (test-expr)
body-statement
============转换为do-while
if (!test-expr)
goto done;
do
body-statement
while (test-expr);
done:
=============直接翻译成goto代码
t = test-expr;
if (!t)
goto done;
loop:
body-statement
t = test-expr;
if (t)
goto loop;
done:

for循环:
for (init-expr; test-expr; update-expr)
body-statement
===========================while
init-expr;
while (test-expr) body-statement
update-expr;
}
=========================do-while
init-expr;
if (!test-expr)
goto done;
do body-statement
update-expr;
g while (test-expr);
done:
===============================goto
init-expr;
t = test-expr;
if (!t)
goto done;
loop:
body-statement
update-expr;
t = test-expr;
if (t)
goto loop;
done:
swith语句:
可以根据一个整数索引值进行多重分支,处理具有多种可能结果的测试时,这种语句特别有用。
它们不仅提高了C代码的可读性,而且通过使用跳转表这种数据结构使得实现更加高效。跳转表是一个数组,表项i是一个代码段的地址,这个代码段实现当开关索引值i时程序应该采取的动作。程序代码用开关索引值来执行一个跳转表内的数组引用,确定跳转指令的目标。和使用一组很长的if-else语句相比,使用跳转表的优点是执行开关语句的时间与开关情况的数量无关。GCC根据开关情况的数量和开关情况值的稀少程度来翻译开关语句,当开关情况数量比较多,并且值的范围跨度比较小时,就会使用跳转表。

7.过程
一个过程调用包括将数据(以过程参数和返回值的形式)和控制从代码的一部分传递到另一部分。
它还必须在进入时为过程的局部变量分配空间,并在退出时释放这些空间。
大多数机器,只提供转移控制到过程和从过程中转移出控制这种简单的指令。数据传递、局部变量的分配和释放通过操纵程序栈来实现。
栈帧结构:
机器用栈来传递过程参数、存储返回信息、保存寄存器用于以后恢复,以及本地存储。
为单个过程分配的那部分栈称为栈帧(stack frame)。
Figure 3.16: Stack Frame Structure. The stack is used for passing arguments, for storing return information,
for saving registers, and for local storage.

过程P调用过程Q,则Q的参数放在P的栈帧中。另外,当P调用Q时,P中的返回地址被压入栈中,形成P的栈帧的末尾。返回地址就是当程序从Q返回时应该继续执行的地方。Q的栈帧从保存的帧指针的值(如%ebp)开始,后面是保存的其他寄存器的值。
过程Q也用栈来保存其他不能存放在寄存器中的局部变量,原因:
1.没有足够多的寄存器存放所有的局部变量
2.有些局部变量是数组或结构,因此必须通过数组或结构引用来访问
3。要对一个局部变量使用地址操作符&,我们必须为它生成一个地址
另外,Q也会用栈帧来存放它调用的其他过程的参数。

call的指令效果是将返回地址入栈,并跳转到被调用过程的起始处。返回地址是在程序中紧跟在call后面的那条指令的地址,这样当被调用过程返回时,执行会从此处继续。
ret指令从栈中弹出地址,并跳转到这个位置,正确使用这条指令,可以使栈做好准备–leave,栈指针要指向前面call指令存储返回地址的位置。
寄存器使用惯例:
程序寄存器组是唯一能被所有过程共享的资源。
需要保证被调用者不会覆盖某个调用者稍后会使用的寄存器的值,所以使用惯例,所有的程序必须遵守。
寄存器%eax,%edx和%ecx被划分为调用者保存寄存器。当P调用Q时,Q可以覆盖这些寄存器,而不会破坏任何P所需要的数据。
寄存器%ebx,%esi和%edi被划分为被调用者保存寄存器。Q必须在覆盖这些寄存器的值之前,先把它们保存到栈中,并在返回前恢复它们。
此外,必须保持寄存器%ebp和%esp。
1 int P(int x)
2 {
3 int y = x*x;
4 int z = Q(y);
5
6 return y + z;
7 }
Procedure P computes y before calling Q, but it must also ensure that the value of y is available after returns. It can do this by one of two means:
_ Store the value of y in its own stack frame before calling Q. When Q returns, it can then retrieve the value of y from the stack.
_ Store the value of y in a callee save register. If Q, or any procedure called by Q, wants to use this register, it must save the register value in its stack frame and restore the value before it returns. Thus,when Q returns to P, the value of y will be in the callee save register, either because the register was never altered or because it was saved and restored.
1 int swap_add(int *xp, int *yp)
2 {
3 int x = *xp;
4 int y = *yp;
5
6 *xp = y;
7 *yp = x;
8 return x + y;
9 }
10
11 int caller()
12 {
13 int arg1 = 534;
14 int arg2 = 1057;
15 int sum = swap_add(&arg1, &arg2);
16 int diff = arg1 – arg2;
17
18 return sum * diff;
19 }

GCC坚持一个x86编程指导方针,也就是一个函数使用的所有栈空间必须是16字节的整数倍,包括保存%ebp值的4个字节和返回值的4个字节。采用这个规则是为了保证访问数据的严格对齐。

为什么popl相当于恢复寄存器的值?P154

GCC产生的代码有时候会使用leave指令来释放栈帧(leave指令在ret前使用,既重置了栈指针,又重置了帧指针),而有时会使用一个或两个popl指令。
可以用push指令或是从栈指针减去偏移量来在栈上分配空间。在返回前,函数必须将栈恢复大原始条件,可以恢复所有被调用者保存寄存器和%ebp,并且重置%esp使其指向返回地址。


8.数组分配和访问
T A[N];
–在存储器中分配一个L*N字节的连续区域;用XA表示起始位置。
–引入了标识符A;可以用A作为指向数组开头的指针,这个指针的值就是XA。可以用0-N-1之间的整数索引来访问数组元素,数组元素i会被存放在地址为XA+L*i的地方。


10.综合理解指针
–每个指针都对应一个类型。如果对象类型T,那么指针的类型为*T。特殊的*void类型代表通用指针,比如malloc函数返回一个通用指针,然后通过显式强制类型转换或者赋值操作那样的隐式强制类型转换,将它转换成一个有类型的指针。指针不是机器代码中的一部分,是C语言提供的一种抽象,帮助程序员避免寻址错误。
–每个指针都有一个值。这个值是某个指定类型对象的地址。特殊的NULL(0)值表示该指针没有指向任何地方。
–指针用&运算符创建。可以出现在赋值语句左边的表达式。这样的例子包括变量以及结构、联合和数组的元素。leal指令设计用来计算存储器引用的地址,&运算符的机器代码实现常常用这条指令来计算表达式的值。
–操作符用于指针的间接引用。间接引用是通过存储器引用来实现的,要么是存储到一个指定的地址,要么是从指定的地址读取。其结果是一个值,它的类型与该指针的类型相关。
–数组与指针紧密联系。一个数组的名字可以像一个指针变量一样引用(但不能修改)。数组引用a[3]与指针运算和间接引用*(a+3)有一样的效果。
–将指针从一种类型强制转换成另一种类型,只改变它的类型,而不改变它的值。
–指针也可以指向函数。这提供了一个很强大的存储和向代码传递引用的功能,这些引用可以被程序的某个其他部分调用。函数指针的值是该函数机器代码表示中第一条指令的地址。
int (*f)(int*)
要从里往外读。因此,我们看到(*f)表明,f是一个指针;而(*f)(int*)表明f是一个指向函数的指针,这个函数以int*作为参数。最后,它是以int*为参数并返回int的函数的指针。
int *f(int*)->
(int *) f(int*)
函数原型,声明了一个函数f,它以一个int*作为参数并返回一个int*。













 第四章 处理器体系结构

1.Y86指令集体系结构

Y86程序用虚拟地址来引用存储器位置。
硬件和操作系统软件联合起来将虚拟地址翻译成实际或物理地址,指明数据实际保存在存储器中哪个地方。
RISC-精简指令集
CISC-复杂指令集
2.逻辑设计和硬件控制语言HCL
存储器和时钟:
时钟寄存器(简称寄存器):存储单个位或字。时钟信号控制寄存器加载输入值。
随机访问存储器(简称存储器):存储多个字。用地址来选择该读或写哪个字,随机访问存储器的例子:1)处理器的虚拟存储器系统,硬件和操作系统软件结合起来使处理器可以在一个很大的地址空间内访问任意的字;2)寄存器文件,再此,寄存器标识符作为地址。
3.Y86的顺序实现
通常处理一条指令包括很多操作。将它们组织成某个特殊的阶段系列,即使指令的动作差异很大,但所有的指令都遵循统一的序列。每一步的具体处理取决于正在执行的指令。
取指(fecth):取指阶段从存储器读取指令字节,地址为程序计数器的值。从指令中抽取出指令指示符字节的两个四位部分,称为icode(指令代码)和ifun(指令功能)。它可能取出一个寄存器指示符字节,指明一个或两个寄存器操作数指示符rA和rB。它还可能取出一个四字节常数字valC。它按顺序方式计算当前指令的下一条指令的地址valP。也就是说,valP等于PC的值加上已取出指令的长度。
译码(decode):译码阶段从寄存器文件读入最多两个操作数,得到值valA和/或valB。通常,它读入指令rA和rB字段指明的寄存器,不过有些指令是读寄存器%esp的。
执行(execute):算术/逻辑单元ALU要么执行指令指明的操作,计算存储器引用的有效地址,要么增加或减少栈指针。得到的值我们成为valE。再次,也可能设置条件码。对于一条跳转指令来说,这个阶段会检验条件码和分支条件,看是不是该选择分支。
访存(memory):访存阶段可以将数据写入存储器,或者从存储器读出数据。
写回(write back):最多可以写两个结果到寄存器文件。
更新PC(PC update):设置成下一条指令的地址。
4.流水线的通用原理
流水线化的一个重要特征就是增加了系统的吞吐量,也就是单位时间内服务的顾客总数,不过它也会轻微地增加延迟,也就是服务一个用户所需要的时间。
















Leave a Reply

Your email address will not be published. Required fields are marked *