ESP指向:在进程调度模块中,将会用到堆栈,而寄存器被压栈到进程表之后,esp指向PCB的某个位置——接下来的堆栈操作将破坏PCB。为了解决上述问题,需要将esp指向专门的内核栈。所以,在进程切换的过程中ESP的指向有三次:进程堆栈——PCB——内核栈。
特权级变换:从外层到内层次,从TSS中取得SS:ESP;初始化的时候,从ring0>>>ring1,这个和恢复进程执行有点像,我们需要完成上下文的初始化,然后使用iretd指令来完成转移。
恢复:首先,我们需要从PCB中恢复寄存器的值,然后指令iretd,设置cs:ip和eflags,这样程序就回到了进程B。为了对从内核到进程的转化有一个感性的认识,我们来看一下转化时刻的代码:
chapter6/i/kernel/kernel.asm
; ====================================================================================
; restart
; ====================================================================================
restart:
mov esp, [p_proc_ready] ;esp 指向LPCB,在进程运行的时候就已经准备好了
lldt [esp + P_LDT_SEL]
lea eax, [esp + P_STACKTOP];eax=esp+P_stacktop
mov dword [tss + TSS3_S_SP0], eax ;这样以后,地址tss + TSS3_S_SP 处,存放的是ss0的地址
restart_reenter:
dec dword [k_reenter]
pop gs
pop fs
pop es
pop ds
popad
add esp, 4
iretd
31 typedef struct s_proc {
32 STACK_FRAME regs; /* process registers saved in stack frame */
33
34 u16 ldt_sel; /* gdt selector giving ldt base and limit */
35 DESCRIPTOR ldts[LDT_SIZE]; /* local descriptors for code and data */
36
37 int ticks; /* remained ticks */
38 int priority;
39
40 u32 pid; /* process id passed in from MM */
41 char p_name[16]; /* name of the process */
42 }PROCESS;
P_LDT_SEL表示的是ldt_sel的索引,来看看PCB总相关变量的一些列定义:
8 P_STACKBASE equ 0
9 GSREG equ P_STACKBASE
10 FSREG equ GSREG + 4
11 ESREG equ FSREG + 4
12 DSREG equ ESREG + 4
13 EDIREG equ DSREG + 4
14 ESIREG equ EDIREG + 4
15 EBPREG equ ESIREG + 4
16 KERNELESPREG equ EBPREG + 4
17 EBXREG equ KERNELESPREG + 4
18 EDXREG equ EBXREG + 4
19 ECXREG equ EDXREG + 4
20 EAXREG equ ECXREG + 4
21 RETADR equ EAXREG + 4
22 EIPREG equ RETADR + 4
23 CSREG equ EIPREG + 4
24 EFLAGSREG equ CSREG + 4
25 ESPREG equ EFLAGSREG + 4
26 SSREG equ ESPREG + 4
27 P_STACKTOP equ SSREG + 4
28 P_LDT_SEL equ P_STACKTOP
29 P_LDT equ P_LDT_SEL + 4
30
31 TSS3_S_SP0 equ 4
好了,看到这里,或许你已经明白了PCB结构,如下:
tss的相关定义:35 typedef struct s_tss {
36 u32 backlink;
37 u32 esp0; /* stack pointer to use during interrupt */
38 u32 ss0; /* " segment " " " " */
....
....
}TSS
73 /* 初始化 8253 PIT */
74 out_byte(TIMER_MODE, RATE_GENERATOR);
75 out_byte(TIMER0, (u8) (TIMER_FREQ/HZ) );
76 out_byte(TIMER0, (u8) ((TIMER_FREQ/HZ) >> 8));
77
78 put_irq_handler(CLOCK_IRQ, clock_handler); /* 设定时钟中断处理程序 */
79 enable_irq(CLOCK_IRQ); /* 让8259A可以接收时钟中断 */
80
81 restart();
82
83 while(1){}
150 ALIGN 16
151 hwint00: ; Interrupt routine for irq 0 (the clock).
152 iretd
接下来,我们来做这四个部分的初始化工作:
51 void TestA()
52 {
53 int i = 0;
54 while(1){
55 disp_str("A");
56 disp_int(i++);
57 disp_str(".");
58 delay(1);
59 }
60 }
思考一下,TestA仅仅是一个进程,而且是被中断调度的对象,显然不是内核的一部分。怎么将控制权转移到进程呢?在前面的章节中,kernel_main是内核函数,跳转过程:
9 typedef struct s_stackframe {
10 u32 gs; /* \ */
11 u32 fs; /* | */
12 u32 es; /* | */
13 u32 ds; /* | */
14 u32 edi; /* | */
15 u32 esi; /* | pushed by save() */
16 u32 ebp; /* | */
17 u32 kernel_esp; /* <- 'popad' will ignore it */
18 u32 ebx; /* | */
19 u32 edx; /* | */
20 u32 ecx; /* | */
21 u32 eax; /* / */
22 u32 retaddr; /* return addr for kernel.asm::save() */
23 u32 eip; /* \ */
24 u32 cs; /* | */
25 u32 eflags; /* | pushed by CPU during interrupt */
26 u32 esp; /* | */
27 u32 ss; /* / */
28 }STACK_FRAME;
29
30
31 typedef struct s_proc {
32 STACK_FRAME regs; /* process registers saved in stack frame */
33
34 u16 ldt_sel; /* gdt selector giving ldt base and limit */
35 DESCRIPTOR ldts[LDT_SIZE]; /* local descriptors for code and data */
36 u32 pid; /* process id passed in from MM */
37 char p_name[16]; /* name of the process */
38 }PROCESS;
知道了数据结构,再来看看它的初始化a/kernel/main.c
26 p_proc->ldt_sel = SELECTOR_LDT_FIRST;
27 memcpy(&p_proc->ldts[0], &gdt[SELECTOR_KERNEL_CS>>3], sizeof(DESCRIPTOR));
28 p_proc->ldts[0].attr1 = DA_C | PRIVILEGE_TASK << 5; // change the DPL
29 memcpy(&p_proc->ldts[1], &gdt[SELECTOR_KERNEL_DS>>3], sizeof(DESCRIPTOR));
30 p_proc->ldts[1].attr1 = DA_DRW | PRIVILEGE_TASK << 5; // change the DPL
31
32 p_proc->regs.cs = (0 & SA_RPL_MASK & SA_TI_MASK) | SA_TIL | RPL_TASK;
33 p_proc->regs.ds = (8 & SA_RPL_MASK & SA_TI_MASK) | SA_TIL | RPL_TASK;
34 p_proc->regs.es = (8 & SA_RPL_MASK & SA_TI_MASK) | SA_TIL | RPL_TASK;
35 p_proc->regs.fs = (8 & SA_RPL_MASK & SA_TI_MASK) | SA_TIL | RPL_TASK;
36 p_proc->regs.ss = (8 & SA_RPL_MASK & SA_TI_MASK) | SA_TIL | RPL_TASK;
37 p_proc->regs.gs = (SELECTOR_KERNEL_GS & SA_RPL_MASK) | RPL_TASK;
38 p_proc->regs.eip= (u32)TestA;
39 p_proc->regs.esp= (u32) task_stack + STACK_SIZE_TOTAL;
40 p_proc->regs.eflags = 0x1202; // IF=1, IOPL=1, bit 2 is always 1.
41
42 p_proc_ready = proc_table;
43 restart();
其中,上面用到的宏定义在protect.h之中,参考:a/include/protect.h
67 #define INDEX_DUMMY 0 /* \ */
68 #define INDEX_FLAT_C 1 /* | LOADER 里面已经确定了的 */
69 #define INDEX_FLAT_RW 2 /* | */
70 #define INDEX_VIDEO 3 /* / */
71 #define INDEX_TSS 4
72 #define INDEX_LDT_FIRST 5
73 /* 选择子 */
74 #define SELECTOR_DUMMY 0 /* \ */
75 #define SELECTOR_FLAT_C 0x08 /* | LOADER 里面已经确定了的 */
76 #define SELECTOR_FLAT_RW 0x10 /* | */
77 #define SELECTOR_VIDEO (0x18+3)/* /<-- RPL=3 */
78 #define SELECTOR_TSS 0x20 /* TSS */
79 #define SELECTOR_LDT_FIRST 0x28
80
81 #define SELECTOR_KERNEL_CS SELECTOR_FLAT_C
82 #define SELECTOR_KERNEL_DS SELECTOR_FLAT_RW
83 #define SELECTOR_KERNEL_GS SELECTOR_VIDEO
84
85 /* 每个任务有一个单独的 LDT, 每个 LDT 中的描述符个数: */
86 #define LDT_SIZE 2
87
88 /* 选择子类型值说明 */
89 /* 其中, SA_ : Selector Attribute */
90 #define SA_RPL_MASK 0xFFFC
91 #define SA_RPL0 0
92 #define SA_RPL1 1
93 #define SA_RPL2 2
94 #define SA_RPL3 3
95
96 #define SA_TI_MASK 0xFFFB
97 #define SA_TIG 0
98 #define SA_TIL 4
填充GDT中进程LDT的描述符:a/kernel/protect.c
109 init_descriptor(&gdt[INDEX_LDT_FIRST],
110 vir2phys(seg2phys(SELECTOR_KERNEL_DS), proc_table[0].ldts),
111 LDT_SIZE * sizeof(DESCRIPTOR) - 1,
112 DA_LDT);
这个函数的实现:
149 PRIVATE void init_descriptor(DESCRIPTOR *p_desc,u32 base,u32 limit,u16 attribute)
150 {
151 p_desc->limit_low = limit & 0x0FFFF;
152 p_desc->base_low = base & 0x0FFFF;
153 p_desc->base_mid = (base >> 16) & 0x0FF;
154 p_desc->attr1 = attribute & 0xFF;
155 p_desc->limit_high_attr2= ((limit>>16) & 0x0F) | (attribute>>8) & 0xF0;
156 p_desc->base_high = (base >> 24) & 0x0FF;
157 }
3)准备GDT和TSS
99 /* 填充 GDT 中 TSS 这个描述符 */
100 memset(&tss, 0, sizeof(tss));
101 tss.ss0 = SELECTOR_KERNEL_DS;
102 init_descriptor(&gdt[INDEX_TSS],
103 vir2phys(seg2phys(SELECTOR_KERNEL_DS), &tss),
104 sizeof(tss) - 1,
105 DA_386TSS);
106 tss.iobase = sizeof(tss); /* 没有I/O许可位图 */
下面,填写tr:
130 xor eax, eax
131 mov ax, SELECTOR_TSS
132 ltr ax
4.3iretd
294 restart:
295 mov esp, [p_proc_ready]
296 lldt [esp + P_LDT_SEL]
297 lea eax, [esp + P_STACKTOP]
298 mov dword [tss + TSS3_S_SP0], eax
299
300 pop gs
301 pop fs
302 pop es
303 pop ds
304 popad
305
306 add esp, 4
307
308 iretd
好了,使用iretd将加载CS:IP,想一想,CS和IP的值是多少?注意,编译以后的main.c中Test函数是位于32b代码段中,这个我们需要用反汇编研究一下。
我们原来的调试,都是在汇编程序的状态,如何按照C语言的行级别来调试内核呢?这里,我们挖一个坑,以后再回填这个地方。