简单的进程调度的实现

强调申明一下以下所说内容都只是涉及到orange's该书的第六章的代码,并不是linux那些复杂的调度过程.下面不再作强调。。

从全局来说(再次申明说的是orange's该书的第六章的代码),于渊是每增加一个进程就增加一个GDT和LDT,增加的GDT里面存放LDT的基址,LDT里面只有两个描述符,一个执行段描述符,一个数据段描述符。也即是说每个进程独显一个LDT表

关于进程调度来说,最为重要的莫过于进程表,里面包纳了进程的所有内容,

于渊提供的进程表,如下:

typedef struct s_stackframe {    /* proc_ptr points here                ↑ Low            */
    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 address for assembly code save()    │            */
    u32    eip;        /*  ┓                        │            */
    u32    cs;        /*  ┃                        │            */
    u32    eflags;        /*  ┣ these are pushed by CPU during interrupt    │            */
    u32    esp;        /*  ┃                        │            */
    u32    ss;        /*  ┛                        ┷High            */
}STACK_FRAME;


typedef struct s_proc {
    STACK_FRAME            regs;            /* process' registers saved in stack frame */

    u16                ldt_sel;        /* selector in gdt giving ldt base and limit*/
    DESCRIPTOR            ldts[LDT_SIZE];        /* local descriptors for code and data */
                                /* 2 is LDT_SIZE - avoid include protect.h */
    u32                pid;            /* process id passed in from MM */
    char                p_name[16];        /* name of the process */

}PROCESS;

大家可以从表中读到许多东西,寄存器,GDT选择子,LDT表,进程名,进程ID,一目了然。。。。

运行进程之前首先要做的是把cpu从内核代码中交给进程代码,这个很简单,只要在堆栈中准备好进程所需要的ss,esp,eflags,cs,eip,然后一条iretd指令便能改朝换代。。。。

代码如下:

restart:
    mov    esp, [p_proc_ready]
    lldt    [esp + P_LDT_SEL] 
    lea    eax, [esp + P_STACKTOP]
    mov    dword [tss + TSS3_S_SP0], eax
    pop    gs
    pop    fs
    pop    es
    pop    ds
    popad
    add    esp, 4
    iretd

如上面代码所示 由于进程表中几乎有了进程的所有信息,所以把堆栈切换到进程表中,直接操作进程表就OK了。。

在这里不得不提的就是TSS结构体,里面存放着三个特权级的堆栈指针,0,1,2这三个特权级(除开3这个特权级)

当进行特权级切换时,从低特权级跳转到高特权级,就要到TSS处获得新执行过程的堆栈指针。

在于渊的代码中,有个小技巧(可能是他从别处偷来的,linux内核或者minix),就是TSS的esp0存放着的总是当前进程表的表尾,这样,每当进程被时钟中断所中断,跳到

时钟中断程序处执行时,由于要进行特权级切换(时钟中断是0特权级),cpu要到TSS处获得0特权级的堆栈,ss0和esp0,而这个堆栈正是被中断的进程的进程表,那么中断程序

就可以通过操纵堆栈来保存被中断进程的信息。

那么如果要进行进程切换也很简单,只要在中断处理程序中,把堆栈指向另一个进程的进程表,就可以通过访问进程表的内容,从而跳到另一个进程处执行

一切都是如此的简单和一目了然,这些虽然是简单的进程调度的实现,并不优雅也不华丽,但是,如我们所见,一切源于雏型也将回归于雏型

 

 

posted @ 2012-05-10 20:29  一维  阅读(606)  评论(0编辑  收藏  举报