2013年3月13日 星期三

x86 process switch implementation (3) - stack save/restore

這個版本程式碼大上不少, 難度也增加不少, 為減少一點複雜度, 這篇省略了 int 0x13 isr 的 stack 部份。這次要把 proc_a, proc_b 的 stack 保存/恢復。

為了要達成在真實模式下無法達到的 stack 保存/恢復 (其實是我想不到什麼方法, 所以只好出動保護模式), 需要使用保護模式的權限切換, 所以程式才變成如此複雜, 若不使用權限切換, 則和真實模式一樣, iret/int 不會保存/恢復 stack。

在這樣的環境下, iret/int 指令除了之前提到的行為還會將 %ss:%esp 的值也一起保存/恢復

一開始, 在權限方面的 gdt, idt 設定有些問題 (要在 ring0/ring3 切換), bochs 幫了很大的忙, 讓我除錯的速度加快不少, 若沒這個 bochs, 我可能還在冥想程式哪裡出錯了。真的很佩服之前寫 os 的人, 頭腦掉很清楚才能寫出正確的 os 程式。

我沒打算解釋保護模式下的權限切換, 從 L209  開始看起即可。這邊的 code 就很單純, 把 stack_frame_a, stack_fram_b 填上需要的值, 和之前的真實模式比起來, 多了 %ss:%esp。而在保護模式下, %ss, %cs 也變成了 selector, 所以如果不熟保護模式, 也不要管這個, 單純看 %eip, % esp 即可, 重點是把 process switch 搞懂, 而不是注重在別的地方。

如果你真想完全搞懂, 需要保護模式相關知識:
  • 如何進入保護模式?
  • 如何設定 idt?
  • 如何從 ring0 -> ring3 -> ring0?

請參考: 杨文博的自己動手寫作業系統

先來檢視這些事情:
  1. stack frame a, stack frame b 的內容
  2. tss
  3. 執行 iret 切換到 proc_a 檢查 %ss:%esp
  4. int $0x13 檢查 %ss:%esp

