通过调试一个简单的多任务内核实例,使大家可以熟练的掌握调试系统内核的方法。 这个内核示例中包含两个特权级 3 的用户任务和一个系统调用中断过程。我们首先说明这个简单内核的基本结构和加载运行的基本原理,然后描述它是如何被加载进机器 RAM 内存中以及两个任务是如何进行切换运行的。
设置断点 (b): 可以在指定的内存地址处设置断点,当程序执行到该地址时,会中断执行,方便用户进行调试。
显示断点状态 (blist): 可以查看当前所有断点的状态,包括断点地址和是否启用。
读/写断点 (unwatch/watch): 可以设置或清除读或写的断点,当指定内存地址被读取或写入时,会触发断点中断。
显示寄存器状态 (trace-reg on): 在单步调试时,会显示每个寄存器的状态,帮助用户了解程序执行的过程。
打印当前堆栈 (print-stack): 可以打印当前的堆栈信息,帮助用户跟踪函数调用和返回。
继续向下执行 ( c): 用于继续执行程序,直到遇到下一个断点或程序结束。
单步执行 (s): 逐步执行程序,一次执行一条指令,方便用户逐行跟踪代码执行流程。
查看寄存器信息 (info cpu/r/sreg/creg): 可以查看CPU寄存器、段寄存器和控制寄存器的信息,帮助用户了解当前的CPU状态。
查看内存内容 (xp /nuf): 可以查看指定内存地址处的内容,用户可以指定显示的单元数量、单元大小和显示格式。支持十六进制、十进制、无符号十进制、八进制和二进制等不同的显示格式。
反汇编指定范围的内存 (u): 可以对指定范围的内存进行反汇编,帮助用户查看内存中的指令内容。
若从task1中调用int 0x80 进入 system_interrupt,执行iret之前,栈底地址为0x10e0,即目前使用krn_stk1

栈中的内容:
| 地址 | 值 | 意义 |
|---|---|---|
| 0x10CC | 0x10ff | 代码指针(即eip) |
| 0x10D0 | 0x000f | 当前局部空间代码段选择符(即cs) |
| 0x10D4 | 0x0202 | 标志寄存器值(即eflags) |
| 0x10D8 | 0x1308 | 堆栈指针(即esp) |
| 0x10DC | 0x0017 | 当前局部空间数据段(堆栈段)选择符(即ds) |
| 0x10e0 | 0x17b8 | 栈底 |
若从task1中调用int 0x80 进入 system_interrupt,执行iret之后,栈底指针为0x1308,即目前使用usr_stk1。同时,栈底指针与执行iret前栈内0x10D8处的值相等,证明此处存储堆栈指针。

栈中的内容:
| 地址 | 值 | 意义 |
|---|---|---|
| 0x1308 | 0x0000 | 栈底 |
若从task0中调用int 0x80 进入 system_interrupt,执行iret之前,栈底指针为0x0E60,即目前使用krn_stk0

栈中的内容:
| 地址 | 值 | 意义 |
|---|---|---|
| 0x0E4C | 0x10eb | 代码指针(即eip) |
| 0x0E50 | 0x000f | 当前局部空间代码段选择符(即cs) |
| 0x0E54 | 0x0246 | 标志寄存器值(即eflags) |
| 0x0E58 | 0x0bd8 | 堆栈指针(即esp) |
| 0x0E5C | 0x0017 | 当前局部空间数据段(堆栈段)选择符(即ds) |
| 0x0E60 | 0x0000 | 栈底 |
若从task0中调用int 0x80 进入 system_interrupt,执行iret之后,栈底指针为0x0BD8,即目前使用init_stack。同时,栈底指针与执行iret前栈内0x0E58处的值相等,证明此处存储堆栈指针。

栈中的内容:
| 地址 | 值 | 意义 |
|---|---|---|
| 0x0BD8 | 0x0bd8 | 栈底 |

因此,通过int指令和iret指令完成的模式切换和特权级的切换。此外,这种切换也是为了确保程序的正确执行和系统的稳定运行。
imer_interrupt 处理程序内,执行ljmp指令从任务0切换至任务1之前,tss0的状态。

timer_interrupt 处理程序内,执行ljmp指令从任务0切换至任务1之后,tss0的状态。

查找得到tss1的地址并加载各寄存器的值: 在切换到任务 1 之前,需要通过 $TSS1_SEL 选择符从 gdt 中查找得到 tss1 的地址。然后从 tss1 中获取任务 1 的各寄存器值,并将这些值加载进各寄存器中。这个操作是为了恢复任务 1 的上下文。
timer_interrupt 处理程序内,执行ljmp指令从任务0切换至任务1之前,tss1以及当前各个寄存器的状态。

timer_interrupt 处理程序内,执行ljmp指令从任务0切换至任务1之后,tss1以及当前各个寄存器的状态。

在 timer_interrupt 处理程序内,执行ljmp指令从任务1切换至任务0之前,tss1的状态。(注:0xe78至0xedf为tss1)

在 timer_interrupt 处理程序内,执行ljmp指令从任务1切换至任务0之后,tss1的状态。

在 timer_interrupt 处理程序内,执行ljmp指令从任务1切换至任务0之前,tss0以及当前各个寄存器的状态。(注:0xbf8至0xc5f为tss0)

在 timer_interrupt 处理程序内,执行ljmp指令从任务1切换至任务0之后,tss0以及当前各个寄存器的状态。

任务切换过程是通过保存和恢复任务的上下文来实现的。在保存上下文时,需要将各寄存器的状态和值存储到tss中。在恢复上下文时,需要从tss中获取各寄存器的值并将其加载到相应的寄存器中。这样就可以实现在一个任务执行过程中,能够在不同任务之间进行切换。