由linux0.11進程調度小窺內存分段機制(轉)

內存分段機制的一個主要應用在於實現操作系統的多任務,它爲應用程序提供了兩個關鍵抽象:一個獨立的邏輯控制流,一個私有的地址空間。本文將針對進程的創建和調度進行分析和實驗,從而更深刻的理解分段機制。有關調試環境的建立見前文:從linux0.11引導代碼小窺內存分段機制

進程調度初始化(sched_init函數)

       在引導代碼執行結束後,執行序列將跳轉到main函數,執行一系列的初始化工作,其中就有對任務0的初始化過程,其代碼包含在kernel/sched.c中的sched_init函數中:

void sched_init(void)

{

       int i;

       struct desc_struct * p;

       if (sizeof(struct sigaction) != 16)

              panic("Struct sigaction MUST be 16 bytes");

/*建立第0號任務的TSSLDT描述符表項

*/

       set_tss_desc(gdt+FIRST_TSS_ENTRY,&(init_task.task.tss));

       set_ldt_desc(gdt+FIRST_LDT_ENTRY,&(init_task.task.ldt));

       p = gdt+2+FIRST_TSS_ENTRY;

       for(i=1;i<NR_TASKS;i++) {

              task[i] = NULL;

              p->a=p->b=0;

              p++;

              p->a=p->b=0;

              p++;

       }

/* Clear NT, so that we won't have troubles with that later on */

       __asm__("pushfl ; andl $0xffffbfff,(%esp) ; popfl");

       ltr(0);   /*將任務0TSS加載到任務寄存器tr*/

       lldt(0);  /*將局部描述符表加載到局部描述符表寄存器*/

       outb_p(0x36,0x43);              /* binary, mode 3, LSB/MSB, ch 0 */

       outb_p(LATCH & 0xff , 0x40);     /* LSB */

       outb(LATCH >> 8 , 0x40);    /* MSB */

       set_intr_gate(0x20,&timer_interrupt);

       outb(inb_p(0x21)&~0x01,0x21);

       set_system_gate(0x80,&system_call);

}

       set_tss_desc函數在include\asm\system.h中定義:

/*8字節的描述符各個字節進行設置

*/

#define _set_tssldt_desc(n,addr,type) \

__asm__ ("movw $104,%1\n\t" \

       "movw %%ax,%2\n\t" \

       "rorl $16,%%eax\n\t" \

       "movb %%al,%3\n\t" \

       "movb $" type ",%4\n\t" \

       "movb $0x00,%5\n\t" \

       "movb %%ah,%6\n\t" \

       "rorl $16,%%eax" \

       ::"a" (addr), "m" (*(n)), "m" (*(n+2)), "m" (*(n+4)), \

        "m" (*(n+5)), "m" (*(n+6)), "m" (*(n+7)) \

       )

/*0x89TSS描述符的屬性,0x82LDT描述符的屬性

*/

#define set_tss_desc(n,addr) _set_tssldt_desc(((char *) (n)),addr,"0x89")

#define set_ldt_desc(n,addr) _set_tssldt_desc(((char *) (n)),addr,"0x82")

      

       上面那段彙編代碼即對GDT一個8字節描述符表項的各個字節進行設置,設置完畢後每個描述符的內容如下表:

系統段

描述符

7

6

5

4

3

2

1

0

addr8

0x00890x0082

addr24

0x0068(段界限)

       0x0089表示可用的386TSS0x9),0x0082表示可用的LDT0x2)。

       init_task爲全局變量,其初始化爲INIT_TASKINIT_TASK宏定義如下:

#define INIT_TASK \

