ELF

ELF 是一种用于于可执行文件、目标文件和库的文件格式。其包含三种文件类型:可重定位文件,可执行文件和共享对象文件。

结构可视化:

ELF 文件由以下三部分组成:

  • ELF 头 (ELF header) - 描述文件的主要特性,如文件类型、入口地址、段表偏移、节表偏移等。
  • 程序头表 (Program header table) - 描述程序的段信息,如段的类型、大小、权限、位置等。
  • 节头表 (Section header table) - 描述程序的节信息,如节的名称、类型、大小、位置等。

ELF 文件提供了两种视图,分别是链接视图和执行视图:

  • 链接视图是以节(section)为单位,用于静态链接器(如ld)处理目标文件或共享目标文件。
  • 执行视图是以段(segment)为单位,用于动态链接器(如/lib64/ld-linux-x86-64.so.2)加载和执行可执行文件或共享目标文件。

MIPS 内存布局

我们需要了解 MIPS 的内核布局,理解地址空间。这对于以后的实验大有裨益。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// /include/mmu.h 文件内容。这个图很重要,需要经常看,最好记下来。
/*
o 4G -----------> +----------------------------+------------0x100000000
o | ... | kseg2
o KSEG2 -----> +----------------------------+------------0xc000 0000
o | Devices | kseg1
o KSEG1 -----> +----------------------------+------------0xa000 0000
o | Invalid Memory | /|\
o +----------------------------+----|-------Physical Memory Max
o | ... | kseg0
o KSTACKTOP-----> +----------------------------+----|-------0x8040 0000-------end
o | Kernel Stack | | KSTKSIZE /|\
o +----------------------------+----|------ |
o | Kernel Text | | PDMAP
o KERNBASE -----> +----------------------------+----|-------0x8001 0000 |
o | Exception Entry | \|/ \|/
o ULIM -----> +----------------------------+------------0x8000 0000-------
o | User VPT | PDMAP /|\
o UVPT -----> +----------------------------+------------0x7fc0 0000 |
o | pages | PDMAP |
o UPAGES -----> +----------------------------+------------0x7f80 0000 |
o | envs | PDMAP |
o UTOP,UENVS -----> +----------------------------+------------0x7f40 0000 |
o UXSTACKTOP -/ | user exception stack | BY2PG |
o +----------------------------+------------0x7f3f f000 |
o | | BY2PG |
o USTACKTOP ----> +----------------------------+------------0x7f3f e000 |
o | normal user stack | BY2PG |
o +----------------------------+------------0x7f3f d000 |
a | | |
a ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
a . . |
a . . kuseg
a . . |
a |~~~~~~~~~~~~~~~~~~~~~~~~~~~~| |
a | | |
o UTEXT -----> +----------------------------+------------0x0040 0000 |
o | reserved for COW | BY2PG |
o UCOW -----> +----------------------------+------------0x003f f000 |
o | reversed for temporary | BY2PG |
o UTEMP -----> +----------------------------+------------0x003f e000 |
o | invalid memory | \|/
a 0 ------------> +----------------------------+ ----------------------------
o
*/
  • kuseg:占低 2GB 的空间(0x00000000 - 0x7FFFFFFF),是用户态唯一可用的地址空间。这部分的地址都需要经过 MMU 进行物理地址的转换。并且对于这段地址的访问都会经过 cache
  • kseg0:占据 512MB 空间(0x80000000 - 0x9FFFFFFF)。MMU 将虚拟地址的最高位清零即得到物理地址。也就是说这段地址是连续映射到物理地址的低 512MB 空间的。对这段内存的存取都会经过cache
  • kseg1:占据 512MB 空间(0xA0000000 - 0xBFFFFFFF)。MMU 将虚拟地址的最高三位清零得到物理地址。这段地址也被连续映射到物理地址的低 512MB 空间。对这段内存的存取不会经过cache,往往使用 MMIO(Memory-Mapped I/O)技术来访问外设。
  • kseg2:这段占 1GB 空间(0xC0000000 - 0xFFFFFFFF)。MMU 需要通过 TLB 进行地址转换,并且对这段地址的访存都会经过 cache

