强调申明一下以下所说内容都只是涉及到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,而这个堆栈正是被中断的进程的进程表,那么中断程序
就可以通过操纵堆栈来保存被中断进程的信息。
那么如果要进行进程切换也很简单,只要在中断处理程序中,把堆栈指向另一个进程的进程表,就可以通过访问进程表的内容,从而跳到另一个进程处执行
一切都是如此的简单和一目了然,这些虽然是简单的进程调度的实现,并不优雅也不华丽,但是,如我们所见,一切源于雏型也将回归于雏型