操作系统真相还原
操作系統真相還原
1、部署工作環境
1.2 Bochs下載安裝
下載
下載地址:https://sourceforge.net/projects/bochs/files/bochs/2.7/
解壓:
crescentp@crescentp:~/bochs$ tar zxvf bochs-2.7.tar.gz編譯
./configure --prefix=/os/bochs --enable-debugger --enable-disasm --enable-iodebug --enable-x86-debugger --with-x --with-x11 make make install配置bochs
可以在安裝目錄下查看配置文件的樣本文件
cat share/doc/bochs/bochsrc-sample.txt在 bin目錄下編寫bochsrc.disk代碼
############################################# # Configuration file for Bochs # By CP ############################################# # 第一步,首先設置Bochs在運行中過程中能夠使用的內存,本列為32MB # 關鍵字為:megs megs: 32# 第二步:設置對應真實機器的BIOS和VGA BIOS # 對應的兩個關鍵字為: romimage 和 vgaromimage romimage: file=/os/bochs/share/bochs/BIOS-bochs-latest vgaromimage: file=/os/bochs/share/bochs/VGABIOS-lgpl-latest# 第三步,設置Bochs所使用的的磁盤,軟盤的關鍵字為floppy # 若只有一個軟盤,則使用的floppya即可,若有多個,則為floppya,floppyb # floppya: 1_44=a.img, status=inserted# 第四步,選擇啟動盤符 #boot: floppy #默認從軟盤啟動 boot: disk #改為從硬盤啟動,我們的任何代碼都將直接寫在硬盤上,所以不會再有讀寫軟盤的操作了# 第五步,設置日志文件的輸出 log: bochs.out# 第六步,開啟或關閉某些功能 # 下面是關閉鼠標,并且打開鍵盤 mouse: enabled=0 # keyboard_mapping: enabled=1, keyboard:keymap=/os/bochs/share/bochs/keymaps/x11-pc-us.map# 硬盤設置 ata0: enabled=1, ioaddr1=0x1f0, ioaddr2=0x3f0, irq=14 # ata0-master: type=disk,path="hd60M.img",mode=flat# 下面的是增加的bochs對gdb的支持,這樣gdb便可以遠程連接到此機器的1234端口調試了 gdbstub: enabled=1, port=1234, text_base=0, data_base=0, bss_base=0運行
bochs配置啟動盤
沒有啟動盤,那我們就配置啟動盤唄
/bin/bximage 是創建啟動盤的工具
bin/bximage -hd=60M -q hd60M.img2、編寫MBR
mbr.S
;主引導程序 ;--------------------------------------------------------------------- SECTION MBR vstart=0x7c00 ; 表示在本程序編譯時,告訴編譯器,把我的其實地址編譯0x7c00mov ax,cs ; 這一塊是用cs寄存器的值,去初始化其它的寄存器mov ds,ax ; 由于BIOS是通過 jmp 0:0x7c00 跳轉到MBR的,故此時cs為0mov es,axmov ss,axmov fs,axmov sp,0x7c00 ;初始化棧指針,0x7c00以下是安全的區域,可以用來當棧使用;清屏利用0x6號功能,上卷全部行,即可清屏 ;--------------------------------------------------------------------- ;INT 0x10 功能號:0x06 功能描述:上卷屏幕 ;--------------------------------------------------------------------- ;輸入; ;AH 功能號 = 0x06 ;AL = 上卷的行數(如果為0,表示全部) ;BH = 上卷行的屬性 ;(CL,CH) = 窗口左上角的(X,Y)位置 ;(DL,DH) = 窗口右下角的(X,Y)位置 ;無返回值mov ax,0x600mov bx,0x700mov cx,0 ;左上角:(0,0)mov dx,0x184f ;右下角:(80,25);VGA文本模式中,一行只能容納80個字符,共25行;下標從0開始,所以0x18=24,0x4f=79int 0x10 ;int 0x10;;;;;;;; 下面這三行代碼獲取光標位置 ;;;;;;;;;;;;; ; .get_cursor 獲取當前光標位置,在光標位置處打印字符mov ah,3 ;輸入:3號子功能是獲取光標位置,需要存入ah寄存器mov bh,0 ;bh寄存器存儲的是待獲取光標的頁號int 0x10 ;輸出,ch=光標開始行,cl=光標結束行;dh=光標所在行號,dl=光標所在列號;;;;;;;; 獲取光標結束位置 ;;;;;;;;;;;;;;;;;;; 打印字符串 ;;;;;;;;;;;;;還是用10h中斷,不過這次調用13號子功能打印字符串mov ax,messagemov bp,ax ;es:bp為串首地址,es此時和cs一致;開頭已經為sreg初始化;光標位置要用到dx寄存器中的內容,cx中光標位置可忽略mov cx,5 ;cx為串長度,不包括結束符0的字符個數mov ax,0x1301 ;子功能號13顯示字符及屬性,要出入ah寄存器;al設置寫字符方式 ah = 01;顯示字符串,光標跟隨移動mov bx,0x2 ;bh存儲的是要顯示的頁號,此處是第0頁;bl是字符屬性,屬性為黑底綠字(bl = 02h)int 0x10 ;執行BIOS 0x10中斷 ;;;;;;;; 打印字符串結束 ;;;;;;;;;;;;;;jmp $ ;是程序懸停在此處message db "1 MBR"times 510-($-$$) db 0 ; $$ 是本section的地址,$是本行的地址,$-$$是本行到本section的偏移量,最后兩個字節是確定的,這就把本扇區的剩余量補0db 0x55,0xaa編譯
nasm -o mbr.bin mbr.S將mbr.bin轉載進入磁盤的第一個扇區
dd if=/os/bochs/mbr.bin of=/os/bochs/hd60M.img bs=512 count=1 conv=notrunc運行
bin/bochs -f bin/bochsrc.disk3、完善MBR
我們不適用int10中斷來顯示MBR,而是通過操控顯存的方式
;主引導程序 ; ;LOADER_BASE_ADDR equ 0xA00 ;LOADER_START_SECTOR equ 0x2 ;--------------------------------------------------------------------- SECTION MBR vstart=0x7c00 ; 表示在本程序編譯時,告訴編譯器,把我的其實地址編譯0x7c00mov ax,cs ; 這一塊是用cs寄存器的值,去初始化其它的寄存器mov ds,ax ; 由于BIOS是通過 jmp 0:0x7c00 跳轉到MBR的,故此時cs為0mov es,axmov ss,axmov fs,axmov sp,0x7c00 ;初始化棧指針,0x7c00以下是安全的區域,可以用來當棧使用mov ax,0xb800 ;顯存文本模式中,其內存地址是0xb8000mov gs,ax ;清屏 ;利用0x6號功能,上卷全部行,即可清屏 ;--------------------------------------------------------------------- ;INT 0x10 功能號:0x06 功能描述:上卷屏幕 ;--------------------------------------------------------------------- ;輸入; ;AH 功能號 = 0x06 ;AL = 上卷的行數(如果為0,表示全部) ;BH = 上卷行的屬性 ;(CL,CH) = 窗口左上角的(X,Y)位置 ;(DL,DH) = 窗口右下角的(X,Y)位置 ;無返回值mov ax,0x600mov bx,0x700mov cx,0 ;左上角:(0,0)mov dx,0x184f ;右下角:(80,25);VGA文本模式中,一行只能容納80個字符,共25行;下標從0開始,所以0x18=24,0x4f=79int 0x10 ;int 0x10;輸出背景色綠色,前景色紅色,并且跳動的字符串“1 MBR”mov byte [gs:0x00],'1'mov byte [gs:0x01],0xA4 ;A表示綠色背景閃爍,4表示前景位紅色mov byte [gs:0x02],' 'mov byte [gs:0x03],0xA4mov byte [gs:0x04],'M'mov byte [gs:0x05],0xA4mov byte [gs:0x06],'B'mov byte [gs:0x07],0xA4mov byte [gs:0x08],'R'mov byte [gs:0x09],0xA4jmp $ ;是程序懸停在此處message db "1 MBR"times 510-($-$$) db 0 ; $$ 是本section的地址,$是本行的地址,$-$$是本行到本section的偏移量,最后兩個字節是確定的,這就把本扇區的剩余量補0db 0x55,0xaa編譯
nasm -o mbr.bin mbr.S將mbr.bin裝載進入磁盤的第一個扇區
dd if=/os/bochs/mbr.bin of=/os/bochs/hd60M.img bs=512 count=1 conv=notrunc運行
bin/bochs -f bin/bochsrc.disk可見這是跳動的
接下來我們通過從硬盤中讀取加載信息,將內核加載器讀入到內存,
mbr.S
;主引導程序 ;------------------------------------------------------------------------------ %include "boot.inc" ;讓編譯器在編譯之前,把boot.inc文件包含進來 SECTION MBR vstart=0x7c00mov ax,csmov ds,axmov es,axmov ss,axmov fs,axmov sp,0x7c00mov ax,0xb800mov gs,ax;清屏 ;利用0x06功能,上卷所有行,則可清屏 ;------------------------------------------------------------------------------- ;INT 0x10 功能號:0x06 功能描述:上卷窗口 ;------------------------------------------------------------------------------- ;輸入; ;AH 功能號:0x06 ;AL = 上卷的行數(如果為0,表示全部) ;BH = 上卷行屬性 ;(CL,CH) = 窗口左上角(X,Y)位置 ;(DL,DH) = 窗口右下角的(X,Y)位置 ;無返回值mov ax,0600hmov bx,0700hmov cx,0 ;左上角:(0,0)mov dx,184fh ;右下角:(80,25) ;因為VGA文本模式中,一行只能容納80個字符,共25行; 下標從0開始,所有0x18=24,0x4f=79int 10h ;int 10h;輸出字符串MBRmov byte [gs:0x00],'1'mov byte [gs:0x01],0xA4mov byte [gs:0x02],' 'mov byte [gs:0x03],0xA4mov byte [gs:0x04],'M'mov byte [gs:0x05],0xA4 ;A表示綠色背景閃爍,4表示前景顏色為紅色mov byte [gs:0x06],'B'mov byte [gs:0x07],0xA4mov byte [gs:0x08],'R'mov byte [gs:0x09],0xA4mov eax,LOADER_START_SECTOR ;起始扇區lba地址,0x2mov bx,LOADER_BASE_ADDR ;寫入的地址,0x900mov cx,1 ;待寫入的扇區數call rd_disk_m_16 ;以下讀取程序的起始部分(一個扇區)jmp LOADER_BASE_ADDR;------------------------------------------------------------------------------------- ;功能:讀取硬盤n個扇區 rd_disk_m_16: ;-------------------------------------------------------------------------------------;eax=LBA扇區號;bx=將數據寫入的內存地址;cx=讀入的扇區數mov esi,eax ;備份eax,因為al在out命令中會使用,會影響到eax的低8位mov di,cx ;備份cx,cx在讀數據的時候會使用到 ;讀寫硬盤 ;第一步:設置要讀取的扇區數mov dx,0x1f2 ;虛擬硬盤屬于ata0,是Primary通道,所以sector count 是由0x1f2訪問mov al,cl ;cl是cx的低8位,就讀一個扇區,這樣就能傳過去了out dx,al ;讀取的扇區數,sector count 記錄要讀取的扇盤數量mov eax,esi ;恢復eax,現在eax存的是其實扇區lba的地址,0x2,第二個扇區;第二步:將LBA地址存入 0x1f3 ~ 0x1f6;LBA地址 7~0 位寫入端口 0x1f3mov dx,0x1f3 ;LBA lowout dx,al ;eax的第8位,就是al;LBA地址 15~8 位寫入端口 0x1f4mov cl,8shr eax,cl ;eax右移8位,讓al的數,變為eax中8位mov dx,0x1f4 ;LBA midout dx,al;LBA地址 23~16 位寫入端口 0x1f5shr eax,cl ;再右移8位mov dx,0x1f5 ;LBA highout dx,alshr eax,cl ;這樣al為0000and al,0x0f ;lba第24~27位or al,0xe0 ;設置7~4位為1110,表示lba模式mov dx,0x1f6 ;就是拼湊出device寄存器的值out dx,al;第3步:向0x1f7端口寫入讀命令,0x20mov dx,0x1f7mov al,0x20out dx,al ;command:0x1f7,寫入命令,寫入的命令是讀命令;第四步:檢測硬盤狀態.not_ready:;同一端口,寫時表示寫入命令字,讀時表示寫入硬盤的狀態,所以不需要更換dx的值nop ;減少打擾硬盤的工作in al,dx ;將Status的寄存器的值讀入到al中and al,0x88 ;第四位為1表示硬盤控制器已準備好數據傳輸,第七位為1表示硬盤忙,保存第4位和第7位cmp al,0x08 ;若第4位為1,表示數據已經準備好了,若第7位為1,表示硬盤處于忙jnz .not_ready ;若未準備好,繼續等,判斷結果是否為0;第5步,從0x1f0端口讀數據mov ax,di ;這個時候di存的是上面備份的cx,及時要讀取的扇區的數量mov dx,256 ;每次in操作只讀取兩個字節,根據讀入的數據總量(扇區數*512字節)mul dx ;dx*ax就是總數量/2,然后將值送到cx中,cx就是要in的次數mov cx,ax ;di為要讀取的扇區數,一個扇區有512個字節,每次讀入一個字,共需要di*512/2次,所以di*256mov dx,0x1f0.go_on_read:in ax,dx ;讀入到ax中mov [bx],ax ;讀入到bx指向的內存add bx,2 ;每次讀入2個字節loop .go_on_read ;cx是循環的次數rettimes 510-($-$$) db 0db 0x55,0xaaboot.inc
;---------- loader 和 kernel----------------- LOADER_BASE_ADDR equ 0x900 LOADER_START_SECTOR equ 0x2編譯:將boot.inc一起編譯
nasm -I include/ -o mbr.bin mbr.Sloader.S
%include "boot.inc" section loader vstart=LOADER_BASE_ADDR;輸出背景色綠色,前景色紅色,并且跳動的字符串"1 MBR" mov byte [gs:0x00],'2' mov byte [gs:0x01],0xA4mov byte [gs:0x02],' ' mov byte [gs:0x04],0xA4mov byte [gs:0x04],'L' mov byte [gs:0x05],0xA4mov byte [gs:0x06],'O' mov byte [gs:0x07],0xA4mov byte [gs:0x08],'A' mov byte [gs:0x09],0xA4mov byte [gs:0x0a],'D' mov byte [gs:0x0b],0xA4mov byte [gs:0x0c],'E' mov byte [gs:0x0d],0xA4mov byte [gs:0x0e],'R' mov byte [gs:0x0f],0xA4jmp $ ;通過死循環使查詢懸停在此 nasm -I include/ -o loader.bin loader.S dd if=/os/bochs/mbr.bin of=/os/bochs/hd60M.img bs=512 count=1 conv=notrunc加載到第二個磁盤
dd if=/os/bochs/loader.bin of=/os/bochs/hd60M.img bs=512 count=4 conv=notrunc seek=2運行
bin/bochs -f bin/bochsrc.disk4、保護模式入門
mbr.S
;主引導程序 ;------------------------------------------------------------------------------ %include "boot.inc" ;讓編譯器在編譯之前,把boot.inc文件包含進來 SECTION MBR vstart=0x7c00mov ax,csmov ds,axmov es,axmov ss,axmov fs,axmov sp,0x7c00mov ax,0xb800mov gs,ax;清屏 ;利用0x06功能,上卷所有行,則可清屏 ;------------------------------------------------------------------------------- ;INT 0x10 功能號:0x06 功能描述:上卷窗口 ;------------------------------------------------------------------------------- ;輸入; ;AH 功能號:0x06 ;AL = 上卷的行數(如果為0,表示全部) ;BH = 上卷行屬性 ;(CL,CH) = 窗口左上角(X,Y)位置 ;(DL,DH) = 窗口右下角的(X,Y)位置 ;無返回值mov ax,0600hmov bx,0700hmov cx,0 ;左上角:(0,0)mov dx,184fh ;右下角:(80,25) ;因為VGA文本模式中,一行只能容納80個字符,共25行; 下標從0開始,所有0x18=24,0x4f=79int 10h ;int 10h;輸出字符串MBRmov byte [gs:0x00],'1'mov byte [gs:0x01],0xA4mov byte [gs:0x02],' 'mov byte [gs:0x03],0xA4mov byte [gs:0x04],'M'mov byte [gs:0x05],0xA4 ;A表示綠色背景閃爍,4表示前景顏色為紅色mov byte [gs:0x06],'B'mov byte [gs:0x07],0xA4mov byte [gs:0x08],'R'mov byte [gs:0x09],0xA4mov eax,LOADER_START_SECTOR ;起始扇區lba地址,0x2mov bx,LOADER_BASE_ADDR ;寫入的地址,0x900mov cx,4 ;待寫入的扇區數,由于loader.bin超過了512個字節,可能是多個扇區call rd_disk_m_16 ;以下讀取程序的起始部分(一個扇區)jmp LOADER_BASE_ADDR;------------------------------------------------------------------------------------- ;功能:讀取硬盤n個扇區 rd_disk_m_16: ;-------------------------------------------------------------------------------------;eax=LBA扇區號;bx=將數據寫入的內存地址;cx=讀入的扇區數mov esi,eax ;備份eax,因為al在out命令中會使用,會影響到eax的低8位mov di,cx ;備份cx,cx在讀數據的時候會使用到 ;讀寫硬盤 ;第一步:設置要讀取的扇區數mov dx,0x1f2 ;虛擬硬盤屬于ata0,是Primary通道,所以sector count 是由0x1f2訪問mov al,cl ;cl是cx的低8位,就讀一個扇區,這樣就能傳過去了out dx,al ;讀取的扇區數,sector count 記錄要讀取的扇盤數量mov eax,esi ;恢復eax,現在eax存的是其實扇區lba的地址,0x2,第二個扇區;第二步:將LBA地址存入 0x1f3 ~ 0x1f6;LBA地址 7~0 位寫入端口 0x1f3mov dx,0x1f3 ;LBA lowout dx,al ;eax的第8位,就是al;LBA地址 15~8 位寫入端口 0x1f4mov cl,8shr eax,cl ;eax右移8位,讓al的數,變為eax中8位mov dx,0x1f4 ;LBA midout dx,al;LBA地址 23~16 位寫入端口 0x1f5shr eax,cl ;再右移8位mov dx,0x1f5 ;LBA highout dx,alshr eax,cl ;這樣al為0000and al,0x0f ;lba第24~27位or al,0xe0 ;設置7~4位為1110,表示lba模式mov dx,0x1f6 ;就是拼湊出device寄存器的值out dx,al;第3步:向0x1f7端口寫入讀命令,0x20mov dx,0x1f7mov al,0x20out dx,al ;command:0x1f7,寫入命令,寫入的命令是讀命令;第四步:檢測硬盤狀態.not_ready:;同一端口,寫時表示寫入命令字,讀時表示寫入硬盤的狀態,所以不需要更換dx的值nop ;減少打擾硬盤的工作in al,dx ;將Status的寄存器的值讀入到al中and al,0x88 ;第四位為1表示硬盤控制器已準備好數據傳輸,第七位為1表示硬盤忙,保存第4位和第7位cmp al,0x08 ;若第4位為1,表示數據已經準備好了,若第7位為1,表示硬盤處于忙jnz .not_ready ;若未準備好,繼續等,判斷結果是否為0;第5步,從0x1f0端口讀數據mov ax,di ;這個時候di存的是上面備份的cx,及時要讀取的扇區的數量mov dx,256 ;每次in操作只讀取兩個字節,根據讀入的數據總量(扇區數*512字節)mul dx ;dx*ax就是總數量/2,然后將值送到cx中,cx就是要in的次數mov cx,ax ;di為要讀取的扇區數,一個扇區有512個字節,每次讀入一個字,共需要di*512/2次,所以di*256mov dx,0x1f0.go_on_read:in ax,dx ;讀入到ax中mov [bx],ax ;讀入到bx指向的內存add bx,2 ;每次讀入2個字節loop .go_on_read ;cx是循環的次數rettimes 510-($-$$) db 0db 0x55,0xaaboot.inc
;---------------loader和kernel ----------LOADER_BASE_ADDR equ 0x900 LOADER_START_SECTOR equ 0x2;-------------- gdt描述符屬性 ------------- DESC_G_4K equ 1_00000000000000000000000b ;G在段描述符第23位,將這里設為1,就是使用4K的密度 DESC_D_32 equ 1_0000000000000000000000b ;D在段描述符第22位,將這里設為1,表示操作數使用32位 DESC_L equ 0_000000000000000000000b ;L:段是否64位模式 64位代碼標記,此處標記為0便可。 DESC_AVL equ 0_00000000000000000000b ; cpu不用此位,暫置為0 DESC_LIMIT_CODE2 equ 1111_0000000000000000b ;段界限,段的長度 DESC_LIMIT_DATA2 equ DESC_LIMIT_CODE2 ; DESC_LIMIT_VIDEO2 equ 0000_000000000000000b ;段界限,設為0 DESC_P equ 1_000000000000000b ;p:是否在內存中 DESC_DPL_0 equ 00_0000000000000b ;DPL,為0的權限 DESC_DPL_1 equ 01_0000000000000b DESC_DPL_2 equ 10_0000000000000b DESC_DPL_3 equ 11_0000000000000b DESC_S_CODE equ 1_000000000000b ;代碼段 DESC_S_DATA equ DESC_S_CODE ;數據段,都是1,表示非系統段 DESC_S_sys equ 0_000000000000b DESC_TYPE_CODE equ 1000_00000000b ;x=1,c=0,r=0,a=0 代碼段是可執行的,非依從的,不可讀的,已訪問位a清0. DESC_TYPE_DATA equ 0010_00000000b ;x=0,e=0,w=1,a=0 數據段是不可執行的,向上擴展的,可寫的,已訪問位a清0.DESC_CODE_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + DESC_L + DESC_AVL + DESC_LIMIT_CODE2 + DESC_P + DESC_DPL_0 + DESC_S_CODE + DESC_TYPE_CODE + 0x00 ;代碼段的平坦模型 DESC_DATA_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + DESC_L + DESC_AVL + DESC_LIMIT_DATA2 + DESC_P + DESC_DPL_0 + DESC_S_DATA + DESC_TYPE_DATA + 0x00 DESC_VIDEO_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + DESC_L + DESC_AVL + DESC_LIMIT_VIDEO2 + DESC_P + DESC_DPL_0 + DESC_S_DATA + DESC_TYPE_DATA + 0x0b;-------------- 選擇子屬性 --------------- RPL0 equ 00b ;訪問權限 RPL1 equ 01b RPL2 equ 10b RPL3 equ 11b TI_GDT equ 000b ;訪問GDT TI_LDT equ 100b ;訪問LDTloader.S
%include "boot.inc"section loader vstart=LOADER_BASE_ADDRLOADER_STACK_TOP equ LOADER_BASE_ADDR ;保護模式下的棧jmp loader_start;構建 gdt 及其內部的描述符GDT_BASE: dd 0x00000000 ;GDT的第0個段不可用dd 0x00000000; 定義了三個有用的段描述符CODE_DESC: dd 0x0000FFFF ;段描述符的低4字節,其中的低2字節是段長度 FFFF,高2字節是段基址 0000,dd DESC_CODE_HIGH4 ;代碼段的高四節,已經定義好了DATA_STACK_DESC: dd 0x0000FFFF ;數據段和棧段的段描述符dd DESC_DATA_HIGH4 ;顯存段描述符,0xb8000~0xbffff是用于文本模式的顯示內存,段基址:0x8000 長度 0007 VIDEO_DESC: dd 0x80000007 ;limit=(0xbffff-0xb8000)/4k = 0x7 4k是段粒度dd DESC_VIDEO_HIGH4 ;此時dpl為0GDT_SIZE equ $ - GDT_BASE ;19~20 獲取GDT的大小,為加載GDT做準備GDT_LIMIT equ GDT_SIZE - 1times 60 dq 0 ;此處預留60個描述符的空位,為了將來往GDT中添加其它描述符,提前保留空間SELECTOR_CODE equ (0x0001<<3) + TI_GDT + RPL0 ;相當于(CODE_DESC - GDT_BASE)/8 + TI_GDT + RPL0,構建代碼段的段選擇子,段描述符+TI+PRLSELECTOR_DATA equ (0x0002<<3) + TI_GDT + RPL0 ;同上SELECTOR_VIDEO equ (0x0003<<3) + TI_GDT + RPL0 ;同上;以下是gdt的指針,前2字節是gdt界限,后四字節是gdt起始地址gdt_ptr dw GDT_LIMIT ;定義GDT的指針.前16位,是GDT以字節為單位的長度,也就是GDT大小減1dd GDT_BASE ;GDT的起始位置loadermsg db '2 loader in real.'loader_start:;-------------------------------------------------- ;INT 0x10 功能號:0x13 功能描述:打印字符串 ;-------------------------------------------------- ;輸入 ;AH 子功能號=13H ;BH = 頁碼 ;BL = 屬性(若AL=00H或01H) ;CX = 字符串的長度 ;(DH,DL) = 坐標(行,列) ;ES:BP=字符串的地址 ;AL=顯示輸出方式 ; 0--字符串中只含顯示字符,其顯示屬性在BL中,顯示后,光標位置不變 ; 1--字符串中只含顯示字符,其顯示屬性在BL中,顯示后,光標位置改變 ; 2--字符串中含顯示字符和顯示屬性.顯示后,光標位置不變 ; 3--字符串中含顯示字符和顯示休息,顯示后,光標位置改變 ;無返回值mov sp,LOADER_BASE_ADDRmov bp,loadermsg ;ES:BP = 字符串地址mov cx,17 ;字符串長度mov ax,0x1301 ;AH = 13,AL=01hmov bx,0x001f ;頁號為0(BH = 0) 藍底粉紅字(BL = 1fh)mov dx,0x1800 ;dh=18h=24行,dl=0,最后一行的行首int 0x10 ;10h 號中斷;------------------------ 準備進入保護模式 -------------------------- ;1 打開A20 ;2 加載gdt ;3 將cr0的pe位置1;---------------- 打開A20 ----------------------------------in al,0x92or al,0000_0010Bout 0x92,al;---------------- 加載GDT ----------------------------------lgdt [gdt_ptr];---------------- cr0 第0位置1 -------------------------------mov eax,cr0or eax,0x00000001mov cr0,eaxjmp dword SELECTOR_CODE:p_mode_start ;刷新流水線[bits 32] p_mode_start:mov ax,SELECTOR_DATA ;83~89 用選擇子初始化成各段寄存器mov ds,axmov es,axmov ss,axmov esp,LOADER_STACK_TOPmov ax,SELECTOR_VIDEOmov gs,axmov byte [gs:160], 'P'jmp $編譯:
nasm -I include/ -o mbr.bin mbr.S nasm -I include/ -o loader.bin loader.S裝載至磁盤
dd if=/os/bochs/mbr.bin of=/os/bochs/hd60M.img bs=512 count=1 conv=notrunc dd if=/os/bochs/loader.bin of=/os/bochs/hd60M.img bs=512 count=4 conv=notrunc seek=2運行
bin/bochs -f bin/bochsrc.disk5、保護模式進階、向內核邁進
mbr.S
;主引導程序 ;------------------------------------------------------------------------------ %include "boot.inc" ;讓編譯器在編譯之前,把boot.inc文件包含進來 SECTION MBR vstart=0x7c00mov ax,csmov ds,axmov es,axmov ss,axmov fs,axmov sp,0x7c00mov ax,0xb800mov gs,ax;清屏 ;利用0x06功能,上卷所有行,則可清屏 ;------------------------------------------------------------------------------- ;INT 0x10 功能號:0x06 功能描述:上卷窗口 ;------------------------------------------------------------------------------- ;輸入; ;AH 功能號:0x06 ;AL = 上卷的行數(如果為0,表示全部) ;BH = 上卷行屬性 ;(CL,CH) = 窗口左上角(X,Y)位置 ;(DL,DH) = 窗口右下角的(X,Y)位置 ;無返回值mov ax,0600hmov bx,0700hmov cx,0 ;左上角:(0,0)mov dx,184fh ;右下角:(80,25) ;因為VGA文本模式中,一行只能容納80個字符,共25行; 下標從0開始,所有0x18=24,0x4f=79int 10h ;int 10h;輸出字符串MBRmov byte [gs:0x00],'1'mov byte [gs:0x01],0xA4mov byte [gs:0x02],' 'mov byte [gs:0x03],0xA4mov byte [gs:0x04],'M'mov byte [gs:0x05],0xA4 ;A表示綠色背景閃爍,4表示前景顏色為紅色mov byte [gs:0x06],'B'mov byte [gs:0x07],0xA4mov byte [gs:0x08],'R'mov byte [gs:0x09],0xA4mov eax,LOADER_START_SECTOR ;起始扇區lba地址,0x2mov bx,LOADER_BASE_ADDR ;寫入的地址,0x900mov cx,4 ;待寫入的扇區數,由于loader.bin超過了512個字節,可能是多個扇區call rd_disk_m_16 ;以下讀取程序的起始部分(一個扇區)jmp LOADER_BASE_ADDR;------------------------------------------------------------------------------------- ;功能:讀取硬盤n個扇區 rd_disk_m_16: ;-------------------------------------------------------------------------------------;eax=LBA扇區號;bx=將數據寫入的內存地址;cx=讀入的扇區數mov esi,eax ;備份eax,因為al在out命令中會使用,會影響到eax的低8位mov di,cx ;備份cx,cx在讀數據的時候會使用到 ;讀寫硬盤 ;第一步:設置要讀取的扇區數mov dx,0x1f2 ;虛擬硬盤屬于ata0,是Primary通道,所以sector count 是由0x1f2訪問mov al,cl ;cl是cx的低8位,就讀一個扇區,這樣就能傳過去了out dx,al ;讀取的扇區數,sector count 記錄要讀取的扇盤數量mov eax,esi ;恢復eax,現在eax存的是其實扇區lba的地址,0x2,第二個扇區;第二步:將LBA地址存入 0x1f3 ~ 0x1f6;LBA地址 7~0 位寫入端口 0x1f3mov dx,0x1f3 ;LBA lowout dx,al ;eax的第8位,就是al;LBA地址 15~8 位寫入端口 0x1f4mov cl,8shr eax,cl ;eax右移8位,讓al的數,變為eax中8位mov dx,0x1f4 ;LBA midout dx,al;LBA地址 23~16 位寫入端口 0x1f5shr eax,cl ;再右移8位mov dx,0x1f5 ;LBA highout dx,alshr eax,cl ;這樣al為0000and al,0x0f ;lba第24~27位or al,0xe0 ;設置7~4位為1110,表示lba模式mov dx,0x1f6 ;就是拼湊出device寄存器的值out dx,al;第3步:向0x1f7端口寫入讀命令,0x20mov dx,0x1f7mov al,0x20out dx,al ;command:0x1f7,寫入命令,寫入的命令是讀命令;第四步:檢測硬盤狀態.not_ready:;同一端口,寫時表示寫入命令字,讀時表示寫入硬盤的狀態,所以不需要更換dx的值nop ;減少打擾硬盤的工作in al,dx ;將Status的寄存器的值讀入到al中and al,0x88 ;第四位為1表示硬盤控制器已準備好數據傳輸,第七位為1表示硬盤忙,保存第4位和第7位cmp al,0x08 ;若第4位為1,表示數據已經準備好了,若第7位為1,表示硬盤處于忙jnz .not_ready ;若未準備好,繼續等,判斷結果是否為0;第5步,從0x1f0端口讀數據mov ax,di ;這個時候di存的是上面備份的cx,及時要讀取的扇區的數量mov dx,256 ;每次in操作只讀取兩個字節,根據讀入的數據總量(扇區數*512字節)mul dx ;dx*ax就是總數量/2,然后將值送到cx中,cx就是要in的次數mov cx,ax ;di為要讀取的扇區數,一個扇區有512個字節,每次讀入一個字,共需要di*512/2次,所以di*256mov dx,0x1f0.go_on_read:in ax,dx ;讀入到ax中mov [bx],ax ;讀入到bx指向的內存add bx,2 ;每次讀入2個字節loop .go_on_read ;cx是循環的次數rettimes 510-($-$$) db 0db 0x55,0xaaloader.S
%include "boot.inc"section loader vstart=LOADER_BASE_ADDRLOADER_STACK_TOP equ LOADER_BASE_ADDR ;保護模式下的棧;構建 gdt 及其內部的描述符GDT_BASE: dd 0x00000000 ;GDT的第0個段不可用dd 0x00000000; 定義了三個有用的段描述符CODE_DESC: dd 0x0000FFFF ;段描述符的低4字節,其中的低2字節是段長度 FFFF,高2字節是段基址 0000,dd DESC_CODE_HIGH4 ;代碼段的高四節,已經定義好了DATA_STACK_DESC: dd 0x0000FFFF ;數據段和棧段的段描述符dd DESC_DATA_HIGH4 ;顯存段描述符,0xb8000~0xbffff是用于文本模式的顯示內存,段基址:0x8000 長度 0007 VIDEO_DESC: dd 0x80000007 ;limit=(0xbffff-0xb8000)/4k = 0x7 4k是段粒度dd DESC_VIDEO_HIGH4 ;此時dpl為0GDT_SIZE equ $ - GDT_BASE ;19~20 獲取GDT的大小,為加載GDT做準備GDT_LIMIT equ GDT_SIZE - 1times 60 dq 0 ;此處預留60個描述符的空位,為了將來往GDT中添加其它描述符,提前保留空間SELECTOR_CODE equ (0x0001<<3) + TI_GDT + RPL0 ;相當于(CODE_DESC - GDT_BASE)/8 + TI_GDT + RPL0,構建代碼段的段選擇子,段描述符+TI+PRLSELECTOR_DATA equ (0x0002<<3) + TI_GDT + RPL0 ;同上SELECTOR_VIDEO equ (0x0003<<3) + TI_GDT + RPL0 ;同上;total_mem_bytes用于保存內存容量,以字節為單位,此位置比較好記;當前偏移 loader.bin 文件頭0x200字節 loader.bin的加載地址是0x900 4個段描述符的定義 32 dq了60個 480 = 512 = 0x200;故total_mem_bytes內存中的地址是0xb00 將來在內核咱們會引用此地址total_mem_bytes dd 0 ;存儲獲取到的內存容量;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;以下是gdt的指針,前2字節是gdt界限,后四字節是gdt起始地址gdt_ptr dw GDT_LIMIT ;定義GDT的指針.前16位,是GDT以字節為單位的長度,也就是GDT大小減1dd GDT_BASE ;GDT的起始位置;人工對齊:total_mem_bytes 4+gdt_ptr 6+ards_buf 244+ards_nr2,共256字節ards_buf times 244 db 0ards_nr dw 0 ;用于記錄ARDS結構體數量,每執行一次中斷就會獲得一個ards結構的數據loader_start:;int 15h eax = 0000E820h,edx = 534D4150h('SMAP')獲取內存布局xor ebx,ebx ;第一次調用ebx要為0mov edx,0x543d4150 ;edx只賦值一次,循環體中不改變mov di,ards_buf ;ards結構緩沖區,es:di,BIOS將獲取到的內存信息寫到此寄存器指向的內存,每次都以ARDS格式返回 .e820_mem_get_loop: ;循環獲取每個ARDS內存范圍描述結構mov eax,0x0000e820 ;子功能號,eax用來指定子功能號,每次執行 int 0x15后,eax值會變為0x534d4150mov ecx,20 ;ARDS結構的字節大小,ARDS地址范圍描述符是20字節int 0x15jc .e820_failed_so_try_e801 ;若cf位為1嘖有錯誤發生,嘗試0xe801子功能add di,cx ;使di增加20字節指向緩沖區中新ARDS結構位置inc word [ards_nr] ;記錄ARDS數量cmp ebx,0 ;ebx存放后續值,下一個ARDS的地址,在cf為0的情況下,若ebx為0,表示這是最后一個ARDS結構jnz .e820_mem_get_loop;在只有ards結構中,找出(base_add_low + length_low)的最大值,即內存大小mov cx,[ards_nr] ;遍歷每一個ARDS結構體,循環次數是ARDS的數量mov ebx,ards_bufxor edx,edx ;edx為最大的內存容量,在此先清0 .find_max_mem_area: ;無需判斷type是否為1,最大內存塊一定是可被使用的mov eax,[ebx] ;base_add_lowadd eax,[ebx+8] ;length_lowadd ebx,20 ;指向緩沖區中下一個ARDS結構cmp edx,eax ;冒泡排序,找出最大,edx寄存器始終是最大的內存容量jge .next_ardsmov edx,eax .next_ards:loop .find_max_mem_areajmp .mem_get_ok;----------- int 15h ax = E801h 獲取內存大小,最大支持4G ------------------- ; 返回后,ax cx 值一樣,以KB為單位,bx dx 值都一樣,以64KB為單位 ; 在ax和cx寄存器中為低16MB,在bx和dx寄存器中為16Mb到4GB .e820_failed_so_try_e801:mov ax,0xe801 ;功能號int 0x15jc .e801_failed_so_try88 ;若當前e801方法失敗,就嘗試0x88方法;1. 先算出低15MB的內存,ax和cx中是以KB為單位的內存數量,將其轉化成以byte為單位mov cx,0x400 ;cx和ax一樣,cx用作乘數mul cxshl edx,16and eax,0x0000FFFFor edx,eaxadd edx,0x100000 ;ax只是15MB,故要加1MBmov esi,edx ;先把低15MB的內存容量存入esi寄存器備份;2. 再將16MB以上的內存轉化為byte為單位,寄存器bx和dx中是以64KB為單位的內存數量xor eax,eaxmov ax,bxmov ecx,0x10000 ;0x10000 十進制為 64KBmul ecx ;32位乘法,默認的被乘數是eax,積為64位,高32位存入edx,低32位存入eaxadd esi,eax ;由于此方法只能測出4GB以內的內存,故32位eax足夠了 edx肯定為0mov edx,esi ;edx為總內存大小jmp .mem_get_ok;----------- int 15h ah = 0x88 獲取內存大小,只能獲取64MB之內 ----------------------- .e801_failed_so_try88:;int 15后,ax存入的是以KB為單位的內存容量mov ah,0x88int 0x15;jc .error_hlrand eax,0x0000FFFF;16位乘法,被乘數是ax,積為32位.積的高16位在dx中,積的低16位在ax中mov cx,0x400 ;0x400等于1024,將ax中的內存容量換為byte為三維mul cxshl edx,16 ;把dx移到高16位or edx,eax ;把積的低16位組合到edx,為32位的積add edx,0x100000 ;0x88子功能只會返回1MB以上的內存,故實際內存要加上1MB.mem_get_ok:mov [total_mem_bytes],edx ;將內存換成byte單位后存入total_mem_bytes處;----------------------進入保護模式-------------------------------- ;1 打開A20 ;2 加載gdt ;3 將cr0的pe位置1;---------------- 打開A20 ----------------------------------in al,0x92or al,0000_0010Bout 0x92,al;---------------- 加載GDT ----------------------------------lgdt [gdt_ptr];---------------- cr0 第0位置1 -------------------------------mov eax,cr0or eax,0x00000001mov cr0,eaxjmp dword SELECTOR_CODE:p_mode_start ;刷新流水線[bits 32] p_mode_start:mov ax,SELECTOR_DATA ;83~89 用選擇子初始化成各段寄存器mov ds,axmov es,axmov ss,axmov esp,LOADER_STACK_TOPmov ax,SELECTOR_VIDEOmov gs,axmov byte [gs:160], 'P'jmp $boot.inc
;---------------loader和kernel ----------LOADER_BASE_ADDR equ 0x900 LOADER_START_SECTOR equ 0x2;-------------- gdt描述符屬性 ------------- DESC_G_4K equ 1_00000000000000000000000b ;G在段描述符第23位,將這里設為1,就是使用4K的密度 DESC_D_32 equ 1_0000000000000000000000b ;D在段描述符第22位,將這里設為1,表示操作數使用32位 DESC_L equ 0_000000000000000000000b ;L:段是否64位模式 64位代碼標記,此處標記為0便可。 DESC_AVL equ 0_00000000000000000000b ; cpu不用此位,暫置為0 DESC_LIMIT_CODE2 equ 1111_0000000000000000b ;段界限,段的長度 DESC_LIMIT_DATA2 equ DESC_LIMIT_CODE2 ; DESC_LIMIT_VIDEO2 equ 0000_000000000000000b ;段界限,設為0 DESC_P equ 1_000000000000000b ;p:是否在內存中 DESC_DPL_0 equ 00_0000000000000b ;DPL,為0的權限 DESC_DPL_1 equ 01_0000000000000b DESC_DPL_2 equ 10_0000000000000b DESC_DPL_3 equ 11_0000000000000b DESC_S_CODE equ 1_000000000000b ;代碼段 DESC_S_DATA equ DESC_S_CODE ;數據段,都是1,表示非系統段 DESC_S_sys equ 0_000000000000b DESC_TYPE_CODE equ 1000_00000000b ;x=1,c=0,r=0,a=0 代碼段是可執行的,非依從的,不可讀的,已訪問位a清0. DESC_TYPE_DATA equ 0010_00000000b ;x=0,e=0,w=1,a=0 數據段是不可執行的,向上擴展的,可寫的,已訪問位a清0.DESC_CODE_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + DESC_L + DESC_AVL + DESC_LIMIT_CODE2 + DESC_P + DESC_DPL_0 + DESC_S_CODE + DESC_TYPE_CODE + 0x00 ;代碼段的平坦模型 DESC_DATA_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + DESC_L + DESC_AVL + DESC_LIMIT_DATA2 + DESC_P + DESC_DPL_0 + DESC_S_DATA + DESC_TYPE_DATA + 0x00 DESC_VIDEO_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + DESC_L + DESC_AVL + DESC_LIMIT_VIDEO2 + DESC_P + DESC_DPL_0 + DESC_S_DATA + DESC_TYPE_DATA + 0x0b;-------------- 選擇子屬性 --------------- RPL0 equ 00b ;訪問權限 RPL1 equ 01b RPL2 equ 10b RPL3 equ 11b TI_GDT equ 000b ;訪問GDT TI_LDT equ 100b ;訪問LDT編譯:
nasm -I include/ -o mbr.bin mbr.S nasm -I include/ -o loader.bin loader.S裝載至磁盤
dd if=/os/bochs/mbr.bin of=/os/bochs/hd60M.img bs=512 count=1 conv=notrunc dd if=/os/bochs/loader.bin of=/os/bochs/hd60M.img bs=512 count=4 conv=notrunc seek=2運行
bin/bochs -f bin/bochsrc.diskloader.S
自己寫的,注釋很全,但是可能有點問題
%include "boot.inc"section loader vstart=LOADER_BASE_ADDRLOADER_STACK_TOP equ LOADER_BASE_ADDR ;保護模式下的棧;構建 gdt 及其內部的描述符GDT_BASE: dd 0x00000000 ;GDT的第0個段不可用dd 0x00000000; 定義了三個有用的段描述符CODE_DESC: dd 0x0000FFFF ;段描述符的低4字節,其中的低2字節是段長度 FFFF,高2字節是段基址 0000,dd DESC_CODE_HIGH4 ;代碼段的高四節,已經定義好了DATA_STACK_DESC: dd 0x0000FFFF ;數據段和棧段的段描述符dd DESC_DATA_HIGH4 ;顯存段描述符,0xb8000~0xbffff是用于文本模式的顯示內存,段基址:0x8000 長度 0007 VIDEO_DESC: dd 0x80000007 ;limit=(0xbffff-0xb8000)/4k = 0x7 4k是段粒度dd DESC_VIDEO_HIGH4 ;此時dpl為0GDT_SIZE equ $ - GDT_BASE ;19~20 獲取GDT的大小,為加載GDT做準備GDT_LIMIT equ GDT_SIZE - 1times 60 dq 0 ;此處預留60個描述符的空位,為了將來往GDT中添加其它描述符,提前保留空間SELECTOR_CODE equ (0x0001<<3) + TI_GDT + RPL0 ;相當于(CODE_DESC - GDT_BASE)/8 + TI_GDT + RPL0,構建代碼段的段選擇子,段描述符+TI+PRLSELECTOR_DATA equ (0x0002<<3) + TI_GDT + RPL0 ;同上SELECTOR_VIDEO equ (0x0003<<3) + TI_GDT + RPL0 ;同上;total_mem_bytes用于保存內存容量,以字節為單位,此位置比較好記;當前偏移 loader.bin 文件頭0x200字節 loader.bin的加載地址是0x900 4個段描述符的定義 32 dq了60個 480 = 512 = 0x200;故total_mem_bytes內存中的地址是0xb00 將來在內核咱們會引用此地址total_mem_bytes dd 0 ;存儲獲取到的內存容量;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;以下是gdt的指針,前2字節是gdt界限,后四字節是gdt起始地址gdt_ptr dw GDT_LIMIT ;定義GDT的指針.前16位,是GDT以字節為單位的長度,也就是GDT大小減1dd GDT_BASE ;GDT的起始位置;人工對齊:total_mem_bytes 4+gdt_ptr 6+ards_buf 244+ards_nr2,共256字節ards_buf times 244 db 0ards_nr dw 0 ;用于記錄ARDS結構體數量,每執行一次中斷就會獲得一個ards結構的數據loader_start:;int 15h eax = 0000E820h,edx = 534D4150h('SMAP')獲取內存布局xor ebx,ebx ;第一次調用ebx要為0mov edx,0x543d4150 ;edx只賦值一次,循環體中不改變mov di,ards_buf ;ards結構緩沖區,es:di,BIOS將獲取到的內存信息寫到此寄存器指向的內存,每次都以ARDS格式返回 .e820_mem_get_loop: ;循環獲取每個ARDS內存范圍描述結構mov eax,0x0000e820 ;子功能號,eax用來指定子功能號,每次執行 int 0x15后,eax值會變為0x534d4150mov ecx,20 ;ARDS結構的字節大小,ARDS地址范圍描述符是20字節int 0x15jc .e820_failed_so_try_e801 ;若cf位為1嘖有錯誤發生,嘗試0xe801子功能add di,cx ;使di增加20字節指向緩沖區中新ARDS結構位置inc word [ards_nr] ;記錄ARDS數量cmp ebx,0 ;ebx存放后續值,下一個ARDS的地址,在cf為0的情況下,若ebx為0,表示這是最后一個ARDS結構jnz .e820_mem_get_loop;在只有ards結構中,找出(base_add_low + length_low)的最大值,即內存大小mov cx,[ards_nr] ;遍歷每一個ARDS結構體,循環次數是ARDS的數量mov ebx,ards_bufxor edx,edx ;edx為最大的內存容量,在此先清0 .find_max_mem_area: ;無需判斷type是否為1,最大內存塊一定是可被使用的mov eax,[ebx] ;base_add_lowadd eax,[ebx+8] ;length_lowadd ebx,20 ;指向緩沖區中下一個ARDS結構cmp edx,eax ;冒泡排序,找出最大,edx寄存器始終是最大的內存容量jge .next_ardsmov edx,eax .next_ards:loop .find_max_mem_areajmp .mem_get_ok;----------- int 15h ax = E801h 獲取內存大小,最大支持4G ------------------- ; 返回后,ax cx 值一樣,以KB為單位,bx dx 值都一樣,以64KB為單位 ; 在ax和cx寄存器中為低16MB,在bx和dx寄存器中為16Mb到4GB .e820_failed_so_try_e801:mov ax,0xe801 ;功能號int 0x15jc .e801_failed_so_try88 ;若當前e801方法失敗,就嘗試0x88方法;1. 先算出低15MB的內存,ax和cx中是以KB為單位的內存數量,將其轉化成以byte為單位mov cx,0x400 ;cx和ax一樣,cx用作乘數mul cxshl edx,16and eax,0x0000FFFFor edx,eaxadd edx,0x100000 ;ax只是15MB,故要加1MBmov esi,edx ;先把低15MB的內存容量存入esi寄存器備份;2. 再將16MB以上的內存轉化為byte為單位,寄存器bx和dx中是以64KB為單位的內存數量xor eax,eaxmov ax,bxmov ecx,0x10000 ;0x10000 十進制為 64KBmul ecx ;32位乘法,默認的被乘數是eax,積為64位,高32位存入edx,低32位存入eaxadd esi,eax ;由于此方法只能測出4GB以內的內存,故32位eax足夠了 edx肯定為0mov edx,esi ;edx為總內存大小jmp .mem_get_ok;----------- int 15h ah = 0x88 獲取內存大小,只能獲取64MB之內 ----------------------- .e801_failed_so_try88:;int 15后,ax存入的是以KB為單位的內存容量mov ah,0x88int 0x15jc .error_hltand eax,0x0000FFFF;16位乘法,被乘數是ax,積為32位.積的高16位在dx中,積的低16位在ax中mov cx,0x400 ;0x400等于1024,將ax中的內存容量換為byte為三維mul cxshl edx,16 ;把dx移到高16位or edx,eax ;把積的低16位組合到edx,為32位的積add edx,0x100000 ;0x88子功能只會返回1MB以上的內存,故實際內存要加上1MB.mem_get_ok:mov [total_mem_bytes],edx ;將內存換成byte單位后存入total_mem_bytes處;----------------------進入保護模式-------------------------------- ;1 打開A20 ;2 加載gdt ;3 將cr0的pe位置1;---------------- 打開A20 ----------------------------------in al,0x92or al,0000_0010Bout 0x92,al;---------------- 加載GDT ----------------------------------lgdt [gdt_ptr];---------------- cr0 第0位置1 -------------------------------mov eax,cr0or eax,0x00000001mov cr0,eaxjmp dword SELECTOR_CODE:p_mode_start ;刷新流水線,這將導致之前做的預測失效,從而起到了刷新的作用.error_hlt: ;出錯則掛起hlt[bits 32] p_mode_start:mov ax,SELECTOR_DATA ;83~89 用選擇子初始化成各段寄存器mov ds,axmov es,axmov ss,axmov esp,LOADER_STACK_TOPmov ax,SELECTOR_VIDEOmov gs,ax;創建頁目錄表及初始化也內存位圖 call setup_page;要將描述符表位置及偏移量寫入內存 gdt_ptr,一會用新位置重新加載 sgdt [gdt_ptr] ;存儲到原來gdt所有的位置;將gdt描述符中顯存段描述符中的段基址+0xc0000000 使其成為內核所在的高地址,0xc0000000~0xfffffff是內核地址 mov ebx,[gdt_ptr+2] ;前兩字節是偏移量,后面四個字節才是GDT基址 or dword [ebx + 0x18 + 4],0xc0000000 ;顯存段是第3個段描述符,每個段描述符是8字節,故是0x18 ;段描述符的高四節字節的最高位是段基址的31~24位;將gdt的基址加上0xc0000000使其成為內核所在的高地址 add dword [gdt_ptr + 2],0xc0000000add esp,0xc0000000 ;將棧地址同樣映射到內核地址;把頁目錄地址賦給cr3 mov eax,PAGE_DIR_TABLE_POS mov cr3,eax;打開cr0的pg位(第31位) mov eax,cr0 or eax,0x80000000 mov cr0,eax;在開啟分頁后,用gdt新的地址重新加載 lgdt [gdt_ptr] ;重新加載mov byte [gs:160],'V' ;顯存段段基址已經被更新,用字符V表示virtual addrjmp $;---------------------- 創建頁目錄及頁表 ---------------------------------- setup_page: ;先把頁目錄占用的空間逐字節清0mov ecx,4096 ;一個頁目錄項4Byte,一共1024個,所以是4096個mov esi,0 .clear_page_dir:mov byte [PAGE_DIR_TABLE_POS + esi],0inc esiloop .clear_page_dir;開始創建頁目錄項(PDE) .create_pde: ;創建Page Directory Entrymov eax,PAGE_DIR_TABLE_POS ;0x10000000add eax,0x1000 ;此時eax為第一個頁表的位置及屬性,0x10001000mov ebx,eax ;此處為ebx賦值,是為.create_pte做準備,ebx為基址; 下面將頁目錄0和0xc00都存為第一個頁表的地址,每個頁表表示4MB內存 ; 這樣0xc03fffff以下的地址和0x003fffff以下的地址都指向相同的頁表 ; 這是為將地址映射為內核地址做準備or eax,PG_US_U | PG_RW_W | PG_P ;頁目錄項的屬性RW和P為1,US為1,表示用戶屬性,所有特權級都可以訪問mov [PAGE_DIR_TABLE_POS + 0x0],eax ;第一個目錄項,頁目錄表中第1個目錄寫入第一個頁表的位置0x101000及屬性7mov [PAGE_DIR_TABLE_POS + 0xc00],eax ;一個頁表項占4個字節,0xc00表示第768個頁表占用的頁表項,0xc00以上的目錄用于內核空間;也就是頁表的 0xc0000000~0xfffffff 共計1G屬于內核,0x0~0xbfffffff共計3G屬于用戶進程sub eax,0x1000 ;現在是0x100007mov [PAGE_DIR_TABLE_POS + 4092],eax ;使最后一個目錄項指向頁目錄表自己;下面創建頁表項(PTE)mov ecx,256 ;1M低端內存 / 每頁大小 4K = 256mov esi,0mov edx,PG_US_U | PG_RW_W | PG_P ;屬性為7 .create_pte: ;創建Page Table Entrymov [ebx+esi*4],edx ;此時ebx已經在上面通過eax賦值為0x101000,也就是第一個頁表的地址add edx,4096inc esiloop .create_pte;創建內核其它表的PDEmov eax,PAGE_DIR_TABLE_POSadd eax,0x2000 ;此時eax為第二個頁表的位置or eax,PG_US_U | PG_RW_W | PG_P ;頁目錄項的屬性US RW和P位都為1mov ebx,PAGE_DIR_TABLE_POSmov ecx,254 ;范圍為第769~1022所有的目錄項數量mov esi,769 .create_kernel_pde:mov [ebx+esi*4],eaxinc esiadd eax,0x1000loop .create_kernel_pderetloader.S
%include "boot.inc"section loader vstart=LOADER_BASE_ADDRLOADER_STACK_TOP equ LOADER_BASE_ADDR;構建gdt及其內部的描述符GDT_BASE: dd 0x00000000 dd 0x00000000CODE_DESC: dd 0x0000FFFF dd DESC_CODE_HIGH4DATA_STACK_DESC: dd 0x0000FFFFdd DESC_DATA_HIGH4VIDEO_DESC: dd 0x80000007 ; limit=(0xbffff-0xb8000)/4k=0x7dd DESC_VIDEO_HIGH4 ; 此時dpl為0GDT_SIZE equ $ - GDT_BASEGDT_LIMIT equ GDT_SIZE - 1 times 60 dq 0 ; 此處預留60個描述符的空位(slot)SELECTOR_CODE equ (0x0001<<3) + TI_GDT + RPL0 ; 相當于(CODE_DESC - GDT_BASE)/8 + TI_GDT + RPL0SELECTOR_DATA equ (0x0002<<3) + TI_GDT + RPL0 ; 同上SELECTOR_VIDEO equ (0x0003<<3) + TI_GDT + RPL0 ; 同上 ; total_mem_bytes用于保存內存容量,以字節為單位,此位置比較好記。; 當前偏移loader.bin文件頭0x200字節,loader.bin的加載地址是0x900,; 故total_mem_bytes內存中的地址是0xb00.將來在內核中咱們會引用此地址total_mem_bytes dd 0 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;以下是定義gdt的指針,前2字節是gdt界限,后4字節是gdt起始地址gdt_ptr dw GDT_LIMIT dd GDT_BASE;人工對齊:total_mem_bytes4字節+gdt_ptr6字節+ards_buf244字節+ards_nr2,共256字節ards_buf times 244 db 0ards_nr dw 0 ;用于記錄ards結構體數量loader_start:;------- int 15h eax = 0000E820h ,edx = 534D4150h ('SMAP') 獲取內存布局 -------xor ebx, ebx ;第一次調用時,ebx值要為0mov edx, 0x534d4150 ;edx只賦值一次,循環體中不會改變mov di, ards_buf ;ards結構緩沖區 .e820_mem_get_loop: ;循環獲取每個ARDS內存范圍描述結構mov eax, 0x0000e820 ;執行int 0x15后,eax值變為0x534d4150,所以每次執行int前都要更新為子功能號。mov ecx, 20 ;ARDS地址范圍描述符結構大小是20字節int 0x15jc .e820_failed_so_try_e801 ;若cf位為1則有錯誤發生,嘗試0xe801子功能add di, cx ;使di增加20字節指向緩沖區中新的ARDS結構位置inc word [ards_nr] ;記錄ARDS數量cmp ebx, 0 ;若ebx為0且cf不為1,這說明ards全部返回,當前已是最后一個jnz .e820_mem_get_loop;在所有ards結構中,找出(base_add_low + length_low)的最大值,即內存的容量。mov cx, [ards_nr] ;遍歷每一個ARDS結構體,循環次數是ARDS的數量mov ebx, ards_buf xor edx, edx ;edx為最大的內存容量,在此先清0 .find_max_mem_area: ;無須判斷type是否為1,最大的內存塊一定是可被使用mov eax, [ebx] ;base_add_lowadd eax, [ebx+8] ;length_lowadd ebx, 20 ;指向緩沖區中下一個ARDS結構cmp edx, eax ;冒泡排序,找出最大,edx寄存器始終是最大的內存容量jge .next_ardsmov edx, eax ;edx為總內存大小 .next_ards:loop .find_max_mem_areajmp .mem_get_ok;------ int 15h ax = E801h 獲取內存大小,最大支持4G ------ ; 返回后, ax cx 值一樣,以KB為單位,bx dx值一樣,以64KB為單位 ; 在ax和cx寄存器中為低16M,在bx和dx寄存器中為16MB到4G。 .e820_failed_so_try_e801:mov ax,0xe801int 0x15jc .e801_failed_so_try88 ;若當前e801方法失敗,就嘗試0x88方法;1 先算出低15M的內存,ax和cx中是以KB為單位的內存數量,將其轉換為以byte為單位mov cx,0x400 ;cx和ax值一樣,cx用做乘數mul cx shl edx,16and eax,0x0000FFFFor edx,eaxadd edx, 0x100000 ;ax只是15MB,故要加1MBmov esi,edx ;先把低15MB的內存容量存入esi寄存器備份;2 再將16MB以上的內存轉換為byte為單位,寄存器bx和dx中是以64KB為單位的內存數量xor eax,eaxmov ax,bx mov ecx, 0x10000 ;0x10000十進制為64KBmul ecx ;32位乘法,默認的被乘數是eax,積為64位,高32位存入edx,低32位存入eax.add esi,eax ;由于此方法只能測出4G以內的內存,故32位eax足夠了,edx肯定為0,只加eax便可mov edx,esi ;edx為總內存大小jmp .mem_get_ok;----------------- int 15h ah = 0x88 獲取內存大小,只能獲取64M之內 ---------- .e801_failed_so_try88: ;int 15后,ax存入的是以kb為單位的內存容量mov ah, 0x88int 0x15jc .error_hltand eax,0x0000FFFF;16位乘法,被乘數是ax,積為32位.積的高16位在dx中,積的低16位在ax中mov cx, 0x400 ;0x400等于1024,將ax中的內存容量換為以byte為單位mul cxshl edx, 16 ;把dx移到高16位or edx, eax ;把積的低16位組合到edx,為32位的積add edx,0x100000 ;0x88子功能只會返回1MB以上的內存,故實際內存大小要加上1MB.mem_get_ok:mov [total_mem_bytes], edx ;將內存換為byte單位后存入total_mem_bytes處。;----------------- 準備進入保護模式 ------------------- ;1 打開A20 ;2 加載gdt ;3 將cr0的pe位置1;----------------- 打開A20 ----------------in al,0x92or al,0000_0010Bout 0x92,al;----------------- 加載GDT ----------------lgdt [gdt_ptr];----------------- cr0第0位置1 ----------------mov eax, cr0or eax, 0x00000001mov cr0, eaxjmp dword SELECTOR_CODE:p_mode_start ; 刷新流水線,避免分支預測的影響,這種cpu優化策略,最怕jmp跳轉,; 這將導致之前做的預測失效,從而起到了刷新的作用。 .error_hlt: ;出錯則掛起hlt[bits 32] p_mode_start:mov ax, SELECTOR_DATAmov ds, axmov es, axmov ss, axmov esp,LOADER_STACK_TOPmov ax, SELECTOR_VIDEOmov gs, ax; 創建頁目錄及頁表并初始化頁內存位圖call setup_page;要將描述符表地址及偏移量寫入內存gdt_ptr,一會用新地址重新加載sgdt [gdt_ptr] ; 存儲到原來gdt所有的位置;將gdt描述符中視頻段描述符中的段基址+0xc0000000mov ebx, [gdt_ptr + 2] or dword [ebx + 0x18 + 4], 0xc0000000 ;視頻段是第3個段描述符,每個描述符是8字節,故0x18。;段描述符的高4字節的最高位是段基址的31~24位;將gdt的基址加上0xc0000000使其成為內核所在的高地址add dword [gdt_ptr + 2], 0xc0000000add esp, 0xc0000000 ; 將棧指針同樣映射到內核地址; 把頁目錄地址賦給cr3mov eax, PAGE_DIR_TABLE_POSmov cr3, eax; 打開cr0的pg位(第31位)mov eax, cr0or eax, 0x80000000mov cr0, eax;在開啟分頁后,用gdt新的地址重新加載lgdt [gdt_ptr] ; 重新加載mov byte [gs:160], 'V' ;視頻段段基址已經被更新,用字符v表示virtual addrjmp $;------------- 創建頁目錄及頁表 --------------- setup_page: ;先把頁目錄占用的空間逐字節清0mov ecx, 4096mov esi, 0 .clear_page_dir:mov byte [PAGE_DIR_TABLE_POS + esi], 0inc esiloop .clear_page_dir;開始創建頁目錄項(PDE) .create_pde: ; 創建Page Directory Entrymov eax, PAGE_DIR_TABLE_POSadd eax, 0x1000 ; 此時eax為第一個頁表的位置及屬性mov ebx, eax ; 此處為ebx賦值,是為.create_pte做準備,ebx為基址。; 下面將頁目錄項0和0xc00都存為第一個頁表的地址, ; 一個頁表可表示4MB內存,這樣0xc03fffff以下的地址和0x003fffff以下的地址都指向相同的頁表, ; 這是為將地址映射為內核地址做準備or eax, PG_US_U | PG_RW_W | PG_P ; 頁目錄項的屬性RW和P位為1,US為1,表示用戶屬性,所有特權級別都可以訪問.mov [PAGE_DIR_TABLE_POS + 0x0], eax ; 第1個目錄項,在頁目錄表中的第1個目錄項寫入第一個頁表的位置(0x101000)及屬性(7)mov [PAGE_DIR_TABLE_POS + 0xc00], eax ; 一個頁表項占用4字節,0xc00表示第768個頁表占用的目錄項,0xc00以上的目錄項用于內核空間,; 也就是頁表的0xc0000000~0xffffffff共計1G屬于內核,0x0~0xbfffffff共計3G屬于用戶進程.sub eax, 0x1000mov [PAGE_DIR_TABLE_POS + 4092], eax ; 使最后一個目錄項指向頁目錄表自己的地址;下面創建頁表項(PTE)mov ecx, 256 ; 1M低端內存 / 每頁大小4k = 256mov esi, 0mov edx, PG_US_U | PG_RW_W | PG_P ; 屬性為7,US=1,RW=1,P=1 .create_pte: ; 創建Page Table Entrymov [ebx+esi*4],edx ; 此時的ebx已經在上面通過eax賦值為0x101000,也就是第一個頁表的地址 add edx,4096inc esiloop .create_pte;創建內核其它頁表的PDEmov eax, PAGE_DIR_TABLE_POSadd eax, 0x2000 ; 此時eax為第二個頁表的位置or eax, PG_US_U | PG_RW_W | PG_P ; 頁目錄項的屬性US,RW和P位都為1mov ebx, PAGE_DIR_TABLE_POSmov ecx, 254 ; 范圍為第769~1022的所有目錄項數量mov esi, 769 .create_kernel_pde:mov [ebx+esi*4], eaxinc esiadd eax, 0x1000loop .create_kernel_pderet總結
- 上一篇: SharePoint培训第二天
- 下一篇: 作用点与管理理论--聚焦、定位、TO…