/* state etc */  { 0,15,15, \

/* signals */    0,{{},},0, \

/* ec,brk... */  0,0,0,0,0,0, \

/* pid etc.. */  0,-1,0,0,0, \

/* uid etc */    0,0,0,0,0,0, \

/* alarm */      0,0,0,0,0,0, \

/* math */       0, \

/* fs info */    -1,0022,NULL,NULL,NULL,0, \

/* filp */  {NULL,}, \

       { \

              {0,0}, \

/* ldt */   {0x9f,0xc0fa00}, \  /*代碼長640k,界限粒度4k字節,基址0x0 */

              {0x9f,0xc0f200}, \  /*數據長640k,界限粒度4k字節,基址0x0 */

       }, \

/*tss*/     {0,PAGE_SIZE+(long)&init_task,0x10,0,0,0,0,(long)&pg_dir,\

        0,0,0,0,0,0,0,0, \

        0,0,0x17,0x17,0x17,0x17,0x17,0x17, \

        _LDT(0),0x80000000, \

              {} \

       }, \

}

       在這段代碼中我們關心的是ldttss的設置,每個任務的ldt表有3個表項,第一項未使用,第二項爲code段,第三項爲data 段。在任務0的描述符表設置完畢,這兩個字段的地址也就成爲任務0TSS0描述符和LDT0描述符的內容。

       ltrlldt函數用於將描述符表項在GDT表中的索引加載到相應寄存器中,以任務0爲例,在執行完這兩個函數之後,tr寄存器中的值爲4*8 = 0x20ldt寄存器中的值爲5*8 = 0x28

       下面對sched_init函數進行調試驗證上述內容。首先在內核編譯後產生的System.map文件中找到該函數的地址:0x72bc。啓動bochsdbg,在0x72bc處設置斷點,命令行如下:

<bochs:1> b 0x72bc

<bochs:2> c

(0) Breakpoint 1, 0x72bc in ?? ()

Next at t=16800742

(0) [0x000072bc] 0008:000072bc (unk. ctxt): push ebp                  ; 55

<bochs:3> u /50

……

000072ce: (                    ): mov word ptr ds:0x5cd8, 0x68 ; 66c705d85c00006

800

000072d7: (                    ): mov word ptr ds:0x5cda, ax ; 66a3da5c0000

000072dd: (                    ): ror eax, 0x10             ; c1c810

000072e0: (                    ): mov byte ptr ds:0x5cdc, al ; 8805dc5c0000

000072e6: (                    ): mov byte ptr ds:0x5cdd, 0x89 ; c605dd5c000089

000072ed: (                    ): mov byte ptr ds:0x5cde, 0x0 ; c605de5c000000

000072f4: (                    ): mov byte ptr ds:0x5cdf, ah ; 8825df5c0000

000072fa: (                    ): ror eax, 0x10             ; c1c810

000072fd: (                    ): add eax, 0xffffffe8       ; 83c0e8

00007300: (                    ): mov word ptr ds:0x5ce0, 0x68 ; 66c705e05c00006

800

00007309: (                    ): mov word ptr ds:0x5ce2, ax ; 66a3e25c0000

0000730f: (                    ): ror eax, 0x10             ; c1c810

00007312: (                    ): mov byte ptr ds:0x5ce4, al ; 8805e45c0000

00007318: (                    ): mov byte ptr ds:0x5ce5, 0x82 ; c605e55c000082

0000731f: (                    ): mov byte ptr ds:0x5ce6, 0x0 ; c605e65c000000

00007326: (                    ): mov byte ptr ds:0x5ce7, ah ; 8825e75c0000

0000732c: (                    ): ror eax, 0x10             ; c1c810

……

0000737a: (                    ): mov eax, 0x20             ; b820000000

0000737f: (                    ): ltr ax                    ; 0f00d8

00007382: (                    ): mov eax, 0x28             ; b828000000

00007387: (                    ): lldt ax                   ; 0f00d0

……

<bochs:5> b 0x7387

<bochs:6> c

(0) Breakpoint 2, 0x7387 in ?? ()

Next at t=16801469

(0) [0x00007387] 0008:00007387 (unk. ctxt): lldt ax                   ; 0f00d0

<bochs:7> dump_cpu

……

cs:s=0x8, dl=0x7ff, dh=0xc09a00, valid=1

ss:s=0x10, dl=0xfff, dh=0xc09300, valid=7

