(進程控制)
導讀
大家好,很高興又和大家見面啦!!!
在上一篇內容中,我們共同探討了進程控制的基本概念與實現原理:
- 進程控制是操作系統對進程實施有效管理的核心功能,它通過特定的機制實現進程的創建、終止以及各種狀態間的轉換,從而確保多進程能夠高效併發執行。
我們重點學習了實現進程控制的關鍵工具——原語:
- 原語是由若干指令組成的、用於完成特定功能的過程,具有不可分割性,即執行必須是連續的,在執行過程中不允許被中斷。
這種"原子操作"特性是確保進程狀態轉換一致性和可靠性的基礎。
還記得我們討論過的關鍵問題嗎?——為什麼進程控制必須通過原語來實現?
正是因為進程狀態的轉換涉及多個步驟
- 修改狀態標誌
- 將進程PCB插入相應隊列
- $\cdots$
如果這些操作在執行過程中被中斷,就可能導致進程狀態不一致,進而引發系統錯誤。
原語通過關中斷和開中斷這兩條特權指令,確保了一系列操作的原子性,從而避免了此類問題。
理解了進程控制的"為什麼"和"如何實現"之後,我們現在很自然會問:
- 操作系統具體是如何運用不同的原語來管理進程的完整生命週期的?
- 各種原語的執行細節和應用場景又是怎樣的?
現在,就讓我們帶着這些問題,一起深入探討進程創建、終止、阻塞與喚醒、切換這四類核心原語的具體實現機制,揭開操作系統精準控制進程生命週期的神秘面紗。
一、進程的創建
1.1 創建原語
創建原語指的是操作系統創建一個進程時使用的原語。
操作系統在創建一個新的進程時,會執行以下操作:
- 為新進程分配一個唯一的進程標識號(PID),並申請一個空白 PCB。
- 由於 PCB 是有限的,因此在創建的過程中可能會因為 PCB 申請失敗而導致進程創建失敗
- 為進程分配其運行所需的資源,如內存、文件、I/O 設備 和 CPU 時間等。
- 這些資源可以從操作系統中獲取,也可以從其父進程中獲取。
- 若資源不足(如內存),並不會導致進程創建失敗,而是使該正在創建的進程進入創建態,等待分配所缺失的資源
- 初始化 PCB。
- 該過程主要包括初始化以下信息:
- 標誌信息
- CPU 狀態信息
- CPU 控制信息
- 設置進程的優先級等
- 該過程主要包括初始化以下信息:
- 將 PCB 插入到就緒隊列。
- 若就緒隊列能夠容納新進程,則將新進程插入就緒隊列,等待被調度運行
- 若就緒隊列無法容納新進程,該進程會被掛起
- 就緒隊列的容量是有限的,以下原因均會導致就緒隊列無法容納新進程:
- 系統負載過高:同時運行的進程過多,耗盡了內存資源。
- 內存不足:物理內存有限,無法為更多進程提供運行空間。
- 負荷調節:操作系統為了確保系統整體性能,主動掛起一些不重要的進程。
1.2 創建進程的操作
在操作系統中,當執行以下操作時,均會創建一個新的進程:
- 終端用户登錄系統
- 在分時操作系統中,用户登錄成功時,系統會為其建立一個新的進程
- 作業調度
- 多道批處理系統中,有新的作業放入內存是,會為其建立一個新的進程
- 作業指的是存在於外存中,還未投入到運行中的應用程序
- 系統提供服務
- 用户向操作系統提出某些請求時,會建立一個進程處理該請求
- 用户程序的應用請求
- 由用户進程主動請求創建一個子進程
- 在操作系統中,允許一個進程創建另一個進程,此時創建者為父進程,被創建的進程為子進程。
- 子進程可以繼承父進程所擁有的資源。
- 當子進程終止時,應將其從父進程那裏獲得的資源還給父進程
二、進程的終止
2.1 撤銷原語
撤銷原語又稱為終止原語,是操作系統在終止一個進程時使用的原語。
操作系統在終止一個進程時,會執行以下操作:
- 根據被終止進程的 PID ,檢索出該進程的 PCB,從中讀取該進程的狀態
- 若被終止的進程處於運行態,立即終止該進程的執行,剝奪該進程的 CPU 資源,並將 CPU 資源分配給其他進程
- 若該進程還有子孫進程,則通常需要將其所有子孫進程終止(有些系統無該要求)
- 將該進程所擁有的全部資源歸還給操作系統或者父進程。
- 將該 PCB 從所在隊列中刪除
有些操作系統不允許子進程在父進程終止的情況下存在,對於這類系統,若一個進程終止,則它的所有子進程也終止,這種現象稱為級聯終止。 當然,並不是所有操作系統都是這樣設計的。
2.2 終止進程的操作
在操作系統中,當發生以下事件時,均會引起進程的終止:
-
正常結束。
- 當該進程的任務已經完成並準備退出運行
-
異常結束。
- 進程在運行時,發生了某種異常事件,使程序無法繼續運行。這裏我們例舉幾個典型的異常事件:
- 存儲區越界:比如數組的越界訪問、棧溢出等
- 保護錯:是操作系統和處理器硬件共同構建的一套保護機制,當進程試圖執行以下操作時,就會觸發保護錯:
- 內存訪問違規:試圖讀寫不屬於自己的內存地址,或訪問一個尚未映射物理內存的地址(例如空指針解引用)。
- 非法指令:試圖執行一條為操作系統保留的特權指令(如直接進行I/O操作),而進程自身沒有此權限。
- 資源訪問違規:以不當方式訪問資源,例如嘗試寫入一個只讀文件。
- 運行超時:當存在超時設置不合理、 程序邏輯缺陷算(如死循環)、算法的時間複雜度過高(如使用 $O(N^3)$ 的算法處理大規模的數據)、存在冗餘計算、資源不足(CPU、內存)、網絡延遲或外部依賴、低效I/O操作、數據結構選擇不當(如採用順序表進行頻繁的插入與刪除)
- 算術運算錯:常見的算術錯誤有以下幾種
- 除零錯誤:這是最常見的算術異常之一。在整數運算中,除數是絕對不允許為0的,否則程序會拋出異常(如
Java中的ArithmeticException或Python中的ZeroDivisionError)。 - 整數溢出:當算術運算的結果超出了該數據類型所能表示的範圍時就會發生。
- 浮點數特殊值:浮點數運算在特定情況下(如非零數除以零)可能得到
Infinity(無窮大),而對負數開平方等無效操作會得到NaN(Not a Number)。
- 除零錯誤:這是最常見的算術異常之一。在整數運算中,除數是絕對不允許為0的,否則程序會拋出異常(如
- I/O 故障:當計算機試圖與外部世界(比如硬盤、U盤、打印機、網絡等)交換數據時,這個過程因某種原因失敗了。
- 進程在運行時,發生了某種異常事件,使程序無法繼續運行。這裏我們例舉幾個典型的異常事件:
-
外界干預。進程應外界的請求而終止運行。
- 在 Windows 系統中,用户可以通過快捷鍵 $Ctrl + Alt + Delete$ 打開任務管理器來主動的終止進程
三、進程的阻塞與喚醒
3.1 阻塞原語
阻塞原語是指進程由運行態轉換為阻塞態時使用的原語。
阻塞原語在執行的過程中,會進行以下操作:
- 找到將要被阻塞進程的標識號(PID)對應的 PCB
- 若該進程為運行態,則保護其線程,將其狀態轉為阻塞態,停止運行
- 將該 PCB 插入相應事件的等待隊列,將 CPU 資源調度給其他就緒進程
3.2 阻塞進程的操作
正在執行的進程,由於期待的某些事件未發生:
- 請求系統資源失敗
- 等待某種操作的完成
- 新數據尚未到達
- 無新任務可做等,
進程便通過調用阻塞原語(Block),使自己由運行態變為阻塞態。
阻塞是進程自身的一種主動行為,也因此只有處於運行態的進程,才可能將其轉為阻塞態。
3.3 喚醒原語
喚醒原語是指進程由阻塞態轉換為就緒態時使用的原語。
喚醒原語在執行的過程中會執行以下操作:
- 在該事件的等待隊列中找到相應進程的 PCB
- 將其從等待隊列中移除,並置其狀態為就緒態
- 將該 PCB 插入到就緒隊列中,等待調度程序調度
3.4 喚醒進程的操作
當被阻塞進程所期待的事件發生時,如:
- 所請求的系統資源獲取成功
- 所期待的 I/O 操作已經完成
- 所期待的數據已經到達
- 獲取到新的任務
由有關進程(如釋放系統資源的進程、提供數據的進程、發佈任務的進程)調用喚醒原語(Wakeup),將等待該事件的進程喚醒。
3.5 阻塞與喚醒
Block 原語和 Wakeup 原語是一對作用剛好相反的原語,必須成對使用。
若在某個進程中調用了 Block 原語,則必須在與之合作的或其他相關的進程中安排一條相應的 Wakeup 原語,以便喚醒阻塞進程;否則,阻塞進程將因不能被喚醒而永久地處於阻塞態。
四、進程的切換
4.1 切換原語
切換原語是指實現進程從運行態轉換為就緒態並將新進程從就緒態轉換為運行態的原語。
切換原語在使用時,會執行以下操作:
- 將運行環境信息存入 PCB
- PCB 移入相應隊列
- 選擇另一個進程執行,並更新其 PCB
- 根據 PCB 恢復新進程所需的運行環境
4.2 切換進程的操作
在操作系統中,當出現以下事件時,會通過調用 切換原語 來進行進程的切換:
- 當前進程的 CPU 時間片已經結束
- 有更高優先級的進程到達
- 當前進程主動阻塞
- 當前進程終止
簡單的理解就是,當前正在運行的進程因為各種原因需要結束運行態並轉換為其它狀態時,就需要通過調用切換原語來進行進程的狀態切換,並將 CPU 資源分配給新的進程;
4.3 程序的運行過程
從程序的創建到運行的過程中,會經歷以下步驟:
- 程序源代碼的編寫
- 源程序通過編譯、鏈接等操作生成可執行文件
.exe並存儲與硬盤中 - 將位於硬盤的
.exe文件放入到內存中 - CPU 從內存中依次讀取程序指令
graph LR
a[源文件]
subgraph 硬盤
b[可執行文件]
end
a--->b
subgraph 內存
PCB
c1[指令1]
c2[指令2]
c3[...]
end
b--->c1
c1--->CPU
程序在放入內存中運行時,會創建一個 PCB 用於存儲該進程的管理和控制信息。
但是由於 CPU 的調度是以時間片的形式,且對應的時間片只有若干毫秒。
因此當一個進程對應的 CPU 時間片使用完後,系統會通過 切換原語 將該進程切換為另一個新進程:
graph LR
subgraph A[內存]
PCB1
a1[切換原語]
a2[...]
end
a1--->CPU
subgraph B[內存]
PCB2
b1[指令1]
b2[...]
end
CPU--->b1
在完成切換後,原進程所使用的系統資源會被新進程繼續使用,這時就會存在一個問題:
- 原進程使用系統資源時,產生的相關信息會繼續存放在系統資源內,當新進行繼續使用時,產生的新的相關信息則會覆蓋掉原信息
如果此不做任何處理,那麼當我們重新運行原進程時,就會因為丟失相關的信息而導致運行出錯。
運行環境是指進程運行時所依賴和所處的全部條件、資源和狀態的集合。我們將正在運行的進程所產生的相關信息稱為該進程的上下文;
當我們在通過切換原語切換進程時,可以通過將該進程的運行環境以及上下文信息存儲到該進程對應的 PCB 中。(為了方便理解,這裏我們將該進程的 PCB 稱為 PCB1)
完成切換後,系統會先通過讀取新進程的 PCB 來恢復該進程的運行環境以及上下文(這裏我們將新進程的PCB 稱為 PCB2);
graph LR
subgraph A[內存]
subgraph PCB1
a[通用寄存器信息]
b[地址寄存器信息]
c[控制寄存器信息]
d[標誌寄存器信息]
e[狀態字]
f[...]
end
a1[指令1]
a2[指令2]
a3[切換原語]
end
a3--->CPU
subgraph B[內存]
subgraph PCB2
ba[通用寄存器信息]
bb[地址寄存器信息]
bc[控制寄存器信息]
bd[標誌寄存器信息]
be[狀態字]
bf[...]
end
b1[指令1]
b2[指令2]
b3[切換原語]
end
CPU--->b1
b3--->cpu[CPU]
subgraph C[內存]
subgraph PCB[PCB1]
aa[通用寄存器信息]
ab[地址寄存器信息]
ac[控制寄存器信息]
ad[標誌寄存器信息]
ae[狀態字]
af[...]
end
a4[指令3]
a5[指令4]
a6[切換原語]
end
cpu--->a4
因此,切換原語的執行過程可以總結為2點:
- 記錄舊進程的相關信息並改變舊進程的運行狀態
- 讀取新進程的相關信息並改變新進程的運行狀態
其中進程的相關信息指的是進程當前的運行環境以及上下文。
結語
今天的內容到這裏就全部結束了。通過今天的學習,我們深入探討了操作系統進程控制的核心機制。讓我們回顧一下本文的重要知識點:
核心原語機制回顧
進程創建
進程創建原語完成了新進程的"誕生"過程,包括:
- 分配PID
- 申請PCB
- 分配資源
- 初始化PCB
- 將進程插入就緒隊列
我們瞭解了進程創建的四種典型場景:
- 用户登錄
- 作業調度
- 系統服務
- 用户程序請求
進程終止
進程終止原語負責進程的"善後工作",通過以下操作來實現進程的優雅退出:
- 檢索PCB
- 終止執行
- 回收資源
- 刪除PCB
導致進程終止總共有三種終止情況:
- 正常結束
- 異常結束
- 外界干預
阻塞與喚醒
阻塞與喚醒原語這對相輔相成的機制,實現了進程在運行態與阻塞態之間的智能轉換。
特別需要注意的是,Block 和 Wakeup 必須成對使用,否則可能導致進程永久阻塞。
進程切換
進程切換原語作為多任務併發的關鍵技術,通過以下操作實現了進程間的平滑切換,確保了CPU資源的高效利用:
- 保存上下文
- 更新PCB
- 恢復運行環境,
知識體系構建
這些原語共同構成了操作系統進程管理的基石,它們通過原子操作特性保證了進程狀態轉換的一致性和可靠性。
理解這四種原語的工作機制,不僅幫助我們掌握了進程生命週期管理的核心技術,更為後續學習進程同步、通信等高級主題奠定了堅實基礎。
進程控制機制體現了操作系統設計的精妙之處——通過簡單而可靠的原語操作,構建出複雜而強大的多任務環境。這種"簡單構建複雜"的設計思想,值得我們深入體會和學習。
互動與分享
-
點贊👍 - 您的認可是我持續創作的最大動力
-
收藏⭐ - 方便隨時回顧這些重要的基礎概念
-
轉發↗️ - 分享給更多可能需要的朋友
-
評論💬 - 歡迎留下您的寶貴意見或想討論的話題
感謝您的耐心閲讀! 關注博主,不錯過更多技術乾貨。我們下一篇再見!