原来进程是这样切换的

/***********************支持原创*************************/

 

下面这个结构就是最简陋不过的一个进程表(又称进程控制块,我更习惯于它)。

 
  
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
它是一个宏,再看看这个宏是什么样子的:

代码
 
   
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
多余的不用管,可以看到调用了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]
这个函数就是保存各个Generator Registers寄存器的值,然后mov esp, stacktop这一句才真是切换到了内核态的堆栈,然后save()函数结束,宏里的这两句:

 
  
1 push % 1
2 call [irq_table + 4 * % 1 ]
这才是真正调用相应的中断处理程序,所以说各种类型的中断发生开始时的处理过程是一样的,之后会根据中断号向中断处理函数句柄数组索引相应的处理函数。
3、我们再看在宏内的最后一条ret指令之前,esp指向的是什么地方:

  save()函数里最后几条指令有个push restart(或者是push restart_reenter,这个是处理中断重入的,比较类似),看来ret指令正是要执行restart()函数,我们再来看一下restart()函数:

代码
 
   
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
    此时的ready_process指向的便是下一个要运行的进程,lldt指令便是加载这个进程的ldt。之后是将本进程的进程控制块中STACKFRAME结构的底部地址保存入tr寄存器指向的TSS结构,以便下一个时钟中断的时候能够顺利保存现场。
4、下面我们就看看真正的时钟中断处理程序做了什么:

 
  
1 void clock_handler( int irq)
2 {
3 if (k_reenter != 0 )
4 {
5 return ;
6 }
7
8 schedule();
9 }
    可以看出除了判断中断重入外,就是调用调度函数:schedule()。不放让schedule()函数尽量简单:

 
  
1 void schedule()
2 {
3 ++ ready_process;
4
5 if (ready_process >= process_table + NR_TASKS)
6 {
7 ready_process = process_table;
8 }
9 }

 


    这个调度函数(我都不敢称它为调度函数,因为它实在是太简陋了-_-)仅仅是简单的执行进程队列中的下一个就绪进程。
    接下来就是你尽情扩展奇异魔幻的调度函数的时候啦!

转载于:https://www.cnblogs.com/mwxjmmyjfm/archive/2010/11/12/1876143.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值