原来进程是这样切换的(转载)

http://www.cppblog.com/myjfm/archive/2011/01/09/138216.html
/***********************支持原创*************************/

 


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


typedef struct process_struct
{
    STACKFRAME registers;

    int16 ldt_selector;
    DESCRIPTOR ldt[LDT_SIZE];

    int32 pid;
   
char pname[32];
}PROCESS;

 


这个结构的开始是另外一个结构STACKFRAME,用于当时钟中断发生时存储当前进程的各segment
Registers和general
Registers,也就是书里面讲的“保护现场”。而DESCRIPTOR是又一个结构,这个结构用于保存本进程自己的局部描述符表的信息。下面我们就看看这两个结构:



typedef struct s_stackframe
{
    int32 gs;
    int32 fs;
    int32 es;
    int32 ds;
    int32 edi;
    int32 esi;
    int32 ebp;
    int32 kernel_esp;
//popad指令会忽略它,详情见intel手册
    int32 ebx;
    int32 edx;
    int32 ecx;
    int32 eax;
    int32 retaddr;
    int32 eip;
    int32 cs;
    int32 eflags;
    int32 esp;
    int32 ss;
}STACKFRAME;

typedef
struct s_descriptor
{
    int8 limit_low1;
    int8 limit_low2;
    int8 base_low1;
    int8 base_low2;
    int8 base_high1;
    int8 attr1;
    int8 attr2_limit_high;
    int8 base_high2;
}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完全可以没有,只是在我实现的时候将保存general
registers的过程写成一个函数,因此这个地方保存这个函数的返回地址。
2、下面看一下中断后发生的事情:


align 16
    hwint00:
    hwint_master
0


它是一个宏,再看看这个宏是什么样子的:


%macro hwint_master 1
    call save
   
in al, INT_M_CTLMASK
    or al, (
1 << %1)
   
out INT_M_CTLMASK, al
    mov al, EOI
   
out INT_M_CTL, al
    sti
    push
%1
    call [irq_table
+ 4 * %1]
    pop ecx
    cli
   
in al, INT_M_CTLMASK
    and al,
~(1 << %1)
   
out INT_M_CTLMASK, al
    ret
%endmacro


多余的不用管,可以看到调用了save()函数,这个函数的样子是这样的:


save:
    pushad
    push ds
    push es
    push fs
    push gs
    mov dx, ss
    mov ds, dx
    mov es, dx

    mov esi, esp

    inc dword [k_reenter]
    cmp dword [k_reenter],
0
    jne .
1
    mov esp, stacktop
    push restart
    jmp [esi
+ RETADDR - P_STACKBASE]

.
1:
    push restart_reenter
    jmp [esi
+ RETADDR - P_STACKBASE]


这个函数就是保存各个General
Registers寄存器的值,然后mov esp,
stacktop这一句才真是切换到了内核态的堆栈,然后save()函数结束,宏里的这两句:


    push %1
    call [irq_table
+ 4 * %1]


这才是真正调用相应的中断处理程序,所以说各种类型的中断发生开始时的处理过程是一样的,之后会根据中断号向中断处理函数句柄数组索引相应的处理函数。
3、我们再看在宏内的最后一条ret指令之前,esp指向的是什么地方:
save()函数里最后几条指令有个push
restart(或者是push
restart_reenter,这个是处理中断重入的,比较类似),看来ret指令正是要执行restart()函数,我们再来看一下restart()函数:


restart:
    mov esp, [ready_process]
    lldt [esp
+ P_LDT_SEL]

    lea eax, [esp
+ P_STACKTOP]
    mov dword [tss
+ TSS3_S_SP0], eax

    restart_reenter:
    dec dword [k_reenter]
    pop gs
    pop fs
    pop es
    pop ds
    popad
    add esp,
4
    iretd


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



void clock_handler(int irq)
{
   
if (k_reenter != 0)
    {
       
return ;
    }

    schedule();
}


可以看出除了判断中断重入外,就是调用调度函数:schedule()。不妨让schedule()函数尽量简单:


void schedule()
{
   
++ready_process;

   
if (ready_process >= process_table + NR_TASKS)
    {
        ready_process
= process_table;
    }
}


这个调度函数(我斗胆称之调度函数,可它实在太简陋了,但依然很可爱-_-)仅仅是简单的执行进程队列中的下一个就绪进程。

   
接下来可以完善调度函数!不过那不是现在的事情,我的文件系统还搭起来,毕竟那才是迫在眉睫的。

posted on 2011-05-16 15:21  wanghj_dz  阅读(380)  评论(0编辑  收藏  举报

导航