腳本語(yǔ)言的虛擬機(jī)和操作系統(tǒng)的虛擬機(jī)
虛擬機(jī)是個(gè)用軟件實(shí)現(xiàn)的CPU,而CPU的權(quán)限控制分為系統(tǒng)級(jí)和用戶級(jí)。
例如,Linux內(nèi)核就運(yùn)行在CPU的最高優(yōu)先級(jí)(ring0),而普通應(yīng)用程序則運(yùn)行在最低優(yōu)先級(jí)(ring3)。
雖然英特爾把CPU的權(quán)限分了4個(gè)優(yōu)先級(jí),但實(shí)際只用到了2個(gè)。
對(duì)于虛擬機(jī)來(lái)說(shuō),要想模擬操作系統(tǒng)的運(yùn)行,也必須進(jìn)行權(quán)限分級(jí)。
1,CPU的權(quán)限分級(jí),主要是指內(nèi)存的訪問(wèn)權(quán)限。
intel的CPU分為實(shí)模式和保護(hù)模式,保護(hù)模式最主要的作用就是保護(hù)內(nèi)存的訪問(wèn)權(quán)限。
內(nèi)核代碼可以訪問(wèn)所有的內(nèi)存,但是用戶代碼只能訪問(wèn)進(jìn)程的用戶空間(內(nèi)存)。
用戶空間的內(nèi)存是通過(guò)進(jìn)程的頁(yè)表來(lái)管理的,而進(jìn)程的頁(yè)表只能通過(guò)系統(tǒng)內(nèi)核來(lái)修改。
當(dāng)使用malloc()分配內(nèi)存的時(shí)候,實(shí)際上并不是分配一塊物理內(nèi)存,而只是把用戶空間的某一個(gè)內(nèi)存范圍設(shè)置為可用。
只有當(dāng)進(jìn)程代碼真去讀寫這個(gè)內(nèi)存范圍的時(shí)候,操作系統(tǒng)才會(huì)給它分配物理內(nèi)存,即Linux的寫時(shí)復(fù)制和需求加載機(jī)制。
所以虛擬機(jī)要想“模擬”操作系統(tǒng)的運(yùn)行,首先要模擬CPU的保護(hù)模式。
2,CPU保護(hù)模式的實(shí)現(xiàn),靠的就是幾個(gè)控制寄存器。
對(duì)于intel CPU來(lái)說(shuō),跟保護(hù)模式下相關(guān)的寄存器是cr0, cr1, cr2, cr3。
其中cr0用于控制分段和分頁(yè)機(jī)制,一旦開(kāi)啟內(nèi)存的分段機(jī)制就進(jìn)入了保護(hù)模式。
一旦開(kāi)啟了內(nèi)存的分頁(yè)機(jī)制,操作系統(tǒng)可以支持的進(jìn)程個(gè)數(shù)就是無(wú)限的了。
開(kāi)啟了分頁(yè)之后,操作系統(tǒng)就可以4096字節(jié)的一個(gè)頁(yè)為單位,為進(jìn)程分配“必需的”內(nèi)存空間,非常的靈活。
什么時(shí)候必需?
當(dāng)然是寫時(shí)復(fù)制和需求加載的時(shí)候必需,所以進(jìn)程剛創(chuàng)建時(shí)除了它的task_struct結(jié)構(gòu)之外,只需要給它分配4096字節(jié)做為頁(yè)目錄即可,其他的都可以跟父進(jìn)程共享。
對(duì)于多進(jìn)程多任務(wù)的操作系統(tǒng)來(lái)說(shuō),內(nèi)存的分頁(yè)機(jī)制是必需的,因?yàn)榉侄螜C(jī)制太死板了。
cr3就是頁(yè)目錄基地址寄存器,哪個(gè)進(jìn)程運(yùn)行時(shí)它就指向哪個(gè)進(jìn)程的頁(yè)表,內(nèi)核運(yùn)行時(shí)它就指向內(nèi)核頁(yè)表。
cr2在缺頁(yè)中斷時(shí)用于保存進(jìn)程用戶空間的內(nèi)存地址。在哪個(gè)位置出錯(cuò)了,就保存哪個(gè)地址,然后操作系統(tǒng)就會(huì)為那個(gè)位置(所在的內(nèi)存頁(yè))分配內(nèi)存。
獲取一個(gè)位置addr所在的內(nèi)存頁(yè)非常的簡(jiǎn)單,把它的最低12位清零就行,addr & ~0xfff
3,虛擬機(jī)要想模擬操作系統(tǒng)的運(yùn)行,必須自己實(shí)現(xiàn)MMU的功能。
操作系統(tǒng)的運(yùn)行,首先要依賴這幾個(gè)控制寄存器。
這幾個(gè)控制寄存器的主要作用,其實(shí)就是內(nèi)存管理。
在真實(shí)的硬件上,內(nèi)存管理是通過(guò)MMU實(shí)現(xiàn)的。MMU可以根據(jù)進(jìn)程的頁(yè)表實(shí)現(xiàn)用戶空間的內(nèi)存地址(線性地址)到物理內(nèi)存的映射。
如果在虛擬機(jī)上,這部分功能就只能通過(guò)代碼去實(shí)現(xiàn)了。
虛擬機(jī)要實(shí)現(xiàn)三層內(nèi)存地址的映射:虛擬進(jìn)程的用戶內(nèi)存地址 --> 虛擬物理內(nèi)存的物理地址 --> 虛擬機(jī)所在的真實(shí)進(jìn)程的用戶內(nèi)存地址。
OS虛擬機(jī)的內(nèi)存映射過(guò)程
所以像qemu這種能夠直接運(yùn)行Linux系統(tǒng)的大型虛擬機(jī),是必須要實(shí)現(xiàn)CPU的控制寄存器和系統(tǒng)級(jí)指令的。
系統(tǒng)級(jí)指令,指的是只能在內(nèi)核代碼(或引導(dǎo)扇區(qū))里運(yùn)行的指令,例如:
pushfl 把標(biāo)志寄存器壓棧,
mov cr2, eax 把導(dǎo)致缺頁(yè)的內(nèi)存地址讀到eax寄存器,
mov ax, cs 加載段選擇符,等等。
4,腳本語(yǔ)言的虛擬機(jī)
腳本語(yǔ)言因?yàn)槭沁\(yùn)行在用戶進(jìn)程中,運(yùn)行的代碼也是用戶態(tài)代碼,所以實(shí)現(xiàn)起來(lái)比qemu這類虛擬機(jī)要簡(jiǎn)單的多。
它只需要解釋一些常用指令就行了,不需要處理系統(tǒng)級(jí)的指令,也不需要管理復(fù)雜的內(nèi)存映射。
它只需要把編譯之后的字節(jié)碼文件根據(jù)程序頭的信息加載起來(lái),并且處理動(dòng)態(tài)庫(kù)函數(shù)的調(diào)用(動(dòng)態(tài)鏈接),就可以實(shí)現(xiàn)腳本語(yǔ)言的運(yùn)行了。
最主要的是,腳本語(yǔ)言的字節(jié)碼和編譯器都是腳本語(yǔ)言的作者設(shè)計(jì)的,作者可以實(shí)現(xiàn)字節(jié)碼和虛擬機(jī)的精確匹配,而不需要去實(shí)現(xiàn)CPU的整個(gè)指令集。
系統(tǒng)級(jí)的虛擬機(jī)就不得不實(shí)現(xiàn)CPU的整個(gè)指令集,因?yàn)镺S內(nèi)核被編譯之后有可能用到CPU的所有指令,其中任何一條指令沒(méi)被支持都可能導(dǎo)致內(nèi)核運(yùn)行失敗。
腳本語(yǔ)言的虛擬機(jī)怎么寫,之前已經(jīng)說(shuō)過(guò)了,不再細(xì)說(shuō)了。