p_proc.S
  1 # in protected mode, do process switch
  2 #if 0
  3 note:
  4 Gate selector_code, switch_proc_offset, 0, (DA_386IGate+DA_DPL3)
  5 
  6 when use idt to switch ring3 -> ring0, _DPL3 must be added.
  7 then int instruction will change %ss: %esp, so tss ss0:esp0 need right value.
  8 
  9 #endif
 10 
 11 #define STACK_FRAME_FOR_SS 24
 12 
 13 .code16
 14 .data
 15 .macro Descriptor Base, Limit, Attr
 16     .2byte  \Limit & 0xFFFF
 17     .2byte  \Base & 0xFFFF
 18     .byte   (\Base >> 16) & 0xFF
 19     .2byte  ((\Limit >> 8) & 0xF00) | (\Attr & 0xF0FF)
 20     .byte   (\Base >> 24) & 0xFF
 21 .endm
 22 
 23 .set    DA_386IGate, 0x8E    /* 32-bit Interrupt Gate */
 24 
 25 /* Gate Descriptor data structure.
 26    Usage: Gate Selector, Offset, PCount, Attr
 27     Selector:  2byte
 28     Offset:    4byte
 29     PCount:    byte
 30     Attr:      byte */
 31 .macro Gate  Selector, Offset, PCount, Attr
 32     .2byte     (\Offset & 0xFFFF)
 33     .2byte     \Selector
 34     .2byte     (\PCount & 0x1F) | ((\Attr << 8) & 0xFF00)
 35     .2byte     ((\Offset >> 16) & 0xFFFF)
 36 .endm
 37 
 38 
 39 
 40 .set    DA_32,  0x4000  /* 32-bit segment */
 41 .set    DA_LIMIT_4K, 0x8000     /* 4K */
 42 .set    DA_CR,   0x9A   /* Execute/Read */
 43 .set    DA_DRW,  0x92   /* Read/Write */
 44 .set    DA_DPL3,  0x60  /* DPL = 3 */
 45 .set    DA_C,    0x98   /* Execute-Only */
 46 .set    DA_DRWA, 0x93   /* Read/Write, accessed */
 47 .set    DA_386TSS,   0x89    /* 32-bit TSS(Available) */
 48 
 49 .set    SA_RPL3, 3
 50 
 51 
 52 gdt0: Descriptor  0, 0, 0
 53 gdt1: Descriptor  0, 0xfffff, DA_CR | DA_32 | DA_LIMIT_4K
 54 gdt2: Descriptor  0, 0xfffff, DA_DRW | DA_32 | DA_LIMIT_4K
 55 proc_a_desc:  Descriptor  0, 0xfffff, (DA_C + DA_32 + DA_DPL3)
 56 proc_b_desc:  Descriptor  0, 0xfffff, (DA_C + DA_32 + DA_DPL3)
 57 proc_data_desc:  Descriptor  0, 0xfffff, (DA_DRWA + DA_32 + DA_DPL3)
 58 tss_desc: Descriptor  0, (TSS_LEN - 1), DA_386TSS
 59 
 60 
 61 .set gdt_len, (. - gdt0)
 62 gdt_ptr: .2byte (gdt_len -1 ) # limit
 63          .4byte 0             # base
 64 
 65 .set selector_code, (gdt1-gdt0)
 66 .set selector_data, (gdt2-gdt0)
 67 .set sel_proc_a,  (proc_a_desc - gdt0 + SA_RPL3)
 68 .set sel_proc_b,  (proc_b_desc - gdt0 + SA_RPL3)
 69 .set sel_proc_data,  (proc_data_desc - gdt0 + SA_RPL3)
 70 .set sel_tss,  (tss_desc - gdt0)
 71 
 72 
 73 idt:
 74 .rept 200
 75 Gate selector_code, switch_proc_offset, 0, (DA_386IGate+DA_DPL3)
 76 .endr
 77 .set idt_len, (. - idt)  /* IDT Length */
 78 
 79 idt_ptr: .2byte  (idt_len - 1)  /* IDT Limit */
 80          .4byte  0             /* IDT Base */
 81 
 82 LABEL_TSS:
 83     .4byte  0           /* Back Link */
 84     .4byte  0  /* ESP0 */
 85     .4byte  0 /* SS0 */
 86     .4byte  0           /* ESP1 */
 87     .4byte  0           /* SS1 */
 88     .4byte  0           /* ESP2 */
 89     .4byte  0           /* SS2 */
 90     .4byte  0           /* CR3(PDBR) */
 91     .4byte  0           /* EIP */
 92     .4byte  0           /* EFLAGS */
 93     .4byte  0           /* EAX */
 94     .4byte  0           /* ECX */
 95     .4byte  0           /* EDX */
 96     .4byte  0           /* EBX */
 97     .4byte  0           /* ESP */
 98     .4byte  0           /* EBP */
 99     .4byte  0           /* ESI */
