傻瓜視角看linux引導啟動過程


 

每天開機關機,除了“等”之外,你得了解你的操作系統開機的時候真正做了什么?

 

一. 書上都是這么講的

 

CPU自身初始化:硬件初始工作,以PC/IP寄存器跳轉到BIOS首地址為結束標志。

->加電自檢(Power On Self Test):硬件檢測,內存檢測,系統總線檢測,以開始從總線讀取第一段程序為結束標志。

->加載內核引導程序:這里是由BIOS確定了引導設備之后,從設備的第一個扇區啟動的程序,GRUB的工作就是屬於這個過程,以選擇完一個啟動的系統為結束標志。

->主引導程序:由操作系統定義的,第一個512字節的內容進行的引導,包括對分區表(MBR,GPT)的掃描,但實際上不屬於操作系統,以一次長跳轉作為結束表述。

->次引導程序:加載linux操作系統映像,以將控制權交給映像作為結束標志。

->內核:各種初始化,包括一個init進程的創建,初始化完成后操作系統開始完全工作。(如果內核是壓縮過的話,初始化之前的自解壓過程也屬於這部分)

 

二. 代碼都是這么寫的

 

在BIOS開始工作之前的硬件工作就不在我們的討論范圍之內了,在BIOS中我們完成了對第一啟動設備的選擇之后,我們還會做以下的事情:

 

為了幫助理解,附上一張引導過程之中內存的分布映射圖 (於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之前的所有數據都將被頁目錄表覆蓋(這個覆蓋,是指更改了內存中的內核鏡像文件,而不是磁盤上的內核鏡像文件)。

1、首先,Linux從0x00000地址開始對五頁內存進行清零。(1頁頁目錄表+4頁頁表)

   setup_paging: 
       movl $1024*5,%ecx        /* 5 pages - pg_dir+4 page tables */ 
       xorl %eax,%eax 
       xorl %edi,%edi            /* pg_dir is at 0x000 */ 
       cld;rep;stosl

2、接着,填寫頁目錄表(頁目錄表的位置為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個頁表權限為可讀寫。

3、填寫頁表項的內容

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位

 

無標題


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM