【操作系统】虚拟化CPU、Memory,共享文件
幾個(gè)概念
- 物理的CPU被OS虛擬成了多個(gè)虛擬的CPU,這些虛擬CPU分別運(yùn)行各自的程序,這些正在運(yùn)行的程序被稱為進(jìn)程。
- 物理內(nèi)存被OS虛擬成了多個(gè)虛擬地址空間,每個(gè)進(jìn)程都有獨(dú)立的、自己的地址空間,程序的指令和數(shù)據(jù)都在地址空間中
- 磁盤被OS虛擬化為文件系統(tǒng),文件是被多個(gè)程序共享的,它并不是多個(gè)虛擬的磁盤,不過也不是無條件共享,涉及到例如互斥共享等多個(gè)問題,以后再談。
1 Virtualizing the CPU
我們在Linux系統(tǒng)上運(yùn)行C語言程序,體會(huì)一下虛擬化的意義。
Windows對多用戶的支持不是很好,相關(guān)的系統(tǒng)API可能也沒有,推薦適用Linux或Unix系統(tǒng)。
// cpu.c
#include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <sys/time.h> #include <assert.h>int main(int argc,int *argv[]){if(argc != 2){fprintf(stderr,"usage:cpu <string>\n");exit(1);}char *str = argv[1];for(int i = 0;i < 4;i++){sleep(1);printf("%s\n",str);}return 0; }這個(gè)程序很容易,需要運(yùn)行的時(shí)候輸入一個(gè)參數(shù),比如一個(gè)字符,值得解釋的是sleep(1),也就是讓程序暫停1秒,這非常重要,這意味著物理的CPU在這1s時(shí)間可以不用執(zhí)行該進(jìn)程,轉(zhuǎn)而執(zhí)行其他進(jìn)程。
注意,虛擬后的CPU,最終仍然要在真實(shí)物理CPU來執(zhí)行,要想讓每個(gè)進(jìn)程都得到執(zhí)行,那就應(yīng)該以合理的方式讓他們切換執(zhí)行。
我們先運(yùn)行一個(gè)進(jìn)程試試看,輸入命令./cpu A1:
打印了4個(gè)A1,并且是每隔1s打印一個(gè),這與我們的預(yù)期相符。
接下來,我們同時(shí)運(yùn)行多個(gè)進(jìn)程試試看,輸入命令./cpu A1 & ./cpu B2 & ./cpu C3 &
按照直觀的理解,不應(yīng)該是
A1
A1
A1
A1
B2
B2
B2
B2
C3
C3
C3
C3
不應(yīng)該是這樣嗎?但是看起來這3個(gè)進(jìn)程并不是順序執(zhí)行的,而是并發(fā)執(zhí)行的,也就是它們趁著其他進(jìn)程在sleep的時(shí)候,搶占了CPU去執(zhí)行自己了(注意,我們假設(shè)計(jì)算機(jī)只有1個(gè)CPU,而且是單核的)。
這樣一來,就出現(xiàn)了圖中的亂序了。
我們也能充分的感受到,不要讓物理CPU閑著的重要理念,同時(shí)我們也能想象到,多個(gè)進(jìn)程同時(shí)執(zhí)行,就會(huì)涉及到更多的問題,如果是之前的順序執(zhí)行,我們只需要進(jìn)程1執(zhí)行,其他等待–>進(jìn)程1執(zhí)行完成,進(jìn)程2執(zhí)行,其他等待–>進(jìn)程2執(zhí)行完成,進(jìn)程3執(zhí)行–>進(jìn)程3執(zhí)行完成。
也就是說,我們只需要等著一個(gè)程序執(zhí)行完,再執(zhí)行其他程序,這樣很簡單,但是效率非常低,比如,如果正在執(zhí)行的程序不使用CPU,去“sleep”了,或者去找I/O設(shè)備“玩”了,CPU就只能呆著,其他程序也不能進(jìn)來執(zhí)行,CPU利用率很低。
為了避免這種問題,現(xiàn)代OS都采用了類似多道批處理的技術(shù),正在執(zhí)行的程序不執(zhí)行時(shí),其他程序會(huì)進(jìn)入CPU執(zhí)行,而不會(huì)允許CPU空閑,要榨干CPU!
就如上面的程序,當(dāng)一個(gè)進(jìn)程sleep的時(shí)候,其他進(jìn)程就會(huì)進(jìn)入CPU執(zhí)行,但是,具體如何執(zhí)行,取決于OS的調(diào)度程序,取決于OS設(shè)計(jì)的策略,所以目前我們還不能得知它具體是如何運(yùn)作的(也許你可以查看Linux內(nèi)核,不過如果你有此能力,就不會(huì)看見這篇文章了)。
1.1 補(bǔ)充:實(shí)例中的C語言知識(shí)
以下請自學(xué)
1.1.1 main函數(shù)參數(shù),argc和argv
1.1.2 fprintf()
1.1.3 sleep()
2 Virtualizing Memory
我們先上代碼
// mem.c
#include <unistd.h> #include <stdio.h> #include <stdlib.h>int main(int argc,int *argv[]){int *p = malloc(sizeof(int)); // assert(p != NULL);printf("(%d) memory address of p: %08x\n",getpid(),(unsigned)p);*p = 0;for(int i = 0;i < 4;i++){sleep(1);*p = *p + 1;printf("(%d) p: %d\n",getpid(),*p);}return 0; }運(yùn)行程序./mem
運(yùn)行多個(gè)進(jìn)程:./mem & ./mem & ./mem &
這里,我們依然能夠看到的是虛擬化CPU,不過,虛擬化內(nèi)存在哪里呢?目前還看不出來,因?yàn)長inux默認(rèn)是啟動(dòng)地址空間隨機(jī)化的,這樣會(huì)讓系統(tǒng)更安全,不易受到攻擊,不過為了展現(xiàn)虛擬化內(nèi)存,我們應(yīng)該關(guān)掉它。
輸入命令sysctl -w kernel.randomize_va_space=0,再輸入./mem & ./mem & ./mem &
我們可以看到,三個(gè)進(jìn)程居然地址完全一樣!按理說,1個(gè)地址只能對應(yīng)1個(gè)進(jìn)程,所以,你就能體會(huì)到虛擬地址空間的含義了,這并不是真實(shí)的物理地址,它會(huì)通過某種機(jī)制,映射到真實(shí)物理地址去。
3 Sharing Disk Information
還記得我們剛才的兩個(gè)程序嗎?他們同時(shí)啟動(dòng)了多個(gè)進(jìn)程,并且,這幾個(gè)進(jìn)程是同一個(gè)程序,也就是說,同一個(gè)存儲(chǔ)在磁盤的文件,被多次讀取到了內(nèi)存,這也就意味著,磁盤信息是可以被同時(shí)多次讀取的,我們也可以說,這幾個(gè)進(jìn)程共享了一個(gè)磁盤文件。
思考:為什么內(nèi)存和CPU要虛擬化為多個(gè),而磁盤卻是共享的?
- 進(jìn)程是運(yùn)行中的程序,它是“活的;
- 程序是靜止在磁盤中的指令和數(shù)據(jù),它是“死的”。
對于正在運(yùn)行的進(jìn)程來說,我們需要為其獨(dú)立地分配一整套生態(tài)系統(tǒng),保證它正常執(zhí)行,并且每個(gè)程序運(yùn)行時(shí)候的結(jié)果可能不同,所以,就虛擬地提供了CPU和地址空間,讓它們是相互獨(dú)立的;而對于靜止的指令和數(shù)據(jù)來說,完全沒有必要虛擬成多份,那反而是浪費(fèi)空間,當(dāng)然這是針對讀取而言,寫入還需要視情況,不過整體來說,讀取信息是及其場景的,將磁盤設(shè)為共享也是合理的。
另外要談的是,磁盤文件必須通過軟件和硬件協(xié)作的方式,使其持久地保存,而不是很快就消失了,或者被其他數(shù)據(jù)覆蓋掉了。
4 Concurrency
虛擬化對應(yīng)的是進(jìn)程,而并發(fā)對應(yīng)的不僅僅是OS的進(jìn)程,在OS之上的應(yīng)用程序,也存在并發(fā)的問題,他就是多線程編程;虛擬化讓一個(gè)CPU能并發(fā)地執(zhí)行多個(gè)進(jìn)程,而一個(gè)進(jìn)程,也能并發(fā)地執(zhí)行多個(gè)線程。
你一定知道多線程編程,是的,就是那個(gè),我們現(xiàn)在重新審視一下它。
// threads.c
#include <stdio.h> #include <stdlib.h> #include <pthread.h>volatile int counter = 0; int loops;void *worker(void *arg) {int i;for (i = 0; i < loops; i++) {counter++;}return NULL; }int main(int argc, char *argv[]) {if (argc != 2) {fprintf(stderr, "usage: threads <value>\n");exit(1);}loops = atoi(argv[1]);pthread_t p1, p2;printf("Initial value : %d\n", counter);pthread_create(&p1, NULL, worker, NULL);pthread_create(&p2, NULL, worker, NULL);pthread_join(p1, NULL);pthread_join(p2, NULL);printf("Final value : %d\n", counter);return 0; }我們進(jìn)行編譯gcc threads.c -o threads -lpthread,注意,<pthread.h>不是Linux默認(rèn)的庫,編譯鏈接需要加上參數(shù)-lpthread,也就是需要鏈接額外的Import Library:libpthread.a。
我們進(jìn)行測試:
對于輸入的參數(shù)N,輸出結(jié)果應(yīng)該是2N(先知道事實(shí),看不懂多線程程序沒有關(guān)系),但是最后兩個(gè),當(dāng)參數(shù)足夠大,比如5億的時(shí)候,結(jié)果就詭異了。
這是由于計(jì)數(shù)器的值的更新不是原子操作,他需要:
- 內(nèi)存–>寄存器
- 寄存器遞增
- 寄存器–>內(nèi)存
3個(gè)步驟,但是,這幾個(gè)步驟可能被其他操作打斷,這就造成了結(jié)果的詭異。關(guān)于原子操作以后再說。
5 小結(jié)
我們談了幾件事兒
- 物理CPU – 虛擬化CPU – 多進(jìn)程并發(fā)
- 物理內(nèi)存 – 虛擬地址空間 – 進(jìn)程獨(dú)立地址空間
- 磁盤(持久性) – 文件系統(tǒng) – 共享磁盤信息
- OS之上的并發(fā):單個(gè)進(jìn)程中的多線程
版權(quán)聲明
本文是讀書筆記,來自于書籍《Operating System:Three Easy Pieces》
總結(jié)
以上是生活随笔為你收集整理的【操作系统】虚拟化CPU、Memory,共享文件的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 关于js方法中的循环和return
- 下一篇: 【操作系统】进程调度(2a):SJF(短