顯示具有 process switch 標籤的文章。 顯示所有文章
顯示具有 process switch 標籤的文章。 顯示所有文章

2013年5月23日 星期四

x86 process switch implementation (1.3) - race condition

這個是副產品, 在寫 process switch 這系列時, 順到想的, 因為不是很好表達, 我只在 mosut 講這個, 並沒有想要 blog 一篇, 不過還是寫出來好了, 最主要是給我回憶用的。

我一直在思考, 如何才能用程式碼來表達出 race condition, 而不是老師上課教的, 只能用冥想的方式來理解。

以前老師的例子就是
a=0;

proc_a:
a=a+1;

proc_b:
a=a+1;

若是 a 的初始值為 0, 由於 a=a+1 會被 c compiler 編譯成 3 個指令:

mov 0x804a018,%eax
add $0x1,%eax
mov %eax,0x804a018

所以若不一次做完這 3 個組合語言指令, 在 proc_a, proc_b 執行過後, a 的值可能只被加了一次, 最終結果不是 2, 而是 1。

以下的程式碼便是用來產生這樣的結果, 由於我可以決定 int $0x30 的位置, 所以可以很容易複製這樣的行為, 若是靠 timer 來中斷, 要遇到這樣的巧合就很難控制了。

程式很短, 但不代表他很簡單, 需要了解 x86 process switch implementation(0), x86 process switch implementation(1), 否則應該看不懂這篇。

simple_proc.S
  1 #define STACK_FRAME_OFFSET 6
  2 .code16
  3 .text
  4 .global begin
  5 begin:
  6   xchg %bx, %bx #bochs magic break point
  7   cli
  8 
  9   xor     %eax, %eax
 10   mov     %cs,%ax
 11   mov     %ax,%ds
 12 
 13 ## reset 0x30 interrupt
 14   movw    $0x0, %bx
 15   movw    %bx, %es
 16   movw $switch_proc, %es:0xc0 # isr offset
 17   movw %ax, %es:0xc2 #isr seg
 18 
 19 
 20   movw    $0xb800, %ax
 21   movw    %ax, %gs
 22 
 23 ## set stack frame eip
 24   movw $proc_a, stack_frame_a+2
 25   movw $proc_b, stack_frame_b+2
 26 
 27 ## set stack frame cs
 28   movw %cs, %ax
 29   movw %ax, stack_frame_a+4
 30   movw %ax, stack_frame_b+4
 31 
 32 ## set stack frame flag
 33   # get flag
 34   pushf
 35   movw (%esp), %ax
 36   popf
 37   movw %ax, stack_frame_a+6
 38   movw %ax, stack_frame_b+6
 39 
 40   movw $stack_frame_a, cur_proc
 41   movw cur_proc, %sp
 42   popw %ax
 43   iret
 44 
 45   mov $0x4c00, %ax
 46   int $0x21           
 47 
 48 
 49 .global proc_a
 50 proc_a:
 51 1:
 52 #  mov $0x1, %ax
 53 
 54   movw var_a,%ax
 55   int $0x30
 56   addw $0x1,%ax
 57   movw %ax, var_a
 58 
 59   jmp 1b
 60 
 61 .global proc_b
 62 proc_b:
 63 1:
 64 #  mov $0x2, %al
 65 
 66   movw var_a,%ax
 67   addw $0x1,%ax
 68   movw %ax, var_a
 69   int $0x30
 70 
 71   jmp 1b
 72 
 73 .global switch_proc
 74 switch_proc:
 75   pushw %ax
 76   movw cur_proc, %dx
 77   cmp $stack_frame_a, %dx
 78   je 1f
 79   movw $stack_frame_a, cur_proc
 80   jmp 2f
 81 1:
 82   movw $stack_frame_b, cur_proc
 83 2:
 84   movw cur_proc, %sp
 85   popw %ax
 86   iret
 87 
 88 
 89 cur_proc:
 90   .word 0x0
 91 var_a:
 92   .word 0x0
 93 
 94   .space  256, 0
 95 proc_stack_top_a:
 96   .space  256, 0
 97 proc_stack_top_b:
 98 
 99 stack_frame_a:
