進程模型

進程模型

在進程模型中,計算機上所有可運行的軟體,通常也包括作業系統,被組織成若干順序進程(sequential process),簡稱進程(process)。作業系統中最核心的概念是進程, 進程也是並發程式設計中的一個最重要、 最基本的概念。進程是一個動態的過程, 即進程有生命周期, 它擁有資源, 是程式的執行過程, 其狀態是變化的。 Windows、 unix和Linux是目前最流行的幾個作業系統。

運行原理

一個進程就是一個正在執行程式的實例,包括程式計數器、暫存器和變數的當前值。從概念上說,每個進程擁有它自己的虛擬CPU。當然,實際上真正的CPU在各進程之間來回切換。但為了理解這種系統,考慮在(偽)並行情況下運行的進程集,要比我們試圖跟蹤CPU如何在程式間來回切換簡單得多。正如我們所看到的,這種快速的切換稱作多道程式設計。

在圖中a我們看到,在一台多道程式計算機的記憶體中有4道程式。在圖中b,這4道程式被抽象為4個各自擁有自己控制流程(即每個程式自己的邏輯程式計數器)的進程,並且每個程式都獨立地運行。當然,實際上只有一個物理程式計數器,所以在每個程式運行時,它的邏輯程式計數器被裝入實際的程式計數器中。當該程式執行結束(或暫停執行)時,物理程式計數器被保存在記憶體中該進程的邏輯程式計數器中。在圖中c我們看到,在觀察足夠長的一段時間後,所有的進程都運行了,但在任何一個給定的瞬間僅有一個進程真正在運行。

進程模型 進程模型

在此,我們假設只有一個CPU。然而,逐漸這個假設就不為真了,因為新的晶片經常是多核的,包含2個、4個或更多的CPU。但是在現在,一次只考慮一個CPU會更簡單一些。因此,當我們說一個CPU只能真正一次運行一個進程的時候,即使有2個核(或CPU),每一個核也只能一次運行一個進程。

由於CPU在各進程之間來回快速切換,所以每個進程執行其運算的速度是不確定的。而且當同一進程再次運行時,其運算速度通常也不可再現。所以,在對進程編程時決不能對時序做任何確定的假設。例如,考慮一個I/O進程,它用流式磁帶機恢復備份的檔案,它執行一個10 000次的空循環以等待磁帶機達到正常速度,然後發出命令讀取第一個記錄。如果CPU決定在空循環期間切換到其他進程,則磁帶機進程可能在第一條記錄通過磁頭之後還未被再次運行。當一個進程具有此類嚴格的實時要求時,也就是一些特定事件一定要在所指定的若干毫秒內發生,那么必須採取特殊措施以保證它們一定在這段時間中發生。然而,通常大多數進程並不受CPU多道程式設計或其他進程相對速度的影響。

值得注意的是,如果一個程式運行了兩遍,則算作兩個進程。例如,我們可能經常兩次去啟動同一個字處理軟體,或在有兩個可用的印表機的情況下同時列印兩個檔案。像“兩個進程恰好運行同一個程式”這樣的事實其實無關緊要,因為它們是不同的進程。作業系統能夠使它們共享代碼,因此只有一個副本放在記憶體中,但那只是一個技術性的細節,不會改變有兩個進程正在運行的概念。

區別理解

進程和程式間的區別是很微妙的,但非常重要。用一個比喻可以使我們更容易理解這一點。想像一位有一手好廚藝的計算機科學家正在為他的女兒烘製生日蛋糕。他有做生日蛋糕的食譜,廚房裡有所需的原料:麵粉、雞蛋、糖、香草汁等。在這個比喻中,做蛋糕的食譜就是程式(即用適當形式描述的算法),計算機科學家就是處理器(CPU),而做蛋糕的各種原料就是輸入數據。進程就是廚師閱讀食譜、取來各種原料以及烘製蛋糕等一系列動作的總和。

現在假設計算機科學家的兒子哭著跑了進來,說他的頭被一隻蜜蜂蟄了。計算機科學家就記錄下他照著食譜做到哪兒了(保存進程的當前狀態),然後拿出一本急救手冊,按照其中的指示處理蟄傷。這裡,我們看到處理機從一個進程(做蛋糕)切換到另一個高優先權的進程(實施醫療救治),每個進程擁有各自的程式(食譜和急救手冊)。當蜜蜂螫傷處理完之後,這位計算機科學家又回來做蛋糕,從他離開時的那一步繼續做下去。