ds:s=0x10, dl=0xfff, dh=0xc09200, valid=7

es:s=0x10, dl=0xfff, dh=0xc09300, valid=5

fs:s=0x10, dl=0xfff, dh=0xc09300, valid=1

gs:s=0x10, dl=0xfff, dh=0xc09300, valid=1

ldtr:s=0x28, dl=0x84640068, dh=0x8201, valid=1

tr:s=0x20, dl=0x847c0068, dh=0x8901, valid=1

gdtr:base=0x5cb8, limit=0x7ff

idtr:base=0x54b8, limit=0x7ff

……

       0x000072d7 – 0x0000732c即對GDT表的TSS0LDT0表項的內容進行設置。第0x0000737a – 0x00007387即對tr寄存器和ldt寄存器進行設置,完成設置後,以cs段寄存器值0x8爲例,bit0bit1位表示特權級爲0bit2TI字段位0,表示高13位組成的的指是指向GDT表的索引(如果TI字段爲1,表示高13位組成的值是指向LDT的索引)。

啓動任務0move_to_user_mode宏)

       在完成一系列的初始化工作之後,內核將切換到用戶模式任務0中繼續執行。其代碼即宏move_to_user_mode,代碼如下:

#define move_to_user_mode() \

__asm__ ("movl %%esp,%%eax\n\t" \

       "pushl $0x17\n\t" \

       "pushl %%eax\n\t" \

       "pushfl\n\t" \

       "pushl $0x0f\n\t" \ /*壓入任務0的代碼段(cs)選擇符*/

       "pushl $1f\n\t" \ /*壓入標號1的地址,作爲iret的返回地址*/

       "iret\n" \

/*切換到任務0,開始執行其指令序列

*/

       "1:\tmovl $0x17,%%eax\n\t" \

       "movw %%ax,%%ds\n\t" \

       "movw %%ax,%%es\n\t" \

       "movw %%ax,%%fs\n\t" \

       "movw %%ax,%%gs" \

       :::"ax")

       在進程0LDT表中設置的代碼段和數據段的基址都爲0,這與內核代碼段和數據段的基址一致,在壓棧過程中,壓入的返回地址就是內核代碼執行序列的地址,與之前內核執行序列的最關鍵的區別在於:段寄存器中的選擇符爲任務0獨有LDT表的索引,而不再是指向GDT 表的索引。LDT表的基址通過ldlt寄存器來查找:在初始化或任務切換過程中,把描述符對應任務LDT的描述符的選擇子裝入LDTR,處理器根據裝入LDTR可見部分的選擇子,從GDT中取出對應的描述符,並把LDT的基地址、界限和屬性等信息保存到LDTR的不可見的高速緩衝寄存器中。

       下面對move_to_user_mode宏進行調試驗證。首先在Systemp.map文件中找到main函數地址0x664c,通過查看彙編指令流找到move_to_user_mode宏的位置,命令行如下:

<bochs:1> b 0x664c

<bochs:2> c

(0) Breakpoint 1, 0x664c in ?? ()

Next at t=16769622

(0) [0x0000664c] 0008:0000664c (unk. ctxt): push ebp                  ; 55

<bochs:3> u /70

……

00006753: (                    ): mov eax, esp              ; 89e0

00006755: (                    ): push 0x17                 ; 6a17

00006757: (                    ): push eax                  ; 50

00006758: (                    ): pushfd                    ; 9c

00006759: (                    ): push 0xf                  ; 6a0f

0000675b: (                    ): push 0x6761               ; 6861670000

00006760: (                    ): iretd                     ; cf

00006761: (                    ): mov eax, 0x17             ; b817000000

00006766: (                    ): mov ds, ax                ; 668ed8

00006769: (                    ): mov es, ax                ; 668ec0

0000676c: (                    ): mov fs, ax                ; 668ee0

0000676f: (                    ): mov gs, ax                ; 668ee8

00006772: (                    ): add esp, 0xc              ; 83c40c

……

<bochs:4> b 0x6761

<bochs:5> c