100   .word 0x9# ax
101   .word 0x0# eip
102   .word 0x1# cs
103   .word 0x2# flag
104 
105 stack_frame_b:
106   .word 0x9# ax
107   .word 0x0# eip
108   .word 0x1# cs
109   .word 0x2# flag
110 
111 #if 0
112 static int a;
113 ++a;
114 
115 mov    0x804a018,%eax
116 add    $0x1,%eax
117 mov    %eax,0x804a018
118 #endif

L66 ~ 68 是一個完整的 a=a+1; 而 L54 ~ 57 被我安插了 int $0x30, 導致 a=a+1 不能完整執行就會 switch 要另外一個 process, 這個程式的表現一樣要使用 bochs single step, 在 blog 上我只能儘量表達清楚, 無法帶你跑一次 (其實我可以錄下 bochs 畫面, 不過不用這麼折磨我吧)。

那麼要如何解決這問題, 其中之一是使用 spin lock, x86 可以參考 test and set 指令實作出 spin lock, 將這 3 個指令保護下來, 一次做完這 3 個指令:
http://en.wikipedia.org/wiki/X86_instruction_listings
http://en.wikipedia.org/wiki/Test-and-set

2013年4月30日 星期二

x86 process switch implementation (1.5) - save/restore in real mode

遲來的 1.5 篇, 我都已經辛苦的把 x86 protected mode 寫完了說。

我終於想出如何 save/restore in x86/dos real mode, 實在令我開心, 距離第一篇將近有 45 天的時間。這個程式是原創等級的程式, 在我苦思如何在 stm32f4discovery (arm cortex m4) 實作 context switch 時來的靈感。在 100 行左右的組合語言程式碼就可完成, 實在是小而美。

simple_proc.S
  1 # save/restore stack in ms dos (x86 real mode)
  2 .code16
  3 .text
  4 .global begin
  5 begin:
  6   xchg %bx, %bx #bochs magic break point
  7   cli
  8 
  9   xor     %eax, %eax
 10   mov     %cs,%ax
 11   mov     %ax,%ds
 12 
 13 ## reset 0x30 interrupt
 14   movw    $0x0, %bx
 15   movw    %bx, %es
 16   movw $switch_proc, %es:0xc0 # isr offset
 17   movw %ax, %es:0xc2 #isr seg
 18 
 19 
 20   movw    $0xb800, %ax
 21   movw    %ax, %gs
 22 
 23 ## set stack frame eip
 24   movw $proc_a, stack_frame_a+2
 25   movw $proc_b, stack_frame_b+2
 26 
 27 ## set stack frame cs
 28   movw %cs, %ax
 29   movw %ax, stack_frame_a+4
 30   movw %ax, stack_frame_b+4
 31 
 32 ## set stack frame flag
 33   # get flag
 34   pushf
 35   movw (%esp), %ax
 36   popf
 37   movw %ax, stack_frame_a+6
 38   movw %ax, stack_frame_b+6
 39 
 40   movw $stack_frame_a, a_sp
 41   movw $stack_frame_b, b_sp
 42 
 43   movw $stack_frame_a, %sp
 44 
 45   popw %ax
 46   iret
 47 
 48 .global proc_a
 49 proc_a:
 50 1:
 51   mov $0x1, %ax
 52   push %cx
 53   int $0x30
 54   jmp 1b
 55 
 56 .global proc_b
 57 proc_b:
 58 1:
 59   mov $0x2, %al
 60   push %bx
 61   int $0x30
 62   jmp 1b
 63 
 64 .global switch_proc
 65 switch_proc:
 66   pushw %ax
 67   movw cur_proc, %dx
 68 
 69 # 0 run proc_a
 70 # 1 run proc_b
 71   cmp $0, %dx
 72   jne 1f
 73   # save cur_esp to a_sp
 74   movw %esp, a_sp
 75   movw b_sp, %ax
 76   movw $1, cur_proc
 77   jmp 2f
 78 1:
 79   # save cur_esp to b_sp
 80   movw %esp, b_sp
 81   movw a_sp, %ax
 82   movw $0, cur_proc
 83 2:
 84 
 85   movw %ax, %sp
 86   popw %ax
 87   iret
 88 
 89 cur_proc:
 90   .word 0x0
 91 a_sp:
 92   .word 0x0
 93 b_sp:
 94   .word 0x0
 95 
 96   .space  256, 0
 97 proc_stack_top_a:
 98   .space  256, 0
 99 proc_stack_top_b:
