1. 程式人生 > 其它 >Linux0.11 head.s

Linux0.11 head.s

/*
 *  linux/boot/head.s
 *
 *  (C) 1991  Linus Torvalds
 */

/*
 *  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.
 */
.text
.globl idt,gdt,pg_dir,tmp_floppy_area
pg_dir:
.globl startup_32

/*
 * 設定資料段為0x10的二進位制為00010000,表示資料段,是全域性描述第2個段
 */
startup_32:
	movl $0x10,%eax
	mov %ax,%ds
	mov %ax,%es
	mov %ax,%fs
	mov %ax,%gs
	/*
	 * 設定棧頂指標
	 *
	 * stack_start定義在kernel/sched.c中
	 * long user_stack[PAGE_SIZE >> 2];
	 * struct {
	 *   long *a;
	 *   short b;
	 * } stack_start { &user_stack, 0x10}
	 * lss設定了棧頂指標和其棧的段地址,0x10表示資料段,地址為stack_start定義起大小為PAGE_SIZE>>2 = 1024位元組
	 */
	lss stack_start,%esp
     call setup_idt //安裝中斷描述符 call setup_gdt //安裝全域性描述符 /* * 重新設定資料段,因為在上面的程式中修改gdt,因此需要重新載入, */ movl $0x10,%eax # reload all the segment registers mov %ax,%ds # after changing gdt. CS was already mov %ax,%es # reloaded in 'setup_gdt' mov %ax,%fs mov %ax,%gs lss stack_start,%esp /* * xorl為異或運算,相同為0,不同為1,此處的意思是清零暫存器 */ xorl %eax,%eax 1: incl %eax # check that A20 really IS enabled movl %eax,0x000000 # loop forever if it isn't cmpl %eax,0x100000 je 1b /* * NOTE! 486 should set bit 16, to check for write-protect in supervisor * mode. Then it would be unnecessary with the "verify_area()"-calls. * 486 users probably want to set the NE (#5) bit also, so as to use * int 16 for math errors. */ movl %cr0,%eax # check math chip andl $0x80000011,%eax # Save PG,PE,ET /* "orl $0x10020,%eax" here for 486 might be good */ orl $2,%eax # set MP movl %eax,%cr0 call check_x87 jmp after_page_tables /* * We depend on ET to be correct. This checks for 287/387. */ check_x87: fninit fstsw %ax cmpb $0,%al je 1f /* no coprocessor: have to set bits */ movl %cr0,%eax xorl $6,%eax /* reset MP, set EM */ movl %eax,%cr0 ret .align 2 1: .byte 0xDB,0xE4 /* fsetpm for 287, ignored by 387 */ ret /* * 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會將256箇中斷向量設定為ignore_int, lea執行為取有效地址指令(偏移地址) * */ setup_idt: lea ignore_int,%edx /* address of edx */ movl $0x00080000,%eax /* EAX is interrupt gat Blow 32bit */ movw %dx,%ax /* selector = 0x0008 = cs */ movw $0x8E00,%dx /* interrupt gate - dpl=0, present */ /* P = 1, DPL=00 D = 1, 32bits */ /* type = 110 interrupt gate */ /*********************************************************************************************** use EAX for interrupt description blow 32bit Use EDX for interrupt descirpt high 32bit
