#import "template.typ": *

#show: project.with(
  title: "应用程序与基本执行环境"
)

= 操作系统启动流程

== 第一阶段
加电后 PC 寄存器（Program Counter；用于执行汇编指令）被设置为 ROM 的物理地址。随后会运行 ROM 当中存储的程序，即固件（Firmware）。固件被用于初始化 CPU 状态，如各寄存器的初始值，以及从硬盘中加载 bootloader 程序。最后将 PC 设置为 bootloader 程序的地址，并跳转以转移控制权。

== 第二阶段
bootloader 和固件所做的事情相似。它同样会初始化一些寄存器状态，并将操作系统代码从硬盘中取出，最后把 PC 寄存器设置为操作系统地址，移交控制权。

== 第三阶段
操作系统将会承担后续控制步骤。

= 程序内存布局与编译流程

== 程序内存布局
#figure(
  image("figures/memory-layout.png", width: 70%),
  caption: [内存布局]
) <memory-layout>

一个程序被编译成可执行文件后，可执行文件大致分为两部分：代码与数据。代码为一条条可以被 CPU 执行的命令，数据则是一些可以被 CPU 读取的内存空间。在 @memory-layout 中，代码只占 `.text` 这一小部分，其余的部分全部为数据部分。数据部分可以根据功能被分为更小的单位：*段*（Section）。不同的段被放置在不同的位置上，这构成了程序的*内存布局*（Memory Layout）。

其中，@memory-layout 中的几个段分别用于以下几个功能：
- 程序当中以初始化的全局数据为 `.rodata` 与 `.data`。前者用于存放只读的全局数据，如常数、常量、字符串等。后者用于存放可修改的全局数据。
- 未初始化数据段 `.bss` 用于保存程序中未初始化的全局数据。通常 `.bss` 会被程序加载者进行零初始化，即将该区域字节全部设置为零。
- 堆（Heap）用于存放程序运行时动态存放的数据。它向高地址增长。
- 栈（Stack）用于存放函数调用时的上下文以及函数中的局部变量。它向低地址增长。

== 编译流程
从源代码到可执行文件的编译流程大致有以下几个阶段：
+ 编译器（Compiler）将每个源文件从某门高级编程语言转化为汇编语言，注意此时源文件仍然是一个 ASCII 或其他编码的文本文件。
+ 汇编器（Assembler）会将上一步生成文件中的汇编指令转化为机器码，得到一个二进制的目标文件（Object File）。
+ 链接器（Linker）会将上一步获得的所有目标文件以及一些外部的目标文件链接在一起形成一个完整的可执行文件。

#figure(
  image("figures/link-sections.png", width: 70%),
  caption: [链接器工作原理]
) <link-sections>

链接器会做如下几件事情：
+ 将来自不同目标文件的段在目标内存布局中重新排列，一般按照段功能排列。
+ 将符号替换为具体地址。在模块化编程中，我们会暴露给其他模块一些函数以及全局变量。他们的名字被称作符号。在源代码级别，我们只需要知道对应的符号即可访问对应的函数和变量。而在机器码级别，我们并不能通过符号来寻找对应的函数和变量。因此在链接过程中我们需要把符号转化为对应的地址。

