Skip to content

Commit ecb1332

Browse files
author
田凯夫
committed
Update ch1 answer
1 parent f5cf295 commit ecb1332

File tree

2 files changed

+60
-18
lines changed

2 files changed

+60
-18
lines changed

source/chapter1/7exercise.rst

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,13 @@
2323

2424
1. `*` 应用程序在执行过程中,会占用哪些计算机资源?
2525
2. `*` 请用相关工具软件分析并给出应用程序A的代码段/数据段/堆/栈的地址空间范围。
26-
3. `*` 请用分析并给出应用程序C的代码段/数据段/堆/栈的地址空间范围。
27-
4. `*` 请结合编译器的知识和编写的应用程序B,说明应用程序B是如何建立调用栈链信息的。
28-
5. `*` 请简要说明应用程序与操作系统的异同之处。
29-
6. `**` 请基于QEMU模拟RISC—V的执行过程和QEMU源代码,说明RISC-V硬件加电后的几条指令在哪里?完成了哪些功能?
30-
7. `*` RISC-V中的SBI的含义和功能是啥?
31-
8. `**` 为了让应用程序能在计算机上执行,操作系统与编译器之间需要达成哪些协议?
32-
9. `**` 请简要说明从QEMU模拟的RISC-V计算机加电开始运行到执行应用程序的第一条指令这个阶段的执行过程。
33-
10. `**` 为何应用程序员编写应用时不需要建立栈空间和指定地址空间?
34-
11. `***` 现代的很多编译器生成的代码,默认情况下不再严格保存/恢复栈帧指针。在这个情况下,我们只要编译器提供足够的信息,也可以完成对调用栈的恢复。
26+
3. `*` 请简要说明应用程序与操作系统的异同之处。
27+
4. `**` 请基于QEMU模拟RISC—V的执行过程和QEMU源代码,说明RISC-V硬件加电后的几条指令在哪里?完成了哪些功能?
28+
5. `*` RISC-V中的SBI的含义和功能是啥?
29+
6. `**` 为了让应用程序能在计算机上执行,操作系统与编译器之间需要达成哪些协议?
30+
7. `**` 请简要说明从QEMU模拟的RISC-V计算机加电开始运行到执行应用程序的第一条指令这个阶段的执行过程。
31+
8. `**` 为何应用程序员编写应用时不需要建立栈空间和指定地址空间?
32+
9. `***` 现代的很多编译器生成的代码,默认情况下不再严格保存/恢复栈帧指针。在这个情况下,我们只要编译器提供足够的信息,也可以完成对调用栈的恢复。
3533

3634
我们可以手动阅读汇编代码和栈上的数据,体验一下这个过程。例如,对如下两个互相递归调用的函数:
3735

source/chapter1/8answer.rst

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -132,11 +132,43 @@
132132
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
133133

134134
1. `*` 应用程序在执行过程中,会占用哪些计算机资源?
135+
136+
占用 CPU 计算资源(CPU 流水线,缓存等),内存(内存不够还会占用外存)等
137+
135138
2. `*` 请用相关工具软件分析并给出应用程序A的代码段/数据段/堆/栈的地址空间范围。
136-
3. `*` 请用分析并给出应用程序C的代码段/数据段/堆/栈的地址空间范围。
137-
4. `*` 请结合编译器的知识和编写的应用程序B,说明应用程序B是如何建立调用栈链信息的。
138-
5. `*` 请简要说明应用程序与操作系统的异同之处。
139-
6. `**` 请基于QEMU模拟RISC—V的执行过程和QEMU源代码,说明RISC-V硬件加电后的几条指令在哪里?完成了哪些功能?
139+
140+
简便起见,我们静态编译该程序生成可执行文件。使用 ``readelf`` 工具查看地址空间:
141+
142+
..
143+
Section Headers:
144+
[Nr] Name Type Address Offset
145+
Size EntSize Flags Link Info Align
146+
...
147+
[ 7] .text PROGBITS 00000000004011c0 000011c0
148+
0000000000095018 0000000000000000 AX 0 0 64
149+
...
150+
[10] .rodata PROGBITS 0000000000498000 00098000
151+
000000000001cadc 0000000000000000 A 0 0 32
152+
...
153+
[21] .data PROGBITS 00000000004c50e0 000c40e0
154+
00000000000019e8 0000000000000000 WA 0 0 32
155+
...
156+
[25] .bss NOBITS 00000000004c72a0 000c6290
157+
0000000000005980 0000000000000000 WA 0 0 32
158+
159+
数据段(.data)和代码段(.text)的起止地址可以从输出信息中看出。
160+
161+
应用程序的堆栈是由内核为其动态分配的,需要在运行时查看。将 A 程序置于后台执行,通过查看 ``/proc/[pid]/maps`` 得到堆栈空间的分布:
162+
163+
..
164+
01bc9000-01beb000 rw-p 00000000 00:00 0 [heap]
165+
7ffff8e60000-7ffff8e82000 rw-p 00000000 00:00 0 [stack]
166+
167+
3. `*` 请简要说明应用程序与操作系统的异同之处。
168+
169+
这个问题相信大家完成了实验的学习后一定会有更深的理解。
170+
171+
4. `**` 请基于QEMU模拟RISC—V的执行过程和QEMU源代码,说明RISC-V硬件加电后的几条指令在哪里?完成了哪些功能?
140172

141173
在 QEMU 源码 [#qemu_bootrom]_ 中可以找到“上电”的时候刚执行的几条指令,如下:
142174

@@ -167,11 +199,23 @@
167199
- (我们还没有用到:将 FDT (Flatten device tree) 在物理内存中的地址写入 ``a1``)
168200
- 跳转到 ``start_addr`` ,在我们实验中是 RustSBI 的地址
169201

170-
7. `*` RISC-V中的SBI的含义和功能是啥?
171-
8. `**` 为了让应用程序能在计算机上执行,操作系统与编译器之间需要达成哪些协议?
172-
9. `**` 请简要说明从QEMU模拟的RISC-V计算机加电开始运行到执行应用程序的第一条指令这个阶段的执行过程。
173-
10. `**` 为何应用程序员编写应用时不需要建立栈空间和指定地址空间?
174-
11. `***` 现代的很多编译器生成的代码,默认情况下不再严格保存/恢复栈帧指针。在这个情况下,我们只要编译器提供足够的信息,也可以完成对调用栈的恢复。(题目剩余部分省略)
202+
5. `*` RISC-V中的SBI的含义和功能是啥?
203+
204+
详情见 `SBI 官方文档 <https://github.com/riscv-non-isa/riscv-sbi-doc/blob/master/riscv-sbi.adoc>`_
205+
206+
6. `**` 为了让应用程序能在计算机上执行,操作系统与编译器之间需要达成哪些协议?
207+
208+
编译器依赖操作系统提供的程序库,操作系统执行应用程序需要编译器提供段位置、符号表、依赖库等信息。 `ELF <https://en.wikipedia.org/wiki/Executable_and_Linkable_Format>`_ 就是比较常见的一种文件格式。
209+
210+
7. `**` 请简要说明从QEMU模拟的RISC-V计算机加电开始运行到执行应用程序的第一条指令这个阶段的执行过程。
211+
212+
接第 5 题,跳转到 RustSBI 后,SBI 会对部分硬件例如串口等进行初始化,然后通过 mret 跳转到 payload 也就是 kernel 所在的起始地址。kernel 进行一系列的初始化后(内存管理,虚存管理,线程(进程)初始化等),通过 sret 跳转到应用程序的第一条指令开始执行。
213+
214+
8. `**` 为何应用程序员编写应用时不需要建立栈空间和指定地址空间?
215+
216+
应用程度对内存的访问需要通过 MMU 的地址翻译完成,应用程序运行时看到的地址和实际位于内存中的地址是不同的,栈空间和地址空间需要内核进行管理和分配。应用程序的栈指针在 trap return 过程中初始化。此外,应用程序可能需要动态加载某些库的内容,也需要内核完成映射。
217+
218+
9. `***` 现代的很多编译器生成的代码,默认情况下不再严格保存/恢复栈帧指针。在这个情况下,我们只要编译器提供足够的信息,也可以完成对调用栈的恢复。(题目剩余部分省略)
175219

176220
* 首先,我们当前的 ``pc`` 在 ``flip`` 函数的开头,这是我们正在运行的函数。返回给调用者处的地址在 ``ra`` 寄存器里,是 ``0x10742`` 。因为我们还没有开始操作栈指针,所以调用处的 ``sp`` 与我们相同,都是 ``0x40007f1310`` 。
177221
* ``0x10742`` 在 ``flap`` 函数内。根据 ``flap`` 函数的开头可知,这个函数的栈帧大小是 16 个字节,所以调用者处的栈指针应该是 ``sp + 16 = 0x40007f1320``。调用 ``flap`` 的调用者返回地址保存在栈上 ``8(sp)`` ,可以读出来是 ``0x10750`` ,还在 ``flap`` 函数内。

0 commit comments

Comments
 (0)