EAX存放的是中斷描述符的低32位
EAX存放的是中斷描述符的高32位
上述的程式碼是將中斷服務子程式處理成如下格式, CS為8表示二進位制為00001000表示段選擇子為1(程式碼段) +----------+---+-----------+---+---+-----------+--------+--------------+-----------+----------+
| H 16bits | P | DPL(2bit) | 0 | D | 3bit type | 3bit 0 | 5bit Reserve | 16bits CS | L 16bits | +----------+---+-----------+---+---+-----------+--------+--------------+-----------+----------+ | | 1 | 00 | 0 | 1 | 110 | 000 | | 8 | | +----------+---+-----------+---+---+-----------+--------+--------------+-----------+----------+ *************************************************************************************************/ lea idt,%edi /*將中斷描述符表的地址存放在edi暫存器中,一般作為目的地址暫存器*/ mov $256,%ecx /*計數暫存器,一共256項*/
/* 如下程式碼將中斷描述符複製到edi暫存器的地址處,並迴圈256次
* 使用lidt載入idt_descr的中斷描述符
*/ rp_sidt: movl %eax,(%edi) /* *edi == eax */ movl %edx,4(%edi) /* *(edi + 4) = eds */ addl $8,%edi /* edi = edi + 8, meas next interrupt select descript */ dec %ecx /* ecs -- */ jne rp_sidt /* if not 0 goto rp_sidt */ lidt idt_descr /* load interrupt select descript */ ret /* * 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 /* * I put the kernel page tables right after the page directory, * using 4 of them to span 16 Mb of physical memory. People with * more than 16MB will have to expand this. */ .org 0x1000 pg0: .org 0x2000 pg1: .org 0x3000 pg2: .org 0x4000 pg3: .org 0x5000 /* * tmp_floppy_area is used by the floppy-driver when DMA cannot * reach to a buffer-block. It needs to be aligned, so that it isn't * on a 64kB border. */ tmp_floppy_area: .fill 1024,1,0
程式執行到此處時會呼叫了setup_paging在setup_paging返回時由於將main函式進行壓堆疊將執行main函式 after_page_tables: pushl $1 # These are the parameters to main :-) pushl $2 pushl $3 pushl $L6 # return address for main, if it decides to. pushl $main jmp setup_paging L6: jmp L6 # main should never return here, but # just in case, we know what happens. /* This is the default interrupt "handler" :-) */ int_msg: .asciz "Unknown interrupt\n\r" .align 2 ignore_int: pushl %eax pushl %ecx pushl %edx push %ds push %es push %fs movl $0x10,%eax mov %ax,%ds mov %ax,%es mov %ax,%fs pushl $int_msg call printk popl %eax pop %fs pop %es pop %ds popl %edx popl %ecx popl %eax iret /* * Setup_paging * * This routine sets up paging by setting the page bit * in cr0. The page tables are set up, identity-mapping * the first 16MB. 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 16 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 16Mb, 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 "16Mb"), but I * won't guarantee that's all :-( ) */ .align 2 setup_paging: movl $1024*5,%ecx /* 5 pages - pg_dir+4 page tables */ xorl %eax,%eax /* EAX is 0 */ xorl %edi,%edi /* pg_dir is at 0x000, EDI is 0, start address*/ cld;rep;stosl /* stosl *EDI = EAX */
     以上程式碼的意思是從0-1024*5的地址空間清零,由於此時0地址處的程式碼已經執行完畢沒有什麼用了
為什麼是1024*5的長度呢,我們在上面的程式碼中可以看到.org 0x1000 pg0: 從0x1000為第一個頁表一共建立了4個
在加上0地址到0x1000一共5個
     問題1:分段是怎麼用的?
     答:執行發出邏輯地址後經分段部件形成線性地址,根據前面的分析我們支援邏輯地址等於線性地址
     問題2:分頁是怎麼進行地址對映的?

     答:分頁機制將線性地址分成三個部分進行查表,高10位表示頁表目錄,中10位表示頁表項,低12位表示偏移,在定址時
     根據高10位找到頁表目錄,頁表目錄存放了頁表的起始地址,根據中10位在頁面中找到對應的物理頁,在加上低12位的偏移形成實體地址
     頁表目錄和頁表的格式如下(頁表目錄和頁表是4KB對齊的):
     +----------------------------------------------+---------------------+
     |頁表目錄或者頁表實體地址的高位BIT(12-31) | 7 |
     +----------------------------------------------+---------------------+
     由於頁表目錄和頁表以4KB對齊,因此我們不用關係其低12位,低12位有其特殊的意義,具體可查詢intel的資料手冊
pg_dir表示頁表的地址,7表示為該頁存在使用者可讀可寫
     由於目前系統的最大記憶體為16MB,因此只用到也變目錄的前4項(為什麼)
一個頁表管理1024項4KB的地址空間既4MB,那麼16MB需要多少呢,答案是4個 movl $pg0+7,pg_dir /* set present bit/user r/w */ movl $pg1+7,pg_dir+4 /* --------- " " --------- */ movl $pg2+7,pg_dir+8 /* --------- " " --------- */ movl $pg3+7,pg_dir+12 /* --------- " " --------- */
     上面的程式碼執行完畢後將會產生如下的記憶體分佈
     --起始地址0x0000
     0x00001 007
     0x00002 007
     0x00003 007
     0x00004 007
     一共四項頁表目錄
