Linux内核源码阅读--kernel(一)
上一篇文章说到 main.c 完成了所有的初始化并打开了登录 shell,现在就来具体分析每个初始化函数的实现细节。先整个 trap_init (main.c Line 128) 函数看看,该函数主要涉及 kernel 目录下的这些文件:
- asm.s
- trap.c
- system_call.s
首先,trap_init 是定义在 trap.c 第 181 行的,该函数设置了各硬件异常的处理中断向量,set_trap_gate 与 set_system_gate 的区别在于前者设置的特权级为 0,后者为 3。因此由 set_system_gate 设置的 3,4,5 号陷阱门可以由任何程序调用。这两个函数的第一个参数是中断号,第二个参数是中断预处理程序的地址。trap_init 中涉及的中断处理程序除了 page_fault 定义在 mm/page.s 之外,其余都定义在 kernel 目录下的 asm.s 与 system_call.s 中
1 | // trap.c Line 181 |
set_trap_gate 与 set_system_gate 定义在 include/asm/system.h 中,它们都调用了 _set_gate 用于将中断描述符填入对应的 IDT 表项中
1 | // include/asm/system.h Line 22 |
以上是填写 IDT 中断描述符的过程,现在来看这些中断服务程序是怎么实现的,先看 asm.s。因为有的中断过程会产生错误号,故不产生错误号与产生错误号的中断过程预处理方式有一些区别,如图所示(上为高地址):
以没有错误号为例,int 指令会将 ss esp eflags cs eip 依次压入栈中,然后预处理程序压入各寄存器保存现场,同时将真正的中断服务程序的地址赋给 eax,再将 0 压入栈中来占位,最后压入一个指向中断返回地址的指针
asm.s
以下为 asm.s 中无错误号的中断预处理程序:
1 | /* Line 14 */ |
然后是关于数学协处理器的硬件中断处理,coprocessor_error 定义在 system_call.s 中
1 | /* Line 85 */ |
asm.s 最后部分是对于有错误号中断的处理
1 | double_fault: /* 8 号中断 */ |
system_call.s
1 | /* Line 33 */ |
nr_system_calls 表示提供的系统调用总数。Linux 系统对外仅提供 int 0x80 指令来调用系统中断服务程序,eax 中为中断号,该中断号不能超出 85(nr_system_calls 从 1 计数),ebx,ecx,edx 用于传递参数
1 | /* Line 67 */ |
sys_call_table 数组定义在 linux/sys.h 中,每个元素对应其中断服务程序的入口
1 | // linux/sys.h Line 93 |
然后是 system_call.s 的剩余部分
1 | /* Line 130 */ |
traps.c
首先包含了一些头文件,定义了一些函数原型
1 | // Line 13 |
接着定义了三个宏,分别用于从段指定位置中读取 1 个字节、读取 4 个字节和取 fs 段寄存器的值
1 | // Line 22 |
然后定义一个 die 函数,用于输出中断/异常的信息,其三个参数分别为错误名称的字符串指针,出错而被中断的程序在栈中信息的指针(第一张图中的 esp0)及错误码。printk 定义在 kernel/printk.c 中,内核态下不能使用 printf,因其会改变 fs 的值,所以新定义了一个 printk 先将 fs 的值保存。
1 | static void die(char * str,long esp_ptr,long nr) |
紧接着定义 asm.s 中调用的那些 do 开头的中断处理程序,这些处理程序都通过 die 来输出信息
1 | // Line 87 |
其中有一个例外,那就是 int 3 的处理程序,它的参数是进入中断后压入栈中的寄存器值,作用是输出寄存器值等信息方便调试
1 | void do_int3(long * esp, long error_code, |