這裡的關鍵思想是:一個進程是某種類型的一個活動,它有程式、輸入、輸出以及狀態。單個處理器可以被若干進程共享,它使用某種調度算法決定何時停止一個進程的工作,並轉而為另一個進程提供服務。

具體模型介紹

Windows、 unix和Linux是目前最流行的幾個作業系統, 由於Linux和unix有很多的相似性, 這裡僅分析Windows和Linux作業系統中的進程模型。

Linux進程模型

1.1 Linux進程描述符
為了管理進程,核心必須對每個進程所做的事情進行清楚的描述,這正是進程描述符的功能。進程描述符都是task_struct類型結構,它的欄位包含了與一個進程相關的所有信息。進程描述符包含了進程的所有信息 。task_struct是一個非常複雜的結構 。這裡重點分析其包含的信息:

進程模型 進程模型
進程狀態 (Stak);
進程調度信息 (SchedulingInfoMh);
各種知識符 (Identifiers);
進程通信有關信息 (IPC, Inter_Process Com-
munication);
時間和定時器信息 (Times and Timers);
進程連結信息 (Links);
檔案系統信息 (File System);
虛擬記憶體信息 (Virtual Memory);
頁面管理信息 (page);
對稱多處理器 (SMP) 信息;
和處理器相關的環境 (上下文) 信息 (Pro-cessor Specific Context)。
下面對task_struct結構進行描述:
(1) 進程狀態

進程描述符中的state欄位描述了進程當前所處的狀態。它由一組標誌組成,其中每個標誌描述一種可能的進程狀態。下面是可能的6種狀態:
① 可運行狀態
進程要么在CPU上執行,要么準備執行。正在運行的進程就是當前進程 (由current所指向的進程),而準備運行的進程只要得到CPU就可以運行,CPU是這些進程唯一等待的系統資源。
② 可中斷的等待狀態
進程被掛起 (睡眠),直到某個條件變為真。

③ 不可中斷的等待狀態
與可中斷的等待狀態類似, 但有一點不同,把信號傳遞到睡眠進程不能改變它的狀態。
④ 暫停狀態
進程的執行被暫停。通常當進程接收到SIGSTOP、SIGTSTP、SIGTTIN或SIGTTOU信號後就處於這種狀態。
⑤ 跟蹤狀態
進程的執行已由debugger程式暫停。
⑥僵死狀態
進程的執行被終止,但在發布wait4 () 系統調用前, 在進程表中仍然有它的任務結構。

(2) 進程調度信息
調度程式利用這部分信息決定系統中哪個進程最應該運行,並結合進程的狀態信息保證系統運轉的公平和高效。這一部分信息通常包括進程的類別 (普通進程還是實時進程)、 進程的優先權等。
(3) 標識符
Linux作業系統中允許用戶使用一個叫做進程標識符process ID (或PID) 的數來標誌進程,PID存放在進程描述符的pid欄位中。PID被順序編號,新創建進程的PID通常是前一個進程的PID加1。不過PID有一個上限值,當核心使用的PID達到這個上限值的時候就必須開始循環使用已閒置的小PID號。
(4) 進程通信有關信息
為了使進程能在同一項任務上協調工作,進程之間必須能進行通信即交流數據。Linux支持多種不同形式的通信機制。它包含如下域:
spinlock_t sigmask_lock: 信號掩碼的自旋鎖
long blocked: 信號掩碼
struct signal *sig: 信號處理函式
struct sem_undo *semundo: 為避免死鎖而在信號量上設定的取消操作
struct sem_queue *semsleeping: 與信號量操作相關的等待佇列
(5) 進程連結信息
程式創建的進程具有父/子關係。如果一個進程創建多個子程式時,則子進程之間具有兄弟關係。
(6) 時間和定時器信息
一個進程從創建到終止叫做該進程的生存期。進程在其生存期內使用CPU的時間,核心都要對其進行記錄,以便進行統計、計費等有關操作。進程耗費CPU的時間由兩部分組成:一是用戶模式 (或稱用戶態) 下耗費的時間、一是在系統模式 (或稱為系統態) 下耗費的時間。每個時鐘滴答,也就是每個時鐘中斷,核心都要更新當前進程耗費CPU的時間信息。進程有3種類型的定時器:實時定時器、虛擬定時器和概況定時器。這3種定時器的特徵共有3個:到期時間、定時間隔和要觸發的事件。到期時間就是定時器到什麼時候完成定時操作,從而觸發相應的事件;定時間隔就是兩次定時操作的時間間隔,它決定了定時操作是否繼續進行。如果定時間隔大於o,則在定時器到期時,該定時器的到期時間被重新賦值,以使定時操作能繼續進行下去,直到進程結束或停止用定時器,只不過對不同的定時器,到期時間的重新賦值操作是不同的。實時定時器不管其所屬的進程是否運行都要更新,所以,時鐘中斷來臨時,系統中所有進程的實時定時器都要被更新,如果有多個進程的實時定時器到期,則核心要一一處理這些定時器所觸發的事件。而虛擬定時器和概況定時器只在進程運行時更新,所以,時鐘中斷來臨時,只有當前進程的概況定時器得到更新,如果當前進程運行於用戶態,則其虛擬定時器也會得到更新。
(7) 檔案系統信息
進程可以打開或關閉檔案,檔案屬於系統資源,Linux核心要對進程使用檔案的情況進行記錄。task_struct結構中有兩個數據結構用於描述進程與檔案相關的信息。其中,fs_struct中描述了兩個vFs索引節點 (vFS inode),這兩個索引節點叫做root和pwd,分別指向進程的可執行映像所對應的根目錄(Home Directory) 和當前目錄或工作目錄。file_struct結構記錄了過程打開的檔案的描述符 (Descrlptor),
(8) 虛擬記憶體信息
除了核心執行緒,每個進程都擁有自己的地址空間(也叫虛擬空間),用mm_struct來描述。其記憶體信息如下:
struct mm_struct *mm: 描述進程的地址空間
struct mm_struct *active_mm: 核心執行緒所借用的地址空間
(9) 頁面管理信息
當物理記憶體不足時,Linux記憶體管理子系統需要把記憶體中的部分頁面交換到外存,其交換是以頁為單位的。
(10) 對稱多處理機
與對稱多處理機相關的域如下:
int has_cpu: 進程當前是否擁有CPU
int processor: 進程當前正在使用的CPU
int lock_depth: 上下文切換時核心鎖的深度
(11) 和處理器相關的環境信息
進程作為一個執行環境的綜合,當系統調度某個進程執行, 即為該進程建立完整的環境時,處理器的暫存器、堆疊等是必不可少的。因為不同的處理器對內部暫存器和堆疊的定義不盡相同,所以叫做 “和處理器相關的環境”,也叫做“處理機狀態”。當進程暫時停止運行時,處理機狀態必須保存在進程的task_struct結構中,當進程被調度重新運行時再從中恢復這些環境,也就是恢復這些暫存器和堆疊的值。
處理機信息的定義形式為struct thread_struct*tss: 任務切換狀態
1.2 Linux進程描述符處理
進程是動態實體,其生命周期範圍從幾毫秒到幾個月。 因此,核心必須能同時處理很多進程,並把進程描述符存放在動態記憶體中。Linux把兩個不同的數據結構緊湊地放在一個單獨為進程分配的存儲區域內:一個是核心態的進程堆疊,另一個是緊挨進程描述符的小數據結構thread_info,叫做執行緒描述符,這塊存儲區域的大小通常為8192個位元組(兩個頁框)。核心使用alloc_thread_info和free_thread_info宏分配和釋放存儲thread_info結構和核心的記憶體區。
1.3 進程鍊表
進程鍊表把所有的進程的描述符連結起來。每個task_struct結構都包含一個list_head類型的tasks欄位,這個類型 的prev和next欄位分別指向前面和後面的task_struct元素。進程鍊表的頭是init_task描述符,它是所謂的0進程(process 0) 或swapper進程的進程描述符。init_task的tasks.prev欄位指向鍊表中最後插入的進程描述符的tasks欄位。當核心尋找一個新進程在CPU上運行時, 必須只考慮可運行進程 (即處在TASK_RUNNING狀
態的進程)。Linux2.6實現的運行佇列建立多個可運行進程鍊表,每種進程優先權對應一個不同的鍊表,其目的是讓調度程式能在固定的時間內選出 “最佳” 可運行進程,與佇列中可運行的進程數無關。每個task_struct描述符包含一個list_head類型的欄位run_list。這樣,核心就必須為系統中每個運行佇列保存大量的數據,不過運行佇列的
主要數據結構還是組成運行佇列的進程描述符鍊表,所有這些鍊表都由一個單獨的prio_array_t數據結構來實現。

