前言
我們首先來理清一些名詞 x86, x64, IA-32, IA-32E, IA-64, AMD64, Intel64。
x86指的就是之前說的從8086發展起來的80X86系列架構包括80286,80386,80486.., 現在指32位架構。之后Intel拋棄x86搞了個獨立的64位架構稱之為IA-64(安騰系列), 同時將之前的32位架構稱之為IA-32,而AMD呢他在之前x86架構的基礎上加入了64位的支持稱之為x86-64簡稱x64也稱AMD64,之后由於市場的因素,Intel跟着 AMD也在自己的x86基礎上加上64位的支持稱IA-32E或者Intel64。
總結一下
X86 = IA-32
X64 = AMD64 = INTEL64 = IA-32E (Intel和AMD實現上略有差異)
IA-64 自成一脈
注意:我知識粗略記錄基本的知識點,所以AMD和Intel的差異部分的細節不會過多深究。
重要變化
1. 所有的通用寄存器都擴展成了64位的寄存器,擴展方式和16位擴展到32位時一樣,新的寄存器以R開頭並且原來的寄存器還是都保留
比如

2. 段寄存器沒變還是16位。
3. CRX控制寄存器變成了16個 CR0 - CR15,但是大部分過被保留了 只用到 CR0 CR2 CR3 CR4 CR8. CR8是中斷優先級寄存器
4. 虛擬地址被擴展到了64位,但是呢CPU的物理地址線只有48條,所以虛擬地址的高16位是不表示地址的,只是作為標記存在
a. 當 虛擬地址 < 8000 0000 0000 的時候高16位必須是0 (0X0000)
b. 當 虛擬地址 >= 8000 0000 0000 的時候高16位必須是1 (0XFFFF)

5. 尋址方式增加了一個RIP相對尋址
mov ax, [rip + 32位偏移值] (NASM語法 mov ax, [REL 32位偏移值])
引入這個尋址方式的目的是方便程序在內存中的游離,之前的尋址方式多多少少受到 程序在內存中的加載地址的影響實際上相當於絕對尋址,是用這種方式尋址是相當於當前代碼的偏移,那么只要保持程序結構破壞,程序隨便加載到哪個物理地址都沒問題。
6. CALL 立即數:立即數
64位不支持使用 立即數進行FAR CALL
必須將數放到內存中, 使用 CALL [EAX] , 內存中依次存放64位offset 和16位選擇子
7. 任務調度
64位下取消了CPU直接支持的任務切換,將任務切換完全交給了操作系統
所以64位模式下取消了任務門,TSS結構也做了精簡,只用來保存一些堆棧的地址。
首先是特權級切換是的三個堆棧RSP0 RSP1 PRS2,已經IO位圖這是沒變的
接下來的ISTX堆棧就是程序自定義的,在中斷門和陷阱門中,增加了IST 屬性占3位就是用來指向TSS中的ISTX堆棧的,這樣在使用中斷門和陷阱門的時候就能根據提示切換堆棧,0表示不切換堆棧。
TR 使用LTR切換內容。
MSR寄存器
x64架構的CPU 加入了一類稱之為MSR(Model Specific Register)的寄存器,MSR 是包含 控制寄存器 和 狀態寄存器兩類功能的一組寄存器。它即可以控制CPU的一些行為,也存儲一些CPU的狀態。
MSR每個寄存器是64位寬的,MSR是一組寄存器,他們沒有自己的名字,他們的訪問方式和 使用 IN/OUT指令的IO操作是一樣的都是使用地址來進行訪問。
使用 RDMSR 和 WRMSR 兩個指令來進行讀寫,相當於IO操作的IN/OUT指令,
ECX 存放讀寫地址
EDX 存放64位值的低32位
EAX 存放64位值的高32位
之所以明明通用寄存器已經是64位一個64位的數據還要分兩個寄存器存是因為MSR寄存器是為了兼容16位和32位。這個指令在16位和32位都可以使用。
EFER(C0000080H): 這是一個比較特殊的MSR寄存器的名字, 他控制着是否進入64位模式