movl $pg3+4092,%edi /* end of this page */ movl $0xfff007,%eax /* 16Mb - 4096 + 7 (r/w user,p) */ std /* derection -4 */ 1: stosl /* fill pages backwards - more efficient :-) *EDI=EAS */ subl $0x1000,%eax jge 1b cld
     以上的程式碼倒序從pg3的最後一項開始填充直到eax為0,這樣填充下來後如下圖
   頁表目錄
-------------------------0x0000
     0x00001 007
     -------------------------0x0004

     0x00002 007
     -------------------------0x0008
     0x00003 007
     -------------------------0x000C
     0x00004 007
     ...
     ...
     ...
   pg0頁表0
     -------------------------0x1000
     0x00000 007
     -------------------------0x1004
     0x00001 007
     -------------------------0x1008
     ...
     ...
     ...
   pg1頁表1
     -------------------------0x2000
     0x00400 007
     -------------------------0x2004

     0x00401 007
     -------------------------0x2008
     ...
     ...
     ...
   pg2頁表2
     -------------------------0x3000
     0x00800 007
     -------------------------0x3004
     0x00801 007
     -------------------------0x3008
     ...
     ...
     ...
   pg3頁表3
     -------------------------0x4000
     0x00c00 007
     -------------------------0x4004
     0x00c01 007
     -------------------------0x4008
     ...
     ...
     ...
     -------------------------0x4ff4
     0x00ffd 007
     -------------------------0x4ff8
     0x00ffe 007
     -------------------------0x4ffc
     0x00fff 007
     -------------------------0x5000
   我們再舉一個例子,如果要訪問0x00401555地址處的資料,我們知道系統需先經過段對映由於段對映後線性地址和邏輯地址一樣
   因此訪問的地址還是0x00401555,段對映後需要進行頁對映,我們根據上面的問題2對地址進行分解
   0x00401555的高10位為1尋找第一個頁表目錄值為0x00002 007,中10位為1在頁表1中的第一項值為0x00401 007,低12位為555,
   根據0x00401 007去掉007,再加上555,最終的地址為0x00401555,我們發現饒了一圈邏輯地址和實體地址一樣
   這種對映方式成為平坦對映
   我們再來一個例子,地址0x00FFF111地址,我們根據規則將其分解
   高10位為0000000011即3
   中10位為1111111111即1023,每一個頁面佔4個地址即1023*4 = 4092 = 0xFFC
   低12位為111
   如上面藍色字型的對映過程,其最終地址還是0x00fff111

xorl %eax,%eax /* pg_dir is at 0x0000 */ movl %eax,%cr3 /* cr3 - page directory start */ movl %cr0,%eax orl $0x80000000,%eax movl %eax,%cr0 /* set paging (PG) bit */ ret /* this also flushes prefetch-queue */ .align 2 .word 0 idt_descr: .word 256*8-1 # idt contains 256 entries .long idt .align 2 .word 0 gdt_descr: .word 256*8-1 # so does gdt (not that that's any .long gdt # magic number, but it works for me :^) .align 8 idt: .fill 256,8,0 # idt is uninitialized /* * 第二次的全域性描述符地址,基地址從0開始一共16M的程式碼段和資料段,並且預留了252個全域性段給LDT和TSS */ gdt:   .quad 0x0000000000000000 /* NULL descriptor */   .quad 0x00c09a0000000fff /* 16Mb */   .quad 0x00c0920000000fff /* 16Mb */   .quad 0x0000000000000000 /* TEMPORARY - don't use */   .fill 252,8,0 /* space for LDT's and TSS's etc */

 總結:

head模組是system模組最開始部分,head模組執行完畢後全域性描述符表,中斷描述符表,頁表都建立起來了,我們通過也可以知道系統建立了平坦對映,且這些資訊都在1MB記憶體以下,後續程式碼將跳入main函式中進行,

 本文原創,轉載註明出處!