操作系統的核心功能就是管理電腦硬體,而CPU就是電腦中最核心的硬體。操作系統通過多進程圖像實現對CPU的管理。所以多進程圖像是操作系統的核心圖像。 ...
操作系統的核心功能就是管理電腦硬體,而CPU就是電腦中最核心的硬體。而通過學習筆記3的簡史回顧,操作系統通過多進程圖像實現對CPU的管理。所以多進程圖像是操作系統的核心圖像。
參考資料:
- 課程:哈工大操作系統(本部分對應 L8 && L9)
- 實驗:操作系統原理與實踐_Linux - 藍橋雲課 (lanqiao.cn)
- 筆記:操作系統學習導引 · 語雀 (yuque.com)
1. 從使用CPU開始直觀理解CPU管理
要想管理CPU,就要知道如何使用CPU。
CPU的工作原理已經很熟悉:
- 取指執行
- 程式存放在記憶體中,每段指令對應一個地址
- CPU發出取指命令,將想去地址通過地址匯流排傳到PC
- 記憶體根據地址取出對應地址的指令
- 從匯流排傳回,CPU解釋執行
所以,管理CPU最直觀的方法就是,設置PC的初值,CPU就能按照規則依次執行下去。
這一點在計組實驗的前四周手搖實驗室設備進行指令執行,也可以有類似的印象。
這樣做有什麼問題?
-
來看下麵一段程式
int main(int argc,char* argv[]){ int i,to,*fp,sum=0; to = atoi(agv[1]); for(i = 1; i <=to; i++){ sum = sum + i; fprintf(fp,"%d",sum); } }
-
如果要讓CPU工作,就是要讓PC指向這段程式的起始地址。
-
但是!程式和程式之間是不一樣的。例如將
fprintf()
替換為其他計算語句fprintf()
是一個IO指令,而替換為計算語句則成為計算指令 -
替換前後的運行時長進行比較,則前者:後者≈106:1
說明,IO特別慢
-
而假設我們遇到一種程式,有106個計算指令,然後一條IO指令,如果還是按照上面所說的設置PC初值,讓其自動執行,那麼對於CPU來說,其忙碌的計算指令只占到了總時長的一半(另一半在等待IO),利用率不高。
而如果IO語句再多一點,CPU利用率就更低了。
怎麼辦?
2. CPU管理的核心:併發
-
舉一個燒水的例子,首先往燒水壺裡倒水,然後放在插座上,然後就可以去做別的事情了,等燒水壺響了,這就是中斷,這時我們就可以來用燒水壺裡的熱水了,燒水的過程就類似IO
-
所以解決方案為:多道程式交替執行,一個CPU上交替執行多個程式,即併發
這樣一道程式執行到像IO這樣慢的步驟時,CPU切換到另一個程式進行,而另一個程式進入等待後,再切換回來。
可見,上圖兩個程式A、B充分利用了CPU的計算資源,總時長從80降到了45.
註意兩個名詞:並行和併發:
並行多人同時工作,併發一個人交替工作。
並且這裡一個隱含條件是切換程式的開銷要小於運行程式的開銷。
如何實現併發呢?
-
即控制 PC 進行切換
-
適當的時候修改PC,使得PC指向另一個程式的指令,但是只修改PC會有問題
-
例如下圖左右兩個程式,當PC按照邏輯切換回地址53繼續程式1的執行,那麼ax和bx寄存器應當存儲什麼值?
很顯然,如果要繼續程式1,當然應當為1 和 1,而不是 10 和 10.
所以當程式切換時,除了切換PC,還要切換很多內容
-
我們需要記錄 切換前的上下文,保護現場。
-
每個程式有一個存放信息的結構:PCB,process control block,進程式控制制塊。
就像我們正在看書,突然被人叫走做別的事,我們就應當停下來,記錄當前頁碼以及故事情節,然後離開,這樣回來後才能繼續閱讀。
-
這樣,我們實際運行過程中的程式,就跟我們單純彙編得到的代碼不一樣了。即運行程式和靜態程式不一樣。
不同之處簡單來說就在於需要PCB來記錄程式運行起來的樣子。
而程式 + 所有這些不一樣 ---> 進程
-
如何描述這種不同呢?
!進程! 這個概念就用來刻畫運行中的程式。比如上圖中的程式1 和程式2,就是兩個進程。
也即進行中的程式,名字其實很形象。
- 進程有開始、結束,程式沒有;
- 進程會走走停停,是動態的,有狀態的,而程式沒有;
- 進程需要記錄ax,bx..... 程式不用;
3. 簡單總結1
-
到這裡,我們進程描述CPU的管理:
-
使用CPU:啟動一個進程,讓CPU去執行這個進程;
-
更高效的使用CPU:啟動多個進程,讓CPU去執行多個進程;
-
跑多個程式/進程的樣子,就是CPU管理的核心樣子。
這就是多進程圖像。
-
4. 多進程圖像
前文講到,為了讓CPU更好的工作,我們需要讓CPU執行多進程,而這個過程如何表徵呢?
- 對於用戶而言
- 就是一個個 PID 進程號;
- 可供用戶查看各進程運行情況;
- 對於下層操作系統而言
- 負責管理 各個進程;具體為記錄情況、按照合理的次序推進;
- 分配資源、進行調度;
多進程圖像從開機一直存在到關機結束。
4.1 開機到關機過程中的多進程圖像
-
系統啟動時,最後啟動的 main.c 中最後執行了
fork()
if(!fork()){init();} // fork,啟動進程的介面
代碼意思是:啟動一個進程,執行
init()
,即執行 shell,接下來就能再 shell 里操作,這就是電腦提供給用戶使用的界面(初代版本)。可以理解為,操作系統要讓用戶使用電腦,需要創建一個初始化的進程。
補充1:
shell是一個子進程,父進程(main函數)因為成功創建子進程,所以fork()>0 不進
init
而子進程fork()==0 進入init,啟動shell補充2:
fork()函數返回值是0或1, 返回0代表當前進程是新fork出來的子進程, 非零(也就是為1)代表當前進程為父進程, if條件里的就是父進程的邏輯,一直等待用戶輸入命令, 然後執行, 一直重覆進行
-
shell 再根據用戶輸入啟動其他進程,執行用戶的命令也是在創建進程;
// shell 的核心代碼 int main(){ while(1){ scanf("%s",cmd); if(!fork()){ exec(cmd); wait(); } } }
-
此後,電腦每執行一個任務,就開啟一個進程。
4.2 查看當前進程情況 | 任務管理器
在 win10 以上版本中,Ctrl + Shift + Esc 就可看到任務管理器。
- 其中Explorer是整個Windows的文件系統,如果關掉整個進程,就只能看見背景了。
- 如果感覺電腦特別慢,就可以打開任務管理器,查看占用CPU資源比例大的進程。
- 操作系統就是通過管理進程,來管理用戶對電腦的使用。
4.3 操作系統如何實現多進程圖像
為了實現多進程圖像,操作系統都應該解決哪些問題?
- 多進程如何組織?
- 多進程如何切換?
- 多進程交替時,如何相互影響?
-
多進程如何組織?也即多進程如何存放?
-
操作系統感知進程依賴於PCB,組織和存放進程也靠PCB,通過PCB形成一些數據結構(隊列),來組織多進程;如下圖:
PCB在這裡相當於結構體,組成數據結構的基本單位。
-
組織好多進程,才能合理推進多進程。
-
-
如何推進多進程?
-
一個進程正在執行
-
另一些進程在排隊(就緒隊列)等待執行
-
還有一些在等待觸發事件,即使排到也不能調度執行
比如上圖中的第三列PCB,在等待磁碟操作。
PCB是用來記錄進程信息的數據結構
-
總結:多進程對應的PCB分別放在不同的地方,執行不同的處理。
-
把進程通過狀態區分開來,通過操作系統對進程狀態的轉移控制,多進程就向前推進了。
-
-
多進程如何交替/切換?
這部分後續會詳細講解,下麵還是簡略的過程。
-
情境:一個進程啟動磁碟讀寫,等待時進行切換。
下圖展示了關鍵代碼,代碼註釋見圖中紅色字體;
-
schedule()函數是重點,即調度函數;
-
下圖中的
getNext
從就緒隊列中挑出下一個需要占用CPU的進程;選擇哪一個進程合適,即進程調度問題,也會用一講來講解。
-
switch_to
就是用 PCB 進行進程上下文的切換,pCur
、pNew
分別指當前進程的 PCB 和調度得到的下一個進程的 PCB ,即進行執行現場的更替。交替的三部分:
- 隊列操作+調度+切換
-
-
進程如何調度?
- 這裡先講兩個基礎調度演算法。
- FIFO,First In First Out.
- 顯然是比較公平的策略,但是沒有考慮進程執行的任務輕重緩急;
- Priority.
- 對進程賦予優先順序,但如何賦予也是個問題。
- FIFO,First In First Out.
- 這裡先講兩個基礎調度演算法。
-
切換進程
-
調度找到下一個占用CPU的進程後,就要進行切換;
這個過程需要精細控制,所以需要 彙編代碼,下圖為偽代碼;
-
做的事情也不難想象,先把將要停下的進程信息保存到PCB1中(將當前CPU的各種信息(寄存器等)保存到pCur中),
再從將要進行的進程的PCB2中取出信息賦到對應寄存器/位置(將pNew中的寄存器等信息恢復到CPU中)
-
-
多進程交替時,如何相互影響?
互斥、鎖的概念。
-
多進程看似不打照面,但實際上它們同時在一個記憶體來存放。
多個進程交替執行會相互影響,包括正面的多進程合作,負面的記憶體地址衝突等等
-
比如,進程1中,修改了某個地址的值,而這個地址,正好時進程2 包含的地址,這時就會引起進程2崩潰。
-
如何解決進程間矛盾?
限制對進程2地址的讀寫。即:!記憶體映射!
其實涉及記憶體管理了,可見記憶體管理也服務於CPU管理的多進程圖像。
通過一個映射表,將真實物理地址轉化為虛擬存儲地址;
兩個進程的100記憶體地址,是虛擬邏輯地址,會映射到不同的物理記憶體;下圖中展示了兩個進程的100地址分別映射到了物理地址780和1260
-
還有一些時候,進程之間需要進行合作,如何進行進程間合作?
-
-
-
舉例1(淺顯):
-
- 不同的應用程式提交列印任務,列印任務會被放到“待列印文件隊列”
- 列印進程會從“待列印文件隊列”中一個接一個的取出列印任務,控制印表機列印
- 如果對存入列印進程的任務不進行管理,如任務1沒放完,任務2就開始放,後面切換時就會出現順序執行所不會遇到的亂序問題。
-
舉例2(稍深):生產者-消費者實例
-
-
、
-
生產者和消費者通過共用數據
buffer[]
進行合作 -
如果緩衝區滿了,就不應該再放了,
用
counter
記錄,如果==buffer_size,說明滿了,死迴圈;沒滿則counter++
;如果要避免緩衝區滿而還向里放的情況,counter 這個信號量必須要保持正確(我突然感覺這是工程代碼調試的一個關鍵)
-
如果多個進程都在記憶體中交替執行,counter可能就會出錯。
下麵是個具體的例子:
初始counter=5,生產者執行counter++,消費者執行counter--,在寄存器層面將會是:
// 生產者P register = counter; register = register+1; counter = register; // 消費者C register = counter; register = register -1; counter = register;
當生產者的程式執行到中間切換到消費者,可能的代碼序列如右上角所示,counter 直接亂了。後續合作就也會亂套。
-
解決合作問題(合作各方的合理推進順序)的核心在於 !進程同步!
給 counter 上鎖,即寫 counter 時阻斷其他進程訪問 counter.
-
-
5. 簡單總結2
-
理解CPU管理的基本想法
-
直觀感受了操作系統的多進程
-
具體瞭解了多進程圖像,探討了操作系統如何實現多進程圖像。
補充操作系統 多進程圖像的發展:
批處理(順序)--->多道程式處理---->分時系統
-
這時多進程圖像的大致輪廓,後續會一一展開: