Skip to content

Commit 9de77f0

Browse files
authored
Merge pull request #5 from dramforever/ch3-read-linux
ch3: Add Linux code reading exercise
2 parents d9d662d + 31346f4 commit 9de77f0

File tree

2 files changed

+48
-0
lines changed

2 files changed

+48
-0
lines changed

source/chapter3/5exercise.rst

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,42 @@
3535
12. `*` 上下文切换为什么需要用汇编语言实现?
3636
13. `*` 有哪些可能的时机导致任务切换?
3737
14. `**` 在设计任务控制块时,为何采用分离的内核栈和用户栈,而不用一个栈?
38+
15. `***` 我们已经在 rCore 里实现了不少操作系统的基本功能:特权级、上下文切换、系统调用……为了让大家对相关代码更熟悉,我们来以另一个操作系统为例,比较一下功能的实现。看看换一段代码,你还认不认识操作系统。
39+
40+
阅读 Linux 源代码,特别是 ``riscv`` 架构相关的代码,回答以下问题:
41+
42+
1. Linux 正常运行的时候, ``stvec`` 指向哪个函数?是哪段代码设置的 ``stvec`` 的值?
43+
2. Linux 里进行上下文切换的函数叫什么?(对应 rCore 的 ``__switch`` )
44+
3. Linux 里,和 rCore 中的 ``TrapContext`` 和 ``TaskContext`` 这两个类型大致对应的结构体叫什么?
45+
4. Linux 在内核态运行的时候, ``tp`` 寄存器的值有什么含义? ``sscratch`` 的值是什么?
46+
5. Linux 在用户态运行的时候, ``sscratch`` 的值有什么含义?
47+
6. Linux 在切换到内核态的时候,保存了和用户态程序相关的什么状态?
48+
7. Linux 在内核态的时候,被打断的用户态程序的寄存器值存在哪里?在 C 代码里如何访问?
49+
8. Linux 是如何根据系统调用编号找到对应的函数的?(对应 rCore 的 ``syscall::syscall()`` 函数的功能)
50+
9. Linux 用户程序调用 ``ecall`` 的参数是怎么传给系统调用的实现的?系统调用的返回值是怎样返回给用户态的?
51+
52+
阅读代码的时候,可以重点关注一下如下几个文件,尤其是第一个 ``entry.S`` ,当然也可能会需要读到其它代码:
53+
54+
* ``arch/riscv/kernel/entry.S`` (与 rCore 的 ``switch.S`` 对比)
55+
* ``arch/riscv/include/asm/current.h``
56+
* ``arch/riscv/include/asm/processor.h``
57+
* ``arch/riscv/include/asm/switch_to.h``
58+
* ``arch/riscv/kernel/process.c``
59+
* ``arch/riscv/kernel/syscall_table.c``
60+
* ``arch/riscv/kernel/traps.c``
61+
* ``include/linux/sched.h``
62+
63+
此外,推荐使用 https://elixir.bootlin.com 阅读 Linux 源码,方便查找各个函数、类型、变量的定义及引用情况。
64+
65+
一些提示:
66+
67+
* Linux 支持各种架构,查找架构相关的代码的时候,请认准文件名中的 ``arch/riscv`` 。
68+
* 为了同时兼容 RV32 和 RV64,Linux 在汇编代码中用了几个宏定义。例如, ``REG_L`` 在 RV32 上是 ``lw`` ,而在 RV64 上是 ``ld`` 。同理, ``REG_S`` 在 RV32 上是 ``sw`` ,而在 RV64 上是 ``sd`` 。
69+
* 如果看到 ``#ifdef CONFIG_`` 相关的预处理指令,是 Linux 根据编译时的配置启用不同的代码。一般阅读代码时,要么比较容易判断出这些宏有没有被定义,要么其实无关紧要。比如,Linux 内核确实应该和 rCore 一样,是在 S-mode 运行的,所以 ``CONFIG_RISCV_M_MODE`` 应该是没有启用的。
70+
* 汇编代码中可能会看到有些 ``TASK_`` 和 `PT_` 开头的常量,找不到定义。这些常量并没有直接写在源码里,而是自动生成的。
71+
72+
在汇编语言中需要用到的很多 ``struct`` 里偏移量的常量定义可以在 ``arch/riscv/kernel/asm-offsets.c`` 文件里找到。其中, ``OFFSET(NAME, struct_name, field)`` 指的是 ``NAME`` 的值定义为 ``field`` 这一项在 ``struct_name`` 结构体里,距离结构体开头的偏移量。最终这些代码会生成 ``asm/asm-offsets.h`` 供汇编代码使用。
73+
* ``#include <asm/unistd.h>`` 在 ``arch/riscv/include/uapi/asm/unistd.h`` , ``#include <asm-generic/unistd.h>`` 在 ``include/uapi/asm-generic/unistd.h`` 。
3874

3975
.. chyyuu:任务与进程,类似青蛙生长过程中的蝌蚪与青蛙的区别与联系。
4076

source/chapter3/6answer.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,15 @@
7171
用户程序可以任意修改栈指针,将其指向任意位置,而内核在运行的时候总希望在某一个合法的栈上,所以需要用分开的两个栈。
7272

7373
此外,利用后面的章节的知识可以保护内核和用户栈,让用户无法读写内核栈上的内容,保证安全。
74+
75+
15. `***` (以下答案以 Linux 5.17 为准)
76+
77+
1. ``arch/riscv/kernel/entry.S`` 里的 ``handle_exception`` ; ``arch/riscv/kernel/head.S`` 里的 ``setup_trap_vector``
78+
2. ``arch/riscv/kernel/entry.S`` 里的 ``__switch_to``
79+
3. ``TrapContext`` 对应 ``pt_regs`` ; ``TaskContext`` 对应 ``task_struct`` (在 ``task_struct`` 中也包含一些其它的和调度相关的信息)
80+
4. ``tp`` 指向当前被打断的任务的 ``task_struct`` (参见 ``arch/riscv/include/asm/current.h`` 里的宏 ``current`` ); ``sscratch`` 是 ``0``
81+
5. ``sscratch`` 指向当前正在运行的任务的 ``task_struct`` ,这样设计可以用来区分异常来自用户态还是内核态。
82+
6. 所有通用寄存器, ``sstatus``, ``sepc``, ``scause``
83+
7. 内核栈底; ``arch/riscv/include/asm/processor.h`` 里的 ``task_pt_regs`` 宏
84+
8. ``arch/riscv/kernel/syscall_table.c`` 里的 ``sys_call_table`` 作为跳转表,根据系统调用编号调用。
85+
9. 从保存的 ``pt_regs`` 中读保存的 ``a0`` 到 ``a7`` 到机器寄存器里,这样系统调用实现的 C 函数就会作为参数接收到这些值,返回值是将返回的 ``a0`` 写入保存的 ``pt_regs`` ,然后切换回用户态的代码负责将其“恢复”到 ``a0``

0 commit comments

Comments
 (0)