MSR大致包括如下幾種功能類型:
1. Thermal
2. Frequency
3. C State
4. Microcode
5. EIST
6. TM
7. Key Features Of CPU
8. Voltage
9. Cache Control
10. MTRR
11. DCA(Direct Cache Access)
12. Machine Check
13. 硬件聯機控制
14.other
進入64位模式
64位模式相當於是保護模式的一個子模式,所以保護模式現在有兩個子模式 分別就是32位模式和64位模式,CPU一開始運行的時候是處於16位的實模式,之后可以切換到保護模式,切換到保護模式的時候可以選是32位的保護模式還是64位的保護模式。所有模式都是可以相互切換的。
AMD將64位模式稱為Long Mode模式,Intel稱之為IA-32E模式。
32位下的內存也管理是可選的,而64位模式內存的頁管理是必須啟用的。
所以64位模式的啟用條件是
1. 啟用段內存管理進入保護模式 CR0.PE = 1
2. 啟用PAE內存也管理模式 CR4.PAE = 1
3. 啟用64位模式 EFER.LME = 1
4. 啟用頁內存管理 CR0.PG = 1
5. 上述四點必須安裝描述的順序啟用
如果條件全部滿足,CPU會置 EFER.LMA = 1表示64位模式啟動了
內存段管理
64位段管理是在32位段管理的基礎上增加了64位訪問的能力。
1. GDTR, LDTR, IDTR, TR 寄存器的地址不封被擴展為64位,所以他們總體上從 48 位擴展為 80位寄存器,16位的limit沒變。
2. 數據段的方向被取消了,也就是說現在的數據段只能是從低地址往高地址漲,有效范圍是0-limit
3. 段描述符(數據段與代碼段)中的段基地址和界限會被忽略,實際64位下的段描述符僅僅描述的屬性,不在描述大小位置,而是可以訪問所有內存地址。
4. 代碼段描述符增加一個L屬性位,用於表明這個是要一個64位代碼段。
L = 1 : 64位代碼段
L = 0:
D = 1 32位代碼段
D = 0 16位代碼段
5. 門描述符在原先的基礎上新增8字節被擴展到了16字節,主要原因是采用64位地址之后原先的偏移地址的描述就不夠了。
所以GDTR和LDTR的表項是8字節和16字節混合的,所以為了防止誤將16字節的門描述符的高8字節解釋成段描述符,其對應的屬性字段位置的數據必須設置為0
額外介紹幾個指令
LDS,LES,LFS,LGS,LSS
格式
LDS reg16,mem32
說明
其意義是同時給一個段寄存器和一個16位通用寄存器同時賦值
具體如下:reg16=mem32的低字,DS=mem32的高字
內存頁管理
內存的也管理也是在32位的PAE模式下擴展而來,如下圖

X64位采用4級頁表, 32位NoPAE是2級頁表 32位PAE是3級頁表,所以X64是在X32PAE下有增加了一級頁表
X64位下一個也可以是1G, 2M, 4K大小的。分別由PDPE.PS, PDE.PS來控制
PDPE.PS = 1 : 表明當前PDPE就已經描述了一個1G的頁了,不用再向下級頁表查詢,否則PDPE描述的下一級頁表的頁表地址
PDE.PS = 1: 表明當前PDE就已經描述了一個2M的頁了,不用再向下級頁表查詢,否則PDE描述的下一級頁表的頁表地址
CR3寄存器,也被擴展為64位,低12位中3,4位作為PWT()和PCD標志,其他位被忽略,高地址作為物理地址指向PML4T, 高地址的有效為同樣受地址線影響,無效位必須是0.
PWT: Page Write Through
PWT= 1時,寫數據到緩存(Cache)的時候也要將數據寫入內存中。
PCD: Page Cache Disable
PCD = 1時,禁止某個頁寫入緩存,直接寫內存。
比如,做頁表用的頁,已經存儲在TLB中了,可能不需要再緩存了
各級頁表項結構如下

首先注意到的是所有的頁都是字節對齊的,比如1G的頁是1G對齊的,那么地址的低30位則必然為零,所以PDPTE的1G模式下1G的物理地址是從30位開始描述,低30位會被自動補0,同理根據頁大小的不同的地址的描述也從不同的位開始
其次注意到的是 "根據地址線擴展” 這一字段以及位描述中的 ? , 這是因為,不同的CPU 的有效地址線位數不同,所以到底有多少位有效是根據CPU來決定的
舉個例子,假設CPU地址線是48位,那么PTE描述的低12位肯定是0,那么就有48-12 = 36 位需要描述,那么 PTE的 47 - 12位就描述了這個地址,而62-48位則會被忽略。
標志位解釋
P: 頁存在內存標志位
R/W: 表示頁是否可寫 R/W = 1 表示頁可寫, 當R/W = 0 是表示頁只讀 ,當CR0.WP = 0 是 CPL=0的情況下不收R/W控制,CR0.WP=1的時候無論何時都收R/W控制
U/S: U/S = 0 只有特權級可以對頁進行任何訪問 U/S = 1 所有特權級可以對頁進行讀寫訪問, 當開啟SMEP功能時 只有CPL=3 可以進行執行訪問,如果SMEP未開啟,則所有權限都可以執行訪問
PWT: 頁緩存類型??
PCD: 也是否可以Cache
A: 頁已經被訪問標識,由CPU自動置位
D: 頁被寫過標志,由CPU自動置位
PAT: Page Attribute Table 標志
PS: PS =1 標識此表項直接指向物理頁了,不用查詢下一級表
G: 當 CR4.PGE=1時此位有效
G = 1 標識是全局頁,在切換CR3的指定時候此頁的換頁描述緩存不會被自動刷新
G =0 普通頁,切換的CR3的時候此 頁的換頁描述緩存會被自動刷新
XD: 只有當EFER.NXE = 1時 此控制生效,否則忽略此控制開關
XD = 1 代碼不允許執行
XD = 0 允許代碼執行
頁的緩存
就像段寄存器有內部緩存和頁表項緩存TLB一樣,內存頁也有緩存,就是CPU內部的多級高速緩存。也都緩存由3個開關來控制
PWT: Page Write Through, 當PWT = 1 時 CPU寫Cache的同時同步更新內存值,這會比較慢, 當PWT =0 時 寫Cache時不會更新內存值,只有當Cache換出時才更新內存.
PCD: Page Cache Disable, 當PCD = 1 的時候 靜止頁緩存。
PAT: 當CPU支持PAT模式的時候 PWT 和PCD將不再單獨解釋,而是和PAT組成一個3位的指針 指向 MSR寄存器中的 8 個PATX寄存器,然后在這個8個寄存中存儲對應的行為類型
排列順序 PAT PCD PWT 一個單位可以表示0-7,對應PAT0-PAT7