100 
101   .space  256, 0
102 stack_frame_a:
103   .word 0x9# ax
104   .word 0x0# eip
105   .word 0x1# cs
106   .word 0x2# flag
107 
109   .space  256, 0
110 stack_frame_b:
111   .word 0x9# ax
112   .word 0x0# eip
113   .word 0x1# cs
114   .word 0x2# flag


當從 proc_a 到 (int $0x30) switch_proc 的 stack 變化, 這次不再是手工圖了, 有了漂亮的表格可看, 數字部份就是 stack 的位址變化。

497
495%cx
493flag
491%cs
48f%eip
48d%ax

所以再次回到 proc_a 時, 只要讓 esp 回到 0x495 即可。說的果然比做的容易多了, 那麼要怎麼實作呢?

這次捨棄固定的 stack_frame_a, stack_frame_b, 在每次進入 switch_proc 把 esp 存起來即可, 再根據 cur_proc 決定要存在 a_sp 或 b_sp, 並把 esp 指向 a_sp 或 b_sp 完成下次的 process switch。

有了這個, 這程式可以說是 context switch, 不只是 process switch 了, 已經可以把 context save/restore。

這是 proc_b 的 stack 變化過程:

59f
59d%bx
flag
%cs
597%eip
595%ax

要體驗她, 一樣需要在 bochs single step 過才能有所體會。

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

2013年3月16日 星期六

x86 process switch implementation (0) - in dos/x86


我曾經在「os process」提到 os porcess 的切換, 那時候令我興奮不已。但當時的概念還是很模糊, 我決定要用我自己的話來說明這個概念, 也證明我真的搞懂。

化繁為簡是我的學習原則, 對於 process 切換的掌握度還不夠, 打算用自己的方法來實作一個 process 切換的程式。希望這程式符合幾點:
  1. 程式碼小
  2. 在 dos 下執行
  3. 使用 x86 real mode
上述這些條件都是為了簡單, 若能用小小的程式以及簡單的執行環境就可以完成 process 切換, 相信理解起來會容易些。在 dos 使用 .com 執行檔, 可以讓我的程式最大有 64 K 那麼大 (那麼小), 對於所有程式碼都是自己打造的來說, 已經非常夠用, boot 磁區那 512 byte 才真的不夠人用。

那這程式有多小呢? 大概像下面這樣:

simple_proc.S
1 #define STACK_FRAME_OFFSET 6
 2 .code16
 3 .text
 4 .global begin
 5 begin:
 6   xchg %bx, %bx #bochs magic break point
 7   cli
 8 
 9   xor     %eax, %eax
10   mov     %cs,%ax
11   mov     %ax,%ds
12 
13 ## reset 0x30 interrupt
14   movw    $0x0, %bx
15   movw    %bx, %es
16   movw $switch_proc, %es:0xc0 # isr offset
17   movw %ax, %es:0xc2 #isr seg
18 
19 
20   movw    $0xb800, %ax
21   movw    %ax, %gs
22 
23 ## set stack frame eip
24   movw $proc_a, stack_frame
25   movw $proc_b, stack_frame+STACK_FRAME_OFFSET
26 
27 ## set stack frame cs
28   movw %cs, %ax
29   movw %ax, stack_frame+2
30   movw %ax, stack_frame+STACK_FRAME_OFFSET+2
31 
32 ## set stack frame flag
33   # get flag
34   pushf
35   movw (%esp), %ax
36   popf
37   movw %ax, stack_frame+4
38   movw %ax, stack_frame+STACK_FRAME_OFFSET+4
39 
40   int $0x30
41 
42   mov $0x4c00, %ax
43   int $0x21           
44 
45 cur_proc:
46   .word 0x0

