NFV關(guān)鍵技術(shù):x86架構(gòu)基礎(chǔ)(下篇)
Labs 導(dǎo)讀
標(biāo)準(zhǔn)服務(wù)器技術(shù)是網(wǎng)絡(luò)功能虛擬化(NFV)實現(xiàn)的一個關(guān)鍵因素,了解一些x86架構(gòu)的基礎(chǔ)知識對大家后續(xù)了解電信云關(guān)鍵技術(shù),尤其是掌握虛擬化技術(shù)原理和關(guān)鍵優(yōu)化方案是必須具備的。本文接著上篇從x86架構(gòu)的中斷和異常、IO架構(gòu)等部分進(jìn)行闡述講解。
1、中斷與異常
程序的執(zhí)行往往不只是按順序執(zhí)行那么簡單,一些異常和中斷會打斷順序執(zhí)行的程序流,轉(zhuǎn)而進(jìn)入一條完全不同的執(zhí)行路徑。中斷提供給外部設(shè)備一種“打斷CPU當(dāng)前執(zhí)行任務(wù),并響應(yīng)自身服務(wù)”的手段。中斷(interrupt)是異步的事件,典型的比如由I/O設(shè)備觸發(fā);異常(exception)是同步的事件,典型的比如處理器執(zhí)行某條指令時發(fā)現(xiàn)出錯了等等,其實異常的本質(zhì)就是同步中斷。
中斷通常被定義為一個打斷CPU芯片指令執(zhí)行的事件,該事件對應(yīng)到CPU芯片內(nèi)部或者外部的電路產(chǎn)生的電子信號。
中斷信號可以被劃分為同步中斷和異步中斷:
- 同步中斷,該類型中斷由CPU的控制單元在執(zhí)行指令的時候產(chǎn)生,并且是在當(dāng)前指令執(zhí)行完畢下一個指令執(zhí)行之前產(chǎn)生。
- 異步中斷,該類型中斷由其他硬件設(shè)備在任意的時間產(chǎn)生,并且遵循CPU的時鐘信號傳遞給CPU。
對于Intel的CPU而言,它將同步中斷稱作異常,而將異步中斷稱作中斷。
通常中斷(即異步中斷)由時鐘定時器或者其他I/O設(shè)備產(chǎn)生,如鍵盤接收到敲擊某個按鍵的信號后產(chǎn)生的中斷信號。而異常(即同步中斷)則通常由于編程錯誤或者由CPU檢測到異常條件需要內(nèi)核進(jìn)行處理而產(chǎn)生,如上面講到的Page Fault Exception(缺頁異常),異??梢杂沙绦蛲ㄟ^int或者sysenter指令主動產(chǎn)生。
對于Intel x86 CPU而言,它將中斷和異常進(jìn)行了如下歸類:
中斷,即異步中斷,中斷信息隨著CPU的時鐘信號傳遞到CPU內(nèi)部。中斷分為可屏蔽中斷和不可屏蔽中斷兩類。
- 可屏蔽中斷,所有由I/O設(shè)備產(chǎn)生的IRQ請求都被歸為可屏蔽中斷。一個可屏蔽中斷可以有兩種狀態(tài),屏蔽或者不屏蔽,當(dāng)一個中斷被屏蔽時,該中斷信號將被對應(yīng)的控制單元所忽略。
- 不可屏蔽中斷,即控制單元無法忽略該類型的中斷信號,CPU肯定會接收到該類型的中斷,一般對應(yīng)到一些緊要的事件,比如硬件錯誤。
異常,即同步中斷,中斷信號在CPU執(zhí)行完某個指令后產(chǎn)生并接收到。處理器檢測到的異常,即當(dāng)CPU執(zhí)行指令的時候檢測到硬件上存在一些異常條件的時候就會產(chǎn)生該信號。這種類型的異常根據(jù)產(chǎn)生時在內(nèi)核堆棧中保存的EIP寄存器的值(即異?;謴?fù)后CPU重新執(zhí)行的位置)進(jìn)行細(xì)分:
- Faults,該異常可以被內(nèi)核正確糾正,并且糾正后重新執(zhí)行引起該異常的指令時不會造成程序的中斷或者功能的異常。這時候保存到EIP寄存器的值是引起異常的指令的地址,故異常恢復(fù)的時候會重新執(zhí)行該指令,如Page Fault Exception(缺頁異常),當(dāng)訪問的內(nèi)存地址沒有被映射到物理內(nèi)存時,產(chǎn)生異常,內(nèi)核分配新的物理內(nèi)存頁并建立映射關(guān)系,然后異常處理完畢后,CPU重新訪問該地址,即可訪問到正確的物理內(nèi)存。
- Traps,該異常發(fā)生時,內(nèi)核堆棧EIP寄存器保存的地址指向引起該異常的指令的下一條指令,即當(dāng)該異常處理返回后會繼續(xù)程序的執(zhí)行,而不是重新執(zhí)行引起異常的指令。x86 CPU的硬件虛擬化功能就是利用陷入(Traps)再模擬的方法,當(dāng)CPU執(zhí)行虛擬機指令的時候,如果執(zhí)行的是敏感指令,就會觸發(fā)Traps類型的異常,讓VMM(Virtual Machine Monitor)對該敏感指令進(jìn)行模擬,然后繼續(xù)恢復(fù)虛擬機的運行。
- Aborts,當(dāng)發(fā)生嚴(yán)重的錯誤時,CPU已經(jīng)無法保證內(nèi)核堆棧中EIP寄存器存放的值是引起該異常的指令的地址。該異常用于匯報嚴(yán)重的錯誤,如硬件錯誤或者是內(nèi)存的不一致性。該異常信號讓CPU切換到相應(yīng)的abort exception handler,該處理函數(shù)由于無法確認(rèn)錯誤,只能結(jié)束當(dāng)前進(jìn)程。
我們在寫程序時,經(jīng)常會在容易產(chǎn)生錯誤的地方進(jìn)行異常拋出,然后針對拋出的異常定義執(zhí)行策略。這類編程產(chǎn)生的異常,由程序主動執(zhí)行int或者int3之類的指令產(chǎn)生。CPU像處理Traps一樣處理這些程序主動產(chǎn)生的異常,該類異常通常被稱為軟件中斷(software interrupt)。這類異常主要有兩種用途:實現(xiàn)系統(tǒng)調(diào)用和通知某個debugger特定的事件發(fā)生。
這些異?;蛑袛嘤?~255的數(shù)字唯一標(biāo)識,也就是經(jīng)常說的中斷信號量。對于不可屏蔽中斷和異常來說,相應(yīng)的中斷信號量是固定的,而可屏蔽中斷對應(yīng)的中斷信號量則可以通過設(shè)置中斷控制器來更改。
2、x86系統(tǒng)的I/O架構(gòu)
計算機所處理的任務(wù)其實只有兩種:CPU運算和I/O操作。這部分內(nèi)容是后續(xù)學(xué)習(xí)計算虛擬化中I/O虛擬化的基礎(chǔ)。I/O(輸入/輸出)是CPU訪問外部設(shè)備的方法。設(shè)備通常通過寄存器和設(shè)備RAM將自身功能展現(xiàn)給CPU,CPU通過讀/寫這些寄存器和RAM完成對設(shè)備的訪問及其他操作。按訪問方式的不同,x86架構(gòu)的I/O分為如下兩類:
2.1 端口I/O(后文簡稱為Port I/O)
即通過I/O端口訪問設(shè)備寄存器。x86有65536個8位的I/O端口,編號為0x0~0xFFFF。CPU將端口號作為設(shè)備端口的地址,進(jìn)而對設(shè)備進(jìn)行訪問。這65536個端口構(gòu)成了64KB的I/O端口地址空間。I/O端口地址空間是獨立的,不是線性地址空間或物理地址空間的一部分。需要使用特定的操作命令I(lǐng)N/OUT對端口進(jìn)行訪問,此時CPU通過一個特殊的芯片管腳標(biāo)識這是一次I/O端口訪問,于是芯片組知道地址線上的地址是I/O端口號并相應(yīng)地完成操作。此外,2個或4個連續(xù)的8位I/O端口可以組成16位或32位的I/O端口。
2.2 內(nèi)存映射I/O(Memory Map I/O,后文簡稱為MMIO)
即通過內(nèi)存訪問的形式訪問設(shè)備寄存器或設(shè)備RAM。MMIO要占用CPU的物理地址空間,它將設(shè)備寄存器或設(shè)備RAM映射到物理地址空間的某段地址,然后使用MOV等訪存指令訪問此段地址,即可訪問到映射的設(shè)備。MMIO方式訪問設(shè)備也需要進(jìn)行線性地址到物理地址的轉(zhuǎn)換,但是這個轉(zhuǎn)換過程中的MMIO地址不可緩存到TLB中。MMIO是一種更普遍、更先進(jìn)的I/O訪問方式,很多CPU 架構(gòu)都沒有Port I/O,采用統(tǒng)一的MMIO方式。
3、DMA技術(shù)
直接內(nèi)存訪問(Direct Memory Access,后文簡稱為DMA)是所有現(xiàn)代計算機的重要特色。DMA允許設(shè)備繞開CPU直接向內(nèi)存中復(fù)制或讀取數(shù)據(jù)。如果設(shè)備向內(nèi)存復(fù)制數(shù)據(jù)都經(jīng)過CPU,則CPU會有大量中斷負(fù)載,中斷過程中,CPU對其他任務(wù)來講無法使用,不利于系統(tǒng)性能的提高。通過DMA,CPU只負(fù)責(zé)初始化這個傳輸動作,而傳輸動作本身由DMA 控制器(簡稱為DMAC)來實行和完成。在實現(xiàn)DMA傳輸時,由DMAC直接控制總線,在DMA傳輸前,CPU要把總線控制權(quán)交給DMAC,結(jié)束DMA傳輸后,DMAC立即把總線控制權(quán)交回給CPU。
一個完整的DMA 傳輸過程的基本流程如下:
- DMA請求:CPU對DMAC進(jìn)行初始化,并向I/O端口發(fā)出操作命令,I/O端口提出DMA請求。
- DMA響應(yīng):DMAC對DMA請求進(jìn)行優(yōu)先級判別和屏蔽判別,然后向總線控制芯片提出總線請。CPU執(zhí)行完當(dāng)前總線周期后釋放總線控制權(quán)。此時,總線控制芯片發(fā)出總線應(yīng)答,表示DMA請求已被響應(yīng),并通過DMAC通知I/O端口開始DMA傳輸。
- DMA傳輸:DMAC獲得總線控制權(quán)后,CPU即可掛起或只執(zhí)行內(nèi)部操作,由DMAC發(fā)出讀/寫命令,直接控制RAM與I/O端口進(jìn)行DMA傳輸。
- DMA結(jié)束:當(dāng)完成規(guī)定的成批數(shù)據(jù)傳送后,DMAC釋放總線控制權(quán),并向I/O端口發(fā)出結(jié)束信號。當(dāng)I/O端口接收到結(jié)束信號后,停止I/O設(shè)備的工作并向CPU提出中斷請求,使CPU執(zhí)行一段檢查本次DMA傳輸操作正確性判斷的代碼,并從不介入的狀態(tài)退出。
由此可見,DMA無須CPU直接控制傳輸,也沒有中斷處理方式那樣保留現(xiàn)場和恢復(fù)現(xiàn)場的過程,通過硬件(DMAC)為RAM與I/O設(shè)備開辟了一條直接傳送數(shù)據(jù)的通路,極大地提高了CPU效率。需要注意的是,DMA操作訪問的必須是連續(xù)的物理內(nèi)存。DMA 傳輸?shù)倪^程如下圖所示。
4、進(jìn)程、線程和協(xié)程
4.1 什么是進(jìn)程和線程
進(jìn)程是什么呢?大白話講,進(jìn)程就是應(yīng)用程序的啟動實例。比如我們運行一個游戲,打開一個軟件,就是開啟了一個進(jìn)程。進(jìn)程擁有代碼和打開的文件資源、數(shù)據(jù)資源、獨立的內(nèi)存空間。
線程又是什么呢?線程從屬于進(jìn)程,是程序的實際執(zhí)行者。一個進(jìn)程至少包含一個主線程,也可以有更多的子線程。線程擁有自己的??臻g。
對操作系統(tǒng)來說,線程是最小的執(zhí)行單元,進(jìn)程是最小的資源管理單元。無論進(jìn)程還是線程,都是由操作系統(tǒng)所管理的。線程一般具有五種狀態(tài):初始化>>>可運行>>>運行中>>>阻塞>>>銷毀。線程不同狀態(tài)之間的轉(zhuǎn)化均需要CPU開銷來完成。
4.2 什么是協(xié)程
協(xié)程英文Coroutines,是一種比線程更加輕量級的存在。正如一個進(jìn)程可以擁有多個線程一樣,一個線程也可以擁有多個協(xié)程。最重要的是,協(xié)程不是被操作系統(tǒng)內(nèi)核所管理,而完全是由程序所控制(也就是在用戶態(tài)執(zhí)行)。這樣帶來的好處就是性能得到了很大的提升,不會像線程切換那樣消耗資源。
在Python語言中有個生成器的概念,里面有個關(guān)鍵字yield,當(dāng)程序執(zhí)行到y(tǒng)ield關(guān)鍵字時,會暫停在那一行,等到主線程調(diào)用send方法發(fā)送了數(shù)據(jù),協(xié)程才會接到數(shù)據(jù)繼續(xù)執(zhí)行。但是,yield讓程序暫停,和線程的阻塞是有本質(zhì)區(qū)別的。通過yield關(guān)鍵字的暫停完全由程序控制,線程的阻塞狀態(tài)是由操作系統(tǒng)內(nèi)核來進(jìn)行切換。大家可以在Python腳本中寫入如下代碼并執(zhí)行體驗下:
- def consume():
- while True:
- # consume等待接收數(shù)據(jù)
- number = yield
- print("我要執(zhí)行啦。。。。開始計數(shù):",number)
- consumer = consume()
- next(consumer)
- for num in range(0,100):
- print("開始執(zhí)行:",num)
- consumer.send(num)
協(xié)程Python代碼
NFV關(guān)鍵技術(shù):X86架構(gòu)基礎(chǔ)(上篇)
【本文為51CTO專欄作者“移動Labs”原創(chuàng)稿件,轉(zhuǎn)載請聯(lián)系原作者】