原来进程是这样切换的
/***********************支持原创*************************/
下面这个结构就是最简陋不过的一个进程表(又称进程控制块,我更习惯于它)。
1 typedef struct process_struct
2 {
3 STACKFRAME registers;
4
5 int16 ldt_selector;
6 DESCRIPTOR ldt[LDT_SIZE];
7
8 int32 pid;
9 char pname[32];
10 }PROCESS;
这个结构的开始是另外一个结构STACKFRAME,用于当时钟中断发生时存储当前进程的各segment Registers和generator Registers,也就是书里面讲的“保护现场”。而DESCRIPTOR是又一个结构,这个结构用于保存本进程自己的局部描述符表的信息。下面我们就看看这两个结构:
代码
下面说说当时钟中断的时候首先会发生什么:1 typedef struct s_stackframe
2 {
3 int32 gs;
4 int32 fs;
5 int32 es;
6 int32 ds;
7 int32 edi;
8 int32 esi;
9 int32 ebp;
10 int32 kernel_esp; //popad指令会忽略它,详情见intel手册
11 int32 ebx;
12 int32 edx;
13 int32 ecx;
14 int32 eax;
15 int32 retaddr;
16 int32 eip;
17 int32 cs;
18 int32 eflags;
19 int32 esp;
20 int32 ss;
21 }STACKFRAME;
22
23 typedef struct s_descriptor
24 {
25 int8 limit_low1;
26 int8 limit_low2;
27 int8 base_low1;
28 int8 base_low2;
29 int8 base_high1;
30 int8 attr1;
31 int8 attr2_limit_high;
32 int8 base_high2;
33 }DESCRIPTOR;
1、假定在某个时刻,进程1在运行,突然发生了时钟中断,则系统将立马从ringx(x > 0)切换到ring0态。那么ring0态的堆栈寄存器从哪里得到的呢?
还有另外一个寄存器tr,它的名字叫任务状态段寄存器,它指向的一块内存区(TSS)种有你手工保存的ring0的ss、esp值,硬件就是自动从那里获得的。之后硬件便自动将ss、esp、eflags、cs、eip这五个寄存器的值压入刚获得的ring0的ss:esp指向的堆栈中,而这个堆栈指向的区域正是当前运行的进程的进程控制块的STACKFRAME结构的底部。因此保存完毕esp就指向了STACKFRAME结构的retaddr处。之后便进入中断处理程序。当然这个retaddr完全可以没有,只是在我实现的时候将保存generator registers的过程写成一个函数,因此这个地方保存这个函数的返回地址。
2、下面看一下中断后发生的事情:
1 align 16
2 hwint00:
3 hwint_master 0
代码
多余的不用管,可以看到调用了save()函数,这个函数的样子是这样的:
1 %macro hwint_master 1
2 call save
3 in al, INT_M_CTLMASK
4 or al, (1 << %1)
5 out INT_M_CTLMASK, al
6 mov al, EOI
7 out INT_M_CTL, al
8 sti
9 push %1
10 call [irq_table + 4 * %1]
11 pop ecx
12 cli
13 in al, INT_M_CTLMASK
14 and al, ~(1 << %1)
15 out INT_M_CTLMASK, al
16 ret
17 %endmacro
代码
这个函数就是保存各个Generator Registers寄存器的值,然后mov esp, stacktop这一句才真是切换到了内核态的堆栈,然后save()函数结束,宏里的这两句:
1 save:
2 pushad
3 push ds
4 push es
5 push fs
6 push gs
7 mov dx, ss
8 mov ds, dx
9 mov es, dx
10
11 mov esi, esp
12
13 inc dword [k_reenter]
14 cmp dword [k_reenter], 0
15 jne .1
16 mov esp, stacktop
17 push restart
18 jmp [esi + RETADDR - P_STACKBASE]
19
20 .1:
21 push restart_reenter
22 jmp [esi + RETADDR - P_STACKBASE]
1 push %1
2 call [irq_table + 4 * %1]
3、我们再看在宏内的最后一条ret指令之前,esp指向的是什么地方:
save()函数里最后几条指令有个push restart(或者是push restart_reenter,这个是处理中断重入的,比较类似),看来ret指令正是要执行restart()函数,我们再来看一下restart()函数:
代码
此时的ready_process指向的便是下一个要运行的进程,lldt指令便是加载这个进程的ldt。之后是将本进程的进程控制块中STACKFRAME结构的底部地址保存入tr寄存器指向的TSS结构,以便下一个时钟中断的时候能够顺利保存现场。1 restart:
2 mov esp, [ready_process]
3 lldt [esp + P_LDT_SEL]
4
5 lea eax, [esp + P_STACKTOP]
6 mov dword [tss + TSS3_S_SP0], eax
7
8 restart_reenter:
9 dec dword [k_reenter]
10 pop gs
11 pop fs
12 pop es
13 pop ds
14 popad
15 add esp, 4
16 iretd
4、下面我们就看看真正的时钟中断处理程序做了什么:
1 void clock_handler(int irq)
2 {
3 if (k_reenter != 0)
4 {
5 return ;
6 }
7
8 schedule();
9 }
1 void schedule()
2 {
3 ++ready_process;
4
5 if (ready_process >= process_table + NR_TASKS)
6 {
7 ready_process = process_table;
8 }
9 }
这个调度函数(我都不敢称它为调度函数,因为它实在是太简陋了-_-)仅仅是简单的执行进程队列中的下一个就绪进程。
接下来就是你尽情扩展奇异魔幻的调度函数的时候啦!