Windows進程模型

進程模型 進程模型

2.1 Windows進程的特點
Windows進程設計的目標是對多種作業系統環境提供支持。不同作業系統環境支持的進程在很多方面都是不同的,包括:

◇ 進程如何命名
◇ 進程中是否提供執行緒
◇ 進程如何表示
◇ 如何保護進程資源
◇ 進程間的通信和同步使用什麼機制
◇ 進程之間如何聯繫
因此,Windows核心所提供的進程結構和服務是相當簡單和通用的,同時允許每個OS子系統模擬某種特定的進程結構和功能。Windows進程的重要特點如下:
◇ Windows進程作為對象實現
◇ 一個可執行的進程可能含有一個或多個執行緒
◇ 進程對象和執行緒對象都具有內置的同步能力
圖4顯示了進程與它所控制或使用的資源相關聯的方式。每個進程都被指定一個安全訪問標誌,稱為進程的基本標誌。當用戶初次登入時,Windows會創建一個包括用戶安全ID的訪問標誌。每個由用戶創建的進程或代表用戶運行的進程都有該訪問標誌的一個副本。Windows使用這個標誌,使得用戶可以訪問受保護的對象,或者在系統上和受保護的對象上執行限定功能。訪問標誌控制該進程是否可以改變它自己的屬性,在這種情況下,該進程沒有已打開的自身訪問標誌的句柄。如果進程試圖打開這樣的一個句柄,則安全系統確定是否允許這樣做,即確定該進程是否可以改變自己的屬性。與進程相關的還有定義當前分派給該進程的虛擬地址空間的一系列塊。進程不能直接修改這些結構,而必須依賴於虛擬存儲管理器,它為進程提供了記憶體分配任務。進程還包括一個對象表, 表中有該進程知道的其他對象的句柄。對象中包含的每個執行緒都有一個句柄。對象句柄即對象標識符,當一個進程通過名稱創建或打開一個對象時,它會接收到一個句柄,此後通過此句柄來訪問該對象。對象句柄實際上是一個索引,指向與進程相關的句柄表中的表項。
2.2 Windows進程的組成
從最高抽象層次來看, Windows有以下幾個方面組成:
◇ 一個私有的虛擬地址空間
◇ 一個可執行的程式: 定義了代碼和數據,並被映射到進程的虛擬地址空間
◇ 一個已經打開句柄的列表: 指向各種資源,比如信號量、檔案,該進程的所有執行緒都可訪問這些系統資源
◇ 一個被稱為訪問令牌的安全環境: 標識與該進程關聯的用戶、 安全組和特權
◇ 一個被稱為進程ID的唯一標識
◇ 至少一個執行執行緒
2.3 Windows進程的關鍵數據結構
Windows進程的數據結構主要有以下幾塊:
◇ 執行體進程塊(EPROCESS, Executive Process Block):執行體進程對象的對象體,包括進程ID、 父進程ID、程式名、進程優先權、記憶體管理信息、設備映像等。
◇ 核心進程塊 (KPROCESS, Kernel Process Block):核心進程對象的對象體,又稱PCB,包括執行緒調度時需要的信息,如進程狀態、執行緒時間片等。
◇ 進程環境塊 (PEB, Process Environment Block):包括用戶態代碼需要和修改的信息。
◇ Windows環境子系統核心態部件win32k.sys為每個進程建立的進程信息數據結構WIN32KPROCESS。
◇ Windows環境子系統進程csrss(用戶態空間) 為每個進程建立的進程信息數據結構。每個Windows進程用一個對象表示。每個進程由許多屬性定義,並且封裝了它可以執行的許多行為或服務。一個進程在收到相應的訊息後將執行一個服務,調用這類服務的唯一方法是給提供該服務的進程對象傳送訊息。當Windows創建一個進程後,它使用為Windows進程定義的、用做模板的對象類或類型來產生一個新的對象實例。並且在創建對象時,賦予其屬性值。下面簡單給出進程對象中每個對象屬性的定義。
◇ 進程ID: 為作業系統標誌該進程的惟一的值
◇ 安全描述符: 描述創建了對象、可以訪問或使用該對象以及不允許訪問該對象的用戶ID標誌
◇ 基本優先權: 進程中執行緒的基準執行優先權
◇ 默認處理器關聯: 可以運行進程中執行緒的默認處理器集合
◇ 定額限制: 已分頁的和未分頁的系統記憶體的最大值、分頁檔案空間的最大值、用戶進程可以使用處理器時間的最大值
◇ 執行時間: 進程中所有執行緒已執行的時間總量
◇ I/O計數器: 記錄進程中執行緒已經執行的I/O操作的數量和類型的變數
◇VM操作計數器: 記錄進程中執行緒已經執行的虛擬記憶體操作的數量和類型的變數
◇ 異常/調試連線埠: 當進程中的一個執行緒引發異常時,用於進程管理器傳送訊息的進程間通信通道
◇ 退出狀態: 進程終止的原因一個Windows進程必須至少包含一個執行執行緒,該執行緒可能會創建別的執行緒。在Windows作業系統中,進程是資源分配的最小單位,而執行緒則是作業系統調度的最小單位。
Windows執行緒有六種狀態, 以下分別予以介紹:
① 就緒態:可以被調度執行。微核心分派器跟蹤所有就緒執行緒,並按優先權順序進行調度。
② 備用態:備用執行緒已經被選擇下一次在一個特定的處理器上運行。該執行緒在這個狀態等待,直到那個處理器可用。如果備用執行緒的優先權足夠高,正在那個處理器上運行的執行緒可能被這個備用執行緒搶占。否則,該備用執行緒要等到正在運行的執行緒被阻塞或結束其時間片。
③ 運行態:一旦微核心處理執行緒或進程切換,備用執行緒將進入運行狀態並開始執行,執行過程一直持續到被搶占、時間片期滿、被阻塞或終止。在前兩種情況下,它將回到就緒態。
④ 等待態:當執行緒被一個事件 (如阻塞、為了同步自願等待) 或者一個環境子系統指引它把自身掛起時,該執行緒進入等待狀態。當等待的條件滿足時,如果它的所有資源都可用,則執行緒轉到就緒態。
⑤ 過渡態:一個執行緒在等待後,如果準備好運行但資源不可用時,進入該狀態。例如,一個執行緒的棧被換出存儲器。當該資源可用時,執行緒進入就緒態。
⑥ 終止態:一個執行緒可以被自己或者被另一個執行緒終止,或者當它的父進程終止時終止。一旦完成了清理工作,該執行緒從系統中移出,或者被執行體保留,供以後重新初始化。

