我學習寫 os 的其中一個理由是: 我想把 fork 搞懂, 這個 function 實在是太神奇了, 怎麼有辦法呼叫一次, 可以 return 0 又 return 非零呢?實在是令我太好奇了。一般的 function 是不能做到這樣的功能的。而搞懂的方式唯有實作一次才能稱上是真正的理解。
這篇不是要討論 fork 如何實作, 書上用了一章的篇幅來說明, 我怎麼可能一篇 blog 可以說完, 這是記錄我自己的學習過程, 如果你也剛好看到這章, 也卡在這裡, 我的學習經驗也許可以幫點忙。如果不是在讀這本書, 本篇的內容並不足以學會實作 fork。
其他版本的 fork 實作我不清楚, 本書的實作方式是使用 message 傳送的方式來實作, 有一個 mm process 接受執行 fork 的 message, 然後傳回 fork 的執行結果。
所以還要把第八章的 ipc 複習過一遍, message 的流動實在複雜, 要搞懂第十章的 fork, 一定要弄清楚第八章的 ipc。所以實際上是要看完兩個章節才能理解 fork 實作。而我使用了 romfs/ramdisk 來替代第九章的檔案系統實作, 所以我簡化檔案系統的部份,不用去看第九章的硬碟實作。我急著想搞清楚 fork實作嘛!
對付 fork 實作, 我幾乎出動了十八般武藝, 隨著開發 os 的經驗累積, 我的武器愈來愈多, bochs, qemu 的 gdb 除錯, 看著我不熟悉反組譯後的組合語言, 拿出計算機一一計算整個記憶體位址, dump stack 的值。說實在的, 著實辛苦; 不過辛苦過後的果實, 果然特別的香甜。
再來就剩下 exec, wait, exit 的實作了, 這應該簡單多了, 我已經完成 exec 的部份, 整個實作觀念也大底清楚, 剩下的就是實作細節了。
很高興我犯了好幾個錯誤, 當我一一認清這些錯誤時, 正確的 fork 版本終於搞定。
犯下的錯誤:
- 被 fork process base, limit 和 原 process 重疊。
- 原 process 程式碼沒有被複製到被 fork process memory 空間。
- 每一個 process stack 的分配有錯, 原本分配給 process stack 空間太小, 而分配給每一個 process stack 的位址也錯了, 造成 process stack 爛掉。
init call fork() 訊息流動:
MM recv any msg (MM in recv msg state)
INIT
call fork(), fork will send msg to MM then recv msg form MM.
send msg to MM (解除 MM recv msg state, 這樣才能被 schedule 選來執行)
recv msg from MM (INIT in recv msg state)
這時候 EIP 指向 sendrec int $0x90 的下一行, 因為進入 system call 了。等到接收訊息的 system call return, 從 sendrec int $0x90 的下一行繼續執行。下圖程式碼的箭頭部份。
sendrec:
mov $_NR_SENDREC, %eax
mov 4(%esp), %ebx
mov 8(%esp), %ecx
mov 12(%esp), %edx
int $INT_VECTOR_SYS_CALL # int $0x90
-> pop %edx
pop %ecx
pop %ebx
ret
MM
do_fork - send msg to FORK_INIT (因為 INIT in recv msg state, so FORK_INIT in recv msg state, too)
do_fork send msg to FORK_INIT, so 解除 FORK_INT recv msg state
send msg to INIT (解除 INIT recv msg state, 這樣才能被 schedule 選來執行)
可是 FORK_INIT eip 和 INIT 一樣, 但是包含程式碼的位址不同??
這是因為 base 不一樣, init base 是 0, forked init base 是 0xa00000, 雖然 eip 一樣, 但執行位址一個從 0 算, 一個從 0xa00000 算。這是 x86保護模式下的 segment address。
fork 執行流程:
fork -> send_recv -> sendrec (system call) -> sys_sendrec -> msg_send/msg_receive
debug 小秘訣:
停在 do_fork 後, 中斷點設在
mm/mm.c #189 int x=0; // for debug觀察
proc_table[7].p_flags proc_table[3].p_flags ready_process->name再把中斷點設在 restart (b restart), 再觀察:
proc_table[7].p_flags proc_table[3].p_flags ready_process->name等到 ready_process 是 init_fork (process name: INIT_7) 那個後, 再使用 layout asm, 追蹤 init_fork 這個被 fork 的 process。
you often don't really understand the problem until after the first time you implement a solution. The second time, maybe you know enough to do it right. So if you want to get it right, be ready to start over at least once. Eric S. Raymond
這段話經過這次的學習後, 印象更是深刻。
ref:
http://firstmonday.org/htbin/cgiwrap/bin/ojs/index.php/fm/article/view/578/499
沒有留言:
張貼留言
使用 google 的 reCAPTCHA 驗證碼, 總算可以輕鬆留言了。
我實在受不了 spam 了, 又不想讓大家的眼睛花掉, 只好放棄匿名留言。這是沒辦法中的辦法了。留言的朋友需要有 google 帳號。