首先要介紹一下 對於bootloader訪問硬碟時都是lba模式的pio方式,也就是說所有的i/o操作都是通過cpu訪問硬碟的i/o位址暫存器完成。作業系統位於第乙個硬碟上,而訪問第乙個硬碟的扇區可以設定i/o埠0x1f0~0x1f7來改變位址暫存器實現。下述**所顯示的即為0x1f0~0x1f7所對應的功能:i/o位址
功能0x1f0
讀資料,當0x1f7不為忙狀態時,可以讀
0x1f1
可獲得詳細的錯誤資訊
0x1f2
與讀寫的扇區數量,每次讀寫前,都需要表明要讀寫幾個扇區
0x1f3
如果是lba格式,就是讀lba引數的0~7位
0x1f4
如果是lba格式,就是讀lba引數的8~15位
0x1f5
如果是lba格式,就是讀lba引數的16~23位
0x1f6
第0~3位:如果是lba模式就是24-27位 第4位:為0主盤;為1從盤
0x1f7
狀態和命令暫存器。操作時先給命令,再讀取,如果不是忙狀態就從0x1f0埠讀資料
實際操作中,需要知道怎樣與硬碟互動。閱讀材料中同樣給出了答案:所有的io操作是通過cpu訪問硬碟的io位址暫存器完成。硬碟共有8個io位址暫存器,其中第1個儲存資料,第8個儲存狀態和命令,第3個儲存要讀寫的扇區數,第4~7個儲存要讀寫的起始扇區的編號(共28位)。了解這些資訊,就不難程式設計實現啦。
bootloader讀取扇區的功能是在boot/bootmain.c的readsect函式中實現的,先貼**:
static void
waitdisk
(void)
/* readsect - read a single sector at @secno into @dst */
static void
readsect
(void *dst, uint32_t secno)
一般主機板有2個ide通道,每個通道可以接2個ide硬碟。訪問第乙個硬碟的扇區可設定io位址暫存器0x1f0-0x1f7實現的,具體引數見下表。一般第乙個ide通道通過訪問io位址0x1f0-0x1f7來實現,第二個ide通道通過訪問0x170-0x17f實現。每個通道的主從盤的選擇通過第6個io偏移位址暫存器來設定。從outb()可以看出這裡是用lba模式的pio(program io)方式來訪問硬碟的。從磁碟io位址和對應功能表可以看出,該函式一次只讀取乙個扇區。
readseg簡單包裝了readsect,可以從裝置讀取任意長度的內容。
static void
readseg
(uintptr_t va, uint32_t count, uint32_t offset)
}
根據**可以得出讀取硬碟扇區的步驟:
首先從原理上分析載入流程。
根據elf header和program header表的資訊,我們便可以將elf檔案中的所有segment逐個載入到記憶體中
elf定義:
/* file header */
struct elfhdr
;
在這裡我們只需要關注其中的幾個引數,e_magic,是用來判斷讀出來的elf格式的檔案是否為正確的格式;e_phoff,是program header表的位置偏移;e_phnum,是program header表中的入口數目;e_entry,是程式入口所對應的虛擬位址。巨集定義:
#define elfhdr ((struct elfhdr *)0x10000)
#define sectsize 512
bootloader載入os的功能是在bootmain函式中實現的,先貼**:void
bootmain
(void
)struct proghdr *ph,
*eph;
// elf頭部有描述elf檔案應載入到記憶體什麼位置的描述表,
// 先將描述表的頭位址存在ph
ph =
(struct proghdr *)(
(uintptr_t)elfhdr + elfhdr-
>e_phoff)
; eph = ph + elfhdr-
>e_phnum;
// 按照描述表將elf檔案中資料載入記憶體
for(
; ph < eph; ph ++
)// elf檔案0x1000位置後面的0xd1ec位元被載入記憶體0x00100000
// elf檔案0xf000位置後面的0x1d20位元被載入記憶體0x0010e000
// 根據elf頭部儲存的入口資訊,找到核心的入口((
void(*
)(void))
(elfhdr-
>e_entry &
0xffffff))
(); bad:
outw
(0x8a00
,0x8a00);
outw
(0x8a00
,0x8e00);
while(1
);}
輸入make debug
啟動gdb,並在bootmain函式入口處即0x7d0d設定斷點,輸入c跳到該入口
單步執行幾次,執行到call readseg處,由於該函式會反覆讀取硬碟,為節省時間,可在下一條語句設定斷點,避免進入到readseg函式內部反覆執行迴圈語句。(或者直接輸入n即可,不用這麼麻煩)
執行完readseg後,可以通過x/xw 0x10000
查詢elf header的e_magic的值,查詢結果如下,確實與0x464c457f相等,所以校驗成功。注意,我們的硬體是小端位元組序(這從asm檔案的彙編語句和二進位制**的對比中不難發現),因此0x464c45實際上對應字串"elf",最低位的0x7f字元對應del。
(gdb) x/xw 0x10000
0x10000
:0x464c457f
繼續單步執行,由0x7d2f mov 0x1001c,%eax
可知elf header的e_phoff欄位將載入到eax暫存器,0x1001c相對0x10000的偏移為0x1c,即相差28個位元組,這與elf header的定義相吻合。執行完0x7d2f處的指令後,可以看到eax的值變為0x34,說明program header表在檔案中的偏移為0x34,則它在記憶體中的位置為0x10000 + 0x34 = 0x10034.查詢0x10034往後8個位元組的內容如下所示:
(gdb) x/
8xw 0x10034
0x10034
:0x00000001
0x00001000
0x00100000
0x00100000
0x10044
:0x0000dac4
0x0000dac4
0x00000005
0x00001000
struct proghdr
;
program headers:
type offset virtaddr physaddr filesiz memsiz *** align
load 0x001000
0x00100000
0x00100000
0x0dac4
0x0dac4 r e 0x1000
load 0x00f000
0x0010e000
0x0010e000
0x00aac
0x01dc0 rw 0x1000
gnu_stack 0x000000
0x00000000
0x00000000
0x00000
0x00000 rwe 0x10
繼續單步執行,由0x7d34 movzwl 0x1002c,%esi可知elf header的e_phnum欄位將載入到esi暫存器,執行完x07d34處的指令後,可以看到esi的值變為3,這說明一共有3個segment。
後面是通過磁碟i/o完成三個segment的載入,不再贅述。
從硬碟讀了8個扇區資料到記憶體0x10000處,並把這裡強制轉換成elfhdr使用;
校驗e_magic欄位;
根據偏移量分別把程式段的資料讀取到記憶體中。
ucore lab1 練習5的實驗報告.
《ucore lab1 練習5》實驗報告
我們需要在lab1中完成kdebug.c中函式print stackframe的實現,可以通過函式print stackframe來跟蹤函式呼叫堆疊中記錄的返回位址。如果能夠正確實現此函式,可在lab1中執行 make qemu 後,在qemu模擬器中得到類似如下的輸出 請完成實驗,看看輸出是否與上...
MOOS ivp 實驗二 C 程式設計練習(1)
在moos ivp的第二個實驗中,主要任務是在linux系統中進行c 的程式設計練習。總結 主要記錄一些關於vim的相關操作以及linux中c 的相關程式實驗。基本上所有c 實驗的第一步都是構建乙個hello world檔案,此次實驗當然也不例外。1.先建立乙個文件用來編輯,使用 vim hello...
Week4 結對練習 團隊作業1
2017 10 14 10 00pm,以部落格發表日期為準。晚交 0分 遲交兩周以上 倒扣本次作業分數 抄襲 倒扣本次作業分數 閱讀calculator類,主要實現兩個運算元的加減乘除運算,結合calstring類,擴充套件calculator類實現四則混合運算。類的分析可用思維導圖,參考 附加題 ...