51 
52   .space  256, 0
53 proc_stack_top_a:
54   .space  256, 0
55 proc_stack_top_b:
56 
57 stack_frame:
58   .word 0x0# eip
59   .word 0x1# cs
60   .word 0x2# flag
61 
62   .word 0x0# eip
63   .word 0x1# cs
64   .word 0x2# flag
65 
66 .global proc_a
67 proc_a:
68 1:
69   mov $0x1, %ax
70   int $0x30
71   jmp 1b
72 
73 .global proc_b
74 proc_b:
75 1:
76   mov $0x2, %bl
77   int $0x30
78   jmp 1b
79 
80 .global switch_proc
81 switch_proc:
82   movw cur_proc, %dx
83   cmp $stack_frame, %dx
84   je 1f
85   movw $stack_frame, cur_proc
86   jmp 2f
87 1:
88   movw $stack_frame+STACK_FRAME_OFFSET, cur_proc
89 2:
90   movw cur_proc, %sp
91   iret

加了註解還沒超過 100 行的 x86 組合語言。是的, 這是組合語言, 因為 c 語言還沒低階到可以設定暫存器, 設定暫存器是組合語言的工作。

66 .global proc_a
67 proc_a:
68 1:
69   mov $0x1, %ax
70   int $0x30
71   jmp 1b
72 
73 .global proc_b
74 proc_b:
75 1:
76   mov $0x2, %bl
77   int $0x30
78   jmp 1b

proc_a 和 proc_b 便是我們的兩個 process, 你可能會抗議那明明就只是兩個 function。是的, 你沒說錯, 但你寫的 c main 程式是不是也只是個 function, 而你卻認為他是一個 process 呢? 若是使用 call proc_a, call proc_b 的方式來執行, 那是 function 的用法, 不是 process, 所以接下來的程式碼要用很其特的方法 (iret) 來執行 proc_a, proc_b。

這兩個 process 只有 3 行, 夠簡單, 只要略懂組合語言的程式員, 幾乎不用說明就可看懂。使用的 process 切換方式是類似 windows 3.1 的 non-preemptive 方式, 需要由 process 自己釋放 cpu 來讓其他 process 執行。int $0x30 就是用來做這件事情。當然我可以把 int $0x30 包裝成類似 os_yield(), 不過這樣複雜度就提高了。

jmp 1b 就是回到 lable 1, L78 的 jmp 1b 回到 L75, L71 的 jmp 1b 回到 L68, 很直覺, 所以這個 process 只做一件事情, 把 0x1 放到 ax 暫存器, 另外一個 process 把 0x2 放到 bl 暫存器, 就是這麼簡單。



上圖可以說明一切, 也許你會嫌字很醜, 應該用電腦畫才是, 不過手工可是很難得的。重點在 stack_frame, 裡頭有 3 個欄位: 分別代表 eip, cs, flag, 用來儲存 proc_a, proc_b 目前的這 3 個值。int 0x30 isr 便是切換 stack_frame, stack_frame+6 來執行 proc_a, proc_b。

節錄 ref 1 的 int 指令做的事情:
  1. 把旗標暫存器 push 堆疊
  2. 禁止其他中斷發生
  3. 清除陷阱旗標
  4. 把 CS 暫存器 push 堆疊
  5. 把 INT n 的下一指令位址 push 堆疊
  6. 由 0000:(4n) 位址取出中斷服務程式所在位址,並執行長程跳躍指令,至該處繼續執行
節錄 ref 1 的 iret 指令做的事情:
  1. 由堆疊中 pop 4 bytes (cs:ip),並把控制權交到該 4 bytes 所指位址
  2. 由堆疊 pop 旗標暫存器 (2 bytes)
所以就是這樣來讓 proc_a, proc_b 可以輪流執行。先把 proc_a, %cs 的值填到 stack_frame eip, cs 的地方 (stack_frame, stack_frame+2), 將 %sp 指到 stack_frame 或是 stack_frame+6 的地方, 然後發動 iret 即可跳到 proc_a 或是 proc_b。因為 iret 會把 stack_frame, stack_frame+2 載入到 %eip, %cs, 而 cpu 會執行 %cs:%eip 指向的程式碼, 就會去執行 proc_a, 這就是和直接 call proc_a 不同的執行方式。

