黨電腦開啟電源後,x86系列的CPU一開始進入實模式。並從地址0xFFFF0開始自動執行程序,而這個地址是ROM-BIOS中的地址。也就是說,機器開機後會自動自行BIOS中的一段預設的代碼,這段代碼的主要執行系統自檢,並在物理地址0處初始化中斷向量。然後將啟動設備的第一個扇區(512字節)讀入到內存絕對地址0x7c00,然後自動跳轉到這裡,也就是將系統控制權交給了這個引導程序(bootsect.s)。bootsect.s的第一個動作是保護自己,將自己從0x7c00移到到0x90000(利用匯編中的字符串移動指令)。
為什麼移動到0x90000而不是別的地方?因為此後bootsect.s會將system加載到0x10000,而Linus Torvalds給內核預設的最大值為512K(最新內核里的說法是508K),0x90000-0x10000=0x80000=512K;第二個動作則是在緊隨bootsect.s的後面加載setup.s到0x90200(0x90200-0x90000=0x200=512),setup.s佔據4個磁盤扇區的空間(512x4=2048=0x800),這裡是利用BIOS的13中斷來讀取磁盤數據;第三步就是將system加載到0x10000處,同樣使用BIOS13中斷。為什麼一開始加載到0x10000而後來卻要在setup.s中移動到0x00000?因為開機後BIOS會將中斷向量表保存在0x0000~0x0400處,setup.s中還要用到BIOS中斷,如果一開始就加載到0x00000,那......。而後來移動到0x00000應該是想使system中的地址和物理地址一致,而且此後linux再也不需要調用BIOS中斷了,它自己在setup.s中設置了256個啞中斷,指向一个只显示"Unknown interrupt"的中断处理程序。然后会在init的main()中各个硬件的初始化函数中一个一个地分别设置所用到的实际中断向量。
所有的加載動作完成後,bootsect.s也就完成了自己的使命。最後一條指令就是條轉到0x90200,把以後的光榮任務交給setup.s。
setup.s首先利用BIOS中斷讀取有關內存/磁盤/顯示和其他參數,並將這些參數放到0x90000~0x901FF,也就是先前bootsect.s的地盤。現在還在實模式下,只能使用640K的常規內存,當然不能浪費寶貴的內存資源,要儘量廢物利用了緊接著setup.s就移動system到0x00000處,然後加載中斷描述符表寄存器(idt)和全據描述符表寄存器(gdt),開啟A20地址線(通過操作鍵盤控制器8042的P2口實現),重新設置兩個中斷控制器8259A,將硬件中斷號重新設置為0x20~0x2f,最後設置CPU的控制寄存器CR0,從而進入32位保護模式(置位CR0的0位),然後就讓位給head.s。
現在進入到head.s,這是CPU已經運行在保護模式了,head.s位于地址0x00000處。它開始加載各個數據段寄存器,重新設置中斷描述符idt(256項),指向一个只显示"Unknown interrupt"的啞中断处理程序。然後重設全局描述符表gdt,再檢測一下A20是否真的開啟(這個真的很重要,不然只能訪問1M的內存啊),沒有開啟的話就只好死機。如果檢查通過,接著就問是否有數學協處理器,有的話就置位CR0的1位。然後設置MM的分頁處理機制,並把頁目錄表放在0x00000處(也就是head.s自己所在的地方,它準備自殺了),自殺過程一開始就沒路可退了,只好趕緊執行最後一條指令跳轉到init/main.c的main()(通過返回指令將預先放在堆棧中的main.c的地址彈出來實現)
注:我閱讀內核使用的書是<Linux内核完全注释(內核版本0.11)>,可以到http://oldlinux.org下載。
剛開始閱讀內核,這是第一篇筆記,錯漏之處,歡迎指正!更多筆記請留意我的blog。
為什麼移動到0x90000而不是別的地方?因為此後bootsect.s會將system加載到0x10000,而Linus Torvalds給內核預設的最大值為512K(最新內核里的說法是508K),0x90000-0x10000=0x80000=512K;第二個動作則是在緊隨bootsect.s的後面加載setup.s到0x90200(0x90200-0x90000=0x200=512),setup.s佔據4個磁盤扇區的空間(512x4=2048=0x800),這裡是利用BIOS的13中斷來讀取磁盤數據;第三步就是將system加載到0x10000處,同樣使用BIOS13中斷。為什麼一開始加載到0x10000而後來卻要在setup.s中移動到0x00000?因為開機後BIOS會將中斷向量表保存在0x0000~0x0400處,setup.s中還要用到BIOS中斷,如果一開始就加載到0x00000,那......。而後來移動到0x00000應該是想使system中的地址和物理地址一致,而且此後linux再也不需要調用BIOS中斷了,它自己在setup.s中設置了256個啞中斷,指向一个只显示"Unknown interrupt"的中断处理程序。然后会在init的main()中各个硬件的初始化函数中一个一个地分别设置所用到的实际中断向量。
所有的加載動作完成後,bootsect.s也就完成了自己的使命。最後一條指令就是條轉到0x90200,把以後的光榮任務交給setup.s。
setup.s首先利用BIOS中斷讀取有關內存/磁盤/顯示和其他參數,並將這些參數放到0x90000~0x901FF,也就是先前bootsect.s的地盤。現在還在實模式下,只能使用640K的常規內存,當然不能浪費寶貴的內存資源,要儘量廢物利用了緊接著setup.s就移動system到0x00000處,然後加載中斷描述符表寄存器(idt)和全據描述符表寄存器(gdt),開啟A20地址線(通過操作鍵盤控制器8042的P2口實現),重新設置兩個中斷控制器8259A,將硬件中斷號重新設置為0x20~0x2f,最後設置CPU的控制寄存器CR0,從而進入32位保護模式(置位CR0的0位),然後就讓位給head.s。
現在進入到head.s,這是CPU已經運行在保護模式了,head.s位于地址0x00000處。它開始加載各個數據段寄存器,重新設置中斷描述符idt(256項),指向一个只显示"Unknown interrupt"的啞中断处理程序。然後重設全局描述符表gdt,再檢測一下A20是否真的開啟(這個真的很重要,不然只能訪問1M的內存啊),沒有開啟的話就只好死機。如果檢查通過,接著就問是否有數學協處理器,有的話就置位CR0的1位。然後設置MM的分頁處理機制,並把頁目錄表放在0x00000處(也就是head.s自己所在的地方,它準備自殺了),自殺過程一開始就沒路可退了,只好趕緊執行最後一條指令跳轉到init/main.c的main()(通過返回指令將預先放在堆棧中的main.c的地址彈出來實現)
注:我閱讀內核使用的書是<Linux内核完全注释(內核版本0.11)>,可以到http://oldlinux.org下載。
剛開始閱讀內核,這是第一篇筆記,錯漏之處,歡迎指正!更多筆記請留意我的blog。
我补充一点 :-) 在新版的内核中(有多新我不知道)bootsect.s会判断system是否是Big_kernel(大内存编译)如果是则将system装入0x100000h(因为常规内存中装不下),不是的话装入0x10000h。在进入Setup.s执行后也要判断Big_kernel,如果是则继续执行,如果不是则将system搬到0x1000h处(我看书上这末写的,我还没看原码不知道是否正确),我估计是因为开始的0x00000h-0x00400h是Bios中断向量表,0x00401h-0x00800h是BIOS数据区,因此将其放在0x1000h。有些牵强所以我不太确定。
至于如何将system装入0x100000h需要了解一部分内存的知识。
00000h-9FFFFh 常规内存
0xA0000h-FFFFFh 上位内存
0x100000h-10FFEFh。为高端内存,10FFEF以后的所有内存都为扩展内存。
其中高端内存虽然超过了1M但仍能在实时模式下访问,在 bootsect.s中是通过int 15h实现。据说int 15h也是通过先进入保护模式处理完后在恢复我没有找到这方面资料。以上是我的理解,我也是刚开始看时间不长,对代码还不能全面把握,不对的地方还请指出。
至于如何将system装入0x100000h需要了解一部分内存的知识。
00000h-9FFFFh 常规内存
0xA0000h-FFFFFh 上位内存
0x100000h-10FFEFh。为高端内存,10FFEF以后的所有内存都为扩展内存。
其中高端内存虽然超过了1M但仍能在实时模式下访问,在 bootsect.s中是通过int 15h实现。据说int 15h也是通过先进入保护模式处理完后在恢复我没有找到这方面资料。以上是我的理解,我也是刚开始看时间不长,对代码还不能全面把握,不对的地方还请指出。