(0) Breakpoint 1, 0x6761 in ?? ()

Next at t=16878984

(0) [0x00006761] 000f:00006761 (unk. ctxt): mov eax, 0x17             ; b8170000

00

<bochs:6> dump_cpu

……

eip:0x6761

cs:s=0xf, dl=0x9f, dh=0xc0fa00, valid=1

ss:s=0x17, dl=0x9f, dh=0xc0f200, valid=1

ds:s=0x0, dl=0x0, dh=0x0, valid=0

es:s=0x0, dl=0x0, dh=0x0, valid=0

fs:s=0x0, dl=0x0, dh=0x0, valid=0

gs:s=0x0, dl=0x0, dh=0x0, valid=0

ldtr:s=0x28, dl=0x84640068, dh=0x8201, valid=1

tr:s=0x20, dl=0x847c0068, dh=0x8901, valid=1

gdtr:base=0x5cb8, limit=0x7ff

idtr:base=0x54b8, limit=0x7ff

……

       這些調試信息有3個地方值得注意。首先是eip寄存器的指針指向iretd的下一條指令地址處。其次是此時的代碼段描述符爲0xfbit0bit1位表示特權級爲3bit2TI字段位1,表示高13位組成的的指1是指向LDT表的第1項索引(從0開始)。最後是ldtr的值:s=0x28表示該描述符在GDT表的位置爲0x28/8=58字節描述符的值爲0x00 0x0082 0x018464 0x0068,即dldh 的組合,它表示LDT表的地址爲0x00018464(線性地址),查看內存可知代碼段和數據段的基址和段限長,命令行如下:

<bochs:7> xp /6 0x018464

[bochs]:

0x00018464 <bogus+       0>:    0x00000000      0x00000000      0x0000009f

0x00c0fa00

0x00018474 <bogus+      16>:    0x0000009f      0x00c0f200

       這些值即之前分析到的init_task.task.ldt所設置的值。

創建子進程(fork函數)

       fork函數是一個系統調用,用於創建子進程。Linux中所有進程都是進程0的子進程。關於對系統函數的調用過程將在以後的文章進行闡述。這裏僅對其輔助函數進行分析,這些函數位於kernel/fork.c中。copy_process函數用於創建並複製父進程的代碼段和數據段以及環境,代碼如下:

int copy_process(int nr,long ebp,long edi,long esi,long gs,long none,

              long ebx,long ecx,long edx,

              long fs,long es,long ds,

              long eip,long cs,long eflags,long esp,long ss)

{

       struct task_struct *p;

       int i;

       struct file *f;

/*在物理內存上找到一個未被佔用的頁面

*/

       p = (struct task_struct *) get_free_page();

       if (!p)

              return -EAGAIN;

       task[nr] = p;

       *p = *current; /* NOTE! this doesn't copy the supervisor stack */

/* !以下省略對p的某些字段的初始化代碼

*/

       if (last_task_used_math == current)

              __asm__("clts ; fnsave %0"::"m" (p->tss.i387));

/*設置新任務代碼和數據段基址、限長並複製頁表

*/

       if (copy_mem(nr,p)) {

              task[nr] = NULL;

              free_page((long) p);

              return -EAGAIN;

       }

       for (i=0; i<NR_OPEN;i++)

              if (f=p->filp[i])

                     f->f_count++;

       if (current->pwd)

              current->pwd->i_count++;

       if (current->root)

              current->root->i_count++;

       if (current->executable)

              current->executable->i_count++;

/*設置子進程在GDT表的TSSLDT描述符

*/

       set_tss_desc(gdt+(nr<<1)+FIRST_TSS_ENTRY,&(p->tss));

       set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY,&(p->ldt));

       p->state = TASK_RUNNING;       /* do this last, just in case */

       return last_pid;

}

       copy_men函數將設置新任務的代碼和數據段基址、限長並複製頁表。代碼如下:

int copy_mem(int nr,struct task_struct * p)