int 指令則會把下一個指令的 %cs:%eip 存到 %ss:%esp 指到的地方, 所以 int 發動的時候, 會把 proc_a 下個指令存到 stack_frame, stack_frame+2 裡頭, 等著我們下次發動 iret 再讓 proc_a 執行起來; 執行 proc_b 也是同樣的道理, 很容易理解吧!

這程式的執行結果不重要, 重要的是執行過程, 怎麼感受這個執行過程? 這就稍微難一點, 我是用 bochs 內建 debugger single step, 觀察所有暫存器, stack 來檢查程式是否有真的執行切換。

6   xchg %bx, %bx #bochs magic break point

是 bochs magic break point, 程式執行到這行, 會讓 bochs 中斷停下來, 就可使用 single step指令來觀察整個程式行為。

而程式的過程便是在 proc_a, proc_b 之前相互執行, 為了簡單, 我沒有印出任何字元, 所以從螢幕上看不出任何事情, 為了有趣, 我自己倒是寫了一個印出 a, b 的版本, 有興趣的朋友可以自己改看看, 在 proc_a 印出 a, prob_b 印出 b。

這個程式該怎麼執行呢? makefile 規則會把這隻程式編譯成 .com, 直接 copy 到 dos 執行即可, 再使用 bochs 的內建除錯器就可以追蹤整個流程。dos 環境最好不要載入任何記憶體管理程式, ex: himem.sys, emm386.exe, 在我測試改寫 0x30 中斷時, 會造成一些問題, 我花了不少時間排除這問題。

程式很簡單, 說明也很簡單, 希望不要造成誤會, 如果你已經理解這篇的解釋, process switch 並沒有這麼簡單, 我簡化很多東西, 這沒有考慮很週嚴 (ex: 沒有保存所有的暫存器, switch_proc 沒有保存自己的 stack), 真正的 process switch 還要加上不少 code, 而且我還沒搞定 x86 real mode 如何保存 %esp 的問題。x86 保護模式在權限切換時, iret/int 指令會保存 %ss:%esp。這程式若用上保護模式, 那得加上不少 code, 模糊了我要表達的事情, 就先這樣。

儘管有如此缺失, 但用來作為 process switch 的實作理解, 不到 100 行的組合語言程式能發揮如此功用, 已經足夠。

下篇文章 x86 process switch implementation (1) 就會複雜一點了。

soure code:
https://github.com/descent/process
git commit : d25cb21e036b953f19ec69610c411c550dcfa8d6

這個程式的除錯相當麻煩, 沒有 ice 等級的除錯器, 要寫好她相當困難。好在有 bochs。

x86 中斷改寫參考資料:
  1. 第 36 章 中斷
  2. 中断矢量表的结构
  3. 中断服务程序
  4. http://books.google.com.tw/books?id=LPZDMQvMvwMC&pg=PA208&lpg=PA208&dq=%E4%BF%AE%E6%94%B9%E4%B8%AD%E6%96%B7%E5%90%91%E9%87%8F+dos&source=bl&ots=d0LzeMfr-F&sig=CgacTkwyR_bi6pQQN3kC7onPa0g&hl=zh-TW&sa=X&ei=8n4rUcemIoqIkwXjsYGIDQ&ved=0CDcQ6AEwAQ#v=onepage&q=%E4%BF%AE%E6%94%B9%E4%B8%AD%E6%96%B7%E5%90%91%E9%87%8F%20dos&f=false
  5. https://www.google.com.tw/search?q=%E4%BF%AE%E6%94%B9%E4%B8%AD%E6%96%B7%E5%90%91%E9%87%8F+dos&ie=utf-8&oe=utf-8&aq=t&rls=org.mozilla:en-US:official&client=firefox-a&channel=fflb

2013年3月15日 星期五

x86 process switch implementation (1) - save %ax

前面那一個版本只差了一些小地方:

49 .global proc_a
50 proc_a:
51 1:
52   mov $0x1, %ax
53   int $0x30
54   jmp 1b
55 
56 .global proc_b
57 proc_b:
58 1:
59   mov $0x2, %al
60   int $0x30
61   jmp 1b