注意,只有低 2GB 的空间是用户态可用的,kseg0-kseg1 都是仅内核态可用的地址空间。

在载入内核时,TLB 需要操作系统配置管理,因此可选的地址空间只有 kseg0kseg1kseg1 往往只有访问外设时才会用到,因此我们将内核的 .text.data.bss 段都放到 kseg0

在真实的系统中,bootloader 会在载入内核前进行 cache 的初始化工作。因此在使用 kseg1cache 已经被配置好了。

控制加载地址

我们是通过 Link Script 来控制内核的加载地址的。它记载了各个节应该如何映射到段,以及各个段应该被加载的位置。

在链接过程中,目标文件被看作是节的集合。最重要的三个段的意义如下:

  • .text:保存可执行文件的操作指令。
  • .data:保存已初始化的全局变量和静态变量。
  • .bss:保存未初始化的全局变量和静态变量。

printk

lab1 中,我们要实现一个 printk 函数(在 kern 中进行 print 的函数)。

我们需要读并理解以下三个文件:

  • kern/console.c:完成了向控制台写入、读取字符的功能。实际上是读取、写入一个特殊的内存。
  • kern/printk.c:实现了 printk。通过调用 vprintfmt 来输出字符。
  • lib/print.c:实现了格式化输出的主体逻辑。我们在课程中接触的输出函数都需要通过调用此函数来达到输出的目的。(如 printk,以及以后的 debugfprintf

lab1 中我们主要需要补全 lib/print.c 中的函数,这个实验很重要,是我们以后进行其他实验的基础。在补全过程中主要考察的是 C 语言基础能力。

这里提一嘴,C 语言基础能力在 OS 中是很重要的,无论是实验还是平时课下,都需要拥有良好的 C 语言基础才能够顺利通过。

异常码

include/err.h 中定义了许多异常码。在报错时需要明白自己是报的什么错误,才能更好的定位 bug

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// Unspecified or unknown problem
#define E_UNSPECIFIED 1 // 未知的错误

// Environment doesn't exist or otherwise cannot be used in requested action
#define E_BAD_ENV 2 // 进程不存在或无法在请求的操作中使用

// Invalid parameter
#define E_INVAL 3 // 不合法的参数(一般用于函数判断参数)

// Request failed due to memory shortage
#define E_NO_MEM 4 // 空间不足

// Invalid syscall number
#define E_NO_SYS 5 // 未知的系统调用号

// Attempt to create a new environment beyond the maximum allowed
#define E_NO_FREE_ENV 6 // 没有可分配的空闲进程

// Attempt to send to env that is not recving.
#define E_IPC_NOT_RECV 7 // 尝试发送到未接受的进程

// File system error codes -- only seen in user-level

// No free space left on disk
#define E_NO_DISK 8 // 没有空余磁盘块

// Too many files are open
#define E_MAX_OPEN 9 // 打开文件过多

// File or block not found
#define E_NOT_FOUND 10 // 未找到磁盘块

// Bad path
#define E_BAD_PATH 11 // 错误的路径

// File already exists
#define E_FILE_EXISTS 12 // 文件已存在

// File not a valid executable
#define E_NOT_EXEC 13 // 文件不是可执行程序

string 函数

位于 include/string.h 下的头文件定义了一系列 C 语言函数,并于 lib/string.c 中实现。我们在课下也可以实现更多的字符处理函数,为以后的 lab 提供遍历。

函数的具体实现可以自己写也可以在网上找 C 标准库的源码。我个人比较推荐实现的函数有:

  • strcat
  • atoi
  • strncmp

这东西没事多扩充实现一点,将来在做挑战性任务的时候总会轻松一些的…