段寄存器存放 基地址
AX 通用寄存器
CS 存放要被cpu執(zhí)行的代碼的基地址 code segment
IP 別名為指令指針寄存器,存放段地址的偏移地址
CS*16+IP 就是cpu要執(zhí)行的指令
debug是dos、windows都提供的實模式程序調(diào)試工具,可以查看cpu各種寄存器中的內(nèi)容和機器碼級跟蹤程序的運行
r命令用來查看和改變各個寄存器內(nèi)容,
d命令查看內(nèi)存中的內(nèi)容,
u命令將內(nèi)存機器碼轉(zhuǎn)為匯編指令,
a命令以匯編指令格式在內(nèi)存寫入指令
t命令但不跟蹤
數(shù)據(jù)段:全局變量
代碼段:代碼
堆棧段:局部變量
CPU根據(jù)DS(Data Segment)這個寄存器和任意一個通用寄存器的值或其他數(shù)值組成數(shù)據(jù)段的物理地址如:
DS:[0] DS:[BX] (內(nèi)存尋址) (內(nèi)存訪問)
movds:[13ABH],1234H 內(nèi)存地址的內(nèi)容進行賦值 內(nèi)存地址=1234
mov[13ABH],1234H CPU默認指向ds
---------------------4-----------------------------
CPU如何知道一段內(nèi)存空間被當作棧使用?執(zhí)行入棧出棧時,如何知道哪個單元式棧頂單元?
cpu通過ss這個寄存器和sp通用寄存器來感知堆棧段的存在。
ss存放基地址,sp存放棧頂?shù)钠频刂?,任何時候ss:sp都指向棧頂元素
---------------------5,6-----------------------------
匯編語言中變量如何定義
如何屏幕顯示
如何進行調(diào)試
assume關(guān)鍵字
如何讓匯編語言“知道”,我們編寫的應(yīng)用程序有多少個段組成。
assume表示用來假設(shè)某一段寄存器和程序中的某一個用segment...ends定義的段相關(guān)聯(lián)
db指令
define byte
label db initializer,initializer,initializer
label 表示任選標號,相當于c語言變量名
msg db "hello world"
vga B800F
字體屬性格式
7 6 5 4 3 2 1 0
BL R G B I R G B
閃爍 背景色 高亮 前景色
紅底綠字: 01000010B
vga顯存地址空間
在80*25列彩色模式下顯示器可以顯示25行80列
每個字符可以有256種屬性(背景色前景色閃爍等)
一個字符在顯存中占兩個字節(jié),分別存放ascii碼值和屬性
顯示緩沖總共分為8頁,每頁4kb,顯卡可以顯示任意頁內(nèi)容一般情況下顯示第0頁內(nèi)容即B8000H~B8F9FH
es,擴展段寄存器
不能直接給段寄存器賦值,應(yīng)該先給通用寄存器賦值,然后再傳給段寄存器
loop關(guān)鍵字
語法:
標號: 指令
指令2
look 標號
cx內(nèi)部規(guī)定為loop的循環(huán)因子
內(nèi)存訪問
helloworld屬于數(shù)據(jù)段內(nèi)容,那么其中每個字符都可以通過數(shù)據(jù)段地址存在ds寄存器中,那么要獲得數(shù)據(jù)段第一個字節(jié)內(nèi)容就要如下表示
ds:[0] ds:[si]
si寄存器相當于通用寄存器
代碼段地址自動獲取 偏移地址不知道
;;;;;;;;;;;;;;;;;;;;;hello.asm;;;;;;;;;;;;;;;;;;;;;;;;;;;;
assume cs:code,ds:data
data segment
db "hello world"
data ends
code segment
start:
mov ax,data
mov ds,ax
mov bx,0b800h
mov es,bx
mov cx,11
mov si,0
mov bx,0
mov ah,01000010b
s:mov al,ds:[si]
moves:[bx],ax
moves:[bx+1],ah
inc si
addbx,2
loop s
movax,4c00h
int21h
code ends
end start
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-------------------7-----------------------------
什么是中斷?
任何一個通用CPU,都具有一種能力,可以在執(zhí)行完成當前正在執(zhí)行的指令之后,檢測到從CPU外部或內(nèi)部產(chǎn)生的一種特殊信息,
并立即對接受的信息進程處理。這種信息稱之為中斷信息。
中斷的意思是指,CPU不再接著(剛執(zhí)行完的指令)向下執(zhí)行,而是轉(zhuǎn)去處理這個特殊信息。
關(guān)于中斷的疑問
中斷發(fā)生時CPU如何找到中斷處理程序?
中斷處理程序有很多種,那么每個中斷處理程序放在哪里?
中斷處理程序完成后CPU如何繼續(xù)運行之前被中斷的程序?
中斷向量表
在內(nèi)存中保存,其中存放著256個中斷源所對應(yīng)的中斷處理程序入口
中斷向量表一般都保存在內(nèi)存0000:0000到0000:03FE
一個表項存放一個中斷向量,也就是一個中斷程序入口地址,這個地址包括段地址和偏移地址,每個表占兩個字(4個字節(jié)),高地址存放段地址,低地址存放偏移地址。
遇到中斷時CPU把cs和ip內(nèi)存入棧暫時保存起來。等中斷程序執(zhí)行結(jié)束后通過出棧指令重新獲得原來的cs和ip的值(這就是c語言函數(shù)調(diào)用具體實現(xiàn))
中斷過程這些操作,cpu自動完成
div指令
除法指令:div 寄存器
除數(shù):有8位和16位兩種,在一個寄存器或內(nèi)存單元中。
被除數(shù):默認放在ax或dx中,如果除數(shù)為8位,被除數(shù)位16位默認放在ax中。如果除數(shù)為16位被除數(shù)為32位分別放在ax和dx中,dx存放高位,ax存放地位。
結(jié)果:如果除數(shù)位8位,則al存儲除法的商,ah存儲除法的余數(shù),如果除數(shù)是16位,則ax存儲商,dx存儲余數(shù)。
修改中斷表:
如何讓CPU不去執(zhí)行原來的中斷處理程序,而去執(zhí)行我們自己編寫的處理程序?
修改中斷向量表的入口地址就可以實現(xiàn)。
-------------------8----------------------
如何修改中斷向量表?
系統(tǒng)默認在0000:0000到0000:03FE專門存放中斷向量表。并且每個表占用兩個字。
那么我們就知道0號中斷表項所在的內(nèi)存地址是0000:0000開始的4個字節(jié)中。匯編代碼就是要對這4個字節(jié)賦予我們自己編寫的中斷處理程序入口地址。
匯編偽代碼如下:
movds:[0],我們自己中斷處理程序偏移地址
mov ds:[2],我們自己中斷處理程序段地址
中斷隨時都可以產(chǎn)生,那么當中斷產(chǎn)生時必須馬上執(zhí)行中斷處理程序,那么中斷處理程序必須放在內(nèi)存何處?
要保證任何時候中斷處理程序存放位置不能被其他程序覆蓋。
因此我們必須在內(nèi)存中找出一段空間是任何程序不適用的。
在正常情況下內(nèi)存地址0000:0200到0000:0300這段內(nèi)存是沒有其他程序使用的。
中斷處理程序內(nèi)存分布
當中斷被觸發(fā)時程序?qū)⒈粓?zhí)行,但是程序最開始是數(shù)據(jù)定義指令,而不是代碼執(zhí)行指令,如何解決?
我們希望一開始執(zhí)行中斷處理程序時馬上跳轉(zhuǎn)到顯示字符串的匯編代碼中執(zhí)行,這時我們就需要使用匯編指令:jump
跳轉(zhuǎn)指令jump分為三種:
段間跳轉(zhuǎn) jump far標號 把cs和ip寄存器的值變?yōu)闃颂査诘膬?nèi)存地址
段內(nèi)跳轉(zhuǎn)指令 jump near標號 只修改ip寄存器值為標號的偏移地址
段內(nèi)短跳轉(zhuǎn)指令 jump short標號 不修改cs和ip的值,編譯器自動計算跳轉(zhuǎn)的位置,不超過256
-------------------9--------------------------
如何把一段匯編代碼拷貝到指定的內(nèi)存位置?
可以用loop指令語句來實現(xiàn),但是該語句比較繁瑣,不太合適。
匯編語言提供rep和movsb指令實現(xiàn)相同的功能。
movsb指令:
字節(jié)傳送指令:指令在存儲單元之間傳送字符串
使用movsb指令時ds:si指向了要拷貝字符串的首地址,es:di指向了要拷貝的目的地址。
cld指令拷貝數(shù)據(jù)的方向是從低字節(jié)往高字節(jié)拷貝,也就是說每拷貝一個字節(jié)si和di加1。
std指令和cld相反。
;;;;;;;;;;;;;;;;;;;;;;;;;;ins.asm;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
assume cs:code
code segment
;第一步:把中斷向量表中0號表項內(nèi)容進行修改,使之指向我們自己編寫的中斷處理程序的入口地址
start:
mov ax,0
mov ds,ax
mov word ptr ds:[0],0200h
mov word ptr ds:[2],0
;第三步:把我們剛才編寫好的0號中斷處理程序拷貝到中斷向量表中0號表項所指向的內(nèi)存地址中
;0000:0200
mov ax,cs
mov ds,ax
mov si,offset int0;ds:si 可得拷貝源地址
mov ax,0
mov es,ax
mov di,200h;目的地址設(shè)置完畢es:di
mov cx,offset int0end - offsetint0;計算出程序總共占多少內(nèi)存
cld
rep movsb;自動利用es:di,ds:si,cx
;第四步:利用代碼自動引發(fā)0號中斷處理程序
mov ax,1000h
mov bh,1
div bh
mov ax,4c00h
int 21h
;第二步:編寫自己的中斷處理程序,實現(xiàn)在屏幕中央顯示字符串的功能
int0:jmp short int0start
db "i am student"
int0start:mov ax,0b800h
mov es,ax ;配置顯存首地址
;要把字符串一個個拷貝到顯存地址空間中
mov ax,cs
mov ds,ax
mov si,202h
mov di,12*160+36*2
mov cx,12
s:mov al,ds:[si]
mov es:[di],al
inc si
add di,2
loop s
mov ax,4c00h
int 21h
int0end:nop
code ends
end start
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-------------------10--------------------------
主要內(nèi)容
開發(fā)環(huán)境搭建
認識引導(dǎo)程序
nasm匯編
編寫第一個啟動程序
Visual PC2004 用來啟動我們編寫的啟動程序
VMware運行l(wèi)inux,利用nasm匯編器在linux上進行啟動程序的編譯工作
知識預(yù)備
理解計算機加電過程
BIOS對系統(tǒng)內(nèi)存的分配
認識引導(dǎo)程序的概念
使用nasm進行編譯
制作軟盤鏡像
當我們按下電源按鈕后,計算機是如何從無到有將操作系統(tǒng)運行起來的?
按下開機按鈕后,將發(fā)送電信號給BIOS。
BIOS獲得電信號后將啟動自檢查程序,檢查周邊設(shè)備是否通電完畢。
檢查完畢后自檢程序把控制權(quán)交還給BIOS,BIOS將讀取引導(dǎo)驅(qū)動器中的啟動程序。
在系統(tǒng)加電時,最初的1MB內(nèi)存是BIOS為我們準備好的,如下:
0x00000~0x003FF 中斷向量表
0x00400~0x004FF BIOS數(shù)據(jù)區(qū)
0x00500~0x07BFF 自由內(nèi)存區(qū)
0x07C00~0x07DFF 引導(dǎo)程序加載區(qū) 正好512字節(jié)
ox07E00~0x9FFFF 自由內(nèi)存區(qū)
0xA0000~0xBFFFF 顯示內(nèi)存區(qū)
0xC0000~0xFFFFF 中斷處理程序
認識引導(dǎo)程序
什么樣程序才能稱為引導(dǎo)程序?
BIOS將所檢查啟動磁盤的第一個扇區(qū)512字節(jié)載入內(nèi)存,放于內(nèi)存0x0000:0x07c00處。
如果第一扇區(qū)最后兩個字節(jié)是55AA,那么它就是一個引導(dǎo)程序。
引導(dǎo)程序特點
大小是512字節(jié),不能多也不能少,因為BIOS只讀取512B到內(nèi)存中。
它結(jié)尾必須是55AA,這是引導(dǎo)扇區(qū)標志。
它總是放在磁盤第一個扇區(qū)上(0磁頭0此道1扇區(qū))因為BIOS只讀取第一個扇區(qū)。
NASM匯編
是一個為可移植性與模塊化而設(shè)計的一個80*86的匯編器。它支持相當多的目標文件格式包括linux和windows
引導(dǎo)程序編寫
如何使用nasm編寫一個引導(dǎo)程序?
什么是nasm,它和masm有什么區(qū)別?
如何在linux下安裝nasm?
如何用nasm編譯自己編寫的匯編代碼?
nasm和masm區(qū)別
nasm擁有一個相當簡單的內(nèi)存引用規(guī)則,是任何對內(nèi)存中內(nèi)容的存取操作必須要在地址上加方括號。但任何地址值的操作不需要。
比如mov ax,bar的指令表示把bar的地址賦給ax寄存器,這相當于masm中 mov ax,offset bar。
要獲得bar變量的值則:mov ax,[bar]。
masm movax,es:di nasm mov ax,[es:di]
linux 下安裝nasm,下載nasm的rpm包,安裝 rpm -ivh nasm***.rpm
使用:nasm hello.asm -o hello ,反匯編:ndisasm hello
-------------------11-----------------------------
BIOS中斷程序
系統(tǒng)BIOS為我們提供了眾多的中斷處理程序給我們調(diào)用,其中中斷編號為10h的中斷處理程序?qū)iT實現(xiàn)顯示功能。
特別注意:編號為10h的中斷并不只提供一個程序,而是提供了很多子程序供我們調(diào)用。
$和$$關(guān)鍵字
在nasm中$表示當前指令的偏移地址
在nasm中$$表示指令所在的開始地址
因此我們就可以推算出剩余字節(jié)數(shù)公式
剩余字節(jié)數(shù)=510-($-$$)
;;;;;;;;;;;;boot.asm;;;;;;;;;;;;;;;;
;我們的啟動程序?qū)崿F(xiàn)很簡單的功能,在屏幕中央打印一行字符串即可
org 07c00h ;org指令明確告訴編譯器我程序的段地址是7C00h,而不是原來的0000
;int匯編指令 “int 10h”調(diào)用bois里的中斷程序:顯示字符串
mov ax,cs
mov es,ax
mov bp,msgstr ;es:bp指向的內(nèi)容就是我們要顯示的字符串地址了
mov cx,12 ;顯示的字符串長度
mov dh,12 ;顯示的行號
mov dl,36 ;顯示的列號
movbh,0 ;顯示的頁數(shù)
moval,1 ;顯示的是串結(jié)構(gòu)
mov bl,0ch ;顯示的字符屬性
mov ah,13h ;明確調(diào)用13h子程序
msgstr: db "hello my os!"
int 10h
times 510-($-$$) db 0 ;重復(fù)n次每次填充值為0
dw 55aah
jmp $ ;不斷跳轉(zhuǎn)到當前位置,是個死循環(huán)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;boot.asm;;;;;;;;;;;;;;;;;;
;我們的啟動程序?qū)崿F(xiàn)很簡單的功能,在屏幕中央打印一行字符串即可
org 07c00h ;org指令明確告訴編譯器我程序的段地址是7C00h,而不是原來的0000
;int匯編指令 “int 10h”調(diào)用bois里的中斷程序:顯示字符串
mov ax,cs
mov es,ax
mov bp,msgstr ;es:bp指向的內(nèi)容就是我們要顯示的字符串地址了
mov cx,12 ;顯示的字符串長度
mov dh,12 ;顯示的行號
mov dl,36 ;顯示的列號
movbh,0 ;顯示的頁數(shù)
moval,1 ;顯示的是串結(jié)構(gòu)
mov bl,0ch ;顯示的字符屬性
mov ah,13h ;明確調(diào)用13h子程序
msgstr: db "hello my os!"
int 10h
times 510-($-$$) db 0 ;重復(fù)n次每次填充值為0
dw 55aah
jmp $ ;不斷跳轉(zhuǎn)到當前位置,是個死循環(huán)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
/////////////////write_image.c//////////////////////
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
int main(int argc,char *argv[])
{
int fd_source;
int fd_dest;
int read_count=0;
char buffer[512]={0};
fd_source=open("boot.bin",O_RDONLY);
if(fd_source<0)
{
perror("open boot.binerror:");
return 0;
}
fd_dest=open("v1.vfd",O_WRONLY);
while((read_count=read(fd_source,buffer,512))>0)
{
write(fd_dest,buffer,read_count);
memset(buffer,0,512);
}
printf("write image ok!");
return 0;
}
////////////////////////////////////////////
-----------------13--------------------------------
主要內(nèi)容
實模式概念
保護模式概念
選擇子
段描述符
系統(tǒng)地址寄存器
實模式概念
計算機加電后,cpu就默認屬于real-model(實模式)下
實模式只能訪問地址在1M以下的內(nèi)存稱為常規(guī)內(nèi)存,我們把地址在1M以上的內(nèi)存稱為擴展內(nèi)存。
seg:offset 20位地址,只能訪問1M空間
通過這種組合指向的內(nèi)存地址就是實際的物理內(nèi)存地址
32位cpu
intel退出32位cpu時完全兼容了16位cpu。
所謂兼容其中很重要的一點就是可以繼續(xù)使用16位的內(nèi)存尋址方式。
大家都知道16cpu內(nèi)存尋址是通過段寄存器:通用寄存器來表示實際的內(nèi)存地址。
32位cpu地址線32根,最大內(nèi)存為4GB,如何利用原來的seg:offset(實現(xiàn)20根地址線)表示方式,來表示32根地址線?
保護模式
在保護模式下cpu依然使用段寄存器和通用寄存器來表示內(nèi)存地址,但如何用20位地址來實現(xiàn)32位地址線尋址能力?
有張表,紀錄段地址 開始地址 大?。ǘ谓缦蓿?屬性
16位段寄存器紀錄表的【【索引】】
新的內(nèi)容訪問思路
在實模式下,我們把內(nèi)存分成一個個內(nèi)存段來表示,那么在保護模式下內(nèi)存也被分為一個個內(nèi)存段表示。
那么我們就把實現(xiàn)分好的內(nèi)存段信息存入一張表格中,然后段寄存器中保存你要訪問內(nèi)存段所在的這張表格的索引。
保存表中索引的段寄存器,我們稱為段選擇子。
表中每個表示32位內(nèi)存段信息我們稱之為段描述符。
整張表稱之為【【段描述符表】】。
段選擇子
段選擇子16位(段寄存器16位),其中高13位存放描述符表中的索引,其低3位用來表示段描述符表中所指向的段描述符的屬性。
因此表中段描述符最大個數(shù)為2^13=8096個。
TI(TableIndicator):用來表示是從全局描述符表中讀取描述符還是從局部描述符表中讀取描述符。
RPL(Request Priviledge Level):用于特權(quán)檢查.
形成物理地址
段寄存器-(索引號)-->段描述符表--->段描述符A--->線性地址空間(物理地址) 不考慮分頁時,線性地址=物理地址
段描述符結(jié)構(gòu)
既然段描述符包含了段的開始地址和段的界限,那么了解該結(jié)構(gòu)至關(guān)重要
段描述符共8個字節(jié),每個字節(jié)都具有具體含義
段界限(segmentlimit)20位被分為兩個部分,第一部分保存在1,2字節(jié)中,第二部分保存在7
段基地址(segmentbase)32位被分成兩個部分,第一部分23個字節(jié)被存放在3,4,5字節(jié)中,第二部分放在8
段屬性(attributes)包含了該段屬性和段界限的第二部分
段基地址剩余部分(base)包含了段界限剩余的8位
內(nèi)存分配
由于我們現(xiàn)在編寫的在裸機上編寫程序,因此內(nèi)存必須我們自己在4GB內(nèi)存中進行分配
-----------------14--------------------------
段屬性
段屬性位于段描述符的第6和第7個字節(jié),用來描述該段是數(shù)據(jù)段還是代碼段或者堆棧段,對于數(shù)據(jù)段或者堆棧段來說是否可讀是否可寫,
對于代碼段來說是否可執(zhí)行以及段描述符所指定的內(nèi)存段在物理內(nèi)存中是否存在。
從左往右
0~3 TYPE :說明存儲段描述符所描述的存儲段的具體屬性。是屬于代碼段還是數(shù)據(jù)段,可讀可寫還是可執(zhí)行。
4 DT :說明了該描述符所指定的系統(tǒng)端描述符海華絲存儲段描述符。
5~6 DPL :表示描述符特權(quán)級別。
7 P :表示描述符對地址的轉(zhuǎn)換是否有效。
第二個字節(jié)
0~3 Limit :段界限第二部分剩余的4位。
4 AVL :軟件可利用完,80386對該位未做規(guī)定
5 :0
6 D :表示如果該段是代碼段,是否是16位還是32位代碼段,如果該段是數(shù)據(jù)段是否是16還是32位,1表示32位
7 G :段界限粒度位G=0表示段邊界64k,G=1表示段邊界4GB
段界限
我們既然分了8M的內(nèi)存段,那么段界限就是8M,那么8M占用多少字節(jié),怎樣用16進制表示并爭取填充到段描述符中呢?
8M=2^23=800000H
不能直接把800000這個16進制直接寫段描述符的相應(yīng)位置中,并且20位的段界限 23位二進制數(shù)如何解決?
段界限公式
段界限=limit*4k+0FFFH
800000=limit*4k+0FFFH
limit就是要填寫到段描述符中的段界限位置
limit=(800000-0FFFH)/4k=7FFH
段描述符的填寫
我們的偏移地址都是通過8M通過公式得出段界限為7FF
我們第一個內(nèi)存的段從內(nèi)存00000處開始,所以段基地址全為0
那么我們創(chuàng)建的段位數(shù)據(jù)段并且是可讀可寫的,那么就必須在attributes字段中填寫相應(yīng)的數(shù)據(jù)。
base attributes segmentbase segment limit
0000 000000000000 07FF
TYPE:我們定義的是數(shù)據(jù)段并且我們要求該段可讀可寫那么tpye值填為0010,如果我們創(chuàng)建的是代碼段可讀可執(zhí)行,那么為1010
DT :DT用來區(qū)別系統(tǒng)段還是存儲段,我們這邊都是存儲段。
DPL :表示內(nèi)存段的權(quán)限,這里為00表示
P :表示描述符對地址轉(zhuǎn)換是否有效,1表示有效
Limit:表示剩余4位段界限描述符 0000
AVL :保留為0
D :1 我們編寫的是保護模式,32位
G :1
數(shù)據(jù)段描述符
根據(jù)以上內(nèi)容我們可以定義符合數(shù)據(jù)段描述符的匯編代碼
dw07FFh ;段界限
dw0h ;段基地址0~18位
db0h ;段基地址19~23位
db10010010b ;段描述符的第6個字節(jié)屬性(數(shù)據(jù)段可讀可寫)
db11000000b ;段描述符的第7個字節(jié)屬性
db0 ;段描述符的最后一個字節(jié)也就是段基地址的第二部分
代碼段描述符
代碼段描述符和數(shù)據(jù)段描述符基本一致,不同在于段基地址和段屬性
dw07FFh ;段界限(保持不變)
dw1h ;段基地址0~18位 不同
db80h ;段基地址19~23位 不同
db10011010b ;段描述符的第6個字節(jié)屬性(代碼段可讀可執(zhí)行) 不同
db11000000b ;段描述符的第7個字節(jié)屬性
db0 ;段基地址的第二部分
----------------------15---------------------------------
Intel規(guī)定描述符表的第一個描述符必須是空描述符,也就是第一個描述符全部填充為0
DTR寄存器
全部定義好數(shù)據(jù)段和代碼段描述符后,我們知道這個描述符表是存在了內(nèi)存的某個位置,
那么CPU如何取得這描述符表所在的位置以及大小?
我們必須把剛剛創(chuàng)建好的描述符表所在地址和長度保存起來供CPU使用
80386CPU有個專門保存描述符表的48位寄存器稱之為GDTR寄存器
GDTR寄存器共48位:32位描述符表基地址 16位描述符表界限
gdtr匯編指令
通過lgdt匯編指令可以把GDTR描述符表的大小和起始位置存入gdtr寄存器中,指令格式如下:
lgdt [描述段描述符表的地址]
A20地址線
早期的8086只有20根地址線,只能訪問1M的地址空間。CPU尋址則按段+偏移的方式進行。
在32位CPU情況下,如果內(nèi)存訪問到1M內(nèi)存尾部時再向下訪問將會出現(xiàn)什么情況?
16位段+16位偏移的可能范圍是0-0x10FFEF(即0xFFFF0+0xFFFF),即1M+65520自字節(jié)的范圍。
由于只有20根地址線,所以在對1M~1M+65520范圍進行訪問時會發(fā)生“地址回繞”的現(xiàn)象,
就是說實際會訪問到0~65520的地方。
在32位CPU下不會產(chǎn)生地址回繞,但有些16位程序正是利用地址回繞特性來編寫的,
那么如何兼容這些程序呢?
讓32位德二進制數(shù)據(jù)高12位清空為0,剩下的低20二進制數(shù),如何實現(xiàn)呢。
我們只讓32位數(shù)據(jù)和高全為0低全為1的數(shù)相與操作。
;;;;;;;;;;;;;;;;;;;boot.asm,增加了gdt_data等;;;;;;;;;;;;;;;;;;;;;;;;;;
;我們的啟動程序?qū)崿F(xiàn)很簡單的功能,在屏幕中央打印一行字符串即可
org 07c00h ;org指令明確告訴編譯器我程序的段地址是7C00h,而不是原來的0000
;int匯編指令 “int 10h”調(diào)用bois里的中斷程序:顯示字符串
gdt_table_start:
gdt_null:
dd 0h
dd0h ;Intel規(guī)定段描述符表的第一個表項必須為0
gdt_data_addr equ $-gdt_table_start
gdt_data:
dw07FFh ;段界限
dw0h ;段基地址0~18位
db0h ;段基地址19~23位
db10010010b ;段描述符的第6個字節(jié)屬性(數(shù)據(jù)段可讀可寫)
db11000000b ;段描述符的第7個字節(jié)屬性
db0 ;段描述符的最后一個字節(jié)也就是段基地址的第二部分
gdt_code_addr equ $-gdt_table_start
gdt_code:
dw07FFh ;段界限(保持不變)
dw1h ;段基地址0~18位 不同
db80h ;段基地址19~23位 不同
db10011010b ;段描述符的第6個字節(jié)屬性(代碼段可讀可執(zhí)行) 不同
db11000000b ;段描述符的第7個字節(jié)屬性
db0 ;段基地址的第二部分
gdt_table_end:
gdtr_addr:
dwgdt_table_end-gdt_table_start-1 ;段描述符表長度
ddgdt_table_start ;段描述符表基地址
lgdt [gdtr_addr] ;讓CPU讀取gdtr_addr所指向內(nèi)存內(nèi)容保存到GDT內(nèi)存當中
;A20地址線問題
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-----------------------16---------------------------------------
A20地址線
由于在當時的8042鍵盤控制器上恰好有空閑的端口引腳,于是使用了該引腳昨晚與門控制這個地址比特位。
該信號即被稱為A20。如果它為0,則比特20及以上地址都被清除。從而實現(xiàn)了兼容性。
由于鍵盤的控制器速度很慢,因此就不能使用鍵盤對A20線來進行操作,
為此引進了一個A20快速門選項(Fast GateA20),它使用I/O端口0x92來處理A20信號線,避免了使用慢速的鍵盤控制器操作方式。
端口概念
在計算機系統(tǒng)中,所有設(shè)備都和CPU相連接,盡管相連接但CPU不能直接跟外圍設(shè)備進行交互數(shù)據(jù),
CPU只能和每個設(shè)備的寄存器交互數(shù)據(jù),然后再由寄存器把數(shù)據(jù)傳給設(shè)備。
那么我們給每個設(shè)備的寄存器進行編號,這些編號就稱之為【【端口號】】。
端口的讀
我們用in匯編命令來讀取設(shè)備寄存器中的內(nèi)容,格式如下:
in accumport
其中port就是要讀取的端口號,accum表示把port端口號的內(nèi)容放到accum中。accum必須是AL或者AX。
該命令的含義就是讀取port端口號內(nèi)容到AL或者AX寄存器中。
端口的寫
我們使用out匯編指令往指定設(shè)備中寫入數(shù)據(jù),格式如下:
out portaccume
該命令含義是把accume中的值寫入port端口中。
開啟A20地址線
32位計算機加電時默認情況是關(guān)閉A20地址線的,CPU要轉(zhuǎn)入保護模式都必須開啟A20地址線,如何開啟呢?
A20地址線代碼如下:
in al,92h
or al,00000010b
out 92h,al
cli匯編指令
在轉(zhuǎn)入保護模式之前,我們必須廢除原來的中斷向量表。
在匯編語言中使用cli匯編指令來廢除實模式下的中斷向量表。
這就意味著在保護模式下必須重新建立32位的中斷向量表和中斷處理程序。
轉(zhuǎn)入保護模式
當我們一切準備好之后,如何明確的告訴CPU我們要進入保護模式?
80386提供了4個32位的控制寄存器CR0~CR3。
其中控制寄存器CR0中某些位時用來標識是否要進入保護模式。
CR1寄存器保留沒有被使用。
CR2和CR3用于分頁機制(不屬于討論范圍)
CR0寄存器
31 PG 控制分頁管理機制。PG=0,禁用分頁管理機制,此時分段管理機制產(chǎn)生的線性地址直接作為物理地址使用。
PG=1,啟用分頁管理機制,此時線性地址經(jīng)分頁管理機制轉(zhuǎn)換物理地址。
30~5
4 ET
3 TS
2 EM
1 MP
0 PE 控制分段管理機制,PE=0,處理器運行于實模式;PE=1,處理器處于保護模式
設(shè)置CR0寄存器
只要對CR0寄存器的第一位置設(shè)為1,就表示要轉(zhuǎn)入保護模式,那么在匯編代碼中如何實現(xiàn)呢?
mov eax,cr0
or eax,1
mov cr0,eax
-----------------------17--------------------
保護模式下段寄存器
在386保護模式下,CPU的物理內(nèi)存依然是段寄存器內(nèi)容加偏移地址形成線性地址。
段寄存器內(nèi)容表示段描述符表中的索引(或者說段描述符所在的段描述符表的位置)。
-----------------------18---------------------
bochs是c++編寫的開源跨平臺的虛擬機,具有良好的可移植性??梢詫Σ僮飨到y(tǒng)進行調(diào)試時它最大的特色。
continue(c) 程序繼續(xù)運行知道遇到斷點為止。
step(s) 單步跟蹤。
vbreak(vb) 在虛擬地址上設(shè)置一個斷點。 vb 段地址:偏移地址
pbreak(b) 在物理地址上設(shè)置一個斷點。
lbreak(lb) 在線性地址上設(shè)置一個斷點。
disassemble 反匯編指令。
info b 顯示斷點
-----------------------19---------------------
bug
我們實際的代碼段和數(shù)據(jù)段的基地址是由我們代碼中的data_32和code_32來表示的。
我們要修改代碼段描述符和數(shù)據(jù)段描述符跟段基地址有關(guān)的字節(jié)。
根據(jù)描述結(jié)構(gòu)我們只要修改3,4,5,8這幾個字節(jié)的內(nèi)容,填上我們新的及地址就可以了。
;;;;;;;;;;;;;;;;;;;boot.asm;;;;;;;;;;;;;;;;;;;;;;;;
;我們的啟動程序?qū)崿F(xiàn)很簡單的功能,在屏幕中央打印一行字符串即可
[BITS 16]
org 07c00h ;org指令明確告訴編譯器我程序的段地址是7C00h,而不是原來的0000
;int匯編指令“int 10h”調(diào)用bois里的中斷程序:顯示字符串
jmp main
gdt_table_start:
gdt_null:
dd 0h
dd0h ;Intel規(guī)定段描述符表的第一個表項必須為0
gdt_data_addr equ $-gdt_table_start
gdt_data:
dw07FFh ;段界限
dw0h ;段基地址0~18位
db0h ;段基地址19~23位
db10010010b ;段描述符的第6個字節(jié)屬性(數(shù)據(jù)段可讀可寫)
db11000000b ;段描述符的第7個字節(jié)屬性
db0 ;段描述符的最后一個字節(jié)也就是段基地址的第二部分
gdt_video_addr equ $-gdt_table_start
gdt_video: ;用來描述顯存地址空間的段描述符
dw 0FFh ;顯存段界限就是1M
dw 8000h
db 0Bh
db 10010010b
db 11000000b
db 0
gdt_code_addr equ $-gdt_table_start
gdt_code:
dw07FFh ;段界限(保持不變)
dw1h ;段基地址0~18位 不同
db80h ;段基地址19~23位 不同
db10011010b ;段描述符的第6個字節(jié)屬性(代碼段可讀可執(zhí)行) 不同
db11000000b ;段描述符的第7個字節(jié)屬性
db0 ;段基地址的第二部分
gdt_table_end:
gdtr_addr:
dwgdt_table_end-gdt_table_start-1 ;段描述符表長度
ddgdt_table_start ;段描述符表基地址
;A20地址線問題
main:
xor eax,eax
add eax,data_32
mov word [gdt_data+2],ax
shr eax,16
mov byte [gdt_data+4],al
mov byte [gdt_data+7],ah
xor eax,eax
add eax,code_32
mov word [gdt_code+2],ax
shr eax,16
mov byte [gdt_code+4],al
mov byte [gdt_code+7],ah
;初始化代碼段描述符的基地址
cli
lgdt [gdtr_addr] ;讓CPU讀取gdtr_addr所指向內(nèi)存內(nèi)容保存到GDT內(nèi)存當中
enable_a20:
in al,92h
or al,00000010b
out92h,al
;設(shè)置cr0寄存器第一位為1
mov eax,cr0
or eax,1
mov cr0,eax
;跳轉(zhuǎn)到保護模式中
jmp gdt_code_addr:0
[BITS 32]
;保護模式的功能就是屏幕中央打印hello world
data_32:
db "hello world"
code_32:
mov ax,gdt_data_addr
mov ds,ax
mov ax,gdt_video_addr
mov gs,ax
movcx,11 ;顯示的字符串長度
movedi,(80*10+12)*2 ;在屏幕中央顯示
mov bx,0
movah,0ch
s:mov al,[ds:bx]
mov [gs:edi],al
mov [gs:edi+1],ah
inc bx
add edi,2
loop s
jmp $
times 510-($-$$) db0
dw 0aa55h
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;