linux內(nèi)核學(xué)習(xí)之啟動(dòng)程序模塊
linux引導(dǎo)程序解析
bootsect程序,駐留在磁盤的第一個(gè)扇區(qū)中(0磁道 0磁頭 1 扇區(qū))。在BIOS加點(diǎn)檢測(cè)之后,該引導(dǎo)程序會(huì)自動(dòng)地加載在內(nèi)存的0x7c00處。
bootsect程序在運(yùn)行時(shí),會(huì)首先將自身移動(dòng)到0x90000處開始執(zhí)行,并將從第二個(gè)扇區(qū)開始的共4個(gè)扇區(qū)大小的setup程序移動(dòng)到,緊緊挨著該程序的0x90200處。
然后會(huì)使用BIOS中斷int13 取當(dāng)前引導(dǎo)盤的參數(shù),接著在屏幕上顯示Loading System的字符串,最后把磁盤上setup后面的system模塊加載到內(nèi)存0x10000開始的地方。隨后確定根文件系統(tǒng)的設(shè)備號(hào),若沒有指定,則根據(jù)所保存的引導(dǎo)盤的每磁道扇區(qū)數(shù)目,判斷出盤的類型和種類,并保存在設(shè)備號(hào)root_dev中。
最后長(zhǎng)跳轉(zhuǎn)到setup程序的開始處,執(zhí)行setup程序。
下面為分析的源代碼:
.global begtext , begdata , begbss , endtext , enddata , endbss
.text
begtext:
.data
begdata:
.bss
begbss:
.text
SETUPLEN = 4 ! nr of setup-sectors
BOOTSEG = 0x07c0
INITSEG = 0x9000
SETUPSEG = 0x9020
SYSSEG = 0x1000
ENDSEG = SYSSEG + SYSSIZE
ROOT_DEV = 0x306
entry start
start:
!將bootsect自身移動(dòng)到0x90000處,并跳轉(zhuǎn)開始執(zhí)行
mov ax , #BOOTSEG
mov ds , ax
mov ax , #INITSEG
mov es , ax
mov cx , #256
sub si , si
sub di , di
rep
movw
jmpi go , INITSEG
!跳轉(zhuǎn)過后修改段寄存器
go:
mov ax , cs
mov ds , ax
mov es , ax
mov ss , ax
mov sp , #0xFF00
!利用BIOS中斷INT 13將 setup模塊,從磁盤第2個(gè)扇區(qū)開始讀到0x90200開始處,共讀4個(gè)扇區(qū)
load_setup:
mov dx , #0x0000 ! drive 0 , head 0
mov cx , #0x0002 ! sector 2 , track 0
mov bx , #0x0200 ! address = 512 , in INITSEG
mov ax , #0x0200 + SETUP_LEN ! service 2 , nr of sectors
int 0x13
jnc ok_load_setup
mov dx , #0x0000 !出錯(cuò)則重新執(zhí)行加載程序
mov ax , #0x0000
int 0x13
j load_setup
!利用int13 中斷,得到磁盤驅(qū)動(dòng)器的參數(shù),特別是每道磁道的扇區(qū)數(shù)量
mov ax , 0x0800
mov dl , 0x00
int 0x13
!重新設(shè)置es的值
mov sectors , cx
mov ax , #INITSEG
mov es , ax
!print some message
mov ah , #0x03 !讀光標(biāo)位置,返回光標(biāo)位置在dx中
xor bh , bh
int 0x10
mov cx , #24
mov bx , #0x0007
mov bp , #msg1
mov ax , #0x1301
int 0x10
! ok we have written the message ,現(xiàn)在開始將system模塊加載到0x10000開始處
mov ax , #SYSSEG
mov es , ax
call read_it !讀磁盤上system模塊,es為輸入?yún)?shù)
call kill_motor !關(guān)閉馬達(dá)
!確定根文件系統(tǒng)所在的設(shè)備號(hào)
seg cs
mov ax , root_dev
cmp ax , #0
jne root_defined
seg cs
mov bx , sectors
mov ax , #0x0208
cmp bx , #15
je root_defined
mov ax , #0x021c
cmp bx , #18
je root_defined
undef_root:
jmp undef_root
root_defined:
seg cs
mov root_dev , ax
jmpi 0 , SETUPSEG !此處跳進(jìn)setup程序
!下面是將system模塊加載進(jìn)內(nèi)存的子函數(shù)
sread: .word 1 + SETUPLEN !sectors read of current track
head: .word 0
track: .word 0
!保證es在64kb處
read_it :
mov ax , es
test ax , #0xfff
die: jne die
xor bx , bx
rp_read: !接著判斷是否已經(jīng)讀入全部的數(shù)據(jù),比較當(dāng)前所讀的段是否就是系統(tǒng)數(shù)據(jù)末端所處的段
mov ax , es !如果不是,就跳轉(zhuǎn)至下面的ok1標(biāo)號(hào)處繼續(xù)讀數(shù)據(jù)
cmp ax , #ENDSEG
jb ok1_read
ret !如果到達(dá)了系統(tǒng)末端,就結(jié)束此循環(huán)
ok1_read: !計(jì)算和驗(yàn)證當(dāng)前磁道上需要讀取的扇區(qū)數(shù)目,放在ax寄存器中,根據(jù)當(dāng)前磁道還未讀取的扇區(qū)數(shù)和
!段內(nèi)數(shù)據(jù)字節(jié)開始偏移的位置,計(jì)算如果全部讀取這些未讀扇區(qū),所讀的字節(jié)是否會(huì)超過64kb的限制
!若會(huì)超過,則根據(jù)此次最多能讀入的字節(jié)數(shù),反算出需要讀取的扇區(qū)數(shù)。
seg cs
mov ax , sectors !取每磁道的扇區(qū)數(shù)
sub ax , sread !減去當(dāng)前磁道已讀扇區(qū)數(shù)
mov cx , ax !cx = ax 為當(dāng)前磁道的未讀扇區(qū)數(shù)
shl cx , #9 !當(dāng)前未讀的字節(jié)數(shù)
add cx , bx !此次操作之后,段內(nèi)偏移地址現(xiàn)在的值
jnc ok2_read !若沒有超過64kb,則跳轉(zhuǎn)至ok2_read
je ok2_read
!若加上此次將讀取的磁道上所有未讀扇區(qū)時(shí)會(huì)超過64kb,則反算出 可以最多加載多少 扇區(qū)數(shù)目
xor ax , ax
sub ax , bx
shr ax , #9 !轉(zhuǎn)換成扇區(qū)數(shù)目
ok2_read:
!讀當(dāng)前磁道上指定開始扇區(qū)(cl)和需讀扇區(qū)數(shù)(al)的數(shù)據(jù)到es:bx開始處。然后將磁道上已經(jīng)讀取的扇區(qū)數(shù)目
!與磁道最大扇區(qū)數(shù)sectors作比較,如果小于sectors說明當(dāng)前磁道上還有扇區(qū)未讀
call read_track
mov cx , ax !cx等于當(dāng)前操作以讀扇區(qū)數(shù)目
add ax , sread !加上當(dāng)前磁道已讀扇區(qū)數(shù)目
seg cs
cmp ax , sectors !如果當(dāng)前磁道上還有扇區(qū)未讀,則跳轉(zhuǎn)到ok3_read
jne ok3_read
!如果該磁道的當(dāng)前磁頭面所有扇區(qū)已經(jīng)讀完,則讀該磁道的下一磁頭面(1號(hào)磁頭)上的數(shù)據(jù),如果已經(jīng)讀完則去讀下一磁道
mov ax , #1
sub ax , head !判斷當(dāng)前的磁頭號(hào),如果是0磁頭,則去讀1磁頭
jne ok4_read !讀1號(hào)磁頭
inc track !讀下一磁道
ok4_read:
mov head , ax !保存當(dāng)前的磁頭號(hào)
xor ax , ax !清除當(dāng)前磁道的已讀扇區(qū)數(shù)
ok3_read:
!如果當(dāng)前磁道上還有未讀的扇區(qū),則首先保存當(dāng)前磁道的已讀扇區(qū)數(shù)目,然后調(diào)整存放數(shù)據(jù)的開始位置,若小于64kb邊界值
!則跳轉(zhuǎn)到rp_read處,繼續(xù)讀數(shù)據(jù)
mov sread , ax !保存當(dāng)前磁道的已讀扇區(qū)數(shù)
shl cx , #9 !上次已讀扇區(qū)數(shù)*512字節(jié)
add bx , cx !調(diào)整當(dāng)前段內(nèi)數(shù)據(jù)開始位置
jnc rp_read
!否則說明已經(jīng)讀取64kb數(shù)據(jù),此時(shí)調(diào)整當(dāng)前段,為讀下一段數(shù)據(jù)作準(zhǔn)備
mov ax , es
add ax , #0x1000
mov es , ax
xor bx , bx
jmp rp_read
!read_track 子程序,讀當(dāng)前磁道上指定開始扇區(qū)和需讀扇區(qū)數(shù)的數(shù)據(jù)到es:bx開始處。
!int 0x13 , ah =2 ,al=需讀扇區(qū)數(shù),es:bx 緩沖區(qū)開始位置
read_track:
push ax
push bx
push cx
push dx
mov dx , track
mov cx , sread
inc cx
mov ch , dl
mov dx , head
mov dh , dl
mov dl , #0
and dx , #0x0100
mov ah , #2
int 0x13
jc bad_rt
pop dx
pop cx
pop bx
pop ax
ret
bad_rt: mov ax , #0
mov dx , #0
int 0x13
pop dx
pop cx
pop bx
pop ax
jmp read_track
!關(guān)閉軟驅(qū)馬達(dá)的子程序
kill_motor:
push dx
mov dx , #0x3f2
mov al , #0
outb
pop dx
ret
sectors:
.word 0 !存放當(dāng)前啟動(dòng)軟盤每磁道的扇區(qū)數(shù)
msg1:
.byte 13 , 10
.ascii "Loading system