我們注意到 除CR3寄存器不帶PAT 之外,其余表項都是帶PAT PWT PCD,這里要區分他們描述的是他們地址所指向的內存頁,而不是尋址經過他的所有頁。
舉個例子就是,CR3的PWT和PCD只決定CR3地址指向的存儲着PLM4T表的那頁的屬性
同樣PLM4T的 PAT PWT PCD 值只決定它直接指向的1G頁或者PDTT所在的那一頁的屬性,而不是說他所指向的PDTT中所有下級表指定的也都受它控制。
特權級快速切換
SYSENTER/SYSEXIT
SYSENTER/SYSEXIT 是intel 所以amd64下不能在long mode下使用 ,使用msr
注意: sysenter 是CPU硬編碼的直接進ring0,無論此時處於ring幾,sysexit則是是CPU硬編碼的直接進ring3,無論此時是幾。
這里需要注意的是IA32 SYSTEMENTER_CS, 他值存儲一個值,那就是進入時的CS的選擇子,其余選擇子默認是順序存放的。
而 IP 和 SP 兩指針寄存器則是進入時使用MSR的寄存器,退出時使用CX 和 DX寄存器

SYSCALL/SYSRET
syscall/sysret是amd的所以intel64下只能在long mode 下使用,使用star系列寄存器
syscall通過從IA32_LSTAR MSR加載RIP(syscall的下一條指令地址會保存到RCX).RFLAGS保存到R11寄存器,然后用IA32_FMASK MSR(MSR 地址:C0000084H)屏蔽RFLAGS的值,更具體的說是清除在IA32_FMASK MSR中設置的每一位.

syscall
syscall會從IA32_STAR[47:32]中加載CS和SS.
syscall不會保存堆棧指針(RSP).
RCX ← RIP;保存syscall下一條指令地址到RCX
RIP ← IA32_LSTAR;RIP=IA32_LSTAR
R11 ← RFLAGS;R11=RFLAGS
RFLAGS ← RFLAGS AND NOT(IA32_FMASK);//根據IA32_FMASK屏蔽RFLAGS的相關位
CS.Selector ← IA32_STAR[47:32] AND FFFCH;確保CS的RPL為0
SS.Selector ← IA32_STAR[47:32] + 8;
sysret
RIP=RCX
RFLAGS=R11
返回32位代碼:CS=CS_Selector|3 SS=(CS_Selector+8)|3
返回64位代碼:CS=(CS_Selector+16)|3 SS=(CS_Selector+8)|3
返回時不會修改(ESP or RSP)
其他指令
swapgs
MSR 中有兩個 寄存器IA32_KERNEL_GS_BASE 和 IA32_GS_BASE
之前說過,段寄存器的緩存中的 基地址全部被強制設成了0, 而GS是個例外, 可以通過設置 IA32_GS_BASE的值來改變 GS寄存器緩存中BASE的值,也就是說在改變 IA32_GS_BASE的值的時候CPU會把數據同步到GS寄存器的BASE值中,
而 swapgs 的作用是交換 IA32_KERNEL_GS_BASE 和 GS.BASE的值
比如我在IA32_KERNEL_GS_BASE 中保存了內核模式下我要使用的全局數據的指針,
那么我在進入內核后直接 使用 swapgs,那么此時GS寄存器就直接指向我內核的全局數據了,當我要退出內核之前
我使用 swapgs 吧值換回來,這樣就還原成原來的樣子了。
monitor/mwait
IA32_MISC_ENABLE , 開始這個功能
使用 monitor 設置一個內存地址,CPU的監視器就是監視 從這個地址開始的一個固定范圍的內存寫操作(這個方位是CPU固定的使用CPUID可以查看方位大小), 如果監視到寫操作就是設置相應的標志位,而mwait的作用就是用來等待標志位被置位,在標志位被置位前CPU將暫停工作(中斷操作也會喚醒mwait操作, 喚醒后會繼續執行下去,還是重新睡眠?可以通過標志位判斷喚醒的原因?)