100     .4byte  0           /* EDI */
101     .4byte  0           /* ES */
102     .4byte  0           /* CS */
103     .4byte  0           /* SS */
104     .4byte  0           /* DS */
105     .4byte  0           /* FS */
106     .4byte  0           /* GS */
107     .4byte  0           /* LDT Segment Selector */
108     .2byte  0           /* Trap Flag: 1-bit */
109     .2byte  (. - LABEL_TSS + 2)     /* I/O Map Base Address */
110     .byte   0xff        /* End */
111 .set    TSS_LEN, (. - LABEL_TSS)
112 
113 
114 
115 .text
116 .global begin
117 begin:
118   xchg %bx, %bx #bochs magic break point
119 
120     /* Initialize 32-bits code segment descriptor. */
121     xor     %eax, %eax
122     mov     %cs, %ax
123     shl     $4, %eax
124 #    addl    $seg_code32, %eax
125     movw    %ax, (gdt1 + 2)
126     shr     $16, %eax
127     movb    %al, (gdt1 + 4)
128     movb    %ah, (gdt1 + 7)
129 
130     /* Initialize 32-bits data segment descriptor. */
131     xor     %eax, %eax
132     mov     %cs, %ax
133     shl     $4, %eax
134 #    addl    $seg_code32, %eax
135     movw    %ax, (gdt2 + 2)
136     shr     $16, %eax
137     movb    %al, (gdt2 + 4)
138     movb    %ah, (gdt2 + 7)
139 
140 
141     # Initialize tss
142 
143 
144         xor     %eax, %eax
145         mov     %ds, %ax
146         shl     $4, %eax
147         addl    $(LABEL_TSS), %eax
148         movw    %ax, (tss_desc + 2)
149         shr     $16, %eax
150         movb    %al, (tss_desc + 4)
151         movb    %ah, (tss_desc + 7)
152 
153 
154 
155     /* Initialize 32-bits proc ring3 segment descriptor. */
156     xor     %eax, %eax
157     mov     %cs, %ax
158     shl     $4, %eax
159     movw    %ax, (proc_a_desc + 2)
160     shr     $16, %eax
161     movb    %al, (proc_a_desc + 4)
162     movb    %ah, (proc_a_desc + 7)
163 
164   xor     %eax, %eax
165   mov     %cs, %ax
166   shl     $4, %eax
167   add     $gdt0, %eax      /* eax <- gdt base*/
168   movl    %eax, (gdt_ptr + 2)
169   lgdt gdt_ptr
170 
171     /* Prepared for loading IDTR */
172     xor     %eax, %eax
173     mov     %ds, %ax
174     shl     $4, %eax
175     add     $idt, %eax      /* eax <- idt base*/
176     movl    %eax, (idt_ptr + 2)
177 
178   cli
179 
180   lidt idt_ptr
181 
182 
183 
184   movw $selector_data, LABEL_TSS+8 # ss0
185   movl $proc_stack_top, LABEL_TSS+4 # esp0
186 
187 
188 
189 
190   # open a20 line
191   inb $0x92, %al
192   orb $0b00000010, %al
193   outb %al, $0x92
194 
195 
196   # enable protect mode
197   movl %cr0, %eax
198   orl $1, %eax
199   movl %eax, %cr0
200 
201 
202   ljmpl $selector_code, $seg_code32
203 
204 
205 
206   mov $0x4c00, %ax
207   int $0x21           
208 
209 .code32
210 seg_code32:
211   mov $selector_data, %ax
212   mov %ax, %ds
213   mov %ax, %ss
214   mov $proc_stack_top, %esp
215 
216 ## set stack frame flag
217   # get flag
218   pushf
219   movl (%esp), %eax
220   popf
221   movl %eax, stack_frame_a+12
222   movl %eax, stack_frame_b+12
223   cli
224 
225 ## set stack frame eip
226   movl $proc_a, stack_frame_a+4
227   movl $proc_b, stack_frame_b+4
228 
229 ## set stack frame cs
230   xor     %eax, %eax
231   movw $sel_proc_a, %ax
232   #movw $selector_code, %ax
233   movw %ax, stack_frame_a+8
234   movw %ax, stack_frame_b+8
235 
236 
237 ## set stack frame esp
238   movw $proc_stack_top_a, stack_frame_a+16
239   movw $proc_stack_top_b, stack_frame_b+16
240 
241 ## set stack frame ss
242 #  movw %ss, %ax
243   movw $sel_proc_data, stack_frame_a+20
244   movw $sel_proc_data, stack_frame_b+20
245 
246 
247   movl $stack_frame_a, cur_proc
248   movl cur_proc, %esp
249   popl %eax
250   xchg %bx, %bx #bochs magic break point
251 
252   movw $selector_data, LABEL_TSS+8 # ss0
253   movl $stack_frame_a+STACK_FRAME_FOR_SS, LABEL_TSS+4 # esp0
254 
255   mov $sel_tss, %ax    /* Load TSS to TR register */
256   ltr %ax
257 
258   iretl
259 
260 
261 
262 
263   mov $selector_data, %ax
264   int $0x30
265   mov %ax, %ds
266   mov %ax, %es
267   mov %ax, %ss
268   mov $0xb8004, %edi    /* Destination */
269   movb $'P', (%edi)
270   mov $0xb8005, %edi    /* Destination */
271   movb $0x9, (%edi)
272 
273 
274 
275 
276 .global proc_a
277 proc_a:
278 1:
279   mov $0x1, %ax
280   int $0x30
281   jmp 1b
282 
283 .global proc_b
284 proc_b:
285 1:
286   mov $0x2, %al
287   int $0x30
288   jmp 1b
289 
290 
291 .global int0x30
292 int0x30:
293 .set int0x30_offset, (. - seg_code32)
294   mov $0x1, %cx;
295   iret
296 
297 .global switch_proc
298 switch_proc:
299 .set switch_proc_offset, (. - begin+0x100)
300 #  movw %cs:my_ss, %ds
301 #  movw %cs:my_ss, %ss
302 #  movw cur_proc, %sp
303   push %eax
304   mov $selector_data, %ax
305   mov %ax, %ds
306 
307 #if 0
308   inb $0x21, %al
309   orb $0x1 ,%al
310   outb %al, $0x21
311 #endif
312 
313   movl cur_proc, %edx
314   cmpl $stack_frame_a, %edx
315   je 1f
316   movl $stack_frame_a, cur_proc
317   jmp 2f
318 1:
319   movl $stack_frame_b, cur_proc
320 2:
321 
322 #if 0
323   cli
324   inb $0x21, %al
325   andb $0xfe ,%al
326   outb %al, $0x21
327 #endif
328 
329   movl cur_proc, %eax
330   addl $24, %eax
331   movl %eax, LABEL_TSS+4 # esp0
332   movl cur_proc, %esp
333   pop %eax
334   iret
335 
336 stack_frame_a:
337   .long 0x9# eax
338   .long 0x0# eip
339   .long 0x0# cs
340   .long 0x2# flag
341   .long 0x2# esp
342   .long 0x2# ss
343 
344 stack_frame_b:
345   .long 0x9# ax
346   .long 0x0# eip
347   .long 0x0# cs
348   .long 0x2# flag
349   .long 0x2# esp
350   .long 0x2# ss
351 cur_proc:
352   .long 0x0
353 
354   .space  256, 0
355 proc_stack_top:
356 
357   .space  256, 0
358 proc_stack_top_a:
359 
360   .space  256, 0
361 proc_stack_top_b:

寄件者 write_os

手工圖又來了, 老問題, 很醜是吧!別為難我了, 看到那複雜的曲線, 我可畫不了精美的版本, 就將就點。 stack_fame_a, stack_frame_b 多了兩個欄位用來保存 %ss, %esp, iret 發動前, 將 %ss:%esp 指到 ,  iret 發動後, eip, es, flag, esp, ss 就會依序被填入
stack_fame_a + 4
stack_fame_a + 8
stack_fame_a +12
stack_fame_a +16
stack_fame_a + 20
所指到的值。

而 int $0x30 發動時, stack 會怎麼變化呢?
這時是從 ring 3 -> ring0, stack 會使用 tss 的 ss0:esp0 來填入 %ss:%esp。
所以 tss 的 ss0:esp0 該怎麼設定呢?
填入 的位址。

假設現在要切換到 proc_a, 程式要做的事情有:
tss 的 esp0 要填入 的位址 (ss 簡單起見, 使用相同的 selector), %esp 填入 的位址, 最後發動 iret。

從 proc_a 執行 int $0x13 時, stack 變化方式為:
esp 的值變成 ③ 而
ss, esp, flag, es, eip 依序 push 到 stack,當最終切換到 int 0x13 isr 時, eps 在 的位置, 在 push %eax 之後, 剛好把 proc_a 所有的東西 (%eax, ss, esp, flag, es, eip) 都保存下來。

這時 int 0x13 isr 繼續準備 proc_b 的切換動作:
tss 的 esp0 要填入 的位址 (ss 簡單起見, 使用相同的 selector), %esp 填入 的位址, 最後發動 iret。

疑, 沒有我想像中的難表達, 不過不知道讀者們有沒搞懂, 希望沒讓你們更糊塗了。

最後還剩下 isr 本身的 stack, 這部份相對簡單, 應該可以不用出  x86 process switch implementation (4) 了。

source code:
https://github.com/descent/process protected branch

① ② ③ ④ symbol ref:
http://blog.yam.com/picryam/article/10903476
http://blog.aican.info/2008/04/blog-post_10.html

沒有留言:

張貼留言

使用 google 的 reCAPTCHA 驗證碼, 總算可以輕鬆留言了。

我實在受不了 spam 了, 又不想讓大家的眼睛花掉, 只好放棄匿名留言。這是沒辦法中的辦法了。留言的朋友需要有 google 帳號。