proc_a, proc_b 會同時使用 %ax, 所以在切換這兩個 process 時, %ax 需要 save/restore, 總不能切回 proc_a 時, %ax 變成 2, 這就不對了。要怎麼做呢?process switch 的重點也在於此。

87 stack_frame_a:
88   .word 0x9# ax
89   .word 0x0# eip
90   .word 0x1# cs
91   .word 0x2# flag
92 
93 stack_frame_b:
94   .word 0x9# ax
95   .word 0x0# eip
96   .word 0x1# cs
97   .word 0x2# flag

在 stack frame 上用一個欄位來保存 %ax, 在 int 0x30 切換 process 時, 保存和回復這個 %ax 即可。聽起來很簡單, 但一開始我可是花了一星期才搞懂 (希望大家不要像我一樣, 花這麼多的時間)。L65, L75 就是用來做這件事情。

這一樣要使用 bochs 內建 debugger 來跑過一次才能有所體會, 並且將相關暫存器, 記憶體內容 dump 出來, 程式體驗有時候甚至比理解觀念還重要。我只能解釋到這裡, 其他就得靠讀者們自己跑程式碼。不過還是希望不要讓大家誤會 process switch 是如此簡單, 一樣是省略很多不太容易說明的細節 (ex: stack 的保存與回復) 才想到的解釋方式。會 save/restore %ax 之後, 當然其他的 register 應該也沒問題, 依樣畫葫蘆即可。

這系列的目的不是說明 process switch 的實作, 只是希望能幫助有興趣的同好在看相關的程式碼時, 能容易看些。

simple_proc.S
 1 #define STACK_FRAME_OFFSET 6
 2 .code16
 3 .text
 4 .global begin
 5 begin:
 6   xchg %bx, %bx #bochs magic break point
 7   cli
 8 
 9   xor     %eax, %eax
10   mov     %cs,%ax
11   mov     %ax,%ds
12 
13 ## reset 0x30 interrupt
14   movw    $0x0, %bx
15   movw    %bx, %es
16   movw $switch_proc, %es:0xc0 # isr offset
17   movw %ax, %es:0xc2 #isr seg
18 
19 
20   movw    $0xb800, %ax
21   movw    %ax, %gs
22 
23 ## set stack frame eip
24   movw $proc_a, stack_frame_a+2
25   movw $proc_b, stack_frame_b+2
26 
27 ## set stack frame cs
28   movw %cs, %ax
29   movw %ax, stack_frame_a+4
30   movw %ax, stack_frame_b+4
31 
32 ## set stack frame flag
33   # get flag
34   pushf
35   movw (%esp), %ax
36   popf
37   movw %ax, stack_frame_a+6
38   movw %ax, stack_frame_b+6
39 
40   movw $stack_frame_a, cur_proc
41   movw cur_proc, %sp
42   popw %ax
43   iret
44 
45   mov $0x4c00, %ax
46   int $0x21           
47 
48 
49 .global proc_a
50 proc_a:
51 1:
52   mov $0x1, %ax
53   int $0x30
54   jmp 1b
55 
56 .global proc_b
57 proc_b:
58 1:
59   mov $0x2, %al
60   int $0x30
61   jmp 1b
62 
63 .global switch_proc
64 switch_proc:
65   pushw %ax
66   movw cur_proc, %dx
67   cmp $stack_frame_a, %dx
68   je 1f
69   movw $stack_frame_a, cur_proc
70   jmp 2f
71 1:
72   movw $stack_frame_b, cur_proc
73 2:
74   movw cur_proc, %sp
75   popw %ax
76   iret
77 
78 
79 cur_proc:
80   .word 0x0
81 
82   .space  256, 0
83 proc_stack_top_a:
84   .space  256, 0
85 proc_stack_top_b:
86 
87 stack_frame_a:
88   .word 0x9# ax
89   .word 0x0# eip
90   .word 0x1# cs
91   .word 0x2# flag
92 
93 stack_frame_b:
94   .word 0x9# ax
95   .word 0x0# eip
96   .word 0x1# cs
97   .word 0x2# flag
98 

