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