{

       unsigned long old_data_base,new_data_base,data_limit;

       unsigned long old_code_base,new_code_base,code_limit;

/*取得任務0LDT表中的代碼段和數據段的基址和段限長

*/

       code_limit=get_limit(0x0f);

       data_limit=get_limit(0x17);

       old_code_base = get_base(current->ldt[1]);

       old_data_base = get_base(current->ldt[2]);

       if (old_data_base != old_code_base)

              panic("We don't support separate I&D");

       if (data_limit < code_limit)

              panic("Bad data_limit");

/*設置被創建進程的基址

*/

       new_data_base = new_code_base = nr * 0x4000000;

       p->start_code = new_code_base;

       set_base(p->ldt[1],new_code_base);

       set_base(p->ldt[2],new_data_base);

/*將新進程的線性地址內存頁對應到實際物理地址內存頁面

*/

       if (copy_page_tables(old_data_base,new_data_base,data_limit)) {

              free_page_tables(new_data_base,data_limit);

              return -ENOMEM;

       }

       return 0;

}

       Linux0.11內核將整個4G的地址空間劃分成64塊,供64個進程使用,子進程和父進程的代碼段或數據段的地址由於選擇不同的段基址將使得它們在邏輯上是分離的(分頁機制和寫時複製可能使得任務0和任務1的代碼或數據存放在同一物理頁面)。

進程調度(schedule函數)

       schedule函數實現進程調度,位於kernel/sched.h中,代碼如下:

void schedule(void)

{

       int i,next,c;

       struct task_struct ** p;

/* check alarm, wake up any interruptible tasks that have got a signal */

/*對所有進程進行檢測,喚醒任何一個已經得到信號的任務

*/

       for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)

              if (*p) {

                     if ((*p)->alarm && (*p)->alarm < jiffies) {

                                   (*p)->signal |= (1<<(SIGALRM-1));

                                   (*p)->alarm = 0;

                            }

                     if (((*p)->signal & ~(_BLOCKABLE & (*p)->blocked)) &&

                     (*p)->state==TASK_INTERRUPTIBLE)

                            (*p)->state=TASK_RUNNING;

              }

/* this is the scheduler proper: */

/*根據進程的時間片和優先權來選擇隨後要執行的任務

*/

       while (1) {

              c = -1;

              next = 0;

              i = NR_TASKS;

              p = &task[NR_TASKS];

              while (--i) {

                     if (!*--p)

                            continue;

                     if ((*p)->state == TASK_RUNNING && (*p)->counter > c)

                            c = (*p)->counter, next = i;

              }

              if (c) break;

              for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)

                     if (*p)

                            (*p)->counter = ((*p)->counter >> 1) +

                                          (*p)->priority;

       }

/*cpu切換到新進程執行

*/

       switch_to(next);

}

       switch_to宏代碼完成cpu切換任務的工作,這也是我們研究內存分段機制的重點,它的代碼位於include/linux/sched.h中,代碼如下:

#define switch_to(n) {\

struct {long a,b;} __tmp; \

__asm__("cmpl %%ecx,_current\n\t" \

       "je 1f\n\t" \

       "movw %%dx,%1\n\t" \

       "xchgl %%ecx,_current\n\t" \

       "ljmp %0\n\t" \  /*此處完成任務切換*/

       "cmpl %%ecx,_last_task_used_math\n\t" \

       "jne 1f\n\t" \

       "clts\n" \

       "1:" \

       ::"m" (*&__tmp.a),"m" (*&__tmp.b), \

       "d" (_TSS(n)),"c" ((long) task[n])); \

}

       任務切換的具體操作見下圖:

             1:任務切換操作示意圖(摘自Linux內核完全註釋)

       接下來將通過調試驗證切換過程,首先在System.map文件中找到schedul的地址:0x6b8c,啓動bochsdgb,找到switch_to宏中ljmp指令的位置,命令行如下:

<bochs:1>  b 0x6b8c

<bochs:2> c

(0) Breakpoint 1, 0x6b8c in ?? ()

Next at t=16886214

(0) [0x00006b8c] 0008:00006b8c (unk. ctxt): push ebp                  ; 55

<bochs:3> u /100

……

00006c6b: (                    ): cmp dword ptr ds:0x1919c, ecx ; 390d9c910100

00006c71: (                    ): jz .+0x6c8a               ; 7417

00006c73: (                    ): mov word ptr ss:[ebp+0xfffffffc], dx ; 668955f

c

00006c77: (                    ): xchg dword ptr ds:0x1919c, ecx ; 870d9c910100

00006c

7d: (                    ): jmp far ss:[ebp+0xfffffff8] ; ff6df8

00006c80: (                    ): cmp dword ptr ds:0x191a0, ecx ; 390da0910100

00006c86: (                    ): jnz .+0x6c8a              ; 7502

00006c88: (                    ): clts                      ; 0f06

……

<bochs:4> b 0x6c7d

<bochs:5> c

(0) Breakpoint 2, 0x6c7d in ?? ()

Next at t=16886886

(0)    [0x00006c7d] 0008:00006c7d (unk. ctxt): jmp far ss:[ebp+0xfffffff8] ; ff6df8

      

       0x00006c7d處的jmp far指令跳轉的地址爲段選擇符:偏移值,其中段選擇符爲ss:[ebp+0xfffffff8]32位到47位,偏移值爲ss:[ebp+0xfffffff8]0位到31位,如果段選擇符爲任務狀態段選擇符TSScpu將自動切換進程,此時cpu會把所有寄存器的狀態保存到當前任務寄存器TR中的TSS段選擇符所指向的當前任務數據結構的tss結構中,然後把新任務狀態段選擇符所指向的新任務數據結構中tss結構中的寄存器信息恢復到cpu中,系統就正式運行新切換的任務了。通過調試查看這一個過程,命令行如下:

<bochs:6> dump_cpu

……

ebp:0x1915c

……

eip:0x6c7d

……

ldtr:s=0x28, dl=0x84640068, dh=0x8201, valid=1

tr:s=0x20, dl=0x847c0068, dh=0x8901, valid=1

gdtr:base=0x5cb8, limit=0x7ff

idtr:base=0x54b8, limit=0x7ff

……

<bochs:7> print-stack

   00019148 [00019148]  0003

   0001914c [0001914c]  0000

   00019150 [00019150]  0ffc

 

 00019154 [00019154]  1f248

   00019158 [00019158]  0030

   0001915c [0001915c]  1f248

   00019160 [00019160]  6ca4

   00019164 [00019164]  743b

   00019168 [00019168]  0003

   0001916c [0001916c]  3e400

   00019170 [00019170]  1fae4

   00019174 [00019174]  0017

   00019178 [00019178]  0017

   0001917c [0001917c]  0017

   00019180 [00019180]  67a1

   00019184 [00019184]  000f

      

       %ebp寄存器的值爲0x1915cljmp跳轉的地址爲0x30:0x1f248%gdtr的基址爲0x5cb8,段描述符的地質爲0x5cb8+0x30 = 0x5ce8,取出該描述符進行判斷:

<bochs:8> x /2 0x5ce8

[bochs]:

0x00005ce8 <bogus+       0>:    0xf2e80068      0x000089ff

       這段調試信息告訴我們:這個描述符是一個基地址爲0x00fff2e8,段限長爲0x68的可用的386TSS描述符。因此cpu將自動進行進程切換。Cpu將取出從地址0x00fff2e8開始的0x68個字節內容對接下來要執行的進程tss的設置:

<bochs:9> x /26 0x00fff2e8

[bochs]:

0x00fff2e8 <bogus+       0>:    0x00000000      0x01000000      0x00000010

0x00000000

0x00fff2f8 <bogus+      16>:    0x00000000      0x00000000      0x00000000

0x00000000

0x00fff308 <bogus+      32>:    0x0000677c      0x00000616      0x00000000

0x0003e400

0x00fff318 <bogus+      48>:    0x00000021      0x00000003      0x0001f248

