AT&T 組合語言指令詳解
以下資料皆為網路搜尋的;
在閱讀linux/unix內核源代碼的時候,必須先掌握彙編,大家都知道,內核代碼用的編譯器是gcc,而gcc採用的是AT&T的彙編格式,與MS的intel有些區別。
一 AT&T的基本語法
語法上主要有以下幾個不同.
★ 寄存器命名原則
AT&T: %eax Intel: eax
★ 源/目的操作數順序
AT&T: movl %eax,%ebx Intel: mov ebx,eax
★ 常數/立即數的格式
AT&T: movl $_value,%ebx Intel: mov eax,_value
把_value的位址放入eax寄存器
AT&T: movl $0xd00d,%ebx Intel: mov ebx,0xd00d
★ 操作數長度標識
AT&T: movw %ax,%bx Intel: mov bx,ax
★尋址方式
AT&T: immed32(basepointer,indexpointer,indexscale)
Intel: [basepointer + indexpointer*indexscale + imm32)
Linux工作於保護模式下,用的是32位線性位址,所以在計算位址時
不用考慮segment-offset的問題.上式中的地址應為:
imm32 + basepointer + indexpointer*indexscale
下面是一些例子:
★直接尋址
AT&T: _booga ; _booga是一個全局的C變量
注意加上$是表示位址引用,不加是表示值引用.
註:對於局部變量,可以通過堆棧指針引用.
Intel: [_booga]
★寄存器間接尋址
AT&T: (%eax)
Intel: [eax]
★變址尋址
AT&T: _variable(%eax)
Intel: [eax + _variable]
AT&T: _array(,%eax,4)
Intel: [eax*4 + _array]
AT&T: _array(%ebx,%eax,8)
Intel: [ebx + eax*8 + _array]
二 基本的行內彙編
基本的行內彙編很簡單,一般是按照下面的格式
asm("statements");
例如:asm("nop"); asm("cli");
asm 和 __asm__是完全一樣的.
如果有多行彙編,則每一行都要加上 "\n\t"
例如:
asm( "pushl %eax\n\t"
"movl $0,%eax\n\t"
"popl %eax");
實際上gcc在處理彙編時,是要把asm(...)的內容"列印"到彙編
檔中,所以格式控制字元是必要的.
再例如:
asm("movl %eax,%ebx");
asm("xorl %ebx,%edx");
asm("movl $0,_booga);
在上面的例子中,由於我們在行內彙編中改變了edx和ebx的值,但是
由於gcc的特殊的處理方法,即先形成彙編檔,再交給GAS去彙編,
所以GAS並不知道我們已經改變了edx和ebx的值,如果程式的上下文
需要edx或ebx作暫存,這樣就會引起嚴重的後果.對於變量_booga也
存在一樣的問題.為瞭解決這個問題,就要用到擴展的行內彙編語法.
三 擴展的行內彙編
擴展的行內彙編類似於Watcom.
基本的格式是:
asm ( "statements" : output_regs : input_regs : clobbered_regs);
clobbered_regs指的是會被改變的寄存器.
下面是一個例子(為方便起見,我使用全局變量):
int count=1;
int value=1;
int buf[10];
void main()
{
asm(
"cld \n\t"
"rep \n\t"
"stosl"
:
: "c" (count), "a" (value) , "D" (buf[0])
: "%ecx","%edi" );
}
得到的主要彙編代碼為:
movl count,%ecx
movl value,%eax
movl buf,%edi
#APP
cld
rep
stosl
#NO_APP
cld,rep,stos就不用多解釋了.
這幾條語句的功能是向buf中寫上count個value值.
冒號後的語句指明輸入,輸出和被改變的寄存器.
通過冒號以後的語句,編譯器就知道你的指令需要和改變哪些寄存器,
從而可以優化寄存器的分配.
其中符號"c"(count)指示要把count的值放入ecx寄存器
類似的還有:
a eax
b ebx
c ecx
d edx
S esi
D edi
I 常數值,(0 - 31)
q,r 動態分配的寄存器
g eax,ebx,ecx,edx或內存變量
A 把eax和edx合成一個64位的寄存器(use long longs)
我們也可以讓gcc自己選擇合適的寄存器.
如下面的例子:
asm("leal (%1,%1,4),%0"
: "=r" (x)
: "0" (x) );
這段代碼實現5*x的快速乘法.
得到的主要彙編代碼為:
movl x,%eax
#APP
leal (%eax,%eax,4),%eax
#NO_APP
movl %eax,x
幾點說明:
1.使用q指示編譯器從eax,ebx,ecx,edx分配寄存器.
使用r指示編譯器從eax,ebx,ecx,edx,esi,edi分配寄存器.
2.我們不必把編譯器分配的寄存器放入改變的寄存器列表,因為寄存器
已經記住了它們.
3."="是標示輸出寄存器,必須這樣用.
4.數字%n的用法:
數字表示的寄存器是按照出現和從左到右的順序映射到用"r"或"q"請求
的寄存器.如果我們要重用"r"或"q"請求的寄存器的話,就可以使用它們.
5.如果強制使用固定的寄存器的話,如不用%1,而用ebx,則
asm("leal (%%ebx,%%ebx,4),%0"
: "=r" (x)
: "0" (x) );
注意要使用兩個%,因為一個%的語法已經被%n用掉了.
下面可以來解釋letter 4854-4855的問題:
1、變量加下劃線和雙下劃線有什麼特殊含義嗎?
加下劃線是指全局變量,但我的gcc中加不加都無所謂.
2、以上定義用如下調用時展開會是什麼意思?
#define _syscall1(type,name,type1,arg1) \
type name(type1 arg1) \
{ \
long __res; \
/* __res應該是一個全局變量 */
__asm__ volatile ("int $0x80" \
/* volatile 的意思是不允許優化,使編譯器嚴格按照你的彙編代碼彙編*/
: "=a" (__res) \
/* 產生代碼 movl %eax, __res */
: "0" (__NR_##name),"b" ((long)(arg1))); \
/* 如果我沒記錯的話,這裡##指的是兩次宏展開.
即用實際的系統調用名字代替"name",然後再把__NR_...展開.
接著把展開的常數放入eax,把arg1放入ebx */
if (__res >= 0) \
return (type) __res; \
errno = -__res; \
return -1; \
}
四.AT&T彙編與Intel彙編的比較
Intel和AT&T語法的區別
Intel和AT&T彙編語言的語法表面上各不相同,這將導致剛剛學會INTEL彙編的人第一次見到AT&T彙編時
會感到困惑,或者反之。因此讓我們從基礎的東西開始。
前綴
在Intel彙編中沒有寄存器前綴或者立即數前綴。而在AT&T彙編中寄存器有一個「%」前綴,立即數有
一個「$」前綴。Intel語句中十六進制和二進制數據分別帶有「h」和「b」後綴,並且如果十六進制
數字的第一位是字母的話,那麼數值的前面要加一個「0」前綴。
例如,
Intex Syntax
mov eax,1
mov ebx,0ffh
int 80h
AT&T Syntax
movl $1,%eax
movl $0xff,%ebx
int $0x80
就像你看到的,AT&T非常難懂。[base+index*scale+disp] 看起來比disp(base,index,scale)更好理解。
操作數的用法
intel語句中操作數的用法和AT&T中的用法相反。在Intel語句中,第一個操作數表示目的,第二個
操作數表示源。然而在AT&T語句中第一個操作數表示源而第二個操作數表示目的。在這種情形下AT&T語法
的好處是顯而易見的。我們從左向右讀,也從左向右寫,這樣比較自然。
例如,
Intex Syntax
instr dest,source
mov eax,[ecx]
AT&T Syntax
instr source,dest
movl (%ecx),%eax
存儲器操作數
如同上面所看到的,存儲器操作數的用法也不相同。在Intel語句中基址寄存器用「[」和「]」括起來
而在AT&T語句中是用「(」和「)」括起來的。
例如,
Intex Syntax
mov eax,[ebx]
mov eax,[ebx+3]
AT&T Syntax
movl (%ebx),%eax
movl 3(%ebx),%eax
AT&T語法中用來處理複雜的操作的指令的形式和Intel語法中的形式比較起來要難懂得多。在Intel語句
中這樣的形式是segreg:[base+index*scale+disp]。在AT&T語句中這樣的形式是
%segreg:disp(base,index,scale)。
Index/scale/disp/segreg 都是可選並且可以去掉的。Scale在本身沒有說明而index已指定的情況下
缺省值為1。segreg的確定依賴於指令本身以及程式運行在實模式還是pmode。在實模式下它依賴於
指令本身而pmode模式下它是不需要的。在AT&T語句中用作scale/disp的立即數不要加「$」前綴。
例如
Intel Syntax
instr foo,segreg:[base+index*scale+disp]
mov eax,[ebx+20h]
add eax,[ebx+ecx*2h]
lea eax,[ebx+ecx]
sub eax,[ebx+ecx*4h-20h]
AT&T Syntax
instr %segreg:disp(base,index,scale),foo
movl 0x20(%ebx),%eax
addl (%ebx,%ecx,0x2),%eax
leal (%ebx,%ecx),%eax
subl -0x20(%ebx,%ecx,0x4),%eax
後綴
就像你已經注意到的,AT&T語法中有一個後綴,它的意義是表示操作數的大小。「l」代表long,
「w」代表word,「b」代表byte。Intel語法中在處理存儲器操作數時也有類似的表示,
如byte ptr, word ptr, dword ptr。"dword" 顯然對應於「long」。這有點類似於C語言中定義的
類型,但是既然使用的寄存器的大小對應著假定的數據類型,這樣就顯得不必要了。
例子:
Intel Syntax
mov al,bl
mov ax,bx
mov eax,ebx
mov eax, dword ptr [ebx]
AT&T Syntax
movb %bl,%al
movw %bx,%ax
movl %ebx,%eax
movl (%ebx),%eax
注意:從此開始所有的例子都使用AT&T語法
AT&T組合語言的相關知識
在Linux源代碼中,以.S為副檔名的文件是“純”組合語言的文件。這裏,我們結合具體的例子再介紹一些AT&T組合語言的相關知識。
1.GNU組合語言程式GAS GNU Assembly和連接程式
當你編寫了一個程式後,就需要對其進行彙編(assembly)和連接。在Linux下有兩種方式,一種是使用組合語言程式GAS和連接程式ld,一種是使用gcc。我們先來看一下GAS和ld:
GAS把組合語言原始檔案(.o)轉換為目標檔(.o),其基本語法如下:
as filename.s -o filename.o
一旦創建了一個目標檔,就需要把它連接並執行,連接一個目標檔的基本語法為:
ld filename.o -o filename
這裏 filename.o是目標檔案名,而filename 是輸出(可執行) 檔。
GAS使用的是AT&T的語法而不是Intel的語法,這就再次說明了AT&T語法是Unix世界的標準,你必須熟悉它。
如果要使用GNC的C編譯器gcc,就可以一步完成彙編和連接,例如:
gcc -o example example.S
這裏,example.S是你的組合語言程式,輸出檔(可執行檔)名為example。其中,副檔名必須為大寫的S,這是因為,大寫的S可以使gcc自動識別組合語言程式中的C預處理命令,像#include、#define、#ifdef、 #endif等,也就是說,使用gcc進行編譯,你可以在組合語言程式中使用C的預處理命令。
2. AT&T中的節(Section)
在AT&T的語法中,一個節由.section關鍵字來標識,當你編寫組合語言程式時,至少需要有以下三種節:
.section .data: 這種節包含程式已初始化的資料,也就是說,包含具有初值的那些變數,例如:
hello : .string "Hello world!\n"
hello_len : .long 13
.section .bss:這個節包含程式還未初始化的資料,也就是說,包含沒有初值的那些變數。當操作
系統裝入這個程式時將把這些變數都置為0,例如:
name : .fill 30 # 用來請求用戶輸入名字
name_len : .long 0 # 名字的長度 (尚未定義)
當這個程式被裝入時,name 和 name_len都被置為0。如果你在.bss節不小心給一個變數賦了初值,這個值也會丟失,並且變數的值仍為0。
使用.bss比使用.data的優勢在於,.bss節不佔用磁片的空間。在磁片上,一個長整數就足以存放.bss節。當程式被裝入到記憶體時,作業系統也只分配給這個節4個位元組的記憶體大小。
注意:編譯程序把.data和.bss在4位元組上對齊(align),例如,.data總共有34位元組,那麼編譯程序把它對其在36位元組上,也就是說,實際給它36位元組的空間。
.section .text :這個節包含程式的代碼,它是唯讀節,而.data 和.bss是讀/寫節。
3.組合語言程式指令(Assembler Directive)
上面介紹的.section就是組合語言程式指令的一種,GNU組合語言程式提供了很多這樣的指令(directiv),這種指令都是以句點(.)為開頭,後跟指令名(小寫字母),在此,我們只介紹在內核源代碼中出現的幾個指令(以arch/i386/kernel/head.S中的代碼為例)。
(1)ascii "string"...
.ascii 表示零個或多個(用逗號隔開)字串,並把每個字串(結尾不自動加“0“位元組)中的字元放在連續的位址單元。
還有一個與.ascii類似的.asciz,z代表“0“,即每個字串結尾自動加一個”0“位元組,例如:
int_msg:
.asciz "Unknown interrupt\n"
(2).byte 運算式
.byte表示零或多個運算式(用逗號隔開),每個運算式被放在下一個位元組單元。
(3).fill 運算式
形式:.fill repeat , size , value
其中,repeat、size 和value都是常量運算式。Fill的含義是反復拷貝size個位元組。Repeat可以大於等於0。size也可以大於等於0,但不能超過8,如果超過8,也只取8。把repeat個位元組以8個為一組,每組的最高4個位元組內容為0,最低4位元組內容置為value。
Size和 value為可選項。如果第二個逗號和value值不存在,則假定value為0。如果第一個逗號和size不存在,則假定size為1。
例如,在Linux初始化的過程中,對全局描述符表GDT進行設置的最後一句為:
.fill NR_CPUS*4,8,0 /* space for TSS's and LDT's */
因為每個描述符正好占8個位元組,因此,.fill給每個CPU留有存放4個描述符的位置。
(4).globl symbol
.globl使得連接程式(ld)能夠看到symbl。如果你的局部程式中定義了symbl,那麼,與這個局部程式連接的其他局部程式也能存取symbl,例如:
.globl SYMBOL_NAME(idt)
.globl SYMBOL_NAME(gdt)
定義idt和gdt為全局符號。
(5)quad bignums
.quad表示零個或多個bignums(用逗號分隔),對於每個bignum,其缺省值是8位元組整數。如果bignum超過8位元組,則列印一個警告資訊;並只取bignum最低8位元組。
例如,對全局描述符表的填充就用到這個指令:
.quad 0x00cf9a000000ffff /* 0x10 kernel 4GB code at 0x00000000 */
.quad 0x00cf92000000ffff /* 0x18 kernel 4GB data at 0x00000000 */
.quad 0x00cffa000000ffff /* 0x23 user 4GB code at 0x00000000 */
.quad 0x00cff2000000ffff /* 0x2b user 4GB data at 0x00000000 */
(6)rept count
把.rept指令與.endr指令之間的行重複count次,例如
.rept 3
.long 0
.endr
相當於
.long 0
.long 0
.long 0
(7)space size , fill
這個指令保留size個位元組的空間,每個位元組的值為fill。size 和fill都是常量運算式。如果逗號和fill被省略,則假定fill為0,例如在arch/i386/bootl/setup.S中有一句:
.space 1024
表示保留1024位元組的空間,並且每個位元組的值為0。
(8).word expressions
這個運算式表示任意一節中的一個或多個運算式(用逗號分開),運算式的值占兩個位元組,例如:
gdt_descr:
.word GDT_ENTRIES*8-1
表示變數gdt_descr的置為GDT_ENTRIES*8-1
(9).long expressions
這與.word類似
(10).org new-lc , fill
把當前節的位置計數器提前到new-lc(new location counter)。new-lc或者是一個常量運算式,或者是一個與當前子節處於同一節的運算式。也就是說,你不能用.org橫跨節:如果new-lc是個錯誤的值,則.org被忽略。.org只能增加位置計數器的值,或者讓其保持不變;但絕不能用.org來讓位置計數器倒退。
注意,位置計數器的起始值是相對於一個節的開始的,而不是子節的開始。當位置計數器被提升後,中間位置的位元組被填充值fill(這也是一個常量運算式)。如果逗號和fill都省略,則fill的缺省值為0。
例如:.org 0x2000
ENTRY(pg0)
表示把位置計數器置為0x2000,這個位置存放的就是臨時頁表pg0。
本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/kpgood/archive/2009/04/05/4049495.aspx
2010年1月19日 星期二
2009年12月14日 星期一
●memtest86+教學 Part16
關於SMP在Memtest86+ V4.00與Memtest86 V3.5 之比較可以發現main.c中多了void initialise_cpus(void),因此先將Memtest86 V3.5 轉成exe檔來執行看看多加入SMP功能後測試時間是否減少.得知結果差異不大,因為其實作多核只是以輪流交替執行權來執行程式碼。我想未來應該會有改進吧,到時後就可以享受多核心CPU降低測試時間的好處了。
注意一點Memtest86 V3.5必須使用gcc 4-2-0以下的版本compile,但Memtest86+ V4.00則使用gcc 4-2-x以上版本。
注意一點Memtest86 V3.5必須使用gcc 4-2-0以下的版本compile,但Memtest86+ V4.00則使用gcc 4-2-x以上版本。
2009年6月10日 星期三
●memtest86+教學 Part15
仔細看一下controller.c之中ㄉstatic struct pci_memory_controller controllers[],你就可以知道目前memtest86+到底支援哪些chipsets,可別小看這ㄍ陣列,我們看一下這ㄍ陣列的元素是ㄍ資料結構如下:
struct pci_memory_controller {
unsigned vendor;
unsigned device;
char *name;
int tested;
void (*poll_fsb)(void);
void (*poll_timings)(void);
void (*setup_ecc)(void);
void (*poll_errors)(void);
};
上面綠色字ㄉ那幾ㄍ是函式指標。這種程式的寫法應該不會陌生吧!一般人很少能接觸這麼多種類的平台,即使接觸到ㄌ也不見得拿得到它們的data sheets,因此能寫出特定chipset的那些函式指標,真是佩服;然而有些還是memtest86作者能力所不及,為什麼ㄋ;例如poll_fsb_nothing,這樣ㄉ函式裡什麼東西也沒有,原因我想當然是沒有specifiction。我想Intel真是大方,它會將所有chipsets的data sheet提供給大家download,其他廠商可不會這樣作。這有關係ㄇ,這個關係可大ㄌ,就好比說show_spd這個routine,它目前只能support ich5以上的南橋晶片,若你的平台不是Intel的晶片組,那麼memtest show SPD code的function就只能看到"SMBus Controller not known",連chipset裡頭的相關Register你都碰不到,我想對該chipset就只能束手無策ㄌ。
struct pci_memory_controller {
unsigned vendor;
unsigned device;
char *name;
int tested;
void (*poll_fsb)(void);
void (*poll_timings)(void);
void (*setup_ecc)(void);
void (*poll_errors)(void);
};
上面綠色字ㄉ那幾ㄍ是函式指標。這種程式的寫法應該不會陌生吧!一般人很少能接觸這麼多種類的平台,即使接觸到ㄌ也不見得拿得到它們的data sheets,因此能寫出特定chipset的那些函式指標,真是佩服;然而有些還是memtest86作者能力所不及,為什麼ㄋ;例如poll_fsb_nothing,這樣ㄉ函式裡什麼東西也沒有,原因我想當然是沒有specifiction。我想Intel真是大方,它會將所有chipsets的data sheet提供給大家download,其他廠商可不會這樣作。這有關係ㄇ,這個關係可大ㄌ,就好比說show_spd這個routine,它目前只能support ich5以上的南橋晶片,若你的平台不是Intel的晶片組,那麼memtest show SPD code的function就只能看到"SMBus Controller not known",連chipset裡頭的相關Register你都碰不到,我想對該chipset就只能束手無策ㄌ。
2009年5月9日 星期六
AMD64 & Intel EM64T
此圖是從intel datasheet:Intel64 and IA-32 Architectures Software Develops Manual Volume3A所截取下來,從這張圖就可知道CPU如何在各模式之間切換;及為什麼可以作到向下相容的架構。
圖二
圖一AMD公司設計,可以在同一時間內處理64位的整數運算,並兼容于X86-32架構。其中支持64位邏輯定址,同時提供轉換為32位定址選項;但數據操作指令默認為32位和8位,提供轉換成64位和16位的選項;支持常規用途暫存器,如果是32位運算操作,就要將結果擴展成完整的64位。這樣,指令中有“直接執行”和“轉換執行”的區別,其指令字段是8位或32位,可以避免字段過長。
x86-64(也叫AMD64)的產生也並非空穴來風,x86處理器的32bit尋址空間限制在4GB內存,而IA-64的處理器又不能兼容x86。AMD充分考慮顧客的需求,加強x86指令集的功能,使這套指令集可同時支持64位的運算模式,因此AMD把它們的架構稱之為x86-64。在技術上AMD在x86-64架構中為了進行64位運算,AMD為其引入了新增了R8-R15通用暫存器作為原有X86處理器暫存器的擴充,但在而在32位環境下並不完全使用到這些暫存器。原來的暫存器諸如EAX、EBX也由32位擴張至64位。在SSE單元中新加入了8個新暫存器以提供對SSE2的支持。暫存器數量的增加將帶來性能的提升。與此同時,為了同時支持32和64位代碼及暫存器,x86-64架構允許處理器工作在以下兩種模式︰Long Mode(長模式)和Legacy Mode(傳統模式),Long模式又分為兩種子模式(64bit模式和Compatibility mode兼容模式)。該標準已經被引進在AMD伺服器處理器中的Opteron處理器。
而今年也推出了支持64位的EM64T技術,再還沒被正式命為EM64T之前是IA32E,這是英特爾64位擴展技術的名字,用來區別X86指令集。Intel的EM64T支持64位sub-mode,和AMD的X86-64技術類似,採用64位的線性平面尋址,加入8個新的通用暫存器(GPRs),還增加8個暫存器支持SSE指令。與AMD相類似,Intel的64位技術將兼容IA32和IA32E,只有在營運64位作業系統下的時候,才將會採用IA32E。IA32E將由2個sub-mode組成︰64位sub-mode和32位sub-mode,同AMD64一樣是向下兼容的。Intel的EM64T將完全兼容AMD的X86-64技術。現下Nocona處理器已經加入了一些64位技術,Intel的Pentium 4E處理器也支持64位技術。
應該說,這兩者都是兼容x86指令集的64位微處理器架構,但EM64T與AMD64還是有一些不一樣的地方
AMD的K8不是真正的64bit處理器,其和Intel的EM64T處理器一樣,不是真正的64bit處理器。Microsoft的WINXP64bit也不是真正意義上的64bit作業系統。真正64bit系統(硬體和軟體系統)出來的時候,現下這些所謂的64bit全部會淘汰,而這段時間會相當長,至少三年內難以實現;況且配件的更新速度很快,只要用的順手,何必在意以後會怎樣。順便說一下,現下的K8實際上是40bit處理器,而非64bit處理器;EM64T實際上是32bit處理器,只是在物理內存尋址上擴展到36bit。
AMD64 及 EM64T 倒底誰是一顆真正的 64 bit處理器,不用太在意, 這兩者都是由 32bit 處理器改進而來, 前陣子在 bbs 的 hardware 版, 為了AMD64 是不是創新的架構, 爭論了許久. 不過, 實際使用上, AMD64 不論是要當 32bit或 64 bit處理器來用, 算是不錯, 效能好, 溫度又低!
x86-64(也叫AMD64)的產生也並非空穴來風,x86處理器的32bit尋址空間限制在4GB內存,而IA-64的處理器又不能兼容x86。AMD充分考慮顧客的需求,加強x86指令集的功能,使這套指令集可同時支持64位的運算模式,因此AMD把它們的架構稱之為x86-64。在技術上AMD在x86-64架構中為了進行64位運算,AMD為其引入了新增了R8-R15通用暫存器作為原有X86處理器暫存器的擴充,但在而在32位環境下並不完全使用到這些暫存器。原來的暫存器諸如EAX、EBX也由32位擴張至64位。在SSE單元中新加入了8個新暫存器以提供對SSE2的支持。暫存器數量的增加將帶來性能的提升。與此同時,為了同時支持32和64位代碼及暫存器,x86-64架構允許處理器工作在以下兩種模式︰Long Mode(長模式)和Legacy Mode(傳統模式),Long模式又分為兩種子模式(64bit模式和Compatibility mode兼容模式)。該標準已經被引進在AMD伺服器處理器中的Opteron處理器。
而今年也推出了支持64位的EM64T技術,再還沒被正式命為EM64T之前是IA32E,這是英特爾64位擴展技術的名字,用來區別X86指令集。Intel的EM64T支持64位sub-mode,和AMD的X86-64技術類似,採用64位的線性平面尋址,加入8個新的通用暫存器(GPRs),還增加8個暫存器支持SSE指令。與AMD相類似,Intel的64位技術將兼容IA32和IA32E,只有在營運64位作業系統下的時候,才將會採用IA32E。IA32E將由2個sub-mode組成︰64位sub-mode和32位sub-mode,同AMD64一樣是向下兼容的。Intel的EM64T將完全兼容AMD的X86-64技術。現下Nocona處理器已經加入了一些64位技術,Intel的Pentium 4E處理器也支持64位技術。
應該說,這兩者都是兼容x86指令集的64位微處理器架構,但EM64T與AMD64還是有一些不一樣的地方
AMD的K8不是真正的64bit處理器,其和Intel的EM64T處理器一樣,不是真正的64bit處理器。Microsoft的WINXP64bit也不是真正意義上的64bit作業系統。真正64bit系統(硬體和軟體系統)出來的時候,現下這些所謂的64bit全部會淘汰,而這段時間會相當長,至少三年內難以實現;況且配件的更新速度很快,只要用的順手,何必在意以後會怎樣。順便說一下,現下的K8實際上是40bit處理器,而非64bit處理器;EM64T實際上是32bit處理器,只是在物理內存尋址上擴展到36bit。
AMD64 及 EM64T 倒底誰是一顆真正的 64 bit處理器,不用太在意, 這兩者都是由 32bit 處理器改進而來, 前陣子在 bbs 的 hardware 版, 為了AMD64 是不是創新的架構, 爭論了許久. 不過, 實際使用上, AMD64 不論是要當 32bit或 64 bit處理器來用, 算是不錯, 效能好, 溫度又低!
由 Intel EM64T 技術談 64 位元 CPU
由於軟體的功能越來越強大且多元化,所消耗的系統資源相對的也越多,因此在軟體的功能不斷提升的同時,硬體的處理能力也就捉襟見拙。而新技術不斷的發展,除了代表科技的進步外,也因應這必然的趨勢,造就了電腦平台的變革 —DDRⅡ、PCI Express、Hyper Threading 與 EM64T 等。
由於軟體的功能越來越強大且多元化,所消耗的系統資源相對的也越多,因此在軟體的功能不斷提升的同時,硬體的處理能力也就捉襟見拙。而新技術不斷的發展,除了代表科技的進步外,也因應這必然的趨勢,造就了電腦平台的變革 —DDRⅡ、PCI Express、Hyper Threading 與 EM64T 等。
一、何謂 64 位元 CPU我們知道一個功能強大的軟體其實是由無數行的程式所組合而成,每一行的程式碼均會透過編譯器編譯成數筆資料,送到 CPU 內部的暫存器中等待執行。而所謂的 64 位元也就是在這資料的寬度、暫存器的大小以及執行指令的長度均為 64 位元,另外在記憶體的定址能力同樣也為 64 位元。
二、64 位元 CPU 的發展Intel CPU 的發展從 80386 之後便演進為 32 位元,到 1994 年 Intel 與 HP 共同開發 VLIW(Very Long Instruction Word)架構的 Itanium、Itanium2 的 IA-64 CPU,希望能將 HP 的 PA-RISC、COMPAQ 的 Alpha 以及 x86 架構的 PC 作一整合,以期降低研發高階伺服器處理器成本,並能跨平台的執行不同的作業系統。只是因為 Itanium、Itanium2 的 CPU 使用的是 VLIW 架構,雖然在執行 64 位元的作業系統與應用程式非常順暢,但於 32 位元的作業系統與應用程式中必須透過指令轉譯的方式來執行,導致在 32 位元的環境下效能不彰,因此發展上是叫好不叫座。在 64 位元 CPU 沈潛好一段時間之後,AMD 於 2003 年四月推出 64 位元 x86 架構的處理器 Opteron,以及 Apple 推出 PowerPC G5 的 RISC CPU,既可以執行 64 位元作業系統與應用程式,亦可向下相容於原本 32 位元的作業系統與應用程式,一時間 64 位元處理器的話題又熱絡起來。而 Intel 為了不讓競爭對手專美於前,於 2004 年春季 IDF 論壇中發表了 Intel 64bit Extension Technology 的新技術,並於 4 月份正式定名為 Intel Extended Memory 64 Technology(EM64T),並採用被稱之為 IA-32e 的新操作模式。
三、EM64T 技術Intel EM64T 是為了強化 IA-32 架構所發展的新技術。包含此技術的 IA-32 處理器除了可向下相容於現存的 32 位元軟體,並賦予軟體存取更多記憶體空間。並且允許那些開發於 32 位元定址模式下的軟體擁有執行於 64 位元定址空間的能力。相較於之前 IA32,擁有 EM64T 的 IA32e 最大的不同是將記憶體定址能力由原本 2 的 32 次方提升到 2 的 64 次方,也就是 18446744TB。
四、包含 EM64T 技術處理器的執行模式包含 IA32e 的處理器可執行在 Legacy IA-32 Mode 與 IA-32e Mode。IA-32e Mode 又包含了兩種子模式:1. Compatibility Mode:允許 64 位元的作業系統在不做任何的變動下執行大多數原生的 32 位元軟體。2. 64-bit Mode:允許 64 位元作業系統執行那些存取 64 位元位址空間的應用程式。
五、64-bit Mode 是用來在 64 位元作業系統下執行 64 位元應用程式,包含下列幾項特性:● 64 位元線性定址。● 8 個全新的一般暫存器(General-Purpose Register)。● 8 個全新 Streaming SIMD Extensions(SSE)暫存器支援 SSE、SSE2與SSE3。● 64 位元寬度的 GPR 與指令指標● 相同的 byte-register addressing● 更快速的中斷優化機制● 全新的指令指標相對定址模式64-bit Mode 可透過 64 位元作業系統藉由分割程式碼的準則來啟動。初始定址大小為 64 位元,初始運算元大小為 32 位元。
六、在 Compatibility Mode 中,絕大部分的 16 位元或 32 位元應用程式均可在不重新編譯的狀況下執行於 64 位元作業系統中。相同於 64-bit Mode,Compatibility Mode 可透過 64 位元作業系統藉由分割程式碼的準則來啟動。也就是說在同一時間中,64 位元應用程式也可被執行於 64-bit Mode,而不需重新編譯。
七、Legacy IA-32 Mode 包含保護模式、真實位址模式或是虛擬 8086 模式。針對這些模式撰寫的軟體,EM64T 的處理器均有完整的相容性。
上圖一列出 EM64T 處理器在不同模式下的差異:
上面圖二列出了在不同模式中所支援的暫存器類別:
八、64 位元處理器的迷思是不是購買了 64 位元處理器,相對的執行速度就是原本 32 位元的兩倍?其實以目前的作業系統 Windows XP 或是 Windows 2000 均為 32 位元的作業系統,而應用程式部分亦為 32 位元,也因此在程式碼分割與執行指令上仍為 32 位元寬,而 CPU 資料處理部分仍為 32 位元,也因此與目前的 IA-32 處理器並無差別。在未來 Windows XP 64-bit Edition for Extended System 上市後搭配 64 位元的應用程式,在效能方面才能有所提升。
2009年5月6日 星期三
●memtest86+教學 Part14

圖一
上一篇我們已經知道頁目錄如何建立,並且要另外建立一個指向4個頁目錄基底位址的表格,名稱叫作pdp(page-directory-pointer-table)它非常重要,因為我們現在討論的這種2M分頁方式無論如何一定要建立4GB的頁目錄表,並區分成4等分,每一等分佔用4KByte(4096)的空間,也就是說每一等分負責1GB容量的映射(Mapping);這樣的說法不如來作一張圖(如圖一)會更容易理解。雖然圖中例子是共16G記憶體,但可支援到64G的Mapping。
現在我們來看看誰在使用pdp及頁目錄:
int map_page(unsigned long page)
{
unsigned long i;
struct pde
{
unsigned long addr_lo;
unsigned long addr_hi;
};
extern unsigned char pdp[];
extern struct pde pd2[];
unsigned long window = page >> 19;
if (FLAT (window == mapped_window)) { return 0; }
if (window == 0) { return 0; }
if (!v->pae (window >= 32)) {
/* Fail either we don't have pae support
* or we want an address that is out of bounds
* even for pae. */
return -1; }
/* Compute the page table entries... */
//由於左移符號會無法顯示在文章中,因此我把左移使用shl來表示,右移shr,小於lss
for(i = 0; i lss 1024; i++)
{
pd2[i].addr_lo = ((window & 1) shl 31) + ((i & 0x3ff) shl 21) + 0xE3;
pd2[i].addr_hi = (window shr 1);
}
/*以上這個for迴圈會重新建立pd2~pd3的頁目錄表*/
paging_off();
if (window gtr 1) { paging_on(pdp); }
mapped_window = window;
return 0;
}
這個函式可以說是memtest86分頁技術中最經典的routineㄌ。我們來看誰調用它:
有init.c中的cacheable():
/* Ensure the default set of pages are mapped */
map_page(0);
map_page(0x80000);
及main.c中的do_test():
map_page(v->map[0].pbase_addr)
在cacheable()調用它共2個;第一個map_page(0);代入0結果return 0,而且不會去啟用paging_on(pdp);第二個map_page(0x80000);代入0x80000結果也是return 0,而且不會去啟用paging_on(pdp);然而這只是程式的初始,當然不會去啟用分頁;所以真正會不斷調用它的便是map_page(v->map[0].pbase_addr);而且你會發現pd0~pd3好像只有pd2每次都會重新被計算,其實應該說是pd2及pd3每次都被重新計算,因為它的for迴圈是1024次,然而每個pd佔用512個頁目錄表項。所以我們可以開始推論:假設我們主機板插上6GB的記憶體;當測試0~4G時,並不會啟動分頁模式,當測試範圍為4~6G時;便會進入分頁,並且把pd2~pd3對應到4~6G;也就是說此時cpu access的位址雖然是2~4G,但實際上access 到的卻是實體物理位址4~6G。這樣ㄉ推論沒有說服力;所以一定要實際驗證才行。
順便來看一下paging_on:
static void paging_on(void *pdp)
{
if (!v->pae) return;
__asm__ __volatile__(
/* Load the page table address 此處的%0就是pdp的位址 */
"movl %0, %%cr3\n\t"
/* Enable pae cr4的bit5就是pae位元設為1*/
"movl %%cr4, %%eax\n\t"
"orl $0x00000020, %%eax\n\t"
"movl %%eax, %%cr4\n\t"
/* Enable paging cr0的PG位元設為1*/
"movl %%cr0, %%eax\n\t"
"orl $0x80000000, %%eax\n\t"
"movl %%eax, %%cr0\n\t"
:
: "r" (pdp)
: "ax"
);
}
P.S.
試想,為什麼要分頁,而且還分不同的分頁模式,為什麼要把事情搞的那麼複雜ㄋ,難不成就是要存心搞死我們這些人,難道就沒更好的辦法嗎?回顧歷史,32位元CPU已問世多年,我記得Win95那個時候我的系統記憶體也不過才32MB,但是CPU已經可以Access到4G的空間;所以那時RAM遠比CPU定址能力少得多,為了實現虛擬記憶體,讓作業系統可以多工,每個程式都擁有自己的4G位址空間,所以才搞分頁,因為記憶體容量有限,所以要把很多已執行但卻不會馬上用到的行程(Process)所佔用的空間置換(swap)到硬碟(或其他儲存媒體裝置),使正在執行的行程能有足夠的記體體使用權。曾幾何時,CPU定址仍然是32位元,但DRAM價跌容量升級,使很多使用者的安裝於系統的記憶體容量甚至大於4G,因此時代變了分頁技術也要跟著變,就像我們現在所探討的memtest86,就是因為要能符合時代須求所以要測試比4G容量更大的記憶體,且CPU也已經支援這種技術,所以我們才能借助對memtest86的了解,得知這樣的分頁模式。總之這樣的分頁模式和現行OS所實作的分頁稍有不同,就是因為須求不同的原因;或許未來OS設計會將資訊全數載入記憶體,便不需要swap到硬碟,透過PAE這樣的分頁方式來取得資訊,我想一定能更加速系統的運作,但先決條件是DRAM還要更便宜,容量再加大;純假設。
待續‧‧‧
●memtest86+教學 Part13
分頁我想應該是memtest86的精華,若能真的把這部分搞懂,我想對IA32(x86)的認識又進入到更深一層的境界了。若你想對整個IA-32的分頁做作通盤認識你可以去參考Intel 64 and IA-32 Architectures Software Developer's Manual - Volume 3A System Programming Guide.pdf。由於一般書籍講到的分頁都是使用4kbyte的分頁模式,而且只講到CR3(或稱 PDBR,page directory base register),然而IA-32的分頁方式還分成好幾種模式;而且支援4K、2M、4M的分頁大小;並可定址到64G (36-BIT PHYSICAL ADDRESSING USING THE PAE PAGING MECHANISM);說的好像很複雜,其實就是在搞CR3、CR4這兩個暫存器(register)。然而其實也沒那麼簡單,無論如何,由於memtest86是採用2M分頁(page size extensions)及支援PAE paging mechanism使可定址到64G;因此我們至少要對這種分頁方式作一番說明:
首先我們先來看看爛豬腳(head.S)如何實作頁目錄,你可能會問不是還要有頁表嗎?這個問題非常好但是我不想在此解答,你直接參考我上面說的那份資料以及Paging Extensions for the Pentium Pro Processor 就可知道為何。
在head.S有這麼一段code:注意;在巨集中使用參數必須前面加上"\"符號
.macro ptes64 start, count=64
.quad \start + 0x0000000 + 0xE3 ;為什麼是E3,下面會解說
.quad \start + 0x0200000 + 0xE3
.quad \start + 0x0400000 + 0xE3
.quad \start + 0x0600000 + 0xE3
.quad \start + 0x0800000 + 0xE3
.quad \start + 0x0A00000 + 0xE3
.quad \start + 0x0C00000 + 0xE3
.quad \start + 0x0E00000 + 0xE3
.if \count-1ptes64 "(\start+0x01000000)",\count-1
.endif
.endm
.macro maxdepth depth=1
.if \depth-1maxdepth \depth-1
.endif
.endm
maxdepth
.balign 4096
.globl pd0
pd0: ptes64 0x0000000000000000
.balign 4096
.balign 4096
.globl pd1
pd1: ptes64 0x0000000040000000
.balign 4096
.balign 4096
.globl pd2
pd2: ptes64 0x0000000080000000
.balign 4096
.balign 4096
.globl pd3
pd3: ptes64 0x00000000C0000000
.balign 4096
.balign 4096
.globl pdp
pdp:
.long pd0 + 1
.long 0
.long pd1 + 1
.long 0
.long pd2 + 1
.long pd2 + 1
.long 0
.long pd3 + 1
.long pd3 + 1
.long 0
上面這段code最重要的就是ptes64那個巨集(macro);若你把pd0:、pd1:、pd2:、pd3:後面的ptes64巨集展開,便會得到0~4GB的頁目錄表,而且每個表項相差2MB。差別在於其每一個頁目錄表項佔用一個quad(8Byte),這和我之前介紹的那本"自己動手寫作業系統"所談到的分頁採用long為頁目錄,相差4個bytes;-而且書面說的也不搞pdp,原因如下:
Figure 3-21 shows the format for the page-directory-pointer-table and page-directory entries when 2-MByte pages and extended physicaladdresses are being used.
The major differences in these entries are as follows:
•A page-directory-pointer(pdp)-table entry is added.
•The size of the entries are increased from 32 bits to 64 bits
•The maximum number of entries in a page directory or page table is 512.
•The base physical address field in each entry is extended to 24 bits for 36-bit physical addressing (or extended to MAXPHYADDR-12 bits if MAXPHYADDR is different than 36).
另外針對"至少在各式各樣的Pentium簡介中有四個2M頁[1,2,3,4]"這句話的意思,答案如下:
Figure 3-19 shows how a page-directory-pointer table and page directories can be used to map linear addresses to 2-MByte pages when the PAE paging mechanism enabled. This paging method can be used to map up to 2048 pages (4 page-directory-pointer(pdp)-table entries times 512 page-directory entries) into a 4-GByte linear address space.也就是Figure 3-19中的bit30和bit31。
/*-----------------參考init.c map_page()--------------- *
0xE3 --
* Bit 0 = Present bit. 1 = PDE is present
* Bit 1 = Read/Write. 1 = memory is writable
* Bit 2 = Supervisor/User. 0 = Supervisor only (CPL 0-2)
* Bit 3 = Writethrough. 0 = writeback cache policy
* Bit 4 = Cache Disable. 0 = page level cache enabled
* Bit 5 = Accessed. 1 = memory has been accessed.
* Bit 6 = Dirty. 1 = memory has been written to.
* Bit 7 = Page Size. 1 = page size is 2 MBytes
* --------------------------------------------------*/
透過以上的認知我們要來看其他的code如何來實作分頁‧‧‧待續‧‧‧ 2009年5月4日 星期一
●memtest86+教學 Part12
今天我想把int 15h eax=E820h作個總結;首先我們看一下head.S:
/* Don't disable the a20 line */
/* Load 16bit data segments, to ensure the segment limits are set */
movl $REAL_DS, %eax
movl %eax, %ds
movl %eax, %es
movl %eax, %ss
movl %eax, %fs
movl %eax, %gs
/* Compute the stack base */
leal stack@GOTOFF(%ebx), %ecx
/* Compute the address of meminfo*/
leal mem_info@GOTOFF(%ebx), %edi #取得mem_info標籤的絕對位址並存到edi
/* switch to 16bit mode */
ljmp $REAL_CS, $1f - RSTART #這個ljmp就是跳到.code16,也就是下一行;因為ljmp的格式是 segment:offset ;segment就是$REAL_CS;offset就是$1f - RSTART(下一行1:標籤減掉RSTART;因為有這個定義#define RSTART startup_32就是head.S的頂端;所以$1f - RSTART就等於1:標籤相對於head.S的偏移位址)
1:
.code16
/* Disable Paging and protected mode */
/* clear the PG & PE bits of CR0 */
movl %cr0,%eax
andl $~((1 << 31)(1<<0)),%eax #關閉分頁及保護模式,回到真實模式
movl %eax,%cr0
/* make intersegment jmp to flush the processor pipeline * and reload %cs:%eip (to clear upper 16 bits of %eip). */
ljmp *(realptr - RSTART)
#這樣的語法通常是這種32位元和16位元的混合區段才會用到(切記)
#這樣的語法類似nasm的 jmp dword selector:offset;
#然而masm卻沒有這樣的機制語法;所以masm會如下這樣搞:
# db 0eah
# dw ofsfset
# dw segment
#以上三行是JMP到16bit時的用法,雖等同於 jmp dword ptr label(不支援),但masm好像只
#支援 jmp fword ptr label(從16bit跳到32bit),然而跳到32bit也可以這樣搞:
# db 0eah
# dw ofsfset , 0h
# dw segment
#也就是說從32bit跳到16bit會去提取2ㄍbyte當offset及2ㄍbyte當segment
#當從16bit跳到32bit會去提取4ㄍbyte當offset及2ㄍbyte當segment
real:
/* we are in real mode now
* set up the real mode segment registers : %ds, %ss, %es, %gs, %fs
*/
movw %cs, %ax
movw %ax, %ds
movw %ax, %es
movw %ax, %fs
movw %ax, %gs
movw %ax, %ss
/* Adjust the stack pointer */
movl %ecx, %eax
shrl $4, %eax #取得ss值
movw %ax, %ss
subl %ecx, %esp
/* Save my base pointer */
pushl %ebx
/* Setup %ds to point to my data area */
shrl $4, %edi
movl %edi, %ds
/* Enable interrupts or BIOS's go crazy */
sti
# Get memory size (extended mem, kB)
#define SMAP 0x534d4150
xorl %eax, %eax
movl %eax, (E88) #0x00
movl %eax, (E801) #0x04
movl %eax, (E820NR) #0x08
# Try three different memory detection schemes. First, try
# e820h, which lets us assemble a memory map, then try e801h,
# which returns a 32-bit memory size, and finally 88h, which
# returns 0-64m
# method E820H:
# the memory map from hell. e820h returns memory classified into
# a whole bunch of different types, and allows memory holes and
# everything. We scan through this memory map and build a list
# of the first 32 memory areas, which we return at [E820MAP].
# This is documented at http://www.teleport.com/~acpi/acpihtml/topic245.htm
#int 15h eax=0e820h使用說明:
#INPUT:eax=0e820h ebx=0(第一次呼叫必須為0)
#INPUT:es:di=指向一個可容納32*20byte的位址空間
#INPUT:ecx=20 edx=0534d4150h('SMAP')
#OUTPUT:CF(C旗標=0表示成功,否則失敗)
#OUTPUT:eax=0534d4150h('SMAP') return value
#OUTPUT:ecx=20
#OUTPUT:ebx如果等於0且cf=0,表示已經是最後一個位址範圍描述器
meme820:
xorl %ebx, %ebx # continuation counter
movw $E820MAP, %di # point into the whitelist
# so we can have the bios
# directly write into it.
jmpe820:
movl $0x0000e820, %eax # e820, upper word zeroed
movl $SMAP, %edx # ascii 'SMAP'
movl $20, %ecx # size of the e820rec
pushw %ds # data record.
popw %es #這ㄍpush pop很重要,因為得到ㄉ資料是存放在es:di
int $0x15 # make the call
jc bail820 # fall to e801 if it fails
cmpl $SMAP, %eax # check the return is `SMAP'
jne bail820 # fall to e801 if it fails
# cmpl $1, 16(%di) # is this usable memory?
# jne again820
# If this is usable memory, we save it by simply advancing %di by
# sizeof(e820rec).
good820:
movb (E820NR), %al # up to 32 entries
cmpb $E820MAX, %al
jnl bail820
incb (E820NR)
movw %di, %ax
addw $E820ENTRY_SIZE, %ax #define E820ENTRY_SIZE 20
movw %ax, %di
again820:
cmpl $0, %ebx # check to see if
jne jmpe820 # %ebx is set to EOF
bail820:
# method E801H:
# memory size is in 1k chunksizes, to avoid confusing loadlin.
# we store the 0xe801 memory size in a completely different place,
# because it will most likely be longer than 16 bits.
#以下是0xe801及E88我們不討論,因為現在用的主機板根本不會去呼叫它們
meme801:
‧‧‧
‧‧
‧
/* O.k. the BIOS query is done switch back to protected mode */
cli
/* Restore my saved variables */
popl %ebx
/* Get an convinient %ds */
movw %cs, %ax
movw %ax, %ds
/* Load the global descriptor table */
addr32 lgdt gdt_descr - RSTART
/* Turn on protected mode */
/* Set the PE bit in CR0 */
movl %cr0,%eax
orl $(1 SHL 0),%eax #回到保護模式
movl %eax,%cr0
/* flush the prefetch queue, and relaod %cs:%eip */
data32 ljmp *(protptr - RSTART)
prot:
.code32
‧‧‧
‧‧
‧
realptr:
.word real - RSTART #仔細觀察一下realptr: 這個標籤後面這兩個.word
.word 0x0000 #因為ljmp 後面是先offset值,然後才是segment值
protptr:
.long 0
.long KERNEL_CS #其實這ㄍlong只要定義為word就可以ㄌ
idt_real:
.word 0x400 - 1 # idt limit ( 256 entries)
.word 0, 0 # idt base = 0L
‧‧‧
.globl mem_info #匯出這個符號給c語言使用
mem_info: #以下這裡就是存放位址範圍描述器的地方
. = . + MEMINFO_SIZE #0x28c=(E88+E801+E820NR=0ch)+(20*32)
‧‧‧
stack:
. = . + 4096
stack_top:
由以上的代碼我們已經得到n個位址範圍描述器,接下來我們要回到init.c得知接下來是呼叫memsize.c中的mem_size();
由於test.h有這樣的定義:extern struct mem_info_t mem_info;
struct e820entry
{ unsigned long long addr; /* start of memory segment */
unsigned long long size; /* size of memory segment */
unsigned long type; /* type of memory segment */
};
struct mem_info_t
{ unsigned long e88_mem_k; /* 0x00 */
unsigned long e801_mem_k; /* 0x04 */
unsigned long e820_nr; /* 0x08 */
struct e820entry e820[E820MAX]; /* 0x0c */
/* 0x28c */};
所以就可將這些位址範圍描述器整理如下:
if (e820_nr == 0 && alt_mem_k == 0 && ext_mem_k == 0){
ext_mem_k = mem_info.e88_mem_k;
alt_mem_k = mem_info.e801_mem_k;
e820_nr = mem_info.e820_nr;
for (i=0; i< mem_info.e820_nr; i++) {
e820[i].addr = mem_info.e820[i].addr;
e820[i].size = mem_info.e820[i].size;
e820[i].type = mem_info.e820[i].type; }
}
/* Don't disable the a20 line */
/* Load 16bit data segments, to ensure the segment limits are set */
movl $REAL_DS, %eax
movl %eax, %ds
movl %eax, %es
movl %eax, %ss
movl %eax, %fs
movl %eax, %gs
/* Compute the stack base */
leal stack@GOTOFF(%ebx), %ecx
/* Compute the address of meminfo*/
leal mem_info@GOTOFF(%ebx), %edi #取得mem_info標籤的絕對位址並存到edi
/* switch to 16bit mode */
ljmp $REAL_CS, $1f - RSTART #這個ljmp就是跳到.code16,也就是下一行;因為ljmp的格式是 segment:offset ;segment就是$REAL_CS;offset就是$1f - RSTART(下一行1:標籤減掉RSTART;因為有這個定義#define RSTART startup_32就是head.S的頂端;所以$1f - RSTART就等於1:標籤相對於head.S的偏移位址)
1:
.code16
/* Disable Paging and protected mode */
/* clear the PG & PE bits of CR0 */
movl %cr0,%eax
andl $~((1 << 31)(1<<0)),%eax #關閉分頁及保護模式,回到真實模式
movl %eax,%cr0
/* make intersegment jmp to flush the processor pipeline * and reload %cs:%eip (to clear upper 16 bits of %eip). */
ljmp *(realptr - RSTART)
#這樣的語法通常是這種32位元和16位元的混合區段才會用到(切記)
#這樣的語法類似nasm的 jmp dword selector:offset;
#然而masm卻沒有這樣的機制語法;所以masm會如下這樣搞:
# db 0eah
# dw ofsfset
# dw segment
#以上三行是JMP到16bit時的用法,雖等同於 jmp dword ptr label(不支援),但masm好像只
#支援 jmp fword ptr label(從16bit跳到32bit),然而跳到32bit也可以這樣搞:
# db 0eah
# dw ofsfset , 0h
# dw segment
#也就是說從32bit跳到16bit會去提取2ㄍbyte當offset及2ㄍbyte當segment
#當從16bit跳到32bit會去提取4ㄍbyte當offset及2ㄍbyte當segment
real:
/* we are in real mode now
* set up the real mode segment registers : %ds, %ss, %es, %gs, %fs
*/
movw %cs, %ax
movw %ax, %ds
movw %ax, %es
movw %ax, %fs
movw %ax, %gs
movw %ax, %ss
/* Adjust the stack pointer */
movl %ecx, %eax
shrl $4, %eax #取得ss值
movw %ax, %ss
subl %ecx, %esp
/* Save my base pointer */
pushl %ebx
/* Setup %ds to point to my data area */
shrl $4, %edi
movl %edi, %ds
/* Enable interrupts or BIOS's go crazy */
sti
# Get memory size (extended mem, kB)
#define SMAP 0x534d4150
xorl %eax, %eax
movl %eax, (E88) #0x00
movl %eax, (E801) #0x04
movl %eax, (E820NR) #0x08
# Try three different memory detection schemes. First, try
# e820h, which lets us assemble a memory map, then try e801h,
# which returns a 32-bit memory size, and finally 88h, which
# returns 0-64m
# method E820H:
# the memory map from hell. e820h returns memory classified into
# a whole bunch of different types, and allows memory holes and
# everything. We scan through this memory map and build a list
# of the first 32 memory areas, which we return at [E820MAP].
# This is documented at http://www.teleport.com/~acpi/acpihtml/topic245.htm
#int 15h eax=0e820h使用說明:
#INPUT:eax=0e820h ebx=0(第一次呼叫必須為0)
#INPUT:es:di=指向一個可容納32*20byte的位址空間
#INPUT:ecx=20 edx=0534d4150h('SMAP')
#OUTPUT:CF(C旗標=0表示成功,否則失敗)
#OUTPUT:eax=0534d4150h('SMAP') return value
#OUTPUT:ecx=20
#OUTPUT:ebx如果等於0且cf=0,表示已經是最後一個位址範圍描述器
meme820:
xorl %ebx, %ebx # continuation counter
movw $E820MAP, %di # point into the whitelist
# so we can have the bios
# directly write into it.
jmpe820:
movl $0x0000e820, %eax # e820, upper word zeroed
movl $SMAP, %edx # ascii 'SMAP'
movl $20, %ecx # size of the e820rec
pushw %ds # data record.
popw %es #這ㄍpush pop很重要,因為得到ㄉ資料是存放在es:di
int $0x15 # make the call
jc bail820 # fall to e801 if it fails
cmpl $SMAP, %eax # check the return is `SMAP'
jne bail820 # fall to e801 if it fails
# cmpl $1, 16(%di) # is this usable memory?
# jne again820
# If this is usable memory, we save it by simply advancing %di by
# sizeof(e820rec).
good820:
movb (E820NR), %al # up to 32 entries
cmpb $E820MAX, %al
jnl bail820
incb (E820NR)
movw %di, %ax
addw $E820ENTRY_SIZE, %ax #define E820ENTRY_SIZE 20
movw %ax, %di
again820:
cmpl $0, %ebx # check to see if
jne jmpe820 # %ebx is set to EOF
bail820:
# method E801H:
# memory size is in 1k chunksizes, to avoid confusing loadlin.
# we store the 0xe801 memory size in a completely different place,
# because it will most likely be longer than 16 bits.
#以下是0xe801及E88我們不討論,因為現在用的主機板根本不會去呼叫它們
meme801:
‧‧‧
‧‧
‧
/* O.k. the BIOS query is done switch back to protected mode */
cli
/* Restore my saved variables */
popl %ebx
/* Get an convinient %ds */
movw %cs, %ax
movw %ax, %ds
/* Load the global descriptor table */
addr32 lgdt gdt_descr - RSTART
/* Turn on protected mode */
/* Set the PE bit in CR0 */
movl %cr0,%eax
orl $(1 SHL 0),%eax #回到保護模式
movl %eax,%cr0
/* flush the prefetch queue, and relaod %cs:%eip */
data32 ljmp *(protptr - RSTART)
prot:
.code32
‧‧‧
‧‧
‧
realptr:
.word real - RSTART #仔細觀察一下realptr: 這個標籤後面這兩個.word
.word 0x0000 #因為ljmp 後面是先offset值,然後才是segment值
protptr:
.long 0
.long KERNEL_CS #其實這ㄍlong只要定義為word就可以ㄌ
idt_real:
.word 0x400 - 1 # idt limit ( 256 entries)
.word 0, 0 # idt base = 0L
‧‧‧
.globl mem_info #匯出這個符號給c語言使用
mem_info: #以下這裡就是存放位址範圍描述器的地方
. = . + MEMINFO_SIZE #0x28c=(E88+E801+E820NR=0ch)+(20*32)
‧‧‧
stack:
. = . + 4096
stack_top:
由以上的代碼我們已經得到n個位址範圍描述器,接下來我們要回到init.c得知接下來是呼叫memsize.c中的mem_size();
由於test.h有這樣的定義:extern struct mem_info_t mem_info;
struct e820entry
{ unsigned long long addr; /* start of memory segment */
unsigned long long size; /* size of memory segment */
unsigned long type; /* type of memory segment */
};
struct mem_info_t
{ unsigned long e88_mem_k; /* 0x00 */
unsigned long e801_mem_k; /* 0x04 */
unsigned long e820_nr; /* 0x08 */
struct e820entry e820[E820MAX]; /* 0x0c */
/* 0x28c */};
所以就可將這些位址範圍描述器整理如下:
if (e820_nr == 0 && alt_mem_k == 0 && ext_mem_k == 0){
ext_mem_k = mem_info.e88_mem_k;
alt_mem_k = mem_info.e801_mem_k;
e820_nr = mem_info.e820_nr;
for (i=0; i< mem_info.e820_nr; i++) {
e820[i].addr = mem_info.e820[i].addr;
e820[i].size = mem_info.e820[i].size;
e820[i].type = mem_info.e820[i].type; }
}
訂閱:
文章 (Atom)