任务0的内核堆栈,用户堆栈

     描述任务0的内核堆栈和用户堆栈是如何产生的:

     1,

     linux0.11系统共使用了4种堆栈:系统初始化时临时使用的堆栈;供内核程序自己使用的堆栈(内核堆栈),只有一个,位于系统

     地址空间固定的位置,也就是后来任务0的用户态堆栈;每个任务通过系统调用,执行内核程序时使用的堆栈,也即任务的内核态

     堆栈,每个任务都有自己独立的内核态堆栈;任务在用户态执行的堆栈,位于任务(进程)地址空间末端,即任务的用户态堆栈

    

     2,

     从head.s程序起,系统正式在保护模式下运行,此时堆栈段被设置为内核数据段(0x10),堆栈指针esp设置成指向use_stack

     数组的顶端,保留1页内存作为堆栈使用。head.s L23:lss _stack_start,%esp,_stack_start为指向结构体变量stack_

     start的指针。

     stack_start定义在sched.c L69 ~ L72:

     struct {
                  long * a;
                  short b;
     } stack_start = { & user_stack [PAGE_SIZE>>2] , 0x10 };

     故lss _stack_start,%esp的作用:0x10 -> ss,& user_stack [PAGE_SIZE>>2] -> esp。

     head.s中执行完下列指令: L18 ~ L23

     movl $0x10,%eax
     mov %ax,%ds
     mov %ax,%es
     mov %ax,%fs
     mov %ax,%gs
     lss _stack_start,%esp

     执行完上述指令后,将ds,es,fs,gs,ss设置成0x10,esp设置成& user_stack [PAGE_SIZE>>2],此时供内核程序自己使用

     的堆栈 -- 内核堆栈设置完成:ss:esp。

     任务0的定义在sched.c L58:static union task_union init_task = {INIT_TASK,};

     任务0的值的定义在sched.h L113 ~ L134:

     

task 0 value
1 #define INIT_TASK \
2 /* state etc */ { 0,15,15, \
3 /* signals */ 0,{{},},0, \
4 /* ec,brk... */ 0,0,0,0,0,0, \
5 /* pid etc.. */ 0,-1,0,0,0, \
6 /* uid etc */ 0,0,0,0,0,0, \
7 /* alarm */ 0,0,0,0,0,0, \
8 /* math */ 0, \
9 /* fs info */ -1,0022,NULL,NULL,NULL,0, \
10 /* filp */ {NULL,}, \
11 { \
12 {0,0}, \
13 /* ldt */ {0x9f,0xc0fa00}, \
14 {0x9f,0xc0f200}, \
15 }, \
16 /*tss*/ {0,PAGE_SIZE+(long)&init_task,0x10,0,0,0,0,(long)&pg_dir,\
17 0,0,0,0,0,0,0,0, \
18 0,0,0x17,0x17,0x17,0x17,0x17,0x17, \
19 _LDT(0),0x80000000, \
20 {} \
21 }, \
22 }

     其中任务0的ldt中的代码段,数据段均定义为基址0,段限长640KB,而GDT中的内核代码段,数据段均定义为基址0,段长16MB

     (a):任务0的0x10 -> ss0,PAGE_SIZE+(long)&init_task -> esp0;

     (b):内核堆栈 0x10 -> ss,& user_stack [PAGE_SIZE>>2] -> esp;

    

     3,

     内核程序通过执行move_to_user_mode宏把自己“手工”移动到任务0(进程0)中运行,内核初始化程序main.c就是任务0

     的代码,只是在移动到任务0之前系统正以内核态特权级0运行着main.c,宏move_to_user_mode的功能就是把运行特权级从

     内核态的0级变换到用户态的3级,但是仍然继续执行原来的代码指令流。

     move_to_user_mode定义在 system.h L1 ~ L14:

     

move_to_user_mode
1 #define move_to_user_mode() \
2 __asm__ ("movl %%esp,%%eax\n\t" \
3 "pushl $0x17\n\t" \
4 "pushl %%eax\n\t" \
5 "pushfl\n\t" \
6 "pushl $0x0f\n\t" \
7 "pushl $1f\n\t" \
8 "iret\n" \
9 "1:\tmovl $0x17,%%eax\n\t" \
10 "movw %%ax,%%ds\n\t" \
11 "movw %%ax,%%es\n\t" \
12 "movw %%ax,%%fs\n\t" \
13 "movw %%ax,%%gs" \
14 :::"ax")

     宏move_to_user_mode使用了中断返回指令造成特权级改变的方法,该方法的主要思想:

     在堆栈中构筑中断返回指令需要的内容,把返回地址的段选择符设置称任务0代码段选择符,其特权级为3。此后执行中断

     返回指令iret时将导致系统CPU从特权级0跳转到外层的特权级3上运行。

     宏move_to_user_mode的指令执行过程分析:

     首先往内核堆栈中压入任务0数据段选择符(作为任务0的SS段选择符)和内核堆栈指针,然后压入标志寄存器内容,最后

     压入任务0代码段选择符和执行中断返回后需要执行的下一条指令的偏移位置(该偏移位置是iret后的下一条指令处)。

     当执行iret指令时,CPU把返回地址送入CS:EIP中,同时弹出堆栈中标志寄存器内容。由于CPU判断目的代码段的特权级

     为3,与当前内核态的0级不同,于是CPU会把堆栈中的堆栈段选择符和堆栈指针弹出到SS:ESP中。由于特权级发生了变化,

     段寄存器DS,ES,FS,GS的值变得无效,此时CPU会把这些段寄存器清零,因此在执行了iret指令后要重新加载这些段寄存器。

     此后,系统就开始以特权级3运行在任务0的代码上,所使用的用户态堆栈还是原来在移动之前使用的堆栈(任务0的用户态

     堆栈为内核程序自己使用的内核堆栈即2中的(b))。而其内核态堆栈则被指定为其任务数据结构所在页的顶端开始(任务0

     的内核态堆栈为2中的(a))。

     另附:

     1,  关于任务0的PAGE_SIZE+(long)&init_task -> esp0:

          (long)&init_task:指向存放任务0数据结构的一页页面的首地址(因为任务数据结构存放在一个页面的最前面),加上

          PAGE_SIZE(4096),刚好使得PAGE_SIZE+(long)&init_task指向存放任务0数据结构页面的最末端,将其赋给

          esp0,这样刚进入任务0的内核态时的栈顶指针即指向存放任务0数据结构的页面的最末段。

         

      2,  关于内核程序自己使用的堆栈--内核堆栈,参考下图:

          

posted on 2011-04-28 19:40  将军之盾  阅读(841)  评论(0编辑  收藏  举报