2013年2月17日 星期日

在 x86 真實模式載入大於 1M 的 kernel

懷念完美少女遊戲後, 該來點枯燥乏味的東西, 趕在 2013 春節最後一天完成 (20130217), 真是開心。這問題還真有點困難, 因為在 x86 真實模式下, 只能存取 1M 記憶體位址, 但實際上比 1M 還少, 還記得 dos 的 640K 限制嗎?扣掉 bios/介面卡所使用的記憶體空間, 大概只有 0x0000:0x0500 ~ 0x9000:0xFFFF 這範圍的記憶體可用。
大約是: 0xa0000 - 0x500 = 654080 byte 。

參考這張記憶體配置圖: http://descent-incoming.blogspot.tw/2012/11/elf.html

simple os kernel + romfs ramdisk 就必須小於 654080 byte。

kernel 要超過很不容易, 但是 ramdisk 隨便塞個檔案就容易超過了, 所以該是來解決這問題了。
有兩個方法:
  1. 自己處理磁碟載入的程式碼, 也就是自己刻 floppy disk, ide/sata driver, 不使用 bios 0x13 call, 這樣就可以在保護模式下驅動磁碟機。
  2. 使用 big real mode。

柿子挑軟的吃, 方案一實在太困難, usb floppy 要搞定的東西可不少, 不像以前使用單純的軟碟介面, 光是處理 usb 可就不輕鬆。我決定使用 big real mode。

big real mode 也稱為 unreal mode, flat real mode 簡單說就是在 real mode 下, 但是可以存取 4g 的記憶體空間, 怎麼做到?大概是這樣:
切入保護模式, 設定存取 4g 的 gdt/selector, 設定這個 selector 到某個 segment register (ex: %fs), 切回真實模式, 使用 %fs 來定址。

big real mode:

big real mode 可以調用呼叫真實模式下的 bios call, 所以我就可以使用 int 0x13 來讀取磁碟上的檔案, 每次讀取一個 sector, 然後複製到 1MB 以外的位址 (我是複製到 0x300000), 這樣就可以在真實模式下載入一個超過 1MB 的 kernel。

我使用 %fs:0x300000 這樣的方式將 kernel 複製到這個位址 (in big real mode)。%fs 是 0x28 (base = 0, limit = 4G), 在我的認知, 我認為絕對位址應該是 0x300000, 也就是我複製 kernel 到物理位址 0x300000。而切換到保護模式後 (同一份 gdt), 讀取 0x300000 也應該可以讀到 kernel。

一開始我在 qemu/bochs 測試, 如我所願, 我也使用 bochs 內建 debugger 觀看 0x300000 的位址, 的確是 kernel 的內容。

本來以為已經搞定, 模擬器 qemu/bochs 都沒問題, 開心的不得了, 在真實機器測試應該也可順利通過, but ... 在真實機器下卻不是這樣, 程式完全不正常, 沒什麼比這還令人沮喪了。無法在真實機器執行, 這程式就沒意義了, 對我來說, 這就是錯誤的程式, 我不希望這程式只能活在虛擬機環境。

真實機器可沒有那麼好的 debug 環境, 透過冥想 + debug code, 勉強在真實機器上完成了。

在真實機器上, %fs:0x300000 並不是存取到 0x300000, 而是 0x300000+0x28x16。我如何得知?因為我寫程式去 dump 0x300000 的內容, 並不是我預期的 kernel, 而 0x300000+0x28x16 才是 kernel 的內容。我猜測在真實機器上 %fs:0x300000 存取的不是 0x300000 而是 0x300000+0x28x16, 因為 %fs 是 0x28 的關係。

我疑惑的是 qemu/bochs 的結果和真實機器不同, 是我搞錯什麼了? 在 eeepc 901 和 amd 的機器上測試, %fs:0x300000 都是得到 0x300000+0x28x16, 而不是我預期的 0x300000。

其實我有用 %fs:0xb8000 來顯示 char (in big real mode), 並不是在第0行, 第0列的位址, 才讓我想到有可能差了 0x28X16。下圖的藍色字串便是使用 %fs:0xb8000 來印到螢幕。

寄件者 write_os
在 eeepc 901 上測試, 這個 kernel size: 672596 超過 654080, 證明突破 640K 真實模式下的限制。



上圖是 simple os 執行畫面, 那麼問題出在哪裡呢?

selector 0x28 in %fs, 這是我在 big real mode 設定的 gdt/selector。

LABEL_DESC_4G_RW: Descriptor  0, 0xfffff, DA_DRW | DA_32 | DA_LIMIT_4K

0x28 這個 selector 定義的 segment 如上:
base:0
limit: 0xffffffff

整個 4G 空間。

在 qemu/bochs 保護模式下, 使用 %fs:0x300000 時, 是指到絕對位址 0x300000, 3M 的位址上。但在我的 eeepc 901 真實機器上則是 0x300000 + 0x28*16 = 0x300280

#ifdef REAL_PC
  u32 buff = LOAD_KERNEL_ADDR + (0x28*16);
#else
  u32 buff = LOAD_KERNEL_ADDR;
#endif

所以我的程式多了這個 REAL_PC macro。

下面的圖是另外一台 nb, amd 的 cpu, 也可正常開到 simple os 畫面, 讓我對程式的正確性信心大增。



前面說勉強可用是因為若插入 debug function, insert p_dump_u8(buff, 32); in copy_elf_code(), kernel 還是無法正常載入, dump_u8() 是 debug function, 所以又得靠冥想來解決這問題了。

好累, 沒處理掉這問題, 休假期間都睡不安穩阿!

附上一起和我 debug 的 ibm usb floppy disk, 辛苦啦, 喀喀喀的讀取聲, 緩慢的讀取速度, 還真懷念。

寄件者 write_os

使用 usb storage 測試 1M 的 kernel (1314148 bytes), 也能順利載入。也許你會有個疑問, 為什麼不用軟碟測試?
  1. 軟碟要讀很久, 我等著等著會睡著, 要是失敗了, 很浪費時間。
  2. 軟碟機資料很容易出錯, 有錯的話, 我不知道是程式錯了還是軟碟片本身資料就是錯的。



到這裡都還在真實模式下 (正確來說是 big real mode), 按下空白鍵後就會切到保護模式, 再來便會跳到 kernel 的 code, 執行 kernel。

usb storage 的速度比軟碟片快多了, 畫面的 '.' 代表讀入一個 sector (512 byte), 所以 kernel 檔案愈大看到的 '.' 就愈多, 看著畫面上滿滿的 '.' 還蠻有成就感的。



https://github.com/descent/simple_os
git commit: 1ebf4fc42c484e0d2d2d0e9027ec4e9654ccb341

ref: x86/x64体系探索及编程 by 邓志

沒有留言:

張貼留言

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

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