这是HNU 2024年计算机系统的期末试卷的整理,答案是我和同学还有AI核对了的,但可能仍有不足之处,欢迎大家在评论区讨论。
本文章由于附带了一些解析过程,所以内容可能有些冗长。我将答案放在了每道题的开头部分,需要对答案的同学可以根据目录快速跳转。
相应的,解析部分在答案的后面。
个人感觉:题型总体上比较符合考点,不算太难,可以在两小时之内完成
注:答案的解析部分绝大部分由AI完成
以下是答案及解析部分
一、简答题(10 分)
请用 32 位补码整数和 IEEE 754 单精度浮点数格式分别表示 2049 这个值(结果请用16 进制表示)。(7 分)
请问两种表示方法二进制序列有相同的部分吗?为什么?(3 分)
答案
(1)
-
2049 的 32 位补码:
0x00000801
-
2049 的 IEEE 754 单精度:
0x45001000
(2)
回答:
有相同的部分:2049 可以精确表示为浮点数,其浮点尾数部分中的 00000000001
(表示 2⁻¹¹)恰好与补码中低 11 位 00000000001
相同;
解释:
任何正整数
N
N
N 的二进制补码可写成
1
b
k
−
1
b
k
−
2
…
b
0
\,1\,b_{k-1}\,b_{k-2}\dots b_{0}
1bk−1bk−2…b0(最高位为 1,后面跟
k
k
k 位)。
而浮点数的归一化格式为:
N = 1. ( b k − 1 b k − 2 … b 0 ) 2 × 2 k . N \;=\; 1.\bigl(b_{k-1}b_{k-2}\dots b_{0}\bigr)_2 \;\times\;2^{\,k}. N=1.(bk−1bk−2…b0)2×2k.
- 当整数的有效二进制位数 ≤ 24(包括最高位 1),其最高位之后的位会完整出现在浮点尾数的高位部分。
- 由于浮点数中小数点前的
1
是隐含的,不会存储在尾数中,所以补码的最高有效位不会出现在尾数里。
解析
一、32位补码整数表示
- 2049 的二进制(原码)是:
0000 0000 0000 1000 0000 0001
其中,2049 = 2¹¹ + 1,对应一共 12 位有效二进制位,高位全部补零。
- 作为 32 位补码,符号位为 0,剩余高位用 0 填充,最终的 32 位二进制序列为:
0000 0000 0000 0000 0000 1000 0000 0001
将其每 4 位分组换算成十六进制,即
0x00000801
二、IEEE 754 单精度浮点数表示
- 确定符号位(S)
2049 为正数,故 S = 0。 - 将 2049 转为二进制并归一化(求指数 E 和尾数 M)
- 2049 在二进制下 =
1000 0000 0001₂ = 1·2¹¹ + 1·2⁰
- 归一化写法:
此时“1.”之后的小数部分共有 11 位:前 10 位都是 0,第 11 位是 1,接下来全部补 0。2049.0 = 1.00000000001₂ × 2¹¹
- 2049 在二进制下 =
- 计算指数域(E′)
- 归一化指数 e = 11
- 单精度指数偏移量为 127 → 存储的指数字段 E′ = e + 127 = 11 + 127 = 138
- 138₁₀ 转为二进制 8 位:
138₁₀ = 1000 1010₂
- 求尾数(M)
- “.00000000001” 共 23 位:
- 前 10 位都是 0
- 第 11 位为 1
- 后面其余共 23 – 11 = 12 位都补 0
- 写成一串 23 位:
00000000001 00000000000
- “.00000000001” 共 23 位:
- 拼接得 32 位浮点表示
按 4 位一组分段:S (1 位) | E (8 位) | M (23 位) -------------------------------------------------- 0 | 10001010 | 00000000001000000000000
因此,IEEE 754 单精度表示(十六进制)为:0 100 0101 0 000 0000 0001 0000 0000 0000₂
0x45001000
二、程序填空题(15 分,每空 3 分)
答案
int main()
{
int i = 0, j = 1, k = 2, loop = 3;
int *p = &i;
int arr[3] = { 9, /*(1)*/ 5 , 31 };
scanf("%d,%d", &i, &j);
p = /*(2)*/ &arr[1];
k = /*(3)*/ (*p > j + 1) ? (i + 3) : (2 * i);
for (loop = 0; loop < 2; /*(4)*/ p++, loop++)
{
/*(5)*/ k = *p + 2 * k;
}
return 0;
}
- (1)
5
- (2)
&arr[1]
- (3)
(*p > j + 1) ? (i + 3) : (2 * i)
或*p <= j + 1 ? 2 * i : i + 3
- (4)
p++, loop++
- (5)
k = *p + 2 * k
注:第三小问有无括号均可以
解析
下面结合汇编逐步还原填空(假设栈帧中各变量的偏移已经对应好)。
(1) arr[1] 的初值
从汇编中看到:
movl $9, 16(%esp) # arr[0] = 9
movl $5, 20(%esp) # arr[1] = 5
movl $31, 24(%esp) # arr[2] = 31
因此
arr[3] = { 9, 5, 31 };
即
/*(1)*/ 5
(2) 在 scanf 之后对 p 的赋值
汇编里是:
leal 16(%esp), %eax # %eax = &arr[0]
addl $4, %eax # %eax = &arr[0] + 4 = &arr[1]
movl %eax, 44(%esp) # p = %eax
所以
p = &arr[1];
(3) 计算 k 的那一段
对应汇编:
movl 44(%esp), %eax # %eax = p
movl (%eax), %eax # %eax = *p
movl 32(%esp), %edx # %edx = j
addl $1, %edx # %edx = j + 1
cmpl %edx, %eax # 比较:*p 与 (j+1)
jle .L2 # 如果 *p <= j+1 跳到 L2
movl 28(%esp), %eax # eax = i
addl $3, %eax # eax = i + 3
jmp .L3
.L2:
movl 28(%esp), %eax # eax = i
addl %eax, %eax # eax = i + i
.L3:
movl %eax, 36(%esp) # k = eax
翻译成 C 逻辑就是:
if (*p > j + 1) {
k = i + 3;
} else {
k = 2 * i;
}
为了把它写在一行赋值里,我们可以用三目运算符。比如:
k = (*p > j + 1) ? (i + 3) : (2 * i);
(4) 和 (5) 对应循环体
PS :这里我写成了 loop += 4
,但是在 C 语言里对指针做 p++ 操作时,编译器会自动将它加上“一个元素大小”,对于 int * 来说,就是加 sizeof(int)(通常是 4 字节)。因此答案是 loop ++
。
从汇编看,先在调用 f1
之后(此处与本题无关),会做:
movl $0, 40(%esp) # loop = 0
jmp .L4
.L5:
movl 36(%esp), %eax # %eax = k
leal (%eax,%eax), %edx # %edx = 2*k
movl 44(%esp), %eax # %eax = p
movl (%eax), %eax # %eax = *p
addl %edx, %eax # %eax = *p + 2*k
movl %eax, 36(%esp) # k = *p + 2*k
addl $1, 40(%esp) # loop++
addl $4, 44(%esp) # p = p + 1 (因为每次移动 4 字节,也就是下一个整型)
.L4:
cmpl $1, 40(%esp)
jle .L5
可以看出:
- 循环初始化
loop = 0;
- 条件是
loop <= 1
,等价于 C 里loop < 2
。 - 进入循环体后,本质上做了两件事:
k = *p + 2 * k;
loop++; p++;
- 循环代码
for (loop = 0; loop < 2; p++, loop++) { k = *p + 2 * k; }
三、程序填空题(第三空 3 分,其余三空每空 4 分,共 15 分)
答案
struct s {
int i;
int j;
struct s *pnext;
} *ps1, *ps2;
int f(int k, struct s *ps) {
if (ps->pnext != NULL) /* (1) */
return ps->i * k * ps->j- ps->pnext->i * ps->pnext->j; /* (2) */
else
return k + ps->i + ps->j;
}
int main() {
ps1 = (struct s*)malloc(sizeof(struct s));
ps2 = (struct s*)malloc(sizeof(struct s));
ps1->i = 1;
ps1->j = 2;
ps2->i = 3;
ps2->j = 4;
ps1->pnext = NULL;
ps2->pnext = NULL;
ps2->pnext = ps1; /* (3) */
ps1->j = f( f(ps2->i, ps2), ps1 ); /* (4) */
printf("%d\n", ps1->j);
return 0;
}
- (1)
ps->pnext != NULL
- (2)
ps->i * k * ps->j - ps->pnext->i * ps->pnext->j
- (3)
ps2->pnext = ps1;
- (4)
f( f(ps2->i, ps2), ps1 )
函数 f 的分析
汇编代码(简化后片段):
f:
pushl %ebp
movl %esp, %ebp
movl 12(%ebp), %eax # eax ← ps
movl 8(%eax), %eax # eax ← ps->pnext
testl %eax, %eax
je .L2 # 如果 ps->pnext == NULL,跳到 .L2
# —— ps->pnext != NULL 时,计算:ps->i * k * ps->j - pnext->i * pnext->j ——
movl 12(%ebp), %eax # eax ← ps
movl (%eax), %eax # eax ← ps->i
movl %eax, %edx # edx ← ps->i
imull 8(%ebp), %edx # edx ← ps->i * k
movl 12(%ebp), %eax # eax ← ps
movl 4(%eax), %eax # eax ← ps->j
imull %eax, %edx # edx ← ps->i * k * ps->j
movl 12(%ebp), %eax # eax ← ps
movl 8(%eax), %eax # eax ← ps->pnext
movl (%eax), %ecx # ecx ← pnext->i
movl 12(%ebp), %eax # eax ← ps
movl 8(%eax), %eax # eax ← ps->pnext
movl 4(%eax), %eax # eax ← pnext->j
imull %ecx, %eax # eax ← pnext->i * pnext->j
movl %edx, %ecx # ecx ← 第一部分 ps->i * k * ps->j
subl %eax, %ecx # ecx ← (ps->i*k*ps->j) - (pnext->i*pnext->j)
movl %ecx, %eax # eax ← 结果
jmp .L3
.L2:
# —— ps->pnext == NULL 时,返回 k + ps->i + ps->j ——
movl 12(%ebp), %eax # eax ← ps
movl (%eax), %eax # eax ← ps->i
movl %eax, %edx # edx ← ps->i
addl 8(%ebp), %edx # edx ← ps->i + k
movl 12(%ebp), %eax # eax ← ps
movl 4(%eax), %eax # eax ← ps->j
addl %edx, %eax # eax ← ps->j + (ps->i + k)
.L3:
popl %ebp
ret
由此可知,C 代码中应是:
int f(int k, struct s *ps) {
if (ps->pnext != NULL)
return ps->i * k * ps->j
- ps->pnext->i * ps->pnext->j;
else
return k + ps->i + ps->j;
}
对应填空:
- (1) 应当是
ps->pnext != NULL
- (2) 应当是
ps->i * k * ps->j - ps->pnext->i * ps->pnext->j
main 函数的分析
从汇编看主要关键段:
movl ps2, %eax
movl ps1, %edx
movl %edx, 8(%eax) # 也就是 ps2->pnext = ps1;
movl ps1, %ebx
movl ps1, %esi
movl ps2, %edx
movl ps2, %eax
movl (%eax), %eax # eax ← ps2->i
movl %edx, 4(%esp) # 准备调用 f 的第二个参数 = ps2
movl %eax, (%esp) # 准备调用 f 的第一个参数 = ps2->i
call f # 第一次调用:f(ps2->i, ps2)
movl %esi, 4(%esp) # 准备第二次调用 f 的第二个参数 = ps1
movl %eax, (%esp) # 第一次 f 的返回值做第二次 f 的第一个参数
call f # 第二次调用:f( 上一次返回值 , ps1)
movl %eax, 4(%ebx) # ps1->j = 第二次 f 的返回值
通过以上可以还原出:
- (3) 是让
ps2->pnext
指向ps1
,即ps2->pnext = ps1;
- (4) 是两次调用
f
的嵌套,把结果赋给ps1->j
,即ps1->j = f( f(ps2->i, ps2), ps1 );
四、综合分析题(10 分)
答案
- 该程序运行后计算结果为:
15
- 栈帧图上
- 0BC 处填
0x0804844A
- 08C 处填
0x08048483
- 0BC 处填
- 栈帧图上
- 0C0 处填
0x00000003
- 0C4 处填
0x00000004
- 0C0 处填
- 栈帧图上
- 090 处填
0x00000003
- 094 处填
0x00000004
- 090 处填
解析
1. 该程序运行后计算结果为 ______
答案:
15
解析:
f1(3,4)
调用f2(3,4)
得到12
,再在f1
中加上3
,结果是15
。
2. 在栈帧图上的 “0BC” 和 “08C” 处填上正确的地址值
首先看调用链:
main → f1 → f2
- 当
main
执行到call f1
(地址0x08048445
)时,返回地址会是下一条指令0x0804844a
。 - 当
f1
执行到call f2
(地址0x0804847e
)时,返回地址会是下一条指令0x08048483
。
在栈帧图里:
0x...0BC
存的是f1
被调用时,保存到栈上的 “返回地址”。此时的返回地址就是从main
返回的位置,也就是0x0804844a
。0x...08C
存的是f2
被调用时,保存到栈上的 “返回地址”。此时的返回地址就是从f1
返回的位置,也就是0x08048483
。
因此:
- 位置 0BC:
0x0804844A
- 位置 08C:
0x08048483
3. 在栈帧图上的 “0C0” 和 “0C4” 处填上正确的传递参数值
继续看 f1
被调用时 main
在栈上给 f1
的两个参数:
- 在
main
里,
当执行movl $0x3,0x14(%esp) # 准备 f1 的第1个参数 3 movl $0x4,0x18(%esp) # 准备 f1 的第2个参数 4 …… mov 0x18(%esp),%eax # eax ← 4 mov %eax,0x4(%esp) # 把 4 作为 f1 的第2个参数,写到 [esp+4] mov 0x14(%esp),%eax # eax ← 3 mov %eax,(%esp) # 把 3 作为 f1 的第1个参数,写到 [esp] call f1
call f1
之后,在f1
的栈帧里:EBP+8 (=0x…0C0)
存储 “第一个参数”,也就是3
;EBP+12(=0x…0C4)
存储 “第二个参数”,也就是4
。
所以:
- 0C0 处(
f1
的第一个参数)填:0x00000003
- 0C4 处(
f1
的第二个参数)填:0x00000004
4. 在栈帧图上的 “090” 和 “094” 处填上正确的传递参数值
看 f2
被调用时 f1
在栈上给 f2
的两个参数:
- 在
f1
里,
当执行mov 0x8(%ebp),%eax # eax ← f1 的第1个参数 a(此处 a=3) mov %eax,(%esp) # 把 3 放到 [esp],做 f2 的第1个参数 mov 0xc(%ebp),%eax # eax ← f1 的第2个参数 b(此处 b=4) mov %eax,0x4(%esp) # 把 4 放到 [esp+4],做 f2 的第2个参数 call f2
call f2
之后,在f2
的栈帧里:EBP+8 (=0x…090)
存储 “第一个参数”,也就是3
;EBP+12(=0x…094)
存储 “第二个参数”,也就是4
。
所以:
- 090 处(
f2
的第一个参数)填:0x00000003
- 094 处(
f2
的第二个参数)填:0x00000004
五、分析题(25 分)
有如下两个 C 语言代码文件 (此处略,详情见试卷)
对这两个 C 代码文件进行分离编译,分别形成 main_g.o 和 globals.o 文件,再使用 gcc 命令可形成一个可执行文件 test。请问:
1.test 执行的结果是什么?请按照其在屏幕的输出格式书写作答,并请解释为什么有这样的输出。
Parent process waiting for signal.
Received SIGUSR1 signal. Global variable before change: 42
Global variable after change: 100
Global variable value: 100
解释:
- 程序首先在父进程里打印“Parent process waiting for signal.”,然后进入
pause()
等待信号。 - 子进程
fork()
出来后会睡眠 1 秒,接着向父进程发送SIGUSR1
。 - 父进程收到
SIGUSR1
,进入handle_signal
,此时全局变量global_var
原本是 42,所以先打印 “Global variable before change: 42”,然后把global_var
改为 100,再打印 “Global variable after change: 100”。 - 信号处理结束后,父进程从
pause()
返回,主函数继续执行print_global_var()
,此时打印的是修改后的值 100:“Global variable value: 100”。
由于信号处理函数是在父进程的上下文中执行的,所以全局变量确实被改为了 100,进而最后打印出来也是 100。
2.在命令行里面用 gcc 形成可执行文 test 文件时,main_g.o 和 globals.o 在命令行中出现的顺序必须确定吗?为什么?
不必须,这两个文件都是可重定位目标文件,里面的所有符号都会被 gcc 解析并记录到符号表中,所以顺序不会影响到链接的过程。
3.如下图所示,这是哪个文件的哪一部分内容,你怎么看出来的?
这是可执行文件 test 里面的 main 函数代码段的内容。因为指令的地址都是绝对地址,说明代码段已经被重定位了。 call 的立即数也被修改成为了相对偏移,说明函数调用也被重定位了。
4.下图中 call 指令出现的地方很多都有@PLT,这是什么?其作用是什么?
@PLT
表示的是某个函数在 PLT(Procedure Linkage Table)中的跳转入口,程序通过它来实现动态链接时的函数调用。
作用:
- 为程序里对共享库中函数的调用提供一个“统一入口”,
- 支持程序启动时先不解析全部外部函数地址、而是“调用时再解析”,
- 与 GOT 配合,实现了位置无关的动态链接。
5.为什么在地址 80486af 那一行出现的 call 指令(画横线的部分)没有 PLT 出现?它与其他的 call 相比有什么特点?
它没有 @plt
,因为它不是调用外部库函数,而是直接调用同一个可执行文件内部的函数 print_global_var()
。
特点:
因为 print_global_var()
是在本文件定义的函数,所以当链接器在生成最终的 .text
段时就已经知道了这个函数的绝对地址,可以直接把目标函数的地址编码成一个 PC‐relative 的偏移量,写在指令里即可,不需要经过 PLT中转和动态链接。
补充知识:
与 PLT 调用的区别:凡是 @PLT
的,都是动态库里的函数,需要通过 PLT 表的 “中转” 才能跳转到函数真正的地址;而 call 804861b <print_global_var>
表示的是一个静态地、在本程序里就能找到定义的函数,就不需要 PLT,直接走 “call 0x…(PC 相对偏移)”。
6.根据此图,你能大概推算出(画横线部分)07 ff ff ff 是怎么得来的吗?
首先链接器为 print_global_var()
函数确定它的运行时地址,然后根据 print_global_var()
对应的的重定位表项中的 offset
和 main()
函数的起始地址,相加得到需要重定位的地址。接着链接器计算这个式子:
目标函数(print_global_var)的地址- call 指令的下一条指令的地址
得到 07 ff ff ff
,其中下一条指令的地址=
需要重定位的地址 - 修正量(-4)
,最后将这个数填入到需要重定位的地址里面。
注,需要重定位的(起始)地址指的是 call 指令的操作数部分,对应的机器码上就是, call 这条指令的机器码从左往右数的第二个字节的地方,一共4个字节。
六、分析题(25 分)
答案
虚拟地址(VA)划分
VPN = VA[15:8]
VPO = VA[7:0]
TLBI = VA[9:8]
TLBT = VA[15:10]
物理地址(PA)划分
PPN = PA[11:8]
PPO = PA[7:0]
Cache Tag (CT) = PA[11:5]
Cache Index (CI) = PA[4:2]
Block Offset (CO) = PA[1:0]
A. 虚拟地址(16 位二进制)
15 14 13 12 11 10 9 8 │ 7 6 5 4 3 2 1 0
1 1 1 0 1 0 1 1 0 1 1 0 1 1 1 0
B. 地址翻译
VPN = 0xEB TLB命中? = 是
TLBI = 0x3 缺页? = 否
TLBT = 0x3A PPN = 0x9
C. 物理地址(12 位二进制)
11 10 9 8 7 6 5 4 3 2 1 0
1 0 0 1 0 1 1 0 1 1 1 0
D. Cache 访问
CO = 0x2 物理地址 = 0x96E
CI = 0x3 Cache命中? = 是
CT = 0x4B 返回的 Cache 字节 = 0x3A
PS:返回的 Cache 只有一个字节,是因为题目最开始说了
内存访问是1字节
解析
第一部分:基本参数
1. 虚拟地址字段划分
虚拟地址共 16 位,记为:
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
|-------VPN (8 位)--------|---VPO (8 位)----|
- VPO(Virtual Page Offset):对应虚拟地址的低 8 位,也就是位 [7∶0]。
- VPN(Virtual Page Number):对应虚拟地址的高 8 位,也就是位 [15∶8]。
对于 TLB 来说:
- TLBI(TLB 索引):因为 TLB 有 4 个组,需要 2 位索引,这 2 位来自 VPN 的低 2 位,即虚拟地址的位 [9∶8]。
- TLBT(TLB Tag):对应 VPN 的高 6 位,即虚拟地址的位 [15∶10]。
2. 物理地址字段划分
物理地址共 12 位,记为:
11 10 9 8 7 6 5 4 3 2 1 0
|--PPN (4 位)--|-----PPO (8 位)------|
- PPO(Physical Page Offset):对应物理地址的低 8 位,即位 [7∶0]。
- PPN(Physical Page Number):对应物理地址的高 4 位,即位 [11∶8]。
对于 Cache 而言(总行数 24 行,3 路组相联,每行 4 字节):
- CO(Cache Block Offset):块内偏移需要 2 位,对应物理地址位 [1∶0]。
- CI(Cache Index):组索引需要 3 位,对应物理地址位 [4∶2]。
- CT(Cache Tag):其余高位作为 Cache Tag,即物理地址位 [11∶5]。
第二部分:地址翻译(针对虚拟地址 0xEB6E)
我们分四小题回答。
A. 虚拟地址二进制表示
虚拟地址 0xEB6E,对应的 16 位二进制为:
0x E B 6 E
1110 1011 0110 1110
将它按位写出(从 bit 15 到 bit 0):
15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | 1 | 1 | 0 | 1 | 0 | 1 | 1 | 0 | 1 | 1 | 0 | 1 | 1 | 1 | 0 |
B. 地址翻译流程
- 计算 VPN 与 VPO
- 虚拟页号 VPN = 高 8 位 = 0xEB
- 虚拟页偏移 VPO = 低 8 位 = 0x6E
- TLB 索引与 Tag
- 从 VPN 的低 2 位(虚拟地址的 bit [9∶8])取出 TLBI:
- VPN = 0xEB = 二进制 1110 1011,其中 bit [9∶8] = “11” ⇒ TLBI = 3(十六进制 0x3)。
- VPN 的高 6 位(虚拟地址 bit [15∶10])作为 TLBT:
- bit [15∶10] = 111010₂ = 0x3A。
- 从 VPN 的低 2 位(虚拟地址的 bit [9∶8])取出 TLBI:
- 查 TLB(Index=3组)
在题图中,TLB 中“Index=3”的四条内容为(“Tag / PPN / Valid”):Index=3,Tag=0x28, PPN=0xA, Valid=1 Tag=0x14, PPN=0x0, Valid=1 Tag=0x3A, PPN=0x9, Valid=1 Tag=0x07, PPN=0x2, Valid=1
- 我们要匹配的 TLBT = 0x3A,在 Index=3 组中恰好存在一条 Tag=0x3A,且 Valid=1 ⇒ TLB 命中。
- 因此无需查页表,也不存在缺页。
- 从该 TLB 条目可得物理页号 PPN = 0x9。
- 拼出物理地址
- 物理页号 PPN = 0x9 (4 位)
- 页偏移 VPO = 0x6E (8 位)
- 合成物理地址 =
PPN | PPO
=0x9 * 0x100 + 0x6E = 0x900 + 0x6E = 0x96E
。 - 换成二进制(12 位)为
1001 0110 1110
。
综上,B 部分填写如下(十六进制均大写):
参数 | 值 | 参数 | 值 |
---|---|---|---|
VPN | 0xEB | TLB命中? | 是 |
TLBI | 0x3 | 缺页? | 否 |
TLBT | 0x3A | PPN | 0x9 |
C. 物理地址二进制表示
物理地址 0x96E,12 位二进制:
0x 9 6 E
1001 0110 1110
按位写出(bit 11 到 bit 0):
11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | 0 | 0 | 1 | 0 | 1 | 1 | 0 | 1 | 1 | 1 | 0 |
D. 物理内存引用 → Cache 访问
- 物理地址分字段
- 物理地址 = 0x96E = 二进制
1001 0110 1110
。 - CO(块内偏移) = 位 [1∶0] =
10₂
= 2(十六进制 0x2)。 - CI(Cache 索引) = 位 [4∶2] =
011₂
= 3(十六进制 0x3)。 - CT(Cache Tag) = 位 [11∶5] =
1001011₂
= 0x4B。
- 物理地址 = 0x96E = 二进制
- 查 Cache(Index=3)
题图给出了 Cache 在索引 3 处 3 路的内容:Index=3: 行0: Tag=0x7F, Valid=1, Line=0x53C9FA45 行1: Tag=0x4B, Valid=1, Line=0xD36D3A19 行2: Tag=0x2C, Valid=0, Line=0xF9DEC115 (此路无效)
- 我们的 CT = 0x4B,与“方式1 的 Tag=0x4B”且 Valid=1 完全匹配 ⇒ Cache 命中。
- 取出该路的 Line 字内容:
D3 6D 3A 19
(按大端顺序看,4 字节依次是 0xD3,0x6D,0x3A,0x19)。 - 块内偏移 CO=2 ⇒ 返回第 2 号字节(从 0 开始编号:0→D3,1→6D,2→3A,3→19) ⇒ 返回
0x3A
。
- 结果填写
参数 | 值 | 参数 | 值 |
---|---|---|---|
CO | 0x2 | 物理地址 | 0x96E |
CI | 0x3 | Cache命中? | 是 |
CT | 0x4B | 返回的 Cache 字节 | 0x3A |