.byte 13 , 10 , 13 ,10
.org 508
root_dev:
.word ROOT_DEV !這里存放根文件系統(tǒng)的所在設(shè)備號(hào)
boot_flag:
.word 0xAA55
.text
endtext:
.data
enddata:
.bss
endbss:
2 setup.s程序分析
setup.s是一個(gè)操作系統(tǒng)的加載程序,他的主要作用就是利用BIOS的讀取機(jī)器系統(tǒng)數(shù)據(jù),并將這些數(shù)據(jù)保存到0x90000開始的位置,(覆蓋了bootsect程序所在的地方)。這些參數(shù)將被內(nèi)核中相關(guān)程序使用。參數(shù)諸如光標(biāo)位置 ,顯存等信息。
然后setup程序?qū)ystem模塊從 0x10000-0x8ffff(任務(wù)system模塊不會(huì)超過512kb) ,整體移動(dòng)到絕對(duì)內(nèi)存地址為0x0000處。
接著加載中斷描述表寄存器idtr和全局描述表寄存器gdtr, 開啟A20地址線,重新設(shè)置兩個(gè)中斷控制芯片8259A,將硬件中斷號(hào)重新設(shè)置為0x20---0x2f。最后設(shè)置CPU的控制寄存器CR0,從而進(jìn)入32位保護(hù)模式運(yùn)行,并跳入到system模塊最前面部分的head.s程序繼續(xù)運(yùn)行。
為了能讓head.s在32位保護(hù)模式下運(yùn)行,在本程序臨時(shí)設(shè)置了中斷描述符表(IDT)和全局描述符表(GDT),
在GDT中設(shè)置了當(dāng)前代碼段的描述符和數(shù)據(jù)段的描述符,在head.s中會(huì)重新設(shè)置這些描述符表。必須使用lgdt把描述符表的基地址告知CPU,再將機(jī)器狀態(tài)字置位即可進(jìn)入32位保護(hù)模式。
SYSSEG = 0x1000
SETUPSEG = 0x9020 !本程序所在的段地址
.global begtext , begdata , begbss , endtext , enddata , endbss
.text
begtext:
.data
begdata:
.bss
begbss:
.text
entry start
start:
!保存光標(biāo)位置已備以后需要
!這段代碼使用BIOS中斷取屏幕當(dāng)前的光標(biāo)位置,然后保存在內(nèi)存0x90000處就可以使用
mov ax , #INITSEG
mov ds , ax
mov ah , #0x03
xor bh , bh
int 0x10 !利用BIOS中斷 將當(dāng)前光標(biāo)位置存檔到 dx
mov [0] , dx !將光標(biāo)位置存放在0x90000處
!得到內(nèi)存的大小值
!利用BIOS中斷0x15功能號(hào) ah=0x88取系統(tǒng)所含擴(kuò)展內(nèi)存大小并保存在內(nèi)存0x90002處。
mov ah , #0x88
int 0x15
mov [2] , ax
!得到顯示卡的屬性
!調(diào)用BIOS中斷0x10,功能號(hào)ah=0x0f
!返回:ah=字符列數(shù);al=顯示模式;bh=當(dāng)前顯示頁(yè)
!0x90004存放當(dāng)前頁(yè) ,0x90006存放顯示模式,0x90007存放字符列數(shù)
mov ah , #0x0f
int 0x10
mov [4] , bx !bh=display page
mov [6] , ax !al =video mode , ah = window width
!檢查顯示方式并取參數(shù)
mov ah , #0x12
mov bl , #0x10
int 0x10
mov [8] , ax
mov [10] , bx
mov [12] , cx
!取得第一個(gè)硬盤的信息
mov ax , #0x0000
mov ds , ax
lds si , [4 * 0x41]
mov ax , #INITSEG
mov es , ax
mov di , #0x0080
mov cx , #0x10
rep
movsb
!取得第二個(gè)硬盤
mov ax , #0x0000
mov ds , ax
lds si , [4 * 0x46] !取中斷向量0x46的值,即hd1的參數(shù)值 ------> ds:si
mov ax , #INITSEG
mov es , ax
mov di , #0x0090 !傳輸目的地址 0x9000:0x0090
mov cx , #0x10
rep
movsb
!檢查系統(tǒng)是否有第二個(gè)硬盤
mov ax , #0x01500
mov dl , #0x81
int 0x13
jc no_disk1
cmp ah , #3
je is_disk1
no_disk1: !第二塊硬盤不存在,所以清空參數(shù)表
mov ax , #INITSEG
mov es , ax
mov di , #0x0090
mov cx , #0x10
mov ax , #0x00
rep
stosb
is_disk1:
!從此開始進(jìn)入了保護(hù)模式
cli
!首先把system模塊移動(dòng)到正確的位置
mov ax , #0x0000
cld
do_move:
mov es , ax
add ax , #0x1000
cmp ax , #0x9000
jz end_move
mov ds , ax
sub di , di
sub si , si
mov cx , #0x8000
rep
movsw
jmp do_move
end_move:
!在此處加載段描述符表,這里需要設(shè)置全局描述符表和中斷描述符表
mov ax , #SETUPSEG
mov ds , ax
lidt idt_48
lgdt gdt_48
!打開A20地址線
call empty_8042
mov al , #0xD1
out #0x64 , al
call empty_8042
mov al , #0xDF
out #0x60 , al
call empty_8042
!8259芯片主片端口是0x20 - 0x29 ,從片的端口是0xA0 - 0xA9 。
mov al , #0x11
out #0x20, al
.word 0x00eb , 0x00eb
out #0xA0 , al
.word 0x00eb , 0x00eb
!8259芯片設(shè)置中斷號(hào)從0x20開始
mov al , #0x20
out #0x21 , al
.word 0x00eb , 0x00eb
mov al , #0x28
out #0xA1 , al
.word 0x00eb , 0x00eb
mov al , #0x04
out #0x21 , al
.word 0x00eb, 0x00eb
mov al , #0x02
out #0xA1 , al
.word 0x00eb, 0x00eb
mov al , #0x01
out #0x21 , al
.word 0x00eb , 0x00eb
out #0xA1 , al
.word 0x00eb , 0x00eb
mov al , #0xFF
out #0x21 , al
.word 0x00eb , 0x00eb
out #0xA1 , al
!下面設(shè)置并進(jìn)入32位保護(hù)模式運(yùn)行,首先加載機(jī)器狀態(tài)字,也稱控制寄存器cr0
!在設(shè)置該bit之后,隨后的一條指令必須是一條段間跳轉(zhuǎn)指令,一用于刷新當(dāng)前指令隊(duì)列
!因?yàn)镃PU在執(zhí)行一條指令之前就已經(jīng)從內(nèi)存讀取該指令并對(duì)其進(jìn)行解碼。
mov ax , #0x0001
lmsw ax
jmpi 0,8
!下面這個(gè)子程序檢查鍵盤命令隊(duì)列是否為空
empty_8042:
.word 0x00eb , 0x00eb
in al , #0x64
test al , #2
jnz empty_8042
ret
!全局描述符表開始處
gdt:
.word 0,0,0,0
!代碼段選擇符的值
.word 0x07FF
.word 0x0000
.word 0x9A00
.word 0x00C0
!數(shù)據(jù)段選擇符的值
.word 0x07FF
.word 0x0000
.word 0x9200
.word 0x00C0
idt_48:
.word 0
.word 0 , 0
gdt_48:
.word 0x800
.word 512 + gdt , 0x9
.text
endtext:
.data
enddata:
.bss
endbss:
三 head.s程序
功能描述:
head.s程序在被編譯生成目標(biāo)文件之后會(huì)與內(nèi)核其他程序一起被鏈接成system模塊,位于system模塊最前面,所以稱之為head程序的原因。system模塊將被放置在磁盤上setup模塊之后開始的扇區(qū)中,即從磁盤上第6個(gè)扇區(qū)開始位置。 linux內(nèi)核一般大約有120KB ,在磁盤上大概占用240個(gè)扇區(qū)。
之后我們將在保護(hù)模式下編程,head.s使用 as 和ld 編譯器和連接器。 這段程序?qū)嶋H上處于絕對(duì)地址0處開始的地方,首先是加載各個(gè)數(shù)據(jù)段寄存器,重新設(shè)置中斷描述符表idt,共256項(xiàng),并使各個(gè)表項(xiàng)指向一個(gè)只報(bào)錯(cuò)誤的啞中斷子程序 ignore_int。
在設(shè)置了中斷描述符表之后,本程序又重新設(shè)置了全局段描述符表gdt,主要是把gdt表設(shè)置在比較合理的地方,接著設(shè)置管理內(nèi)存的分頁(yè)處理機(jī)制,將頁(yè)目錄表放在絕對(duì)物理地址0開始處,緊隨后邊將放置可以尋址16MB內(nèi)存的4個(gè)頁(yè)表,并設(shè)置它們的表項(xiàng)。
最后,head.s 程序利用返回指令將預(yù)先放置在堆棧中的 main.c程序的入口地址彈出,去執(zhí)行main()程序。
以下是部分代碼分析
(1)首先是建立IDT和GDT表
setup_idt:
lea ignore_int , %edx
movl $0x00080000 , %eax
movw %dx , %ax
movw $0x8E00 , %dx
lea _idt , %edi
mov $256 , %ecx
rp_sidt:
movl %eax , (%edi) #eax的高16位是選擇符 ,低16位是段內(nèi)偏移的低16位
movl %edx , 4(%edi) #edx的高16位是段內(nèi)偏移地址的高16位,低16位是權(quán)限位
addl $8 , %edi
dec %ecx #重復(fù)設(shè)置總共256個(gè)中斷描述符
jne rp_sidt
lidt idt_descr #加載中斷描述符表寄存器
ret
setup_gdt:
lgdt gdt_descr #加載全局描述符表寄存器
ret
由代碼可知, 256個(gè)idt均指向了一個(gè)啞中斷ignore_int,加載gdt的過程更簡(jiǎn)單,只是將gdt描述符表的基地址加載進(jìn)gdtr寄存器。
(2) 頁(yè)目錄表和頁(yè)表之間的映射
在linux1.1中 , 在絕對(duì)內(nèi)存地址的0x000000處是一個(gè)大小為4k的頁(yè)目錄表,然后在內(nèi)存0x1000,0x2000,0x3000,0x4000處分別是4個(gè)頁(yè)表的首地址,也就是說linux0.11僅僅能訪問16M的 內(nèi)存空間,內(nèi)存映射的算法如下:
首先在內(nèi)存0x00000即頁(yè)目錄表設(shè)置4個(gè)頁(yè)表首地址,注意添加權(quán)限屬性。 然后從最后一個(gè)頁(yè)表的最后一個(gè)表項(xiàng),倒序的將物理地址添加進(jìn)頁(yè)表中,最后一個(gè)頁(yè)表項(xiàng)的內(nèi)容是 64M - 4096 + 7 (7表示頁(yè)面在內(nèi)存,且用戶可讀可訪問)。
setup_paging: #首先為5頁(yè)內(nèi)存進(jìn)行清空處理
#1個(gè)頁(yè)目錄表,4個(gè)頁(yè)表
movl $1024 * 5 , %ecx
xorl %eax , %eax
xorl %edi , %edi
cld ; rep ; stosl
#頁(yè)目錄中只需要4個(gè)頁(yè)目錄, 7是屬性,表示該頁(yè)存在內(nèi)存中,且用戶可以訪問
movl $pg0 + 7 , _pg_dir
movl $pg1 + 7 , _pg_dir + 4
movl $pg2 + 7 , _pg_dir + 8
movl $pg3 + 7 , _pg_dir + 12
#從最后一項(xiàng) 倒序的寫入
movl $pg3 + 4092 , %edi #最后一頁(yè)的最后一項(xiàng)
movl $0xfff007 , %eax #16M - 4096 +7
std
stosl
subl $0x1000 , %eax
jge 1b
#設(shè)置頁(yè)目錄表基址寄存器cr3的值,指向頁(yè)目錄表。cr3中保存的是頁(yè)目錄表的物理地址
xorl %eax , %eax
movl %eax , %cr3
#設(shè)置啟動(dòng)分頁(yè)處理
movl %cr0 , %eax
orl %0x80000000 , %eax
movl %eax , %cr0
#該返回指令執(zhí)行先前壓入堆棧的main函數(shù)的入口地址
ret
(3)head中還需要為程序跳轉(zhuǎn)進(jìn)main函數(shù)作準(zhǔn)備,當(dāng)完成了頁(yè)面設(shè)置的時(shí)候,上面代碼的最后一句ret,即
完成了跳入main函數(shù)中繼續(xù)執(zhí)行。 設(shè)置main函數(shù)的代碼如下:
_tmp_floppy_area:
.fill 1024 , 1 ,0 #共保留1024項(xiàng),每項(xiàng)1字節(jié),
#下面這些代碼為跳轉(zhuǎn)到main函數(shù)中,做準(zhǔn)備
after_page_tables:
pushl $0 #這些是main函數(shù)的參數(shù)
pushl $0
pushl $0
pushl $L6 #main函數(shù)的返回地址
pushl $_main #_main 是編譯程序?qū)ain的內(nèi)部表示法
jmp setup_paging #跳轉(zhuǎn)到建立頁(yè)表映射
L6:
jmp L6
可以看出執(zhí)行完setup_paging之后的ret指令,將把_main 加載進(jìn) 指令寄存器,進(jìn)行執(zhí)行。
(4)完整的代碼如下
.global _idt , _gdt , _pg_dir , _tmp_floppy_area
_pg_dir: #頁(yè)目錄將會(huì)設(shè)置在這里,所以該程序會(huì)被覆蓋掉
startup_32:
movl $0x10 , %eax #0x10已經(jīng)是全局描述符的在描述符表中的偏移值
mov %ax , %ds
mov %ax , %es
mov %ax , %fs
mov %ax , %gs
lss _stack_start , %esp #設(shè)置_stack_start----->ss:esp
call setup_idt #調(diào)用設(shè)置中斷描述符表的子程序
call setup_gdt #調(diào)用設(shè)置全局描述符表的子程序
movl $0x10 , %eax #重新加載所有的段寄存器
mov %ax , %ds
mov %ax , %es
mov %ax , %fs
mov %ax , %gs
lss _stack_start , %esp
#以下代碼用來測(cè)試A20地址線是否已經(jīng)打開,采用的方法是向內(nèi)存0x000000處寫入任意的一個(gè)數(shù)值
#然后看內(nèi)存地址0x100000是否也是這個(gè)數(shù)值,如果一樣的話,就說明A20地址線沒有打開
xorl %eax , %eax
1: incl %eax
movl %eax , 0x000000 #地址就不需要加$
cmpl %eax , 0x100000
je 1b
movl %cr0 , %eax #
andl $0x80000011 , %eax
orl $2 , %eax
movl %eax , %cr0
call check_x87
jmp after_page_tables
check_x87:
fninit #向協(xié)處理器發(fā)送初始化命令
fstsw %ax
cmpb $0 , %al
je 1f
movl %cr0 , %eax
xorl $6 , %eax
movl %eax , %cr0
ret
.align 2
1: .byte 0xDB , 0xE4
#建立IDT表
setup_idt:
lea ignore_int , %edx
movl $0x00080000 , %eax
movw %dx , %ax
movw $0x8E00 , %dx
lea _idt , %edi
mov $256 , %ecx
rp_sidt:
movl %eax , (%edi) #eax的高16位是選擇符 ,低16位是段內(nèi)偏移的低16位
movl %edx , 4(%edi) #edx的高16位是段內(nèi)偏移地址的高16位,低16位是權(quán)限位
addl $8 , %edi
dec %ecx #重復(fù)設(shè)置總共256個(gè)中斷描述符
jne rp_sidt
lidt idt_descr #加載中斷描述符表寄存器
ret
setup_gdt:
lgdt gdt_descr #加載全局描述符表寄存器
ret
#這里設(shè)置四張頁(yè)表,可以用來方位16M的內(nèi)存空間
#每個(gè)頁(yè)表大小為4k,每項(xiàng)為4字節(jié),一張頁(yè)表可以映射1024 * 4kb的內(nèi)存空間 ,即4M
.org 0x1000
pg0:
.org 0x2000
pg1:
.org 0x3000
pg2:
.org 0x4000
pg3:
.org 0x5000 #定義下面的內(nèi)存數(shù)據(jù)塊從偏移0x5000處開始
_tmp_floppy_area:
.fill 1024 , 1 ,0 #共保留1024項(xiàng),每項(xiàng)1字節(jié),
#下面這些代碼為跳轉(zhuǎn)到main函數(shù)中,做準(zhǔn)備
after_page_tables:
pushl $0 #這些是main函數(shù)的參數(shù)
pushl $0
pushl $0
pushl $L6 #main函數(shù)的返回地址
pushl $_main #_main 是編譯程序?qū)ain的內(nèi)部表示法
jmp setup_paging #跳轉(zhuǎn)到建立頁(yè)表映射
L6:
jmp L6
#下面是默認(rèn)的中斷向量句柄
int_msg:
.asciz "Unknown interrupt\n\r"
.align 2
ignore_int:
pushl %eax
pushl %ecx
pushl %edx
push %ds #入棧占4個(gè)字節(jié)
push %es
push %fs
movl $0x10 , %eax
mov %ax , %ds
mov %ax , %es
mov %ax , %fs
pushl $int_msg #向printk函數(shù)傳遞參數(shù)
call _printk #該函數(shù)在/kernel/printk.c中
popl %eax #返回值
pop %fs
pop %es
pop %ds
popl %edx
popl %ecx
popl %eax
iret
.align 2
setup_paging: #首先為5頁(yè)內(nèi)存進(jìn)行清空處理
#1個(gè)頁(yè)目錄表,4個(gè)頁(yè)表
movl $1024 * 5 , %ecx
xorl %eax , %eax
xorl %edi , %edi
cld ; rep ; stosl
#頁(yè)目錄中只需要4個(gè)頁(yè)目錄, 7是屬性,表示該頁(yè)存在內(nèi)存中,且用戶可以訪問
movl $pg0 + 7 , _pg_dir
movl $pg1 + 7 , _pg_dir + 4
movl $pg2 + 7 , _pg_dir + 8
movl $pg3 + 7 , _pg_dir + 12
#從最后一項(xiàng) 倒序的寫入
movl $pg3 + 4092 , %edi #最后一頁(yè)的最后一項(xiàng)
movl $0xfff007 , %eax #16M - 4096 +7
std
stosl
subl $0x1000 , %eax
jge 1b
#設(shè)置頁(yè)目錄表基址寄存器cr3的值,指向頁(yè)目錄表。cr3中保存的是頁(yè)目錄表的物理地址
xorl %eax , %eax
movl %eax , %cr3
#設(shè)置啟動(dòng)分頁(yè)處理
movl %cr0 , %eax
orl %0x80000000 , %eax
movl %eax , %cr0
#該返回指令執(zhí)行先前壓入堆棧的main函數(shù)的入口地址
ret
#140行將壓入堆棧的main指令彈出,并跳到main函數(shù)中去
.align 2
.word 0
idt_descr:
.word 256 * 8 - 1
.long _idt
.align 2
.word 0
gdt_descr:
.word 256 * 8 - 1
.long _gdt
.align 3
_idt: .fill 256 , 8 , 0 #共256項(xiàng),每項(xiàng)8字節(jié),初始化為0
_gdt: .quad 0x0000000000000000
.quad 0x00c09a0000000fff
.quad 0x00c0920000000fff
.quad 0x0000000000000000
.fill 252 , 8 , 0
當(dāng)CPU運(yùn)行在保護(hù)模式下,某一時(shí)刻GDT和LDT分別只能有一個(gè),分別有寄存器GDTR和IDTR指定它們的表基址。在某一時(shí)刻當(dāng)前LDT表的基址由LDTR寄存器的內(nèi)容指定并且使用GDT中的某個(gè)描述符來加載,即LDT也是由GDT中的描述符來決定。但是在某一時(shí)刻同樣也只是由其中的一個(gè)被視為活動(dòng)的。一般對(duì)于每個(gè)任務(wù)使用一個(gè)LDT,在運(yùn)行時(shí),程序可以使用GDT中的描述符以及當(dāng)前任務(wù)的LDT中的描述符
posted on 2010-10-08 19:48 kahn 閱讀(845) 評(píng)論(0) 編輯 收藏 引用