該來的還是跑不掉, 我本來不想提及保護模式, 這得加上不少程式碼, 違反了我提到的簡單原則, 我只想將重點擺在 process switch, 不過最後的說明還是得用到, x86 process switch implementation (2) 要談談恐怖的保護模式。

2013年3月14日 星期四

x86 process switch implementation (2) - x86 protected mode verison

我知道, 長很多是吧!除了進入保護模式的差異, 這程式和 x86 process switch implementation (1) - save %ax 做的是一樣的事情。而 int 0x30 就不僅僅是改改位址就好, 需要設定 idt。而且 stack 長度也由 2byte 變為 4byte。

要完全看懂這程式可能很不容易, 但是如果把焦點一樣放在 process switch, 不要管那些保護模式相關的程式碼, 應該還是可以看懂。

L114 ~ 214 都很熟悉吧!和 x86 real mode 版本完全一樣。那我是不是很無聊做了個保護模式的版本, 只是要炫燿, 當然不是, 我需要保護模式的另外一個功能來完成最後的步驟 - stack save/restore。

請看下一篇, A ... 沒下一篇阿!糟糕 xD!!

p_proc.S
  1 # in protected mode, do process switch
  2 
  3 #define STACK_FRAME_OFFSET 6
  4 .code16
  5 .data
  6 .macro Descriptor Base, Limit, Attr
  7     .2byte  \Limit & 0xFFFF
  8     .2byte  \Base & 0xFFFF
  9     .byte   (\Base >> 16) & 0xFF
 10     .2byte  ((\Limit >> 8) & 0xF00) | (\Attr & 0xF0FF)
 11     .byte   (\Base >> 24) & 0xFF
 12 .endm
 13 
 14 .set    DA_386IGate, 0x8E    /* 32-bit Interrupt Gate */
 15 
 16 /* Gate Descriptor data structure.
 17    Usage: Gate Selector, Offset, PCount, Attr
 18     Selector:  2byte
 19     Offset:    4byte
 20     PCount:    byte
 21     Attr:      byte */
 22 .macro Gate  Selector, Offset, PCount, Attr
 23     .2byte     (\Offset & 0xFFFF)
 24     .2byte     \Selector
 25     .2byte     (\PCount & 0x1F) | ((\Attr << 8) & 0xFF00)
 26     .2byte     ((\Offset >> 16) & 0xFFFF)
 27 .endm
 28 
 29 
 30 
 31 .set    DA_32,  0x4000  /* 32-bit segment */
 32 .set    DA_LIMIT_4K, 0x8000     /* 4K */
 33 .set    DA_CR,   0x9A   /* Execute/Read */
 34 .set    DA_DRW,  0x92   /* Read/Write */
 35 
 36 gdt0: Descriptor  0, 0, 0
 37 gdt1: Descriptor  0, 0xfffff, DA_CR | DA_32 | DA_LIMIT_4K
 38 gdt2: Descriptor  0, 0xfffff, DA_DRW | DA_32 | DA_LIMIT_4K
 39 .set gdt_len, (. - gdt0)
 40 gdt_ptr: .2byte (gdt_len -1 ) # limit
 41          .4byte 0             # base
 42 
 43 .set selector_code, (gdt1-gdt0)
 44 .set selector_data, (gdt2-gdt0)
 45 
 46 idt:
 47 .rept 200
 48 Gate selector_code, switch_proc_offset, 0, DA_386IGate
 49 .endr
 50 .set idt_len, (. - idt)  /* IDT Length */
 51 
 52 idt_ptr: .2byte  (idt_len - 1)  /* IDT Limit */
 53          .4byte  0             /* IDT Base */
 54 
 55 
 56 .text
 57 .global begin
 58 begin:
 59   xchg %bx, %bx #bochs magic break point
 60 
 61     /* Initialize 32-bits code segment descriptor. */
 62     xor     %eax, %eax
 63     mov     %cs, %ax
 64     shl     $4, %eax
 65 #    addl    $seg_code32, %eax
 66     movw    %ax, (gdt1 + 2)
 67     shr     $16, %eax
 68     movb    %al, (gdt1 + 4)
 69     movb    %ah, (gdt1 + 7)
 70 
 71     /* Initialize 32-bits data segment descriptor. */
 72     xor     %eax, %eax
 73     mov     %cs, %ax
 74     shl     $4, %eax
 75 #    addl    $seg_code32, %eax
 76     movw    %ax, (gdt2 + 2)
 77     shr     $16, %eax
 78     movb    %al, (gdt2 + 4)
 79     movb    %ah, (gdt2 + 7)
 80 
 81   xor     %eax, %eax
 82   mov     %cs, %ax
 83   shl     $4, %eax
 84   add     $gdt0, %eax      /* eax <- gdt base*/
 85   movl    %eax, (gdt_ptr + 2)
 86   lgdt gdt_ptr
 87 
 88     /* Prepared for loading IDTR */
 89     xor     %eax, %eax
 90     mov     %ds, %ax
 91     shl     $4, %eax
 92     add     $idt, %eax      /* eax <- idt base*/
 93     movl    %eax, (idt_ptr + 2)
 94 
 95   cli
 96 
 97   lidt idt_ptr
 98 
 99   # open a20 line
