计算系统基础乱写
第一章 引言
图灵机。程序表:(当前状态标号,当前方格符号)加(修改后符号,移动方向,下一状态标号)
算法:有限性,确定性,有效可计算性;
第六章 数据的机器级表示
补码就是 2^k 减原数;符号扩展就是复读符号位。
[sgn:1] [exp:8] [frac:23]
exp==255: frac==0, Inf
frac!=0, NaN
exp==0 : 2^(-126) * 0.frac
default : 2^(exp-127) * 1.frac
解释一下这个为什么不对称:规格化导致 frac 必然是 \([1,2)\) 里面的,而如果在 exp==254 处拆表示方式,必然需要将二进制位放到小数点左边,这样就需要额外信息了。
第七章 数字逻辑电路
晶体管:
- 金属氧化物半导体 Metal-Oxide Semiconductor,栅极 Gate,源极 Source,漏极 Drain。
- N-MOS 栅极通电则导通,P-MOS 栅极通电则断开。P-MOS 图示上栅极有个圆圈。
- 物理结构上源极和漏极完全相同。载流子从源极向漏极移动形成电流,N-MOS 为电子,P-MOS 为空穴。
- CMOS 为同时含有两种 MOS 晶体管的电路。
门电路:
- 把输入同时连上 N-MOS 和 P-MOS 相当于 if-else,则可实现 NOT。由此,利用串联并联实现 AND/NOR 即可。
- 注意要输出 0 必须连 GND,不能不连。
组合逻辑电路:
- 译码器:多路 AND;
- 多路选择器:选择信号与输入做 AND。
- 全加法器接受三个输入,输出二进制表示的结果。拿译码器硬凑就行。
- 可编程逻辑阵列 Programmable Logic Array,就是译码器 + 接线 + 一坨 OR。
- 逻辑完备性 Logical Completeness,即任意逻辑函数可使用给定的门集合实现。
R-S 锁存器:
- 可以存储 1 位的信息。
- 结构:S NAND B = A,R NAND A = B
- 存储:R = S = 1,此时 A XOR B = 1,称为存储了 A 的值。
- 设置:将 S 设为 0 并改回,此时 A 被设为 1。
- 注意:若令 R = S = 0,则 A, B 均变为 1。下次通电时,由于时延,RS 通电顺序不确定,不确定内部状态,需要再次存储。
门控 D 锁存器:
- 结构:在 R, S 前接两个 NAND,两个输入。
- 使用:Write Enable = 1 时,写入 D,否则保持原值。
寄存器 Register:
- 一个 WE,若干个 D。
时序逻辑电路,就是状态机。
第八章 冯·诺伊曼模型
地址空间即单元数,寻址能力即每单元的信息量。对于 DLX,地址空间为 \(2^{32}\),寻址能力为一字节,即 8 位。
字长是 ALU 处理的信息量大小,一般为 32 或 64 位。DLX 为 32 位。
DLX 有 32 个通用寄存器和浮点寄存器 R0,R1,...,R31,F0,F1,...,F31,每个均为 32 位。
DLX 有 \(2^{32}\) 个存储单元,每个存储单元包含 \(8\) 位内容。例如:
*(x4000 0000) = x12
*(x4000 0001) = x98
*(x4000 0002) = x00
程序计数器 Program Counter,指向要运行的下一条指令;
指令寄存器 Instruction Register,保存正在处理的指令。
存储器包含存储单元、用于寻址的地址寄存器 MAR、用于存取单元内容的数据寄存器 MDR。先将目标地址存入 MAR,然后即可将值复制到 MDR 或将 MDR 存入。
IO:键盘和显示器。
- 键盘:KBDR 保存输入字符 ASCII 码,KBSR 提供输入字符状态信息;
- 显示器:DDR 保存显示内容 ASCII 码,DSR 提供状态信息。
DLX 指令执行的五个阶段:(每条 DLX 指令包含三到五个阶段)
- 取指令:加载 PC 至 MAR,PC+=4(即下一个指令的地址),读取,将 MDR (即取出的下一条指令)的值存入 IR;
- 译码/取寄存器:IR[31:26] 输入控制器,同时获取指定寄存器的内容,存入 ALU 的 A,B 寄存器。
- 执行/有效地址/完成分支:进行算术/逻辑运算,或计算出给定的存储单元地址,或进行分支跳转。
- 访问内存:以 LW(将内存加载到寄存器)为例:将 ALUOut 存入 MAR 中并读取存储器,将目标值读到 MDR 中。
- 存储结果:从 MDR 或 ALUOut 存入 Rx 寄存器。
时钟周期:先上升至 3.3V,再降至 0V。
第九章 指令集结构 ISA
DLX 采用高位优先 Big-Endian,即一字长的信息中,左侧高位位于内存低地址端,且字的地址必须是 4 的倍数,即边界对齐。
通用寄存器 GPR,R0 的内容必须为 0。
CISC 复杂指令集计算机,如 x86;RISC 精简指令集计算机,如 MIPS, SPARC, PowerPC, DLX。
xU for unused
I-[31 opcode 26][25 SR1 21][20 DR 16][15 Imm16 0] *calculation
I-[31 opcode 26][25 SR1 21][20 xU 16][15 Imm16 0] *BEQZ/BNEZ
R-[31 opcode 26][25 SR1 21][20 SR2 16][15 DR 11][10 xU 6][5 func 0]
J-[31 opcode 26][25 PCoffset26 0] *J,TRAP,others
J-[31 opcode 26][25 SR2 21][20 SR1 16][15 signedoffset 0] *R/W memory
SR 为源操作数所在寄存器,DR 为目标操作数将写入的寄存器。
- 在 I-类型中,第二个操作数为 Imm16 符号扩展后的值。
-
- 对于 I-类的 BEQZ/BNEQ,先计算 PC+Imm16。若 SR1 为/不为 0,则将其存入 PC 中。
- 在 R-类型中,func 为对应 I-类型的 opcode。
- 在 J-类型中,内存地址为 SR2 的值加 signedoffset,要操作的寄存器 SR1。(注意左右)
-
- 对于 J-类的 J,先计算 PC+PCoffset26,然后将其存入 PC 中。
-
- 对于 J-类的 TRAP,PCoffset26 为 TRAP向量。调用 TRAP向量 对应的服务例程后,PC 指向 TRAP 的下一条指令。
- 通常来说,最右侧的寄存器为目标地址。
第十章 机器语言程序设计
然而你发现这玩意简直等价于 if(STATEMENT){goto LABEL;}
组成的 c 程序,所以不再赘述。
第十一章 汇编语言
包含标记 Label、操作码 Opcode、操作数 Operands、注释 Comments 四部分。
;
; 对 10 个整数求和的程序
;
; 10 个整数及累加和
.data x0000600A
.align 2
numbers: .word #10,#3,#4,#6,#8,#-2,#45,#5,#8,#9
sum: .space 4
;
; 初始化
.text x40000000
.global main
main: ADDI R1,R0,numbers
ADDI R3,R0,#0 ; R3 清零,它将包含和
ADDI R2,R0,#10 ; R2 包含整数个数
;
; 循环计算
again: BEQZ R2,exit
LW R4,0(R1) ; address:R1.val+0
ADD R3,R3,R4
ADDI R1,R1,#4 ; R1 跟踪下一个整数地址
SUBI R2,R2,#1
J again
exit: SW sum(R0),R3 ; address:sum+R0.val
TRAP #0
; 程序结束
; ======= 分割线 =======
; 以下是另一些指令的示例
A: LHI R1,A ; R1=high(A)
; if A=x3000 01A0, then R1=x3000 0000
标记:由字母、数字、下划线组成,并以字母、下划线或 $
开头,以冒号结尾。指令操作码属于保留字,不能用作标记。
立即数:#
为十进制,x
为十六进制,b
为二进制。
伪操作:操作码以 .
开头。数据和指令分别按 .data
,.text
的指示存在不同区域:数据区和代码区。
- 数据区:
.address x0000600A ; 数据起始于单元 x0000600A。由对齐原则,不能直接存储字。
.align 2 ; 将下面的内容加载到以 2 个 0 结尾的地址中。
.word #1,#2,#4 ; 连续存储若干个字。
.ascii "string1","str" ; 若干个字符串。
.asciiz "string1","str" ; 若干个空终止字符串:每个串末尾存储一个字节 0。
.space 4 ; 留出 4 个字节。
- 代码区:
.text x40000000 ; 程序起始于单元 x40000000。可以不给出地址,由链接器分配
.align 2 ;
.global label ; 将 label 声明为全局标记,可以跨文件使用
汇编过程:扫描两遍,第一遍构建符号表,第二遍进行替换。如何计算地址?汇编器记录 Location Counter。(注意区分 PC 与 LC,LC 是汇编器的运行时局部变量,PC 对应一个结构)
链接:由多个目标文件生成可执行程序。在这里处理跨模块的标记引用,并重分配内存。即相当于多文件的汇编。
C-DLX 编译:你发现这玩意几乎等价于第十章。不过下面要介绍变量的存储方式:
- 栈:你知道这是什么。这里的实现是维护一个栈顶指针。
- 全局数据区存储静态存储类变量,运行时栈存储局部变量。
- DLX 使用 R28 作为全局指针,即包含全局数据区的起始地址。
- DLX 使用 R29 作为栈指针,R30 作为框架指针/帧指针,帧指针为方便访问局部变量而用的。
- (更详细的介绍参见第十五章)
x0000 0000 | 系统空间
| ---------- |<-- PC
| 代码区
| ---------- |<-- R28
| 全局数据区
| ---------- |
| 堆
| v -------- |
|
| ^ -------- |<-- R29
| |<-- R30
| 运行时栈
| ---------- |
xFFFF FFFF | 系统空间
第十二章 输入和输出
一个 I/O 设备至少包括两个设备寄存器:一个保存数据,一个保存状态信息。
两种机制:
- x86 指令集等:使用专门的 I/O 指令与 I/O 设备寄存器交互
- DLX 等:使用内存映射方式:内存中的某些地址被分配给寄存器,而非存储单元
地址 | I/O 寄存器 | 数据 |
---|---|---|
xFFFF0000 | 键盘状态寄存器 KBSR | 最低位 |
xFFFF0004 | 键盘数据寄存器 KBDR | 低八位 |
xFFFF0008 | 显示器状态寄存器 DSR | 最低位 |
xFFFF000C | 显示器数据寄存器 DDR | 低八位 |
xFFFF00F8 | 机器控制寄存器 MCR |
DLX 中,读 KBDR 的一个示例:(注意区分:KBDR标记处存储的是KBDR的地址,它不是KBDR本身)
KBDR: .word xFFFF0004 ; KBDR 的起始地址是 xFFFF0004
; 和下面这句没本质区别
A: .word xFFFF0004 ; KBDR 的起始地址是 xFFFF0004
...
LW R1,KBDR(R0) ; R1 存储 KBDR 的位置
LW R4,0(R1) ; 读到 R4 中
通信的方式:
- 轮询:如果 KBSR 未就绪,就重复读取,直到就绪后再读 KBDR。浪费了大量处理器时间
- 中断驱动:I/O 设备向处理器发来中断信号。
第十三章 自陷例程和中断
DLX 有 256 个服务例程,可以被 TRAP 指令调用。
TRAP 向量表长为 256 * 4,存储了 256 个例程的(代码区)起始地址。可以规定数据区为代码区起始地址前 x100 个单元的位置。
TRAP 向量 | 符号 | 作用 |
---|---|---|
x06 | GETC | 读一个字符,将其 ASCII 码复制到 R4[7:0] |
x07 | OUT | 输出一个字符 R4[7:0] |
x08 | PUTS | 输出以 R4 所指地址开头的空终止字符串,每个字符占一个内存单元 |
x09 | IN | 输出一个提示符,读取一个字符至 R4 并回显 |
x0A | GETS | R4 存地址,R5 为长度 n,读取至多 n-1 个字符(回车结束)并存入 R4,空终止 |
x00 | HALT | 停机 |
调用 TRAP 的一个过程:
- 将 26 位 TRAP 向量扩展至 32 位,并左移两位得到向量表的地址,存入 MAR(例如 x09 变为 x00000024 )
- 将向量表内的地址加载进 MDR (例如 x00000024 的内容为 x00003100,它是 IN 子例程的起始地址)
- R31 保存 PC 的当前内容(即用户程序的下一条指令)
- 将 MDR 加载进 PC(即例程的起始地址)
- 执行子例程
- 将 R31 加载进 PC。可以使用
JR R31
或其助记符RET
。
但是这里有一个问题:TRAP 指令及服务例程会破坏当前的寄存器状态。
caller-save:调用者保存;callee-save:被调用者保存;知道哪些寄存器将被使用的程序应该来处理这个问题。
具体的做法:手动 SW/LW
。
中断的产生:
KBSR、DSR 的就绪位([0] 位)为 1 时,表示设备需要服务;中断允许位([1] 位,IE)为 1 时,表示是否有权利请求服务;中断请求信号 IRQ 为其逻辑与。
状态寄存器 SR 是一个特殊寄存器,只能在特权模式下访问。
所有 IE 位可以被 SR[0] 同时改写,即允许/禁止全部中断。SR[1] 表示当前程序的模式:特权模式为 0,用户模式为 1。
中断信号 INT 为所有设备 IRQ 的逻辑或。同时,有一个原因寄存器 CAUSE 保存中断信号的来源:CAUSE[15:10] 表示来自六个硬件的未决中断,[10] 为显示器,[11] 为键盘;CAUSE[9:8] 表示来自两个软件的未决中断。从左到右优先级依次降低。
中断发生时,用 SR[2:3] 保存原来 SR[0:1] 的值,用 EPC 保存 PC 的值。
中断的处理:
SR[15:8] 给出中断阻塞方案,设为 1 表示允许当前位的中断。存在多个中断时,从左到右处理。
两个整数寄存器和特殊寄存器间数据传送的指令:MOVI2S
、MOVS2I
,每个特殊寄存器用 5 位编码。(指令附在本段末尾)
名称 | 编号 D | 编号 H |
---|---|---|
SR | 12 | x0C |
CAUSE | 13 | x0D |
EPC | 14 | x0E |
从中断返回时,清空 CAUSE,然后使用 RFE
指令恢复 PC、SR[1:0] 的值。
RFE | 110001 00000 00000 00000 0000000000
MOVI2S | 100010 00000 ..... ..... 0000000000
MOVS2I | 100011 00000 ..... ..... 0000000000
GPR special
第十四章 子例程
调用/返回时的具体操作类似 TRAP:调用时将 PC 设为子例程起始位置,R31 设为下一条指令。返回时调用 JR R31。
调用子例程的操作码:
PCOffset26
JAL | 101110 00000000000000000000000000
JALR | 101111 00000 000000000000000000000
Rx unused
JAL 等价于 J 指令加上保存 R31,会跳转到下一条指令 + [PCOffset26 的符号扩展] 的位置;JALR 会跳转到给出寄存器里的地址的位置。
调用库例程的时候,需要在代码区写一个 .extern SQRT
表示程序中会调用库例程中的 .global SQRT
。
第十五章 函数
简要介绍一下 C 函数在底层的实现。
可以将 R4-R7 用于参数传递,R2-R3 用于返回值,R31 用于返回地址,R16-R23 用于局部变量,R8-R15、R24、R25 用于存储临时产生的值。因此,参数个数多于四个时,需要使用存储器。
对于每次函数调用,R30 始终指向活动记录的基址(即一段的底部),R29 指向栈顶。
函数调用机制:
- 调用函数:将变元复制到被调用函数可访问的存储区域内(即将超出4个的参数压入栈顶)
压栈:
SUBI R29,R29,#4; SW 0(R29),value
即栈顶指针减四,然后在所指内存存储数据。
压栈结束后JAL subprocess
。
- 被调用函数:在栈上保存(即备份)部分寄存器的值,同时分配局部变量
在栈上备份 R31(如果需要)和 R30,然后将 R30 指向参数之上(即 R30+#4 即为保存的最靠上的参数)。
然后备份寄存器,分配局部变量,最后调整 R29 使得其指向栈顶(即最靠上的元素)。
- 被调用函数:运行
- 被调用函数:运行完成,弹出活动记录,控制返回到调用函数中
弹出参数(如果又调用了其他函数),弹出局部变量,恢复寄存器,恢复动态链接 R30,恢复返回地址 R31,调用
RET
返回控制权。
- 调用函数:取返回值 R2
第十六章 指针和数组
数组在栈上空间的分配还是顺序的。