Windows進程和Linux進程比較

Linux 和 Windows 系統的進程結構都相當複雜。在Linux里,只有進程的概念,但在WIN32里卻還有一個 “執行緒” 的概念,那么Linux和WIN32在這裡究竟有著什麼區別呢?在WIN32里,“進程” 是指一個程式,而 “執行緒” 是一個 “進程”里的一個執行 “線索”。從核心上講,WIN32的多進程與Linux並無多大的區別,在WIN32里的執行緒才相當於Linux的進程,是一個實際正在執行的代碼。但是,WIN32里同一個進程里各個執行緒之間是共享數據段的。這才是與Linux的進程最大的不同。在WIN32下,使用CreateThread函式創建執行緒,與Linux下創建進程不同,WIN32執行緒不是從創建處開始運行的,而是由 CreateThread指定一個函式,執行緒就從那個函式處開始運行。在WIN32中,全局變數是子執行緒與父執行緒共享的,這就是與Linux最大的不同之處。從上面的分析可以看出Windows的進程/執行緒要比Linux複雜。對於多任務系統,共享數據區是必要的,但也是一個容易引起混亂的問題,在WIN32下,執行緒之間的數據是共享的,一個執行緒修改過一個變數後,另一個執行緒卻又修改了它,結果引起程式出問題。 但在Linux下,由於變數本來並不共享,而由程式設計師來顯式地指定要共享的數據,使程式變得更清晰與安全。在進程管理及調度方面,Linux要比Windows的開銷小。Linux是一個單塊式的作業系統,作業系統通常在用戶進程的記憶體空間內進行,可免去發生系統調用時的進程切換開銷。Windows是一個準微核心作業系統,許多功能以單獨的進程實現,從而提高了系統的模組化程度,但進程切換上的開銷要大一些。

相關詞條

熱門詞條

聯絡我們