Labs 導(dǎo)讀
標(biāo)準(zhǔn)服務(wù)器技術(shù)是網(wǎng)絡(luò)功能虛擬化(NFV)實(shí)現(xiàn)的一個(gè)關(guān)鍵因素,了解一些x86架構(gòu)的基礎(chǔ)知識(shí)對(duì)大家后續(xù)了解電信云關(guān)鍵技術(shù),尤其是掌握虛擬化技術(shù)原理和關(guān)鍵優(yōu)化方案是必須具備的。本文主要從x86架構(gòu)的CPU指令集增強(qiáng),內(nèi)存管理、中斷和異常、IO架構(gòu)等部分進(jìn)行闡述,以及包含一些基礎(chǔ)IT的基本概念的講解。
1 x86-64指令集的增強(qiáng)
Intel的x86體系結(jié)構(gòu)是世界上最流行的處理器架構(gòu),從1978年8086/8088處理器問世到現(xiàn)在的Core i7和Core i9,以及Xeon系列處理器,Intel x86體系結(jié)構(gòu)已經(jīng)在CPU領(lǐng)域叱咤40多年。
x86-64是x86架構(gòu)的延伸產(chǎn)品,是一種64位微處理器架構(gòu)及其相應(yīng)的指令集。在x86-64出現(xiàn)以前,Intel與惠普聯(lián)合推出IA-64架構(gòu),此架構(gòu)不與x86兼容,且市場(chǎng)反應(yīng)冷淡。于是,與x86兼容的x86-64架構(gòu)應(yīng)運(yùn)而生。1999年,AMD首次公開64位指令集為IA-32提供擴(kuò)展,稱為x86-64(后來改名為AMD64)。此架構(gòu)后來也為Intel所采用,也就是現(xiàn)在的Intel 64。
x86-64能有效地把x86架構(gòu)移植到64位環(huán)境,并且兼容原有的x86應(yīng)用程序,市場(chǎng)前景廣闊。外界使用x84-64或者x64稱呼這個(gè)64位架構(gòu),以保持中立,不偏袒任何一家廠商。
AMD 64指令集主要特點(diǎn)有:支持64位通用寄存器、64位整數(shù)及邏輯運(yùn)算和64位虛擬地址。
Intel 64架構(gòu)加入了額外的寄存器和其他改良的指令集,可使處理器直接訪問超過4GB的內(nèi)存,允許運(yùn)行更大的應(yīng)用程序。通過64位的存儲(chǔ)器地址上限,其理論存儲(chǔ)器容量上限達(dá)16EB,目前大多數(shù)操作系統(tǒng)和應(yīng)用程序已基本支持完整的64位地址。
2 x86的內(nèi)存架構(gòu)
硬件架構(gòu)中最復(fù)雜、最核心的部分就是其內(nèi)存架構(gòu)。此部分詳細(xì)內(nèi)容因?yàn)槠邢逕o法詳細(xì)展開,面向的人員主要包括CPU架構(gòu)設(shè)計(jì)、操作系統(tǒng)開發(fā)和內(nèi)核底層優(yōu)化等領(lǐng)域,至于運(yùn)維方面后期如果不做內(nèi)核優(yōu)化的同事了解即可,如感興趣可參考《手把手教你設(shè)計(jì)CPU-RISC-V處理器》、《嵌入式操作系統(tǒng)原理》和《處理器虛擬化技術(shù)》等書籍。
2.1 地址空間
地址空間是所有可用資源的集合,我們姑且將它看做一個(gè)大大的數(shù)組,那么地址就是這個(gè)數(shù)組的索引。地址空間可以劃分為物理地址空間和線性地址空間兩大類。
2.1.1 物理地址空間
硬件平臺(tái)通常劃分為CPU、內(nèi)存和其他硬件設(shè)備三個(gè)部分。其中,CPU 是整個(gè)硬件平臺(tái)的主導(dǎo)者,內(nèi)存和其他硬件設(shè)備都是CPU 可以使用的資源。這些資源組合在一起,分布在CPU的物理地址空間內(nèi),CPU使用物理地址索引這些資源。物理地址空間的大小由CPU實(shí)現(xiàn)的物理地址位數(shù)所決定,物理地址位數(shù)由CPU經(jīng)過MMU(Memory Management Unit,內(nèi)存管理單元)轉(zhuǎn)換后的外地址總線位數(shù)決定。外地址總線位數(shù)與CPU處理數(shù)據(jù)的能力(即CPU位數(shù))沒有必然的聯(lián)系,例如:16位的8086 CPU具有20位地址空間。
一個(gè)硬件平臺(tái)只有一個(gè)物理地址空間,但每個(gè)程序都認(rèn)為自己獨(dú)享整個(gè)平臺(tái)的硬件資源。為了讓多個(gè)程序能夠有效地相互隔離,也為了它們能夠有效地使用物理地址空間的資源,引入了線性地址空間的概念。
2.1.2 線性地址空間
線性地址空間的大小由CPU實(shí)現(xiàn)的線性地址位數(shù)決定,線性地址位數(shù)由CPU的內(nèi)地址總線位數(shù)決定。由于CPU的內(nèi)地址總線與CPU的執(zhí)行單元直連,所以,內(nèi)地址總線位數(shù)往往與CPU位數(shù)一致,如果是32位處理器,那么它就實(shí)現(xiàn)了32位線性地址,其線性地址空間為4GB,如果是64位處理器,那么它的線性地址空間的為2的64次方,即16384GB。需要注意的是,線性地址空間的大小與物理地址空間的大小沒有必然聯(lián)系,Intel的PAE平臺(tái)具有4GB的線性地址空間,而其物理地址空間為64GB。但是,線性地址空間會(huì)被映射到某一部分物理地址空間或整個(gè)物理地址空間。也就是說,線性地址空間小于等于物理地址空間。
CPU負(fù)責(zé)將線性地址空間轉(zhuǎn)換成物理地址空間,保證程序能夠正確訪問到該線性地址空間所映射到的物理地址空間。在現(xiàn)代操作系統(tǒng)中,每個(gè)進(jìn)程通常都擁有自己的私有線性地址空間。一個(gè)典型的線性地址空間構(gòu)造如下圖所示。
線性地址空間
2.2 地址
地址是訪問地址空間的索引。根據(jù)訪問地址空間的不同,索引可以分為物理地址和線性地址。但由于x86特殊的段機(jī)制,還存在一種額外的地址——邏輯地址。
2.2.1 邏輯地址
邏輯地址是程序直接使用的地址(x86無法禁用段機(jī)制,邏輯地址一直存在)。邏輯地址由一個(gè)16位的段選擇符和一個(gè)32位的偏移量(32位平臺(tái))構(gòu)成。下面以具體程序?yàn)槔M(jìn)行解釋。比如:我們寫下面一段c語言代碼:
int a = 100; # 定義一個(gè)整型變量
a int *p = &a; # 定義一個(gè)整型指針p,指向變量a在內(nèi)存中的地址
上述語句中的指針變量p存儲(chǔ)的就是變量a的邏輯地址。實(shí)際上,p中存儲(chǔ)的僅是邏輯地址的偏移部分,而偏移對(duì)應(yīng)的段選擇符位于段寄存器中,并沒有在程序中顯示。
2.2.2 線性地址
線性地址又稱虛擬地址。線性地址是邏輯地址轉(zhuǎn)換后的結(jié)果,用于索引線性地址空間。當(dāng)CPU使用分頁機(jī)制時(shí),還需要將線性地址轉(zhuǎn)換成物理地址才能訪問物理平臺(tái)內(nèi)存或其他硬件設(shè)備;當(dāng)分頁機(jī)制未啟用時(shí),線性地址與物理地址相同。
2.2.3 物理地址
物理地址是物理地址空間的索引,是CPU提交到總線用于訪問物理平臺(tái)內(nèi)存或其他硬件設(shè)備的最終地址,在x86下,物理地址有時(shí)也被稱為總線地址。
根據(jù)上面的描述,我們可以總結(jié)如下:
分段機(jī)制啟用,分頁機(jī)制未啟用:邏輯地址--->線性地址=物理地址
分段機(jī)制、分頁機(jī)制同時(shí)啟用:邏輯地址--->線性地址--->物理地址
3 x86內(nèi)存管理機(jī)制
x86架構(gòu)的內(nèi)存管理機(jī)制分為兩部分:分段機(jī)制和分頁機(jī)制。分段機(jī)制為程序提供彼此隔離的代碼區(qū)域、數(shù)據(jù)區(qū)域、棧區(qū)域,從而避免了同一個(gè)處理器上運(yùn)行的多個(gè)程序互相影響。
分頁機(jī)制實(shí)現(xiàn)了傳統(tǒng)的按需分頁、虛擬內(nèi)存機(jī)制,可以將程序的執(zhí)行環(huán)境按需映射到物理內(nèi)存。此外,分頁機(jī)制還可以用于提供多任務(wù)的隔離。
分段機(jī)制和分頁機(jī)制都可以通過配置,支持簡(jiǎn)單的單任務(wù)系統(tǒng)、多任務(wù)系統(tǒng)或共享內(nèi)存的多處理器系統(tǒng)。需要強(qiáng)調(diào)的一點(diǎn)是,處理器無論在何種運(yùn)行模式下都不可以禁止分段機(jī)制,但是分頁機(jī)制卻是可選選項(xiàng)。
3.1 分段機(jī)制
分段機(jī)制是x86架構(gòu)下的樸素內(nèi)存管理機(jī)制,不可以禁用。了解分段機(jī)制有利于對(duì)后續(xù)內(nèi)存虛擬化原理和優(yōu)化方案有更深的了解。
分段機(jī)制將內(nèi)存劃分成以基地址(Base)和長(zhǎng)度(Limit)描述的塊。段可以與程序最基本的元素聯(lián)系起來,程序可以簡(jiǎn)單地劃分為代碼、數(shù)據(jù)和棧,段機(jī)制就有相應(yīng)的代碼段、數(shù)據(jù)段和棧段。
一個(gè)程序根據(jù)分段機(jī)制在內(nèi)存中由邏輯地址、段選擇符、段描述符和段描述符表4個(gè)基本部分構(gòu)成。
1)當(dāng)程序使用邏輯地址訪問內(nèi)存的某個(gè)部分時(shí),CPU通過邏輯地址中的段選擇符索引段描述符表,進(jìn)而得到該內(nèi)存對(duì)應(yīng)的段描述符(段描述符描述段的基地址、長(zhǎng)度以及讀/寫、訪問權(quán)限等屬性信息)
2)根據(jù)段描述符中的段屬性信息檢測(cè)程序的訪問是否合法,如果合法,再根據(jù)段描述符中的基地址將邏輯地址轉(zhuǎn)換為線性地址。
這個(gè)流程可以用如下圖示進(jìn)行總結(jié):
內(nèi)存分段機(jī)制
段選擇符是邏輯地址的一個(gè)組成部分,用于索引段描述符表以獲得該段對(duì)應(yīng)的段描述符。段選擇符作為邏輯地址的一部分,對(duì)應(yīng)用程序是可見的。但是,正如前面在邏輯地址中介紹的,應(yīng)用程序中只存儲(chǔ)和使用邏輯地址的偏移部分,段選擇符的修改和分配由連接器和加載器完成。
為了使CPU能夠快速地獲得段選擇符,x86架構(gòu)提供了6個(gè)段寄存器存放當(dāng)前程序中各個(gè)段的段選擇符。這6個(gè)段寄存器分別如下:
CS(Code-Segment,代碼段):存放代碼段的段選擇符。
DS(Data-Segment,數(shù)據(jù)段):存放數(shù)據(jù)段的段選擇符。
SS(Stack-Segment,棧段):存放棧的段選擇符。
ES、FS、GS:可以存放額外三個(gè)數(shù)據(jù)段的段選擇符,由程序自由使用。
由于段選擇符的存在最終是為了索引段描述符表中的段描述符,為了加速段描述符的訪問,x86架構(gòu)在不同的段寄存器后增加了一個(gè)程序不可見的段描述符寄存器。當(dāng)相應(yīng)段寄存器中加入一個(gè)新的段選擇符后,CPU自動(dòng)將該段選擇符索引的段描述符加載到這個(gè)不可見的段描述符寄存器中。各個(gè)段寄存器的構(gòu)造如下:
段寄存器
段描述符描述某個(gè)段的基地址、長(zhǎng)度以及各種屬性(例如,讀/寫屬性、訪問權(quán)限等)。這是分段機(jī)制的核心思想。當(dāng)CPU通過一個(gè)邏輯地址的段選擇符獲得該段對(duì)應(yīng)的段描述符后,會(huì)使用段描述符中各種屬性字段對(duì)訪問進(jìn)行檢查,一旦確認(rèn)訪問合法,CPU將段描述符中的基地址和程序中邏輯地址的偏移量相加就得到程序的線性地址。
正如前面講到的,x86架構(gòu)在每個(gè)段寄存器后增加了一個(gè)程序不可見的段描述符寄存器,每當(dāng)段寄存器加入一個(gè)新的段選擇符后,CPU自動(dòng)將該段選擇符索引的段描述符加載到這個(gè)段描述符寄存器中。后續(xù)只要不發(fā)生段寄存器的更新操作,CPU就不再查詢段描述符表而是直接使用這個(gè)段描述符寄存器中的值,從而加快CPU的執(zhí)行效率。
x86架構(gòu)提供了兩種段描述符表:GDT(全局段描述符表Global Descriptor Table)和LDT(本地段描述符表Local Descriptor Table)。具體選擇哪個(gè)段描述符表,由段選擇符中的TI字段決定,當(dāng)TI=0時(shí),索引GDT,當(dāng)TI=1時(shí)索引LDT。系統(tǒng)中至少有一個(gè)GDT可以被所有的進(jìn)程訪問。與此同時(shí),系統(tǒng)中可以有一個(gè)或多個(gè)LDT,可以被某個(gè)進(jìn)程私有,也可以被多個(gè)進(jìn)程共享。
GDT是內(nèi)存中的一個(gè)數(shù)據(jù)結(jié)構(gòu)。簡(jiǎn)單地講,可以將GDT看成是一個(gè)數(shù)組,由基地址(Base)和長(zhǎng)度(Limit)描述。
LDT是一個(gè)段,需要用一個(gè)段描述符來描述。LDT的段描述符存放在GDT中,當(dāng)系統(tǒng)中有多個(gè)LDT時(shí),GDT中必須有對(duì)應(yīng)數(shù)量的段描述符。
為了加速對(duì)GDT和LDT的訪問,x86架構(gòu)提供了GDTR寄存器和LDTR寄存器。關(guān)于這兩種寄存器的具體描述如下:
GDTR:包括一個(gè)32位的基地址(Base)和一個(gè)16 位的長(zhǎng)度(Limit)。
LDTR:結(jié)構(gòu)與段寄存器相同(同樣包含對(duì)程序不可見的段描述符寄存器)。
通過段選擇符索引GDT/LDT的過程如下圖所示:
段選擇符索引
➢ x86架構(gòu)內(nèi)存管理中分段機(jī)制總結(jié):
1)在程序加載階段,該進(jìn)程LDT的段選擇符首先索引GDT,獲得LDT的段描述符并將其加載到LDTR寄存器中。此外,該進(jìn)程的CS、DS、SS中加入相應(yīng)的段選擇符,CPU根據(jù)段選擇符的TI字段索引相應(yīng)的段描述符表,獲得相應(yīng)的段描述符,并加載入CS、DS、SS對(duì)應(yīng)的程序不可見的段描述符寄存器。
2)程序執(zhí)行到讀/寫內(nèi)存中的數(shù)據(jù)時(shí),把程序中相應(yīng)變量的邏輯地址轉(zhuǎn)換為線性地址:
進(jìn)行必要的屬性、訪問權(quán)限檢查;
從DS對(duì)應(yīng)的段描述符寄存器獲得該段的基地址;
將變量的32位偏移量和段描述符中的基地址相加,獲得該變量的線性地址。
3.2 分頁機(jī)制
分段機(jī)制的目的是將內(nèi)存中的線性地址空間劃分成以基地址和長(zhǎng)度描述的多個(gè)段進(jìn)行管理,程序?qū)?yīng)的邏輯地址以基地址和偏移量來描述,實(shí)現(xiàn)邏輯地址到線性地址空間的映射。而分頁機(jī)制是使用單位“頁”來管理線性地址空間和物理地址空間的映射關(guān)系。同時(shí),分頁機(jī)制允許一個(gè)頁面存放在物理內(nèi)存中或磁盤的交換區(qū)域(如Linux下的Swap分區(qū),Windows下的虛擬內(nèi)存文件)中,程序可以使用比機(jī)器物理內(nèi)存更大的內(nèi)存區(qū)域,從而實(shí)現(xiàn)現(xiàn)代操作系統(tǒng)中虛擬內(nèi)存機(jī)制。(注意:操作系統(tǒng)的虛擬內(nèi)存原理和映射關(guān)系和后面要講的計(jì)算虛擬化技術(shù)中內(nèi)存虛擬化技術(shù)基本一致,只是VMM實(shí)現(xiàn)時(shí)又多了一層嵌套)。
在x86架構(gòu)下,頁的典型大小為4KB,于是一個(gè)4GB的線性地址空間被劃分成1024×1024個(gè)頁面,參見本文線性地址空間示意圖。物理地址空間的劃分與此類似。x86架構(gòu)允許大于4KB的頁面大。ㄈ2MB、4MB、1GB)等。
分頁機(jī)制的核心思想是通過頁表將線性地址轉(zhuǎn)換為物理地址,并配合旁路轉(zhuǎn)換緩沖區(qū)(Translation Lookaside Buffer,后面簡(jiǎn)稱為TLB)來加速地址轉(zhuǎn)換的過程。分頁機(jī)制主要由頁表、CR3寄存器和TLB三個(gè)部件構(gòu)成,如下圖所示。
內(nèi)存分頁機(jī)制
頁表是用于將線性地址轉(zhuǎn)換成物理地址的主要數(shù)據(jù)結(jié)構(gòu)。一個(gè)地址對(duì)齊到頁邊界后的值稱為頁幀號(hào)(或者頁框架),它實(shí)際上就是該地址所在頁面的基地址。比如:一個(gè)頁大小為4kB,那么第一個(gè)頁幀號(hào)就是0,第二個(gè)頁幀號(hào)就是4097,依次類推。線性地址對(duì)應(yīng)的頁幀號(hào)叫做虛擬頁幀號(hào)(Virtual Frame Number,下面簡(jiǎn)稱為VFN),物理地址對(duì)應(yīng)的頁幀號(hào)叫做物理頁幀號(hào)(Physical Frame Number,下面簡(jiǎn)稱為PFN)或機(jī)器頁幀號(hào)。頁表實(shí)際上是存儲(chǔ)VFN到PFN映射的數(shù)據(jù)結(jié)構(gòu)。
在傳統(tǒng)的32位的保護(hù)模式中(未啟用物理地址擴(kuò)展PAE功能),x86處理器使用兩級(jí)轉(zhuǎn)換方案,在這種方案中,CR3寄存器指向一個(gè)4KB大小的頁目錄表,頁目錄中共有1024個(gè)記錄,每一項(xiàng)記錄大小4B空間,都指向一個(gè)4KB大小的頁表,頁表中也有1024項(xiàng),每項(xiàng)大小4B空間,所以,最后整個(gè)線性地址空間大小就是1024 個(gè)長(zhǎng)為4KB的頁,即總共4GB大小的空間。未啟用PAE 的4KB大小的頁面如下圖所示。
頁表結(jié)構(gòu)
頁目錄項(xiàng)(Page Directory Entry,下面簡(jiǎn)稱為PDE),包含頁表的物理地地址,PDE存放在頁目錄表中。
頁表項(xiàng)(Page Table Entry,下面簡(jiǎn)稱為PTE):包含該線性地址對(duì)應(yīng)的物理頁幀號(hào)PFN,PTE存在頁表中,確定物理頁幀號(hào)PFN 后,再將線性地址的0~11位偏移量與其相加,就可以確定該線性地址對(duì)應(yīng)的物理地址。
虛擬內(nèi)存實(shí)現(xiàn)的關(guān)鍵在于PDE和PTE都包含一個(gè)P(Present)字段:
當(dāng)P=1時(shí),物理頁面存在于物理內(nèi)存中,CPU完成地址轉(zhuǎn)換后可以直接訪問該頁面。
當(dāng)P=0時(shí),物理頁面不在物理內(nèi)存中(在硬盤的交換分區(qū)中),當(dāng)CPU訪問該頁面時(shí),會(huì)產(chǎn)生一個(gè)缺頁錯(cuò)誤中斷,由操作系統(tǒng)的缺頁處理機(jī)制將存放在硬盤上的頁面調(diào)入物理內(nèi)存,使訪問可以繼續(xù)。同時(shí),由于程序的局部性特點(diǎn),操作系統(tǒng)會(huì)將該頁面附近的頁面一起調(diào)入物理內(nèi)存,方便CPU的訪問。所以,為了減少內(nèi)存占用,要求程序開發(fā)人員盡量少的使用全局索引或遞歸調(diào)用等機(jī)制。
P=0時(shí)的PDE和PTE的1~31位都將為操作系統(tǒng)提供物理頁面在硬盤上的信息,這些位存儲(chǔ)著物理頁面在硬盤上的位置。
啟用物理地址擴(kuò)展(之后簡(jiǎn)稱為PAE)后,頁表結(jié)構(gòu)將發(fā)生相應(yīng)的變化。頁表和頁目錄的總大小仍是4KB,但頁表和頁目錄中的表項(xiàng)都從32位擴(kuò)為64位,以使用附加的地址位。這樣,頁表和頁目錄都只有512個(gè)表項(xiàng),變成了原來方案的一半,所以又加入了一個(gè)級(jí):CR3指向頁目錄指針表,即一個(gè)包含4個(gè)頁目錄指針的表。啟用PAE 的4KB大小的頁面使用的三級(jí)頁表如下圖所示:
三級(jí)頁表結(jié)構(gòu)
CR3寄存器也稱為頁目錄基地址寄存器(Page-Directory Base Register,PDBR),存放著頁目錄的物理地址。一個(gè)進(jìn)程在運(yùn)行前,必須將其頁目錄的基地址存入CR3,而且,頁目錄的基地址必須對(duì)齊到4KB頁邊界。啟用PAE時(shí),CR3指向頁目錄指針表,每一項(xiàng)都指向一個(gè)頁目錄表,共有4個(gè)頁目錄表。
為了提高地址轉(zhuǎn)換的效率,x86架構(gòu)使用TLB對(duì)最近用到的頁面映射進(jìn)行緩存。TLB中存放著VFN到PFN的轉(zhuǎn)換記錄,當(dāng)CPU訪問某個(gè)線性地址時(shí),如果其所在頁面的映射存在于TLB中,無須查找頁表,即可得到該線性地址對(duì)應(yīng)的PFN,CPU 再將它與線性地址的偏移相加,就能得到最后的物理地址。
➢ x86架構(gòu)內(nèi)存管理中心分頁機(jī)制總結(jié):
1)CPU訪問一個(gè)線性地址,在TLB中進(jìn)行匹配,如果地址轉(zhuǎn)換在TLB中,則跳到步驟6。否則,發(fā)生了一次TLB Miss(TLB 缺失),繼續(xù)步驟2。
2)查找頁表,如果頁面在物理內(nèi)存中,則跳到步驟4。
3)如果頁面不在物理內(nèi)存中,則產(chǎn)生缺頁錯(cuò)誤,由操作系統(tǒng)的缺頁錯(cuò)誤處理程序進(jìn)行以下處理。
將頁面從磁盤復(fù)制到物理內(nèi)存中。
更改對(duì)應(yīng)的PTE,設(shè)置P 位為1,并對(duì)其他字段進(jìn)行相應(yīng)的設(shè)置。
刷新TLB 中對(duì)應(yīng)的PTE。
從缺頁錯(cuò)誤處理程序中返回。
4)此時(shí),頁面已經(jīng)存在于物理內(nèi)存中,并且頁表也已經(jīng)包含了這個(gè)映射。重新在TLB中進(jìn)行匹配,如果地址轉(zhuǎn)換在TLB中,則跳到步驟6。否則,發(fā)生了一次TLB Miss(TLB 缺失),繼續(xù)步驟5。
5)CPU重新查頁表,把對(duì)應(yīng)的映射插入到TLB中。
6)此時(shí),TLB已經(jīng)包含了該線性地址對(duì)應(yīng)的PFN。將PFN和線性地址中的偏移量相加,就得到了對(duì)應(yīng)的物理地址。
未完待續(xù)......