0x0001f248

0x00fff328 <bogus+      64>:    0x00000000      0x00000ffc      0x00000017

0x0000000f

0x00fff338 <bogus+      80>:    0x00000017      0x00000017      0x00000017

0x00000017

0x00fff348 <bogus+      96>:    0x00000038      0x80000000

       對這些調試信息按照tss字段的順序排列得出下表:























BIT31—BIT16

BIT15—BIT1

BIT0

Offset

Data

0000000000000000

鏈接字段

0

0x00000000

ESP0

4

0x01000000

0000000000000000

SS0

8

0x00000010

ESP1

0CH

0x00000000

0000000000000000

SS1

10H

0x00000000

ESP2

14H

0x00000000

0000000000000000

SS2

18H

0x00000000

CR3

1CH

0x00000000

EIP

20H

0x0000677c

EFLAGS

24H

0x00000616

EAX

28H

0x00000000

ECX

2CH

0x0003e400

EDX

30H

0x00000021

EBX

34H

0x00000003

ESP

38H

0x0001f248

EBP

3CH

0x0001f248

ESI

40H

0x00000000

EDI

44H

0x00000ffc

0000000000000000

ES

48H

0x00000017

0000000000000000

CS

4CH

0x0000000f

0000000000000000

SS

50H

0x00000017

0000000000000000

DS

54H

0x00000017

0000000000000000

FS

58H

0x00000017

0000000000000000

GS

5CH

0x00000017

0000000000000000

LDTR

60H

0x00000038

I/O許可位圖偏移

000000000000000

T

64H

0x80000000

      

       切換後的各個寄存器將按照以上表對應的值進行賦值,繼續調試,來觀察一下這個切換過程,命令行如下:

<bochs:10> n

Next at t=16886887

(0) [0x0000677c] 000f:0000677c (unk. ctxt): test eax, eax             ; 85c0

<bochs:11> dump_cpu

eax:0x0

ebx:0x3

ecx:0x3e400

edx:0x21

ebp:0x1f248

esi:0x0

edi:0xffc

esp:0x1f248

eflags:0x616

eip:0x677c

cs:s=0xf, dl=0x9f, dh=0x4c0fa00, valid=1

ss:s=0x17, dl=0x9f, dh=0x4c0f300, valid=1

ds:s=0x17, dl=0x9f, dh=0x4c0f300, valid=1

es:s=0x17, dl=0x9f, dh=0x4c0f300, valid=1

fs:s=0x17, dl=0x9f, dh=0x4c0f300, valid=1

gs:s=0x17, dl=0x9f, dh=0x4c0f300, valid=1

ldtr:s=0x38, dl=0xf2d00068, dh=0x82ff, valid=1

tr:s=0x30, dl=0xf2e80068, dh=0x89ff, valid=1

gdtr:base=0x5cb8, limit=0x7ff

idtr:base=0x54b8, limit=0x7ff

dr0:0x0

dr1:0x0

dr2:0x0

dr3:0x0

dr6:0xffff0ff0

dr7:0x400

tr3:0x0

tr4:0x0

tr5:0x0

tr6:0x0

tr7:0x0

cr0:0x8000001b

cr1:0x0

cr2:0x0

cr3:0x0

cr4:0x0

inhibit_mask:0

done

       這個切換過程也就一目瞭然了:6個段寄存器的低三位都爲1,表明這個段描述符是局部描述符表的索引;局部描述符表的地址由全局描述符表提供,即0x5cb8 + 0x38地址處的描述符;%eip中的地址作爲切換後的新進程的執行序列;通用寄存器的值從切換進程的tss結構中取得;ldtr的值由cpu自動加載,從tss結構中取得。

後記

       終於完成了內存分段機制的分析,對於內存分段機制的理解實際上可以看成怎麼將一個二維數組映射成一位數組,如果需要通過進程的局部描述符表來尋址,則可以把分段機制看成怎麼將一個三維數組映射成一位數組,就是這麼簡單!(如果說錯了,不要怪我^-^

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章