內存分段機制的一個主要應用在於實現操作系統的多任務,它爲應用程序提供了兩個關鍵抽象:一個獨立的邏輯控制流,一個私有的地址空間。本文將針對進程的創建和調度進行分析和實驗,從而更深刻的理解分段機制。有關調試環境的建立見前文:從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號任務的TSS,LDT描述符表項 */ 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); /*將任務0的TSS加載到任務寄存器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)) \ ) /*0x89爲TSS描述符的屬性,0x82爲LDT描述符的屬性 */ #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 |
addr高8位 |
0x0089或0x0082 |
addr低24位 |
0x0068(段界限) |
0x0089表示可用的386TSS(0x9),0x0082表示可用的LDT(0x2)。
init_task爲全局變量,其初始化爲INIT_TASK,INIT_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, \ {} \ }, \ } |
在這段代碼中我們關心的是ldt和tss的設置,每個任務的ldt表有3個表項,第一項未使用,第二項爲code段,第三項爲data 段。在任務0的描述符表設置完畢,這兩個字段的地址也就成爲任務0的TSS0描述符和LDT0描述符的內容。
ltr和lldt函數用於將描述符表項在GDT表中的索引加載到相應寄存器中,以任務0爲例,在執行完這兩個函數之後,tr寄存器中的值爲4*8 = 0x20,ldt寄存器中的值爲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表的TSS0和LDT0表項的內容進行設置。第0x0000737a – 0x00007387即對tr寄存器和ldt寄存器進行設置,完成設置後,以cs段寄存器值0x8爲例,bit0到bit1位表示特權級爲0,bit2位TI字段位0,表示高13位組成的的指是指向GDT表的索引(如果TI字段爲1,表示高13位組成的值是指向LDT表的索引)。
啓動任務0(move_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") |
在進程0的LDT表中設置的代碼段和數據段的基址都爲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的下一條指令地址處。其次是此時的代碼段描述符爲0xf,bit0到bit1位表示特權級爲3,bit2位TI字段位1,表示高13位組成的的指1是指向LDT表的第1項索引(從0開始)。最後是ldtr的值:s=0x28表示該描述符在GDT表的位置爲0x28/8=5,8字節描述符的值爲0x00 0x0082 0x018464 0x0068,即dl與dh 的組合,它表示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表的TSS和LDT描述符 */ 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; /*取得任務0的LDT表中的代碼段和數據段的基址和段限長 */ 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位,如果段選擇符爲任務狀態段選擇符TSS,cpu將自動切換進程,此時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寄存器的值爲0x1915c,ljmp跳轉的地址爲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結構中取得。
後記
終於完成了內存分段機制的分析,對於內存分段機制的理解實際上可以看成怎麼將一個二維數組映射成一位數組,如果需要通過進程的局部描述符表來尋址,則可以把分段機制看成怎麼將一個三維數組映射成一位數組,就是這麼簡單!(如果說錯了,不要怪我^-^)