我曾經在「os process」提到 os porcess 的切換, 那時候令我興奮不已。但當時的概念還是很模糊, 我決定要用我自己的話來說明這個概念, 也證明我真的搞懂。
化繁為簡是我的學習原則, 對於 process 切換的掌握度還不夠, 打算用自己的方法來實作一個 process 切換的程式。希望這程式符合幾點:
- 程式碼小
- 在 dos 下執行
- 使用 x86 real mode
那這程式有多小呢? 大概像下面這樣:
加了註解還沒超過 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 指令做的事情:
- 把旗標暫存器 push 堆疊
- 禁止其他中斷發生
- 清除陷阱旗標
- 把 CS 暫存器 push 堆疊
- 把 INT n 的下一指令位址 push 堆疊
- 由 0000:(4n) 位址取出中斷服務程式所在位址,並執行長程跳躍指令,至該處繼續執行
- 由堆疊中 pop 4 bytes (cs:ip),並把控制權交到該 4 bytes 所指位址
- 由堆疊 pop 旗標暫存器 (2 bytes)
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 中斷改寫參考資料:
- 第 36 章 中斷
- 中断矢量表的结构
- 中断服务程序
- 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 - 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
沒有留言:
張貼留言
使用 google 的 reCAPTCHA 驗證碼, 總算可以輕鬆留言了。
我實在受不了 spam 了, 又不想讓大家的眼睛花掉, 只好放棄匿名留言。這是沒辦法中的辦法了。留言的朋友需要有 google 帳號。