操作系统进程管理
1 進程概述
進程:?一個具有一定的功能的程序在一個數據集合上的一次動態執行過程.
1.1 進程組成
一個進程應該包括:
- 程序的代碼
- 程序處理的數據
- 程序計數器中的值, 指使下一條將運行的指令
- 一組通用的寄存器的當前值, 堆, 棧
- 一組系統資源(如打開的文件)
總之, 進程包含了正在運行的一個程序的所有狀態信息.
1.2 進程和程序的聯系
- 程序是產生進程的基礎
- 程序的每次運行構成不同的進程
- 進程是程序功能的體現
- 通過多次執行, 一個程序可對應多個進程, 通過調用關系, 一個進程可包括多個程序.
進程和程序的區別:
- 進程是動態的, 程序是靜態的, 程序時有序代碼的集合, 進程是程序的執行, 進程有核心態/用戶態.
- 進程是暫時的, 程序是永久的, 進程是一個狀態變化的過程, 程序可長久保存
- 進程與程序的組成不同, 進程的組成包括程序, 數據和進程控制塊(如進程狀態信息)
1.3 進程的特點
- 動態性: 可動態地創建, 結束進程
- 并發性: 進程可以被獨立調度并占用處理器運行: 并發并行.
- 獨立性: 不同進程的工作不相互影響.
- 制約性: 因訪問共享數據/資源或進程間同步而產生制約
1.4 進程控制結構
進程控制塊: 操作系統管理控制進程運行所用的信息集合. 操作系統用PCB(process control block)來描述進程的基本情況以及運行變化的過程, PCB是進程存在的唯一標志.
進程控制塊的使用:
- 進程的創建: 為該進程生成一個PCB
- 進程的終止: 回收它的PCB
- 進程的組織管理: 通過對PCB的組織管理來實現
PCB含有一下三大類信息:
1. 進程標識信息. 如本進程的標識, 本進程的產生者標識(父進程標識), 用戶標識.
2. 處理器狀態信息保存區. 保存進程的運行現場信息:
- 用戶可見寄存器, 用戶程序可以使用的數據, 地址等寄存器.
- 控制和狀態寄存器, 如程序計數器(PC), 程序狀態字(PSW).
- 棧指針, 過程調用/系統調用/中斷處理和返回時都需要用到它.
3. 進程控制信息:
- 調度和狀態信息, 用于操作系統調度進程并占用處理器使用.
- 進程間通信信息, 為支持進程間的與通信相關的各種標識, 信號, 信件等, 這些信息存在接收方的進程控制塊中.
- 存儲管理信息, 包含有指向本進程映像存儲空間的數據結構.
- 進程所用資源, 說明由進程打開, 使用的系統資源, 如打開的文件等.
- 有關數據結構連接信息, 進程可以連接到一個進程隊列中, 或者連接到相關的其他進程的PCB.
PCB的組織方式:
鏈表:?同一狀態的進程其PCB成一鏈表, 多個狀態對應多個不同的鏈表. 各狀態的進程形成不同的鏈表: 就緒鏈表, 阻塞鏈表.
索引表:?同一狀態的進程歸入一個index表(由index指向PCB), 多個狀態對應多個不同的index表. 各狀態的進程形成不同的索引表: 就緒索引表, 阻塞索引表.
2 進程狀態
2.1 進程的生命周期管理
2.1.1 進程創建
引起進程創建的三個主要條件:
- 系統初始化時
- 用戶請求創建一個新進程
- 正在運行的進程執行了創建進程的系統調用
2.1.2 進程運行
內核選擇一個就緒的進程, 讓它占用處理器并執行
2.1.3 進程等待
在以下情況下, 進程等待(阻塞):
進程只能自己阻塞自己, 因為只有進程自身才知道何時需要等待某種事件發生.
2.1.4 進程喚醒
喚醒進程的原因:
- 被阻塞進程需要的資源可被滿足
- 被阻塞進程等待的事件到達
- 將該進程的PCB插入到就緒隊列
進程只能被別的進程或操作系統喚醒.
2.1.5 進程結束
在以下四種情形下, 進程結束:
- 正常退出(自愿的)
- 錯誤退出(自愿的)
- 致命錯誤(強制性的)
- 被其他進程所殺(強制性的)
2.2 進程狀態變化模型
進程的三種基本狀態:
進程在生命周期結束前處于且僅處于三種基本狀態之一, 不同系統設置的進程狀態數目不同:
- 運行狀態: 當一個進程正在處理器上運行時.
- 就緒狀態: 一個進程獲得了除處理器之外的一切所需資源, 一旦得到處理器即可運行.
- 等待狀態: 一個進程正在等待某一事件而暫停運行時. 如等待某資源, 等待輸入/輸出完成.
進程其他的基本狀態:
- 創建狀態: 一個進程正在被創建, 還沒被轉到就緒狀態之前的狀態.
- 結束狀態: 一個進程正在從系統中消失時的狀態, 這是因為進程結束或由于其他原因所導致.
狀態變化:
- NULL->New: 一個新進程被產生出來執行一個程序.
- New->Ready: 當進程被創建完成并初始化后, 一切就緒準備運行時, 變為就緒狀態.
- Ready->Running: 處于就緒狀態的進程被進程調度程序選中后, 就分配到處理器上來運行.
- Runing->Exit: 當進程表示它已經完成或者因出錯, 當前運行進程會由操作系統結束處理.
- Runing->Ready: 處于運行狀態的進程在其運行過程中, 由于分配給它的處理器時間片用完而讓出處理器.
- Runing->Blocked: 當進程請求某樣東西且必須等待時.
- Blocked->Ready: 當進程要等待的某事件到來時, 它從阻塞狀態變到就緒狀態.
2.3 進程掛起模型
進程在掛起狀態時, 意味著進程沒有占用內存空間, 處在掛起狀態的進程映像在磁盤上.
2.3.1 進程的掛起狀態
掛起狀態有兩種:
- 阻塞掛起狀態: 進程在外存并等待某事件的出現.
- 就緒掛起狀態: 進程在外存, 但只要進入內存, 即可運行.
2.3.2 掛起相關的狀態轉換
把一個進程從內存轉到外存(掛起), 可能有以下幾種情況:
- 阻塞到阻塞掛起: 沒有進程處于就緒狀態或就緒進程進程要求更多內存資源時, 會進行這種轉換, 以提交新進程或進行就緒進程.
- 就緒到就緒掛起: 當有高優先級阻塞(系統認為會很快就緒的)進程和低優先就緒進程時, 系統會選擇掛起低優先級就緒進程.
- 運行到就緒掛起: 對搶先式分時系統, 當有高優先級阻塞掛起進程因事件出現而進入就緒狀態時, 系統可能會把運行進程轉到就緒掛起狀態.
在外存時的狀態轉換:
- 阻塞掛起到就緒掛起: 當有阻塞掛起進程因相關事件出現時, 系統會把阻塞掛起進程轉換為就緒掛起進程.
解掛/激活(把一個進程從外存轉到內存)的情況:
- 就緒掛起到就緒: 沒有就緒進程或掛起進程優先級高于就緒進程時, 會進行這種轉換.
- 阻塞掛起到阻塞: 當一個進程釋放足夠內存時, 系統會把一個高優先級阻塞掛起(系統認為會很快出現所等待的事件)進程轉換為阻塞進程.
2.4 狀態隊列
- 由操作系統來維護一組隊列, 用來表示系統當中所有進程的當前狀態
- 不同的狀態分別用不同的隊列來表示(就緒隊列, 各種類型的阻塞隊列)
- 每個進程的PCB都根據他的狀態加入到相應的隊列當中, 當一個進程的狀態發生變化時, 它的PCB從一個狀態隊列中脫離出來, 加入到另外一個狀態隊列.
(此處為個人理解, 應該只是PCB數據結構的指針進行各個隊列的出隊/入隊.)
3 線程
3.1 線程的使用意義
需要提出一種新的實體, 滿足以下特性:
- 實體之間可以并發地執行
- 實體之間共享相同的地址空間
3.2 線程
3.2.1 線程特性
線程可以定義為: 進程當中的一條執行流程.
從兩個方面來重新理解進程:
- 從資源組合的角度: 進程把一組相關的資源組合起來, 構成了一個資源平臺(環境), 包括地址空間(代碼段, 數據段), 打開的文件等各種資源.
- 從運行的角度: 代碼在這個資源平臺上的一條執行流程(線程).
線程的優點:
- 一個進程中可以同時存在多個線程
- 各個線程之間可以并發地執行
- 各個線程之間可以共享地址空間和文件等資源
線程的缺點:
- 一個線程崩潰, 會導致其所屬進程的所有線程崩潰.
3.2.2 不同操作系統對線程的支持
3.2.3 線程所需的資源
3.2.4 線程與進程的比較
- 進程是資源分配單位, 線程是CPU調度單位
- 進程擁有一個完整的資源平臺, 而線程只獨享必不可少的資源, 如寄存器和棧
- 線程同樣具有就緒, 阻塞和執行三種基本狀態, 同樣具有狀態之間的轉換關系
- 線程能減少并發執行的時間和空間開銷:
- 線程的創建時間比進程短
- 線程的終止時間比進程短
- 同一進程內的線程切換時間比進程短
- 由于同一進程的各線程見共享內存和文件資源, 可直接進行不通過內核的通信.
3.3 線程的實現
主要有三種線程的實現方式:
- 用戶線程: 在用戶空間實現
- 內核線程: 在內核中實現
- 輕量級進程: 在內核中實現, 支持用戶線程
用戶線程和內核線程的對應關系:
- 多對一
- 一對一
- 多對多
3.3.1 用戶線程
在用戶空間實現的線程機制, 它不依賴于操作系統的內核, 由一組用戶級的線程庫函數來完成線程的管理, 包括進程的創建, 終止, 同步和調度等.
- 由于用戶線程的維護由相應進程來完成(通過線程庫函數), 不需要操作系統內核了解用戶線程的存在, 可用于不支持線程技術的多進程操作系統.
- 每個進程都需要它私有的線程控制塊(TCB)列表, 用來跟蹤記錄它的各個線程的狀態信息(PC, 棧指針, 寄存器), TCB由線程庫函數來維護.
- 用戶線程的切換也是由線程庫函數來完成, 無需用戶態/核心態切換, 所以速度特別快.
- 允許每個進程擁有自定義的線程調度算法.
用戶線程的缺點:
- 阻塞性的系統調用如何實現? 如果一個線程發起系統調用而阻塞, 則整個進程在等待.
- 當一個線程開始運行后, 除非它主動地交出CPU的使用權, 否則它所在的進程當中的其他線程將無法運行.
- 由于時間片分配給進程, 故與其他進程比, 在多線程執行時, 每個線程得到的時間片較少, 執行會較慢.
3.3.2 內核線程
是指在操作系統的內核當中實現的一種線程機制, 由操作系統的內核來完成線程的創建, 終止和管理.
- 在支持內核線程的操作系統中, 由內核來維護進程和線程的上下文信息(PCB和TCB)
- 線程的創建, 終止和切換都是通過系統調用/內核函數的方式來進行, 由內核來完成. 因此系統開銷較大.
- 在一個進程當中, 如果某個內核線程發起系統調用而被阻塞, 并不會影響其他內核線程的運行.
- 時間片分給線程, 多線程的進程獲得更多CPU時間
- Windows NT和Windows 2000/XP支持內核線程.
3.3.3 輕量級進程
它是內核支持的用戶線程, 一個進程可有一個或者多個輕量級進程, 每個輕量級進程由一個單獨的內核線程來支持.(Solaris/Linux)
3.4 多線程編程接口舉例
4 進程控制
4.1 進程切換
停止當前運行進程(從運行狀態改變成其他狀態)并且調度其他進程(轉變成運行狀態)
- 必須在切換之前存儲許多部分的進程上下文
- 必須能夠在之后恢復他們, 所以進程不能顯示它曾經被暫停過
- 必須快速(上下文轉換是非常頻繁的)
需要存儲的上下文:
- 寄存器(PC, SP, ..), CPU狀態, ...
- 一些時候可能會費時, 所以我們應該盡可能避免
進程控制塊PCB: 內核的進程狀態記錄
- 內核為每個進程維護了對應的進程控制塊
- 內核將相同狀態的進程的PCB放置在同一個隊列(就緒隊列, I/O等待隊列--每一個設備一個隊列, 僵尸隊列等)
4.2 進程創建
進程創建是操作系統提供給用戶使用的系統調用, 完成新進程的創建工作.
不同系統的進程創建API不同, 如下:
- Windows進程創建API: CreateProcess(filename)
- Unix/Linux進程創建系統調用: fork/exec, 其中fork完成把一個進程復制成兩個進程, 兩個進程只有進程ID不同(PID), 復制完成后exec把新程序加載進來重寫當前進程(PID沒有改變)
Unix/Linux進程創建系統調用示例如下:
int pid = fork(); //創建子進程//父進程執行完這一行之后, 創建出來的子進程和本身的父進程都會//繼續向下執行, 只不過此時子進程的pid是0, 父進程返回的是子//的pid if (pid == 0) {//此時說明是子進程, 就掉用exec執行想要執行的文件 }fork()創建一個繼承的子進程:
- 復制父進程的所有變量和內存
- 復制父進程的所有CPU寄存器(有一個寄存器例外)
fork()的地址空間復制:
- fork()執行過程對于子進程而言, 是在調用時間對父進程地址空間的一次復制(對于父進程fork()返回child PID, 對于子進程返回值為0).
- 系統調用exec()加載新程序取代當前運行進程.
4.3 進程加載
在Linux中會調用exec來加載新程序取代當前運行進程:
- exec()調用允許一個進程"加載"一個不同的程序并且在main開始執行(事實上 _start)
- 它允許一個進程指定參數的數量(argc: argument count)和它字符串參數數組(argv).
- 如果調用成功, 進程PID還是原來的PID, 但是運行的程序改變了(原來運行的是父進程程序, 調用后運行加載的程序)
- 其代碼段, stack, heap都會重寫
fork()的優化:
首先由前面已知, fork()的簡單實現:
- 對子進程分配內存
- 復制父進程的內存和CPU寄存器到子進程里
- 開銷昂貴
但是, 在99%的情況里, 我們在調用fork()之后會繼續調用exec(), 也就意味著:
- 在fork()操作中內存復制是沒有作用的
- 子進程將可能關閉打開的文件和鏈接
- 開銷因此是高的
- 為什么不能結合它們在一個調用中
vfork() --- 早期Unix系統提供
- 一個創建進程的系統調用, 不需要創建一個同樣的內存映像
- 一些時候稱為輕量級fork()
- 子進程應該幾乎立即調用exec()
- 現在不再使用如果我們使用Copy on Write(COW)技術
4.4 進程的等待與退出
wait()系統調用是被父進程用來等待子進程的結束.
當一個進程結束之后, 一些資源(比如PCB等)是無法進行自我回收的.
一個子進程向父進程返回一個值, 所以父進程必須接受這個值并處理, wait()系統調用擔任這個要求
- 它使父進程去睡眠來等待子進程的結束
- 當一個子進程調用exit()的時候, 操作系統解鎖父進程, 并且將通過exit()傳遞得到的返回值作為wait調用的一個結果(連同子進程的pid一起), 如果這里沒有子進程存在, wait()立即返回.
- 當然, 如果這里有為父進程的僵尸等待, wait()立即返回其中一個值(并且解除僵尸狀態).
具體步驟如下:
- 進程結束執行之后, 它調用exit() -- 系統調用
- 這個系統調用:
- 將這個程序的"結果"作為一個參數
- 關閉所有打開文件, 連接等等
- 釋放內存
- 釋放大部分支持進程的操作系統結構
- 檢查是否父進程是存活著的, 如果存活, 它保留結果的值直到父進程需要它: 在這種情況里, 進程沒有真正死亡, 但是它進入了僵尸狀態. 如果父進程沒有存活 它釋放所有的數據結構, 這個進程死亡.
- 清理所有等待的僵尸進程
- 進程終止是最終的垃圾收集(資源回收)
注意:
操作系統根進程(初始進程)會定期掃描僵尸進程, 并代替僵尸進程的父進程對其進行釋放.
總結
- 上一篇: php用不了for循环吗,php中的这两
- 下一篇: java信息管理系统总结_java实现科