计算系统基础乱写

第一章 引言

图灵机。程序表:(当前状态标号,当前方格符号)加(修改后符号,移动方向,下一状态标号)

算法:有限性,确定性,有效可计算性;

第六章 数据的机器级表示

补码就是 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 指令包含三到五个阶段)

  1. 取指令:加载 PC 至 MAR,PC+=4(即下一个指令的地址),读取,将 MDR (即取出的下一条指令)的值存入 IR;
  2. 译码/取寄存器:IR[31:26] 输入控制器,同时获取指定寄存器的内容,存入 ALU 的 A,B 寄存器。
  3. 执行/有效地址/完成分支:进行算术/逻辑运算,或计算出给定的存储单元地址,或进行分支跳转。
  4. 访问内存:以 LW(将内存加载到寄存器)为例:将 ALUOut 存入 MAR 中并读取存储器,将目标值读到 MDR 中。
  5. 存储结果:从 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 的一个过程:

  1. 将 26 位 TRAP 向量扩展至 32 位,并左移两位得到向量表的地址,存入 MAR(例如 x09 变为 x00000024 )
  2. 将向量表内的地址加载进 MDR (例如 x00000024 的内容为 x00003100,它是 IN 子例程的起始地址)
  3. R31 保存 PC 的当前内容(即用户程序的下一条指令)
  4. 将 MDR 加载进 PC(即例程的起始地址)
  5. 执行子例程
  6. 将 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 表示允许当前位的中断。存在多个中断时,从左到右处理。

两个整数寄存器和特殊寄存器间数据传送的指令:MOVI2SMOVS2I,每个特殊寄存器用 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

第十六章 指针和数组

数组在栈上空间的分配还是顺序的。

posted @ 2023-09-18 16:11  Xi'En  阅读(127)  评论(2编辑  收藏  举报