引入
操作系統中最核心的概念是進程:這是對正在運行程序的一個抽象。一個進程就是一個正在執行程序的實例、包括程序計數器、寄存器和變量的當前值。在多道程序設計中,一個 CPU 能在多個進程之間來回快速切換,達到(僞)並行效果。一個進程是某種類型的一個活動,它有程序、輸入、輸出以及狀態。
操作系統的關鍵抽象:
內存大小有限,操作系統會爲每個進程分配獨立的虛擬地址空間。虛擬地址空間會映射到物理地址空間。JAVA虛擬機和VMWARE並沒有CPU,硬盤等,可以說是一個軟的虛擬機。
比喻 : 廚師做蛋糕
- 做蛋糕的食譜: 程序
- 做蛋糕的原料:輸入數據
- 廚師: CPU
廚師閱讀食譜,用原料做蛋糕的一系列動作的總和: 進程
廚師的兒子跑進了,說是被蜜蜂蟄了
- 廚師記錄下當前做到哪一步了(保存當前進程狀態)
- 拿出急救手冊,按其中的指示進行處理(開始另外一個進程)
進程在虛擬地址存儲器中的邏輯佈局
每個進程都有一個虛擬的地址空間, 這個地址空間是分塊的。
程序運行中使用了多個寄存器,但是CPU只有一套寄存器。進程切換,寄存器的值需要保存在內存中,否則會被其他進程沖掉。
內存中的進程
struct task_struct{
pid_t pid; //進程號
long state; //狀態
cputime_t utime, stime; // cpu在用戶態和核心態下
執行的時間
struct files_struct *files; //打開的文件
struct mm_struct *mm; //進程使用的內存
......
}
進程的狀態
運行–>>就緒: 進程在CPU中時間片用完
運行–>>等待:IO操作等。 等待就是說放入CPU維護的一個隊列中去,根本沒有佔用cpu,真正佔用CPU的是運行中的進程。
進程調度:到底誰應該佔據CPU
- 非搶佔式
- 調度程序一旦把 CPU分配給某一進程後便讓它一直運行下去, 直到進程完成或發生某事件而不能運行時,纔將CPU分給其它進程。
- 適用於批處理系統
- 簡單、系統開銷小。
- 搶佔式
- 當一個進程正在執行時,系統可以基於某種策略剝奪CPU給其它進程。剝奪的原則有: 優先權原則、短進程優先原則、時間片原則
- 適用於交互式系統
進程調度:評價標準
公平
- 合理的分配CPU
響應時間短
- 響應時間: 從用戶輸入到產生反應的時間
吐量大
- 吞吐量: 單位時間完成的任務數量
但是, 這些目標是矛盾的!
批處理系統中的調度
先來先服務
- 公平、簡單(FIFO隊列)、非搶佔、不適合交互式
最短作業優先
- 系統的平均等待時間最短
- 但是需要預先知道每個任務的運行時間
交互式調度策略(1) :輪轉
每個進程分配一個固定的時間片
假設進程切換一次的開銷爲1ms
時間片爲4ms
- 20%的時間浪費在切換上
- 時間片爲100ms
- 浪費只有1%, 但是假設有50個進程, 最後一個需要等待5秒 !
交互式系統調度策略(2):優先級
缺點:低優先級的進程可能會被餓死(永遠得不到執行)。
調度策略(3):多級隊列反饋
每個級別都有若干進程,每次執行都選擇高優先級的進程執行,高優先級執行完了在執行低優先級。每一個進程執行完放到同一隊列的尾部,同一級別採用時間片輪轉策略。優先級高分配的時間短。還是會出現低優先級餓死,任務的優先級隨着等待的時間增長優先級不斷增高,動態調整。
進程間同步
生產者進程向隊列中加入文件,消費者進程從隊列中取走文件
生產消費模型代碼實現:
- 多進程情況下, 共享變量counter會出錯!
- 上圖中 regist 類比成 寄存器
- 對一個變量的操作,編譯成機器碼會是多行代碼。所以我們在JAVA中寫的count++不是一個原子操作。亂序執行的情況下就會出問題。
反思一下
問題的核心
- 不可控制的調度
- 在機器層面, counter++, counter– 並不是原子操作
臨界區
- 訪問/修改共享資源(變量, 表,文件。。。)
- 當進程進入臨界區時,不允許其他進程在臨界
區執行
解決臨界區問題
解決臨界區問題(1):暴力手段,關閉中斷
CPU 收到時鐘中斷以後,會檢查當前進程的時間片是否用完, 用完則切換。
在進程P進入臨界區之前,通知OS, 不要做進程切換不就行了!
- 關閉中斷 (時鐘中斷), 這樣CPU就不會被打斷
- 離開臨界區,一定要記住打開中斷
- 但是,把中斷操作開放給應用程序是非常危險的。會讓宕機。
解決臨界區問題(2):用硬件指令來實現鎖
- 上圖三條代碼是原子的
爲了確保原子性,系統會鎖住總線,禁止其他CPU在函數執行完之前去訪問內存
解決臨界區問題(3):信號量
信號量S是個整數變量,除了初始化外,有兩個操作,wait(), signal()
或者是P/V , 或者 down/up。
僞代碼:
爲了確保信號量能工作,需要用一種“原子”的方式實現它
解決臨界區問題(5):不能忙等
list維護了一個等待進程列表。不像上一種方式在鎖住的情況下,進程雖然獲得了時間片但是什麼事也幹不了。通過維護一個等待進程列表解決了忙等問題。
解決臨界區問題(6):用信號量解決打印問題
思考
- 兩個進程之間是怎麼共享變量的?
兩個進程之間通過操作系統內核共享數據