启动进程所需要的基本条件(转载)

http://blog.csdn.net/RichardYSteven/archive/2008/12/25/3606398.aspx

进程是现代计算机系统运行的最小单位,所以没有进程也不能称之为操作系统。


 当系统启动后,设置了GDT, IDT进入了保护模式后,需要哪些东西才能让进程跑起来呢?其实简单说来进程产生的目的并不是要让程序跑起来,而是要让一个系统上有多个进程一起跑。因为如果一个系统上只有一个所谓的“进程”在跑,那就没有必要保存再恢复进程的运行环境了。
 
好,来看看都要加哪些东西才能够让进程跑起来。
 
1.TSS

2. 进程体本身
 
3. 进程表
 
TSS主要用来保存ring0特权级的ss:esp,且这个ss:esp真好指向了正在运行的进程的进程表。这样在时钟中断产生时,特权级发生切换,正好将eip, cs, eflags, esp, ss保存到进程表中。
 
    进程表是一个数组,每个进程都有自己的一个表来保存自己的运行状态。其中还可以包含进程自己的LDT描述符,这样就可以和其他进程分开了。
 
    下面看看代码是怎么搞的。
 
1. TSS 初始化 及其 设置
 
typedef struct s_tss {
 t_32 backlink;
 t_32 esp0;  /* stack pointer to use during interrupt */
 t_32 ss0;  /*   "   segment  "  "    "        "     */
 t_32 esp1;
 t_32 ss1;
 t_32 esp2;
 t_32 ss2;
 t_32 cr3;
 t_32 eip;
 t_32 flags;
 t_32 eax;
 t_32 ecx;
 t_32 edx;
 t_32 ebx;
 t_32 esp;
 t_32 ebp;
 t_32 esi;
 t_32 edi;
 t_32 es;
 t_32 cs;
 t_32 ss;
 t_32 ds;
 t_32 fs;
 t_32 gs;
 t_32 ldt;
 t_16 trap;
 t_16 iobase; /* I/O位图基址大于或等于TSS段界限,就表示没有I/O许可位图 */
 /*t_8 iomap[2];*/
}TSS;
 
 
 
这是TSS的定义。如前所述,TSS主要保存了ring0的ss:esp,且指向了进程表。
 
首先要初始化TSS以及GDT中的TSS描述符。
 
 /* 填充 GDT 中 TSS 这个描述符 */
 memset(&tss, 0, sizeof(tss));
 tss.ss0  = SELECTOR_KERNEL_DS;   这里设置了ss
 init_descriptor(&gdt[INDEX_TSS],
   vir2phys(seg2phys(SELECTOR_KERNEL_DS), &tss),
   sizeof(tss) - 1,
   DA_386TSS);  初始化GDT中的TSS描述符
 tss.iobase = sizeof(tss); /* 没有I/O许可位图 */
 
 
 
 
 
而刚才提到的esp在另一个地方设置。准确的说是在时钟中断的时候设置的。这样就可以根据将要运行哪个进程来该片esp了。
 
截取一部分来看
 
 mov esp, [p_proc_ready]
 lldt [esp + P_LDT_SEL]
 lea eax, [esp + P_STACKTOP]
 mov dword [tss + TSS3_S_SP0], eax

 
 
蓝色部分就是设置了TSS中的ring0 esp。
 
 
 
2.进程表 及 初始化
 
 
 
进程表可谓是重中之重了。先来看看长什么样子。
 
typedef struct s_stackframe { /* proc_ptr points here    ↑ Low   */
 t_32 gs;  /* ┓      │   */
 t_32 fs;  /* ┃      │   */
 t_32 es;  /* ┃      │   */
 t_32 ds;  /* ┃      │   */
 t_32 edi;  /* ┃      │   */
 t_32 esi;  /* ┣ pushed by save()    │   */
 t_32 ebp;  /* ┃      │   */
 t_32 kernel_esp; /* <- 'popad' will ignore it   │   */
 t_32 ebx;  /* ┃      ↑栈从高地址往低地址增长*/ 
 t_32 edx;  /* ┃      │   */
 t_32 ecx;  /* ┃      │   */
 t_32 eax;  /* ┛      │   */
 t_32 retaddr; /* return address for assembly code save() │   */
 t_32 eip;  /*  ┓      │   */
 t_32 cs;  /*  ┃      │   */
 t_32 eflags;  /*  ┣ these are pushed by CPU during interrupt │   */
 t_32 esp;  /*  ┃      │   */
 t_32 ss;  /*  ┛      ┷High   */
}STACK_FRAME;

typedef struct s_proc {
 STACK_FRAME   regs;   /* process' registers saved in stack frame */
 
 t_16    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 */
 t_32    pid;   /* process id passed in from MM */
 char    p_name[16];  /* name of the process */
}PROCESS;
 
 
 
PUBLIC PROCESS   proc_table[NR_TASKS]; 最后他老人家就长这样了
 
 
 
下面就是初始化了。
 
 
 
PUBLIC int tinix_main()
{
 disp_str("-----\"tinix_main\" begins-----\n");
 
 PROCESS* p_proc = proc_table;
 
 p_proc->ldt_sel = SELECTOR_LDT_FIRST; 
 
下面四句将GDT中的描述符复制到了进程自己的LDT中。 但是改变了属性。
 memcpy(&p_proc->ldts[0], &gdt[SELECTOR_KERNEL_CS >> 3], sizeof(DESCRIPTOR));
 p_proc->ldts[0].attr1 = DA_C | PRIVILEGE_TASK << 5; // change the DPL
 memcpy(&p_proc->ldts[1], &gdt[SELECTOR_KERNEL_DS >> 3], sizeof(DESCRIPTOR));
 p_proc->ldts[1].attr1 = DA_DRW | PRIVILEGE_TASK << 5; // change the DPL
 
初始化进程自身的段寄存器,注意,这些寄存器除了gs都设置了SA_TIL属性,表示都是用的LDT的选择子。
 p_proc->regs.cs  = ((8 * 0) & SA_RPL_MASK & SA_TI_MASK) | SA_TIL | RPL_TASK;
 p_proc->regs.ds  = ((8 * 1) & SA_RPL_MASK & SA_TI_MASK) | SA_TIL | RPL_TASK;
 p_proc->regs.es  = ((8 * 1) & SA_RPL_MASK & SA_TI_MASK) | SA_TIL | RPL_TASK;
 p_proc->regs.fs  = ((8 * 1) & SA_RPL_MASK & SA_TI_MASK) | SA_TIL | RPL_TASK;
 p_proc->regs.ss  = ((8 * 1) & SA_RPL_MASK & SA_TI_MASK) | SA_TIL | RPL_TASK;
 p_proc->regs.gs  = (SELECTOR_KERNEL_GS & SA_RPL_MASK) | RPL_TASK;
 p_proc->regs.eip = (t_32)TestA; TestA就是进程体的开始地址啦
 p_proc->regs.esp = (t_32) task_stack + STACK_SIZE_TOTAL;
 p_proc->regs.eflags = 0x1202; // IF=1, IOPL=1, bit 2 is always 1.
 
 p_proc_ready = proc_table;
 restart();

 while(1){}
}
 
 
 
上面红色的语句需要再解释一下。
 
p_proc->ldt_sel 仅仅表示了一个GDT中LDT的选择子。将来会在restart函数中的lldt语句用到。 也就是说告诉系统要用哪一个GDT中的描述符来指定LDT。 实际上 SELECTOR_LDT_FIRST这个段描述符的内容,是在别的地方初始化的。
 
 
 
 // 填充 GDT 中进程的 LDT 的描述符
 init_descriptor(&gdt[INDEX_LDT_FIRST],
   vir2phys(seg2phys(SELECTOR_KERNEL_DS), proc_table[0].ldts),
   LDT_SIZE * sizeof(DESCRIPTOR) - 1,
   DA_LDT);
 
蓝色的地方明确表示出了这个LDT是指向proc_table[0]进程的LDT的。
 
 
 
3. 启动进程
 
 
 
好啦,一切都初始化好了,该是让我们的进程跑起来的时候了。
 
 
 
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

 
怎么样,这下明白了吧。 进程就这么跑起来了。 good!
 
 
 
4. 忘了贴进程体他自己了
 
 
 
怎么把最关键的东西忘了呢。
 
 
 
void TestA()
{
 int i = 0;
 while(1){
  disp_str("A");
  disp_int(i++);
  disp_str(".");
  delay(1);
 }
}
 
 
 
so easy a :)
 
 
 
注: 本贴的代码全出自《自己动手写操作系统》 chapter6/a 章节。


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/RichardYSteven/archive/2008/12/25/3606398.aspx

posted on 2011-05-18 10:23  wanghj_dz  阅读(508)  评论(0编辑  收藏  举报

导航