head.s 通過編譯連結的處理,同時被 boot.s 載入到保護模式零地址開始運行,在最後會調用 main 函數。
代碼:
/* * head.s contains the 32-bit startup code. * * NOTE!!! Startup happens at absolute address 0x00000000, which is also where * the page directory will exist. The startup code will be overwritten by * the page directory. * 注意:啟動程式在 0x00000000,同時這個地址也是頁表的地址,在這個啟動完成後,這裡的程式與資料會被頁表覆蓋。 */.code32 .text.globl idt, gdt, pg_dir, startup_32pg_dir:startup_32:movl $0x10,%eax # 已經處於32 位保護模式,$0x10 是通用描述元表項的選擇符mov %ax,%dsmov %ax,%esmov %ax,%fsmov %ax,%gslss stack_start,%esp # 設定系統堆棧:ss,esp,stack_start 在 kernel/sched.c, 40 行call setup_idt # 設定中斷描述符表call setup_gdt # 設定通用描述元表# 因為修改了gdt,所以需要重新裝載所有的段寄存器movl $0x10,%eax # reload all the segment registersmov %ax,%ds# after changing gdt. CS was alreadymov %ax,%es# reloaded in 'setup_gdt'mov %ax,%fsmov %ax,%gslss stack_start,%espxorl %eax,%eax# 測試A20 地址線是否已經開啟,如果沒有開啟,核心無法使用大於 1M 的記憶體# 採用的方法是向記憶體位址0x000000 處寫入任意數值,# 檢查 0x100000(1M) 處是否也是這個數值。1:incl %eax# check that A20 really IS enabledmovl %eax,0x000000cmpl %eax,0x100000je 1b # 向後尋找標號# CR0 : http://en.wikipedia.org/wiki/Control_registermovl %cr0,%eax# check math chipandl $0x80000011,%eax# Save PG,ET,PEtestl $0x10,%eax # test MP 位jne 1f# ET is set - 387 is presentorl $4,%eax# else set emulate bit, EM 位1:movl %eax,%cr0jmp after_page_tables/*********************************setup_idt*******************************//* * setup_idt * * sets up a idt with 256 entries pointing to * ignore_int, interrupt gates. It then loads * idt. Everything that wants to install itself * in the idt-table may do so themselves. Interrupts * are enabled elsewhere, when we can be relatively * sure everything is ok. This routine will be over- * written by the page tables. *//* * 下面這段是設定中斷描述符表子程式 setup_idt * * 將中斷描述符表idt 設定成具有256 個項,並都指向ignore_int 中斷門。然後載入中斷 * 描述符表寄存器(用lidt 指令)。真正實用的中斷門以後再安裝。當我們在其它地方認為一切 * 都正常時再開啟中斷。該子程式將會被頁表覆蓋掉。*/setup_idt:lea ignore_int,%edx # 將 ignore_int 的有效地址(位移值)存入到 edx 寄存器movl $0x00080000,%eax # 將選擇符 0x0008 置入 eax 的高16 位中movw %dx,%ax/* selector = 0x0008 = cs # 位移值的低16 位置入eax 的低16 位中。此時eax 含有門描述符低4 位元組的值*/movw $0x8E00,%dx/* interrupt gate - dpl=0, present */lea idt,%edi # 將 idt 的有效地址存入 edimov $256,%ecx # 迴圈計數 256,256 個表項rp_sidt:movl %eax,(%edi) # 將 eax 的值存入 idt 的第一項的前32位movl %edx,4(%edi) # 將 edx 的值存入 idt 的第一項的第二個32位addl $8,%edi # 使 edi 指向 idt 的第二項dec %ecxjne rp_sidt # 重複執行lidt idt_descr # 設定中斷描述符表ret/*********************************setup_gdt*******************************//* * setup_gdt * * This routines sets up a new gdt and loads it. * Only two entries are currently built, the same * ones that were built in init.s. The routine * is VERY complicated at two whole lines, so this * rather long comment is certainly needed :-). * This routine will beoverwritten by the page tables. */setup_gdt:lgdt gdt_descr # 設定全域描述附表ret/**********************************************************************//* Linus 將核心的記憶體頁表直接放在頁目錄之後,使用了2 個表來定址8 Mb 的實體記憶體。 * 如果你有多於8 Mb 的記憶體,就需要在這裡進行擴充修改。*/# 每個頁表長為4 Kb 位元組,而每個頁表項需要4 個位元組,因此一個頁表共可以存放1000 個表項,# 如果一個表項定址4 Kb 的地址空間,則一個頁表就可以定址4 Mb 的實體記憶體。# 頁表項的格式為:項的前0-11 位存放一些標誌,如是否在記憶體中(P 位0)、讀寫許可(R/W 位1)、# 普通使用者還是超級使用者使用(U/S 位2)、是否修改過(是否髒了)(D 位6)等;表項的位12-31 是# 頁框地址,用於指出一頁記憶體的物理起始地址。# 從位移0x1000 處開始是第1 個頁表(位移0 開始處將存放頁表目錄)。/*********************************0x1000*******************************/.org 0x1000 # 設定起始地址pg0:/*********************************0x2000*******************************/.org 0x2000pg1:/*********************************0x3000*******************************/.org 0x3000pg2:# This is not used yet, but if you# want to expand past 8 Mb, you'll have# to use it./*********************************0x4000*******************************/.org 0x4000after_page_tables:pushl $0# These are the parameters to main :-)pushl $0pushl $0pushl $L6# return address for main, if it decides to.pushl $mainjmp setup_paging # 跳轉到分頁的設定代碼L6:jmp L6# main should never return here, but# just in case, we know what happens./*********************************default interrupt "handler"*******************************//* This is the default interrupt "handler" :-) */.align 2 # 2位元組對齊ignore_int:incb 0xb8000+160# put something on the screenmovb $2,0xb8000+161# so that we know somethingiret# happened/*********************************Setup_paging*******************************//* * Setup_paging * * This routine sets up paging by setting the page bit * in cr0. The page tables are set up, identity-mapping * the first 8MB. The pager assumes that no illegal * addresses are produced (ie >4Mb on a 4Mb machine). * * NOTE! Although all physical memory should be identity * mapped by this routine, only the kernel page functions * use the >1Mb addresses directly. All "normal" functions * use just the lower 1Mb, or the local data space, which * will be mapped to some other place - mm keeps track of * that. * * For those with more memory than 8 Mb - tough luck. I've * not got it, why should you :-) The source is here. Change * it. (Seriously - it shouldn't be too difficult. Mostly * change some constants etc. I left it at 8Mb, as my machine * even cannot be extended past that (ok, but it was cheap :-) * I've tried to show which constants to change by having * some kind of marker at them (search for "8Mb"), but I * won't guarantee that's all :-( ) */ /* * 這個子程式通過設定控制寄存器cr0 的標誌(PG 位31)來啟動對記憶體的分頁處理功能, * 並設定各個頁表項的內容,以恒等映射前8 MB 的實體記憶體。分頁器假定不會產生非法的 * 地址映射(也即在只有4Mb 的機器上設定出大於4Mb 的記憶體位址)。 * 注意!儘管所有的物理地址都應該由這個子程式進行恒等映射,但只有核心頁面管理函數能 * 直接使用>1Mb 的地址。所有"一般"函數僅使用低於1Mb 的地址空間,或者是使用局部資料 * 空間,地址空間將被映射到其它一些地方去 -- mm(記憶體管理程式)會管理這些事的。 * 對於那些有多於8Mb 記憶體的傢伙 - 太幸運了,我還沒有,為什麼你會有?代碼就在這裡, * 對它進行修改吧。(實際上,這並不太困難的。通常只需修改一些常數等。我把它設定為 * 8Mb,因為我的機器再怎麼擴充甚至不能超過這個界限(當然,我的機器很便宜的)。 * 我已經通過設定某類標誌來給出需要改動的地方(搜尋"8Mb"),但我不能保證作這些 * 改動就行了)。 */# paging : http://en.wikipedia.org/wiki/Paging.align 2setup_paging:movl $1024*3,%ecx # 0x1000=4096 byte=1024*32位, pg2 並沒有使用,也沒有在這裡初始化。xorl %eax,%eax # eax 清零xorl %edi,%edi # edi 清零/* pg_dir is at 0x000 */cld;rep;stosl # cld 設定傳送方向,rep 重複 ecx 次,stos 將 eax 的值傳送到 edi 所指向的記憶體# 下面2 句設定頁目錄中的項,我們共有2 個頁表所以只需設定2 項。# 頁目錄項的結構與頁表中項的結構一樣,4 個位元組為1 項。# "$pg0+7"表示:0x00001007,是頁目錄表中的第1 項。# 則第1 個頁表所在的地址 = 0x00001007 & 0xfffff000 = 0x1000;# 第1 個頁表的屬性標誌 = 0x00001007 & 0x00000fff = 0x07,表示該頁存在、使用者可讀寫。movl $pg0+7,pg_dir/* set present bit/user r/w */movl $pg1+7,pg_dir+4/* --------- " " --------- */# 下面6 行填寫2 個頁表中所有項的內容,共有:2(頁表)*1024(項/頁表)=2048 項(0 - 0xfff),# 也即能映射實體記憶體 2048*4Kb = 8Mb。# 每項的內容是:當前項所映射的實體記憶體地址 + 該頁的標誌(這裡均為7)。# 使用的方法是從最後一個頁表的最後一項開始按倒退順序填寫。一個頁表的最後一項在頁表中的# 位置是1023*4 = 4092。因此最後一頁的最後一項的位置就是$pg3+4092。movl $pg1+4092,%edi# 最後1 項對應實體記憶體頁面的地址是0x7ff000,# 加上屬性標誌7,即為0xfff007.movl $0x7ff007,%eax/* 8Mb - 4096 + 7 (r/w user,p) */std # 方向位置位,edi 值遞減(4 位元組)。1:stosl/* fill pages backwards - more efficient :-) */subl $0x1000,%eax # 每填寫好一項,物理地址值減0x1000。jge 1b # 如果大於等於零,向後跳轉到 1 繼續執行。xorl %eax,%eax/* pg_dir is at 0x0000 */ # 頁目錄表在0x0000 處movl %eax,%cr3/* cr3 - page directory start */# 設定啟動使用分頁處理(cr0 的PG 標誌,位31)movl %cr0,%eaxorl $0x80000000,%eax movl %eax,%cr0/* set paging (PG) bit */ # 添上PG 標誌# 在改變分頁處理標誌後要求使用轉移指令重新整理預取指令隊列,這裡用的是返回指令ret。# 該返回指令的另一個作用是將堆棧中的main 程式的地址彈出,並開始運行/init/main.c 程式。# 本程式到此真正結束了。終於結束了該死的彙編代碼!ret/* this also flushes prefetch-queue *//*********************************idt_descr*******************************/.align 2.word 0idt_descr:.word 256*8-1# idt contains 256 entries.long idt/*********************************gdt_descr*******************************/.align 2.word 0gdt_descr:.word 256*8-1# so does gdt (not that that's any.long gdt# magic number, but it works for me :^)/*********************************idt*******************************/.align 8idt:.fill 256,8,0# idt is uninitialized # 256 項,每項8 位元組,填0。/*********************************gdt*******************************/gdt:.quad 0x0000000000000000/* NULL descriptor */.quad 0x00c09a00000007ff/* 8Mb , 程式碼片段*/.quad 0x00c09200000007ff/* 8Mb , 資料區段*/.quad 0x0000000000000000/* TEMPORARY - don't use */.fill 252,8,0/* space for LDT's and TSS's etc */
需要進一步完善。