kernel.asm操作系统内核程序调用start.c、protect.c、i8259.c设置好GDT、IDT、为了转入第一个进程,
调用main.c设置好LDT和进程表堆栈内容,执行restart();转入第一个进程。
其实很简单,在执行ret命令之前,只要把堆栈按照第三章说明的准备好eip、cs、esp、ss,
就可以跳转到指定的位置。进程表就是按照栈的结构定义的,把esp指向进程表地址就会把进程表变成栈。
准备tss应该是为了后续发生时钟中断准备的。
进程表结构如下:
typedef struct s_stackframe {
u32 gs; /* \ */
u32 fs; /* | */
u32 es; /* | */
u32 ds; /* | */
u32 edi; /* | */
u32 esi; /* | pushed by save() */
u32 ebp; /* | */
u32 kernel_esp; /* <- 'popad' will ignore it */
u32 ebx; /* | */
u32 edx; /* | */
u32 ecx; /* | */
u32 eax; /* / */
u32 retaddr; /* return addr for kernel.asm::save() */
u32 eip; /* \ */
u32 cs; /* | */
u32 eflags; /* | pushed by CPU during interrupt */
u32 esp; /* | */
u32 ss; /* / */
}STACK_FRAME;
typedef struct s_proc {
STACK_FRAME regs; /* process registers saved in stack frame */
u16 ldt_sel; /* gdt selector giving ldt base and limit */
DESCRIPTOR ldts[LDT_SIZE]; /* local descriptors for code and data */
u32 pid; /* process id passed in from MM */
char p_name[16]; /* name of the process */
}PROCESS;
; ====================================================================================
; restart
; ====================================================================================
restart:
mov esp, [p_proc_ready]
lldt [esp + P_LDT_SEL]
lea eax, [esp + P_STACKTOP]
mov dword [tss + TSS3_S_SP0], eax
;把本进程表的堆栈最高地址赋值给0级TSS的esp,本进程执行过程中如果发生中断,就会跳到0级堆栈位置,
内核程序在压栈的时候就会覆盖本进程的进程表内容,达到了保存本进程随时状态的功能。
pop gs
pop fs
pop es
pop ds
popad
add esp, 4
iretd
我们分析过,当进程被中断切到内核态,当前的各个寄存器应该被立即保存 (压栈)。也就是说,每个进程在运行时, tss.esp0应该是当前进程的进程表中保存寄存器值的地方,即struct s_proc中struct s_stackframe的最高地址处。这样,进程被挂起后才恰好保存寄存器到正确的位置。我们假设进程A在运行,那么tss.esp0的值应该是进程表A中regs的最高处,因为我们是不可能在进程A运行时来设置tss.esp0的值的,所以必须在A被恢复运行之前,即iretd执行之前做这件事。换句话说,我们应该在时钟中断处理结束之前做这件事。