100   inb $0x92, %al
101   orb $0b00000010, %al
102   outb %al, $0x92
103 
104 
105   # enable protect mode
106   movl %cr0, %eax
107   orl $1, %eax
108   movl %eax, %cr0
109 
110 
111   xchg %bx, %bx #bochs magic break point
112   ljmpl $selector_code, $seg_code32
113 
114 .code32
115 seg_code32:
116   mov $selector_data, %ax
117   mov %ax, %ds
118   mov %ax, %ss
119   mov $proc_stack_top, %esp
120 
121 ## set stack frame flag
122   # get flag
123   pushf
124   movl (%esp), %eax
125   popf
126   movl %eax, stack_frame_a+12
127   movl %eax, stack_frame_b+12
128   cli
129 
130 ## set stack frame eip
131   movl $proc_a, stack_frame_a+4
132   movl $proc_b, stack_frame_b+4
133 
134 ## set stack frame cs
135   xor     %eax, %eax
136   movw %cs, %ax
137   movw %ax, stack_frame_a+8
138   movw %ax, stack_frame_b+8
139 
140 
141   movl $stack_frame_a, cur_proc
142   movl cur_proc, %esp
143   popl %eax
144   iret
145 
146 
147 
148 
149   mov $selector_data, %ax
150   int $0x30
151   mov %ax, %ds
152   mov %ax, %es
153   mov %ax, %ss
154   mov $0xb8004, %edi    /* Destination */
155   movb $'P', (%edi)
156   mov $0xb8005, %edi    /* Destination */
157   movb $0x9, (%edi)
158 
159 .global proc_a
160 proc_a:
161 1:
162   mov $0x1, %ax
163   int $0x30
164   jmp 1b
165 
166 .global proc_b
167 proc_b:
168 1:
169   mov $0x2, %al
170   int $0x30
171   jmp 1b
172 
173 
174 .global int0x30
175 int0x30:
176 .set int0x30_offset, (. - seg_code32)
177   mov $0x1, %cx;
178   iret
179 .global switch_proc
180 switch_proc:
181 .set switch_proc_offset, (. - begin+0x100)
182   xchg %bx, %bx #bochs magic break point
183   push %eax
184 
185   movl cur_proc, %edx
186   cmpl $stack_frame_a, %edx
187   je 1f
188   movl $stack_frame_a, cur_proc
189   jmp 2f
190 1:
191   movl $stack_frame_b, cur_proc
192 2:
193 
194 
195   movl cur_proc, %esp
196   pop %eax
197   iret
198 
199 stack_frame_a:
200   .long 0x9# eax
201   .long 0x0# eip
202   .long 0x0# cs
203   .long 0x2# flag
204 
205 stack_frame_b:
206   .long 0x9# ax
207   .long 0x0# eip
208   .long 0x0# cs
209   .long 0x2# flag
210 cur_proc:
211   .long 0x0
212 
213   .space  256, 0
214 proc_stack_top:

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