天天開機關機,除了「等」以外,你得了解你的操做系統開機的時候真正作了什麼?linux
一. 書上都是這麼講的post
CPU自身初始化:硬件初始工做,以PC/IP寄存器跳轉到BIOS首地址爲結束標誌。測試
->加電自檢(Power On Self Test):硬件檢測,內存檢測,系統總線檢測,以開始從總線讀取第一段程序爲結束標誌。編碼
->加載內核引導程序:這裏是由BIOS肯定了引導設備以後,從設備的第一個扇區啓動的程序,GRUB的工做就是屬於這個過程,以選擇完一個啓動的系統爲結束標誌。spa
->主引導程序:由操做系統定義的,第一個512字節的內容進行的引導,包括對分區表(MBR,GPT)的掃描,但實際上不屬於操做系統,以一次長跳轉做爲結束表述。操作系統
->次引導程序:加載linux操做系統映像,以將控制權交給映像做爲結束標誌。.net
->內核:各類初始化,包括一個init進程的建立,初始化完成後操做系統開始徹底工做。(若是內核是壓縮過的話,初始化以前的自解壓過程也屬於這部分)指針
二. 代碼都是這麼寫的rest
在BIOS開始工做以前的硬件工做就不在咱們的討論範圍以內了,在BIOS中咱們完成了對第一啓動設備的選擇以後,咱們還會作如下的事情:code
爲了幫助理解,附上一張引導過程之中內存的分佈映射圖 (於linux源碼中的Documentation/x86/boot.txt中)
0A0000 +------------------------+
| Reserved for BIOS | Do not use. Reserved for BIOS EBDA.
09A000 +------------------------+
| Command line |
| Stack/heap | For use by the kernel real-mode code.
098000 +------------------------+
| Kernel setup | The kernel real-mode code.
090200 +------------------------+
| Kernel boot sector | The kernel legacy boot sector.
090000 +------------------------+
| Protected-mode kernel | The bulk of the kernel image.
010000 +------------------------+
| Boot loader | <- Boot sector entry point 0000:7C00(這裏是AT&T彙編段址:偏移於NASM和MASM是反過來的,要注意!)
001000 +------------------------+
| Reserved for MBR/BIOS |
000800 +------------------------+
| Typically used by MBR |
000600 +------------------------+
| BIOS use only |
000000 +------------------------+
1.MBR引導過程:
這部分的引導過程通常有兩種的稍微不一樣的辦法:
第一種辦法是在於淵在《本身動手寫操做系統》中的例子中大部分使用到,是利用FAT12文件系統來幫助咱們進行引導過程,具體的作法是在軟盤之中加入幾個用於文件系統定義劃分的字段,這樣一來因爲字段地址的限制,咱們須要在MBR的前幾個字節直接進行一次短跳轉,而後進入通常在扇區中部的真正入口,如今的linux發行源碼中實際也都是這麼作的,第一步直接執行短跳轉,並且這個跳轉是用匯編的硬編碼作的,具體緣由頗有趣,你們自行Google。到達入口以後咱們須要將進一步來進行引導的文件加載到內存之中的9000:0200地址之上,如上圖所示就是Kernel Setup部分,FAT12文件系統在這裏就幫助咱們在軟盤之中有效率的找到了外存中的二進制映像,爲何這裏不把這個映像直接加載到9000:0000這個整齊的地址之上呢?我想緣由應該大概是節省MBR512K中進行數據訪問的代碼,直接連同最初在7c00:0000到7C00:0200的這一個扇區的數據一塊兒拷貝到高位地址上。
; FAT12 磁盤的頭 ; ---------------------------------------------------------------------- BS_OEMName DB 'ForrestY' ; OEM String, 必須 8 個字節 BPB_BytsPerSec DW 512 ; 每扇區字節數 BPB_SecPerClus DB 1 ; 每簇多少扇區 BPB_RsvdSecCnt DW 1 ; Boot 記錄佔用多少扇區 BPB_NumFATs DB 2 ; 共有多少 FAT 表 BPB_RootEntCnt DW 224 ; 根目錄文件數最大值 BPB_TotSec16 DW 2880 ; 邏輯扇區總數 BPB_Media DB 0xF0 ; 媒體描述符 BPB_FATSz16 DW 9 ; 每FAT扇區數 BPB_SecPerTrk DW 18 ; 每磁道扇區數 BPB_NumHeads DW 2 ; 磁頭數(面數) BPB_HiddSec DD 0 ; 隱藏扇區數 BPB_TotSec32 DD 0 ; 若是 wTotalSectorCount 是 0 由這個值記錄扇區數 BS_DrvNum DB 0 ; 中斷 13 的驅動器號 BS_Reserved1 DB 0 ; 未使用 BS_BootSig DB 29h ; 擴展引導標記 (29h) BS_VolID DD 0 ; 卷序列號 BS_VolLab DB 'OrangeS0.02'; 卷標, 必須 11 個字節 BS_FileSysType DB 'FAT12 ' ; 文件系統類型, 必須 8個字節 ;------------------------------------------------------------------------
第二種辦法是拋開文件系統直接經過BIOS的INT13H中斷來進行對扇區數據的訪問,這樣作只能說更靈活吧(我也找不出其餘優勢了..),在林納斯的創造的linux0.11內核版本中引導部分就是使用的這種辦法,我在實踐這種辦法時候碰到了很是嚴重的問題(至今未解決..)就是使用INT13H讀取軟盤中第二個扇區中的引導代碼的時候,就是返回代碼爲01H的錯誤,有經驗的大神但願能爲我指點迷津。如下是NASM版本的linux0.11的引導代碼:
.globl begtext, begdata, begbss, endtext, enddata, endbss ! 定義了6 個全局標識符; .text ! 文本段; begtext: .data ! 數據段; begdata: .bss ! 堆棧段; begbss: .text ! 文本段; SETUPLEN = 4 ! nr of setup-sectors ! setup 程序的扇區數(setup-sectors)值; BOOTSEG = 0x07c0 ! original address of boot-sector ! bootsect 的原始地址(是段地址,如下同); INITSEG = 0x9000 ! we move boot here - out of the way ! 將bootsect 移到這裏 -- 避開; SETUPSEG = 0x9020 ! setup starts here ! setup 程序從這裏開始; SYSSEG = 0x1000 ! system loaded at 0x10000 (65536). ! system 模塊加載到0x10000(64 kB)處; ENDSEG = SYSSEG + SYSSIZE ! where to stop loading ! 中止加載的段地址; ! ROOT_DEV: 0x000 - same type of floppy as boot. ! 根文件系統設備使用與引導時一樣的軟驅設備; ! 0x301 - first partition on first drive etc ! 根文件系統設備在第一個硬盤的第一個分區上,等等; ROOT_DEV = 0x306 ! 指定根文件系統設備是第2 個硬盤的第1 個分區。這是Linux 老式的硬盤命名 ! 方式,具體值的含義以下: ! 設備號=主設備號*256 + 次設備號(也即dev_no = (major<<8) + minor ) ! (主設備號:1-內存,2-磁盤,3-硬盤,4-ttyx,5-tty,6-並行口,7-非命名管道) ! 0x300 - /dev/hd0 - 表明整個第1 個硬盤; ! 0x301 - /dev/hd1 - 第1 個盤的第1 個分區; ! … ! 0x304 - /dev/hd4 - 第1 個盤的第4 個分區; ! 0x305 - /dev/hd5 - 表明整個第2 個硬盤盤; ! 0x306 - /dev/hd6 - 第2 個盤的第1 個分區; ! … ! 0x309 - /dev/hd9 - 第2 個盤的第4 個分區; ! 從linux 內核0.95 版後已經使用與如今相同的命名方法了。 entry start ! 告知鏈接程序,程序從start 標號開始執行。 start: ! 47--56 行做用是將自身(bootsect)從目前段位置0x07c0(31k) ! 移動到0x9000(576k)處,共256 字(512 字節),而後跳轉到 ! 移動後代碼的go 標號處,也即本程序的下一語句處。 mov ax,#BOOTSEG ! 將ds 段寄存器置爲0x7C0; mov ds,ax mov ax,#INITSEG ! 將es 段寄存器置爲0x9000; mov es,ax mov cx,#256 ! 移動計數值=256 字; sub si,si ! 源地址 ds:si = 0x07C0:0x0000 sub di,di ! 目的地址 es:di = 0x9000:0x0000 rep ! 重複執行,直到cx = 0 movw ! 移動1 個字; jmpi go,INITSEG ! 間接跳轉。這裏INITSEG 指出跳轉到的段地址。 go: mov ax,cs ! 將ds、es 和ss 都置成移動後代碼所在的段處(0x9000)。 mov ds,ax !因爲程序中有堆棧操做(push,pop,call),所以必須設置堆棧。 mov es,ax ! put stack at 0x9ff00. ! 將堆棧指針sp 指向0x9ff00(即0x9000:0xff00)處 mov ss,ax mov sp,#0xFF00 ! arbitrary value >>512 ! 因爲代碼段移動過了,因此要從新設置堆棧段的位置。 ! sp 只要指向遠大於512 偏移(即地址0x90200)處 ! 均可以。由於從0x90200 地址開始處還要放置setup 程序, ! 而此時setup 程序大約爲4 個扇區,所以sp 要指向大 ! 於(0x200 + 0x200 * 4 + 堆棧大小)處。 ! load the setup-sectors directly after the bootblock. ! Note that 'es' is already set up. ! 在bootsect 程序塊後緊根着加載setup 模塊的代碼數據。 ! 注意es 已經設置好了。(在移動代碼時es 已經指向目的段地址處0x9000)。 load_setup: ! 68--77 行的用途是利用BIOS 中斷INT 0x13 將setup 模塊從磁盤第2 個扇區 ! 開始讀到0x90200 開始處,共讀4 個扇區。若是讀出錯,則復位驅動器,並 ! 重試,沒有退路。INT 0x13 的使用方法以下: ! 讀扇區: ! ah = 0x02 - 讀磁盤扇區到內存;al = 須要讀出的扇區數量; ! ch = 磁道(柱面)號的低8 位; cl = 開始扇區(0-5 位),磁道號高2 位(6-7); ! dh = 磁頭號; dl = 驅動器號(若是是硬盤則要置位7); ! es:bx ??指向數據緩衝區; 若是出錯則CF 標誌置位。 mov dx,#0x0000 ! drive 0, head 0 mov cx,#0x0002 ! sector 2, track 0 mov bx,#0x0200 ! address = 512, in INITSEG mov ax,#0x0200+SETUPLEN ! service 2, nr of sectors int 0x13 ! read it jnc ok_load_setup ! ok - continue mov dx,#0x0000 mov ax,#0x0000 ! reset the diskette int 0x13 j load_setup ok_load_setup: ! Get disk drive parameters, specifically nr of sectors/track ! 取磁盤驅動器的參數,特別是每道的扇區數量。 ! 取磁盤驅動器參數INT 0x13 調用格式和返回信息以下: ! ah = 0x08 dl = 驅動器號(若是是硬盤則要置位7 爲1)。 ! 返回信息: ! 若是出錯則CF 置位,而且ah = 狀態碼。 ! ah = 0, al = 0, bl = 驅動器類型(AT/PS2) ! ch = 最大磁道號的低8 位,cl = 每磁道最大扇區數(位0-5),最大磁道號高2 位(位6-7) ! dh = 最大磁頭數, dl = 驅動器數量, ! es:di -?? 軟驅磁盤參數表。 mov dl,#0x00 mov ax,#0x0800 ! AH=8 is get drive parameters int 0x13 mov ch,#0x00 seg cs ! 表示下一條語句的操做數在cs 段寄存器所指的段中。 mov sectors,cx ! 保存每磁道扇區數。 mov ax,#INITSEG mov es,ax ! 由於上面取磁盤參數中斷改掉了es 的值,這裏從新改回。 ! Print some inane message ! 在顯示一些信息('Loading system ...'回車換行,共24 個字符)。 mov ah,#0x03 ! read cursor pos xor bh,bh ! 讀光標位置。 int 0x10 mov cx,#24 ! 共24 個字符。 mov bx,#0x0007 ! page 0, attribute 7 (normal) mov bp,#msg1 ! 指向要顯示的字符串。 mov ax,#0x1301 ! write string, move cursor int 0x10 ! 寫字符串並移動光標。 ! ok, we've written the message, now ! we want to load the system (at 0x10000) ! 如今開始將system 模塊加載到0x10000(64k)處。 mov ax,#SYSSEG mov es,ax ! segment of 0x010000 ! es = 存放system 的段地址。 call read_it ! 讀磁盤上system 模塊,es 爲輸入參數。 call kill_motor ! 關閉驅動器馬達,這樣就能夠知道驅動器的狀態了。 ! After that we check which root-device to use. If the device is ! defined (!= 0), nothing is done and the given device is used. ! Otherwise, either /dev/PS0 (2,28) or /dev/at0 (2,8), depending ! on the number of sectors that the BIOS reports currently. ! 此後,咱們檢查要使用哪一個根文件系統設備(簡稱根設備)。若是已經指定了設備(!=0) ! 就直接使用給定的設備。不然就須要根據BIOS 報告的每磁道扇區數來 ! 肯定到底使用/dev/PS0 (2,28) 仍是 /dev/at0 (2,8)。 ! 上面一行中兩個設備文件的含義: ! 在Linux 中軟驅的主設備號是2(參見第43 行的註釋),次設備號 = type*4 + nr,其中 ! nr 爲0-3 分別對應軟驅A、B、C 或D;type 是軟驅的類型(2??1.2M 或7??1.44M 等)。 ! 由於7*4 + 0 = 28,因此 /dev/PS0 (2,28)指的是1.44M A 驅動器,其設備號是0x021c ! 同理 /dev/at0 (2,8)指的是1.2M A 驅動器,其設備號是0x0208。 seg cs mov ax,root_dev ! 將根設備號 cmp ax,#0 jne root_defined seg cs mov bx,sectors ! 取上面第88 行保存的每磁道扇區數。若是sectors=15 ! 則說明是1.2Mb 的驅動器;若是sectors=18,則說明是 ! 1.44Mb 軟驅。由於是可引導的驅動器,因此確定是A 驅。 mov ax,#0x0208 ! /dev/ps0 - 1.2Mb cmp bx,#15 ! 判斷每磁道扇區數是否=15 je root_defined ! 若是等於,則ax 中就是引導驅動器的設備號。 mov ax,#0x021c ! /dev/PS0 - 1.44Mb cmp bx,#18 je root_defined undef_root: ! 若是都不同,則死循環(死機)。 jmp undef_root root_defined: seg cs mov root_dev,ax ! 將檢查過的設備號保存起來。 ! after that (everyting loaded), we jump to ! the setup-routine loaded directly after ! the bootblock: ! 到此,全部程序都加載完畢,咱們就跳轉到被 ! 加載在bootsect 後面的setup 程序去。 jmpi 0,SETUPSEG ! 跳轉到0x9020:0000(setup.s 程序的開始處)。 !!!! 本程序到此就結束了。!!!! ! 下面是兩個子程序。 ! This routine loads the system at address 0x10000, making sure ! no 64kB boundaries are crossed. We try to load it as fast as ! possible, loading whole tracks whenever we can. ! ! in: es - starting address segment (normally 0x1000) ! ! 該子程序將系統模塊加載到內存地址0x10000 處,並肯定沒有跨越64KB 的內存邊界。咱們試圖儘快 ! 地進行加載,只要可能,就每次加載整條磁道的數據。 ! 輸入:es – 開始內存地址段值(一般是0x1000) sread: .word 1+SETUPLEN ! sectors read of current track ! 當前磁道中已讀的扇區數。開始時已經讀進1 扇區的引導扇區 ! bootsect 和setup 程序所佔的扇區數SETUPLEN。 head: .word 0 ! current head !當前磁頭號。 track: .word 0 ! current track !當前磁道號。 read_it: ! 測試輸入的段值。必須位於內存地址64KB 邊界處,不然進入死循環。清bx 寄存器,用於表示當前段內 ! 存放數據的開始位置。 mov ax,es test ax,#0x0fff die: jne die ! es must be at 64kB boundary ! es 值必須位於64KB 地址邊界! xor bx,bx ! bx is starting address within segment ! bx 爲段內偏移位置。 rp_read: ! 判斷是否已經讀入所有數據。比較當前所讀段是否就是系統數據末端所處的段(#ENDSEG),若是不是就 ! 跳轉至下面ok1_read 標號處繼續讀數據。不然退出子程序返回。 mov ax,es cmp ax,#ENDSEG ! have we loaded all yet? ! 是否已經加載了所有數據? jb ok1_read ret ok1_read: ! 計算和驗證當前磁道須要讀取的扇區數,放在ax 寄存器中。 ! 根據當前磁道還未讀取的扇區數以及段內數據字節開始偏移位置,計算若是所有讀取這些未讀扇區,所 ! 讀總字節數是否會超過64KB 段長度的限制。若會超過,則根據這次最多能讀入的字節數(64KB – 段內 ! 偏移位置),反算出這次須要讀取的扇區數。 seg cs mov ax,sectors ! 取每磁道扇區數。 sub ax,sread ! 減去當前磁道已讀扇區數。 mov cx,ax ! cx = ax = 當前磁道未讀扇區數。 shl cx,#9 ! cx = cx * 512 字節。 add cx,bx ! cx = cx + 段內當前偏移值(bx) ! = 這次讀操做後,段內共讀入的字節數。 jnc ok2_read ! 若沒有超過64KB 字節,則跳轉至ok2_read 處執行。 je ok2_read xor ax,ax ! 若加上這次將讀磁道上全部未讀扇區時會超過64KB,則計算 sub ax,bx ! 此時最多能讀入的字節數(64KB – 段內讀偏移位置),再轉換 shr ax,#9 ! 成須要讀取的扇區數。 ok2_read: call read_track mov cx,ax ! cx = 該次操做已讀取的扇區數。 add ax,sread ! 當前磁道上已經讀取的扇區數。 seg cs cmp ax,sectors ! 若是當前磁道上的還有扇區未讀,則跳轉到ok3_read 處。 jne ok3_read ! 讀該磁道的下一磁頭面(1 號磁頭)上的數據。若是已經完成,則去讀下一磁道。 mov ax,#1 sub ax,head ! 判斷當前磁頭號。 jne ok4_read ! 若是是0 磁頭,則再去讀1 磁頭面上的扇區數據。 inc track ! 不然去讀下一磁道。 ok4_read: mov head,ax ! 保存當前磁頭號。 xor ax,ax ! 清當前磁道已讀扇區數。 ok3_read: mov sread,ax ! 保存當前磁道已讀扇區數。 shl cx,#9 ! 上次已讀扇區數*512 字節。 add bx,cx ! 調整當前段內數據開始位置。 jnc rp_read ! 若小於64KB 邊界值,則跳轉到rp_read(156 行)處,繼續讀數據。 ! 不然調整當前段,爲讀下一段數據做準備。 mov ax,es add ax,#0x1000 ! 將段基址調整爲指向下一個64KB 段內存。 mov es,ax xor bx,bx ! 清段內數據開始偏移值。 jmp rp_read ! 跳轉至rp_read(156 行)處,繼續讀數據。 ! 讀當前磁道上指定開始扇區和需讀扇區數的數據到es:bx 開始處。參見第67 行下對BIOS 磁盤讀中斷 ! int 0x13,ah=2 的說明。 ! al – 需讀扇區數;es:bx – 緩衝區開始位置。 read_track: push ax push bx push cx push dx mov dx,track ! 取當前磁道號。 mov cx,sread ! 取當前磁道上已讀扇區數。 inc cx ! cl = 開始讀扇區。 mov ch,dl ! ch = 當前磁道號。 mov dx,head ! 取當前磁頭號。 mov dh,dl ! dh = 磁頭號。 mov dl,#0 ! dl = 驅動器號(爲0 表示當前驅動器)。 and dx,#0x0100 ! 磁頭號不大於1。 mov ah,#2 ! ah = 2,讀磁盤扇區功能號。 int 0x13 jc bad_rt ! 若出錯,則跳轉至bad_rt。 pop dx pop cx pop bx pop ax ret ! 執行驅動器復位操做(磁盤中斷功能號0),再跳轉到read_track 處重試。 bad_rt: mov ax,#0 mov dx,#0 int 0x13 pop dx pop cx pop bx pop ax jmp read_track /* * This procedure turns off the floppy drive motor, so * that we enter the kernel in a known state, and * don't have to worry about it later. */ ! 這個子程序用於關閉軟驅的馬達,這樣咱們進入內核後它處於已知狀態,之後也就無須擔憂它了。 kill_motor: push dx mov dx,#0x3f2 ! 軟驅控制卡的驅動端口,只寫。 mov al,#0 ! A 驅動器,關閉FDC,禁止DMA 和中斷請求,關閉馬達。 outb ! 將al 中的內容輸出到dx 指定的端口去。 pop dx ret sectors: .word 0 ! 存放當前啓動軟盤每磁道的扇區數。 msg1: .byte 13,10 ! 回車、換行的ASCII 碼。 .ascii "Loading system ..." .byte 13,10,13,10 ! 共24 個ASCII 碼字符。 .org 508 ! 表示下面語句從地址508(0x1FC)開始,因此root_dev ! 在啓動扇區的第508 開始的2 個字節中。 root_dev: .word ROOT_DEV ! 這裏存放根文件系統所在的設備號(init/main.c 中會用)。 boot_flag: .word 0xAA55 ! 硬盤有效標識。 .text endtext: .data enddata: .bss endbss:
在完成了從最初的7c00:0000到9000:0200的跳轉以後,咱們的引導過程就進入了一個新的階段:
二. 內核加載準備
在這個階段裏咱們要具體作的事情有:
1.突破實模式,進入保護模式
2.完成對內存的分頁和分段
保護模式的運行離不開專設的硬件的支持,其中最主要的包括GDTR全局描述符向量寄存器和LDTR局部描述符向量寄存器,而咱們所作的工做主要包括如下幾項:
1.準備GDT,其實就是定義幾個段來爲保護模式的運行制定一些規則
2.用lgdt指令加載GDTR
3.打開A20(A20是地址線的名字,http://blog.csdn.net/yunsongice/article/details/6110648)
4.置CR0寄存器的PE置
5.跳轉進入保護模式
根據head.s中第114行的.org 0x1000可知,物理地址0x1000以前的全部數據都將被頁目錄表覆蓋(這個覆蓋,是指更改了內存中的內核鏡像文件,而不是磁盤上的內核鏡像文件)。
一、首先,Linux從0x00000地址開始對五頁內存進行清零。(1頁頁目錄表+4頁頁表)
1 | setup_paging: |
2 | movl $1024*5,%ecx /* 5 pages - pg_dir+4 page tables */ |
3 | xorl %eax,%eax |
4 | xorl %edi,%edi /* pg_dir is at 0x000 */ |
5 | cld;rep;stosl |
二、接着,填寫頁目錄表(頁目錄表的位置爲0x00000-0x00fff,大小爲4K,每一項佔4字節)。由於只有4個頁表,因此只填寫了前四項。
1 movl $pg0+7,pg_dir /* set present bit/user r/w */ |
2 movl $pg1+7,pg_dir+4 /* --------- " " --------- */ |
3 movl $pg2+7,pg_dir+8 /* --------- " " --------- */ |
4 movl $pg3+7,pg_dir+12 /* --------- " " --------- */ |
這裏,4個頁目錄項的內容分別是$pg0(1,2,3)+7,分別是4個頁表的物理地址+111B。前面的$pg0(1,2,3)是頁表的物理地址,而111B則表明這4個頁表權限爲可讀寫。
三、填寫頁表項的內容
1 movl $pg3+4092,%edi |
2 movl $0xfff007,%eax /* 16Mb - 4096 + 7 (r/w user,p) */ |
3 std |
4 1: stosl /* fill pages backwards - more efficient :-) */ |
5 subl $0x1000,%eax |
6 jge 1b |
這裏的填寫是逆序填寫的,也就是首先將16M物理內存最後一頁的啓始地址+權限(16M-4K+111B)填寫到第4張頁表的最後一項。地址 爲$pg3+4092,其中$pg3爲(第4張頁表的起始地址),4092是由於1024*4(1024項,每項佔用4字節)-4(最後一項頁佔用4字 節)。因此第4張頁表的最後一項是(16M-4K)0xfff000+111B=0xfff007。
最終,std以4遞減edi寄存器 (一個頁表項佔4字節,edi指向正在操做的頁表項),subl $0x1000,%eax將減去0x1000(一頁內存的大小,eax指向正在操做的內存邊界),l:stosl是填寫頁表項,知道eax的內容爲0,這 樣就填寫完了4個頁表。
這樣,內存中的頁目錄表和頁表分佈就是:
物理內存地址 | 所含信息及備註 | 單元內容 |
0X00FF FFFC …… |
…… …… |
|
0X0000 4FFC 0X0000 4FF8 … … 0X0000 4004 0X0000 4000 |
頁表4 4K (0X00004000-0X00004FFC) 4字節爲一項 |
0X00FF F000+7 0X00FF E000+7 … … 0X00C0 2000+7 0X00C0 1000+7 0X00C0 0000+7 |
0X0000 3FFC 0X0000 3FF8 … … 0X0000 3004 0X0000 3000 |
頁表3 4K (0X00003000-0X00003FFC) 4字節爲一項 |
0X00BF F000+7 0X00BF E000+7 … … 0X0080 2000+7 0X0080 1000+7 0X0080 0000+7 |
0X0000 2FFC 0X0000 2FF8 … … 0X0000 2004 0X0000 2000 |
頁表2 4K (0X00002000-0X00002FFC) 4字節爲一項 |
0X007F F000+7 0X007F E000+7 … … 0X0040 2000+7 0X0040 1000+7 0X0040 0000+7 |
0X0000 1FFC 0X0000 1FF8 … … 0X0000 1004 0X0000 1000 |
頁表1 4K (0X00001000-0X00001FFC) 4字節爲一項 |
0X003F F000+7 0X003F E000+7 … … 0X0000 2000+7 0X0000 1000+7 0X0000 0000+7 |
0X0000 0FFC 0X0000 0FF8 … … 0X0000 0004 0X0000 0000 |
PDT(頁目錄表) 4K (0X000000-0X00000FFF) 只有前四項有內容 |
… 0x0000 4000+7 0x0000 3000+7 0x0000 2000+7 0x0000 1000+7 |
最後,把頁目錄表的地址(0x000000)寫到控制寄存器CR3,而後置控制寄存器CR0的PG位,這樣就開啓了內存的分頁管理功能。
Linux0.11在分頁機制下的尋址(兩級表尋址)
第一級表稱爲頁目錄。
存放在1頁4k頁面中。具備1k個4字節長度的表項。這些表項指向第二級表。線性地址的最高10位(位31-22)用做一級表(頁目錄)中的索引值來選擇某個頁目錄項,用以選擇某個二級表。
第二級表稱爲頁表。
長度也是1個頁面,每一個表含有1k個4字節的表項。每一個4字節表項含有相關頁面的20位物理地址。二級頁表使用線性地址中間的10位(位21-12)做爲表 項索引值,在表內索引含有頁面20位物理地址的表項。該20位頁面物理地址和線性地址中的低12位(頁內偏移)組合在一塊兒就獲得了分頁轉換中的輸出值,也 就是最終的物理地址。
也就是說:
線性地址高10位---------索引頁目錄表----------->找到相應頁表
線性地址中間10位---------索引頁表----------->獲得頁表中相應的項,其中的高20位就是物理地址的高20位
線性地址低12位-------------------->物理地址的低12位