二、进程管理
??進程是操作系統實現并發的基礎,了解進程才能明白操作系統運行的實現過程。這篇文章對于進程進行一個討論,希望在讀完之后能對進程有一個清晰的認識。
??進程概念
??對進程概念的介紹,圍繞著這幾個問題:
????1. 為什么要進程?
????2. 什么是進程?
????3. 進程由什么組成?
??1. 為什么要進程?
??多道程序環境下,程序并發執行。為了讓各個程序相對獨立,運行無誤,引入進程這個概念來對并發執行的程序加以描述和控制(這個比較晦澀,從進程本身角度說,程序A運行的時候希望有一個獨立的空間不受他人干擾,進程就提供了這個空間。從使用者的角度說,對操作系統引入進程這個數據結構,能夠更清晰的表達動態系統運行的內在規律)
??2. 什么是進程?
??這個問題說法不一,但我覺得進程就是運行中的程序。這句話也側面解釋了進程和程序之間的區別。程序是靜態的,而進程是動態的。除此之外,進程內部的內容也更多,將在下個問題中介紹。
??3. 進程由什么組成?
??我們常常說的進程其實是進程實體(進程映像) 的簡稱,主要有程序段、相關的數據段、和PCB(下面再詳細介紹)三部分組成。這里的相關數據段包括堆、棧、數據段三類,程序段就是代碼段。
下面詳細介紹一下PCB(這可是進程里面很重要的!)
在介紹PCB之前說一下,為啥要PCB?都知道程序分時運行,到了時間就要換程序執行。那不同程序之間的狀態肯定是不同的(程序A有程序A的數據,程序B有程序B的數據)。之前說過在兩次時間切換之間,操作系統會實現保護現場和恢復現場。那他們的依據從哪來呢?就是PCB!所以PCB就是保存進程中各類數據信息及當前情況的數據結構。
??進程控制塊PCB(process control block)
??PCB中主要包含如下四部分
??1) 進程標識符
??進程標識符的作用就是唯一的標識一個進程,以便于對這個進程進行調用。有兩種類型,針對于用戶和系統使用,分別是外部標識符(用戶),內部標識符。編程序時fork函數返回的應該是外部標識符。
??2) 處理機狀態
??處理機狀態又稱處理機的上下文(還有一個概念是上下文切換,就是針對于這部分內容的切換),主要包括各種寄存器中的內容:通用寄存器、當前指令地址(PC)、程序狀態字(PSW)、用戶棧指針。這部分內容也是PCB存儲中最顯著的。
??3) 進程調度信息
??進程調度信息表明該進程當前的狀態(關于進程狀態的分類稍后介紹),進程的優先級、用于完成調度的其他信息(還有什么?)、如果發生阻塞,存儲阻塞原因。學名叫事件。
??4) 進程控制信息
??指用于進程所必需的信息,包括本進程程序段和數據段的地址(不一定是首地址,可能是當前執行的)、進程同步和通信機制所需要的一些東西(都有哪些?),資源清單,包括總預計和當前已分配、鏈接指針,本進程PCB所在隊列的下一個進程PCB的首地址(怎么理解?PCB之間的存儲以鏈表形式嗎?)
??如果一個進程一個PCB的話,PCB的數量也不會少。所以需要方式進行管理,目前常用的組織方式包括線性方式、鏈接方式、索引方式。在此不再細說,有需要可以自己去查。
??以上介紹的PCB內容都比較基礎,但其實PCB是一個很大的數據結構。涉及很多方面。更為詳細的說明如下圖(我還沒學到,就先不一一介紹了):
??下面來補上面的坑,談談什么是進程狀態。
??進程最基本的狀態有三種:就緒、阻塞、執行
??就緒指進程已經準備好了,只要獲得CPU的資源就可以運行程序。因為處于就緒狀態的進程可能很多,所以以隊列存儲,并配合專門的調度算法。
??執行就是進程正在進行,沒啥好說的。對于單核CPU,同一時間只有一個進程處于執行狀態。
??阻塞指執行狀態的進程因某些操作(通常是I/O)而終止運行,進行等待。阻塞完成后會重新進入就緒狀態。
??執行狀態會有兩種變化方向:一種向就緒狀態(時間片到達),一種向阻塞狀態(發生I/O操作)。三者轉化關系如下圖(圖中引入新的,終止狀態是為了使結構完整):
??我們常常聽到一個名詞叫”掛起”,比如后臺進程的掛起。掛起也是一種改變進程狀態的操作。那為啥要掛起呢?原因有很多,比如這個進程不用,通過掛起可以將這部分資源拿出來給其他的用。或者是通過掛起完成對進程的控制(不讓運行了)。當引入掛起操作后,就區分出活動和靜止兩個分類(活動就緒,活動阻塞,靜止就緒,靜止阻塞)。普通處于就緒狀態就叫活動就緒,掛起后就變成了靜止就緒,普通處于阻塞狀態就叫活動阻塞,掛起后就變成了靜止阻塞。與掛起相對的是激活操作,實現反向功能。當引入創建狀態的時候,從創建狀態可以直接變成就緒狀態。如果進程在內存中變成活動就緒,在磁盤中變成靜止就緒(遠的就是不用,就靜止)。把所有的狀態都放在一張圖里就是下面這個樣子:
??進程通信
??帶著問題說:
????1. 為什么需要進程通信?
????2. 有哪幾種方式實現進程通信?
??1. 為什么需要進程通信
??關于這個問題,老師給了三種原因:
??1) 當無法改寫應用程序時,用進程通信去操作已有的應用程序(怎么理解?)
??2) 完成同一個任務的多個進程需要通信。
??3) 模塊化、方便(怎么理解?)
??實現進程通信的最大目的就是數據共享,操作系統分時特點,同一程序多個進程間相互協作說白了也是為了實現數據共享。不過這里面的進程通信指的是進程間,而不是進程內部。
??2. 有哪幾種方式實現進程通信呢?
說到通信,最容易想的方式肯定是通過文件。一個寫一個讀,只要處理好順序問題就不會有錯。但文件讀寫也屬于I/O操作,實在太慢。所以操作系統額外提供了三種進程通信的機制:消息隊列、共享內存、管道
??1) 消息隊列
??實際上用鏈表維護了一個消息隊列,進程具有讀或寫的權力。發方寫,再讓收方 讀。很類似于文件,但可能存放的位置不同,使用起來更快。但這里存在一個矛盾,
??隊列中每個數據多大呢?選擇定長的話易設計,但不易使用。不定長相反。所以基本只用于早期系統。有關于消息傳遞的編程可以參考下面這個博客:進程通信之消息隊列_我愛加菲貓-CSDN博客_進程通信消息隊列;
??2) 共享內存
??共享內存就更是簡單粗暴了,既然希望共享數據,直接在內核中開辟一段空間作為共享數據區域,所有進程都可以讀寫。因為是在內核區域,所以存取的速度比較快。也是實現通信的最快方式。不過哪有這么好的事兒?訪問同一區域就會遇到數相關問題(先讀后寫),所以共享內存的使用建立在進程同步的基礎之上。
??3) 管道
??消息隊列太慢,共享內存沒有規定容易亂。管道就起一個權衡作用,集百家之所長。在內核中開辟一段空間,以隊列方式管理,管道一端只能輸入、一段只能輸出。但管道空間有限,所以要做一些判斷:滿了不能寫,空了不能讀。管道還涉及到一些重定向的問題,我現在也不會,就不說了(美其名曰:揚長避短)。在目前的系統中,管道是用的最多的一種。
??線程
??線程這個概念,應該聽過很多次了吧。在強調并行計算的時候,都會說到多線程。那線程和進程間有什么聯系呢?簡單的總結:線程是進程的一個變種。下面來聊聊吧!
??1. 為什么需要線程?
??咱們首先來比較一下,一個程序的多個進程間有什么異同?
??相同之處:代碼、數據一樣,權限一樣、文件資源一樣
??不同之處:每個進程有各自的執行狀態,PC等寄存器。
??可以把相同不同分為兩個類:資源、執行狀態。資源都相同,還為啥要每個都存一份。只要保存每個進程各自的執行狀態就行了呀!對嘍,這就是線程的思想。把執行狀態取出來,就叫做線程(感覺有點兒像那個提取公因式,hhh)。
??下面來多說一點兒和線程相關的:
??之前認為進程是操作系統中最小的,但其實不是滴。線程才是處理器調度的基本單位。一個進程可以創建多個線程,多個線程共享地址空間、資源,線程也有三個狀態(就緒、執行、阻塞),操作系統怎么感知線程呢?和進程一樣,進程有PCB,線程有TCB(線程控制塊, Thread Control Block)。每一個線程都要有一個獨立的棧(共用一個會滿滴,所以不行)
??來比較下線程和進程,它們各自都有什么優勢呢?
??線程更快、更小。但獨立性不好(需要共用資源)。進程就反過來了。所以不同不同需求選擇就不同,而不是說線程一定比進程要好。舉個例子網頁瀏覽器多個網頁用的是進程,而不是線程。為什么呢?這就是線程一個最大的(I think)缺點:一個進程中有多個線程,但若一個線程出錯,整個進程被強迫終止。以線程管理網頁就會一個崩,個個崩。所以多線程編程還是多進程編程也是看需要。
??以上常說的線程都叫做內核級線程,是由操作系統內核建立的。但有一個概念要清楚,涉及到操作系統內核的操作必將需要狀態的轉換(用戶態到內核態),這個是要花時間的。所以能在用戶態做的事情是最好的了。所以又設計了用戶級線程,管理模塊叫做utcb,分配在數據段,同時也要有獨立的棧。因為沒有操作系統參與,但線程仍需要調度。所以需要自己在代碼段完成調度算法。優點就是性能高,靈活(允許進程制定屬于自己的調度算法,進程管理靈活)。拿用戶線程和內核線程做一個比較,如下圖:
??系統調用阻塞問題指用戶級線程如果調用了系統調用,就會引發系統調用阻塞。(可能是沒權利吧)。更詳細的比較可以參考:用戶級線程和內核級線程的區別 - 知乎 (zhihu.com)
??用戶級線程的實現方式有多種(依舊依附于內核級線程)。分為多對一(多用戶對應一內核),一對一(一用戶對應一內核)、多對多(多用戶對應多內核)。這里了解的不是很多,就不說了。
??進程同步
??首先思考一個問題:為什么需要進程同步?試想這樣一個場景,對于一個全局變量,很多個進程都可以訪問并且對該變量進行操作。結合計算機分時系統的特點,那會不會出現不同情況下執行結果不一致的情況。答案是會!(原因在此不做解釋,自己去查查或許印象更深)我們稱這種全局變量為臨界資源,針對于代碼中對于臨界資源的操作過程稱為臨界區,針對于臨界資源產生的問題叫做臨界區問題。
??所以我們需要進程同步機制來控制各個進程對于臨界區的使用。書上對此的描述原文是:進程同步機制的主要任務,是對多個相關進程在執行次序上進行協調,使并發執行的諸進程之間按照一定規則(或時序)共享系統資源,并能很好地相互合作,從而使程序的執行具有可再現性。
??進程同步對于臨界區問題的解決有四個原則:空閑讓進、忙則等待、有限等待、讓權等待。我用自己的理解簡單解釋下:
??空閑讓進:如果臨界區空閑,任何一個需要臨界區的進程都可以使用
??忙則等待:自己想要使用臨界區,但此時此刻被占用,要排隊等待,空了再用
??有限等待:等待也是有限度的,要保證進程能夠使用到臨界區
??讓權等待:如果已經知道沒希望了,趕緊換個進程,別占用等待區的位置
??進程同步只是個機制,那具體可以通過什么方法呢?老師和書一共給了三種方法:Peterson方法、硬件同步機制、信號量機制。下面一一展示下(在此不做詳細的說明,每一種方法展開說都太多,有需要的再去找對應的內容即可):
??Peterson方法:
??Peterson方法通過增加turn變量結合flag實現了進程同步機制(對于臨界區的唯一使用,如果沖突會卡在while處)。它的優缺點如下圖:
??硬件同步機制
??之所以會發生臨界區問題,就是因為分時復用嘛。分時復用是操作系統按照時鐘中斷進程調度。如果硬件上在每次進入臨界區代碼前,直接關閉中斷。就不會有時鐘,也沒了分時,也不會有任何沖突問題。這就是硬件同步機制的主要思想。如果用鎖來描述的話,關中斷就是關鎖,開中斷就是開鎖,判斷鎖的狀態叫測試。為了防止多個進程同時測試到鎖打開的情況,測試和關鎖必須是連續的。
??硬件同步的具體落實是通過硬件指令實現的,具體的有兩種:test-and-set、swap。(具體的不說了)
??信號量機制
??信號量其實也經歷了一段發展的過程,基本順序可以分成:整型信號量、記錄型信號量、AND型信號量、信號量集,依次介紹下
??整型信號量:
??所謂整形信號量就是定義一個整型量表示資源的使用情況,只有兩個操作wait()和signal()能夠訪問。也對應的稱wait()為P操作,signal()為V操作。
??兩個操作的實現如下(簡單描述):
??這個例子中S就是信號量,初始化為某一正整數n,代表資源的數目。當資源的數目大于0時(有可用的資源)執行資源自減,然后去做某一處理。當資源為0時會一直卡在while處等待,直至某一進程結束運行后歸還資源再進行分配。通過這種方式,我們就好像建立了一堵墻,拿到鑰匙(資源)才能夠訪問臨界區。
??想想這種方法有什么缺點呢?如果按照這個思路的話,拿不到資源的進程會一直卡在while處,什么也不做,那這個時間片的各種資源就相當于浪費了。把這種現象稱為“忙等”。為了消除忙等,引入了記錄型信號量。
??記錄型信號量
??這種方法是解決了忙等問題。運用的思想是通過改變進程的狀態。之前說過阻塞狀態的進程是不占據資源的。所有如果進程執行到指定位置但沒有資源可用,就改變進程狀態為阻塞狀態。同時用鏈表這種結構對所有阻塞進程進行保存。對于二者的代碼描述如下:
??之前說資源可以有很多個,當只有一個的時候。這時候的信號量叫做互斥信號量。
??使用記錄型信號量幫助我們解決了忙等問題,但再想一下,當面臨多個信號量時wait()的順序是否會影響呢?答案是肯定的!看下面這個:
??如果這個交錯執行的話,就發現死鎖了。引起問題的原因就是順序不對,那要是能一起執行就好了,這就是AND型信號量!
??對于信號量集就是為了模型更一般化,在這里不進行詳細討論了。平時用的感覺也少。
??對于信號量(PV操作)更重要的在應用上,這篇文章主要是一些概念。后面會有一篇專門研究PV操作的幾個典型例題,去了解如何使用PV操作。
??進程這部分到這里也就寫完了,但感覺還遠沒有領悟到精髓,后面再繼續學習吧!
因作者水平有限,如果有錯誤之處,請在下方評論區指正,謝謝!
總結
- 上一篇: python2 http请求post、g
- 下一篇: 什么是HOOK功能?