1. 程式人生 > >從零開始之驅動發開、linux驅動(二十九、linux中的程序)

從零開始之驅動發開、linux驅動(二十九、linux中的程序)

一、程序

1、什麼是程序

  • 程序的概念是作業系統中最基本、最重要的概念。它是多道程式系統出現後,為了刻畫系統內部出現的動態情況,描述系統內部各道程式的活動規律而引進的一個新概念,所有多道程式設計的作業系統都建立在程序的基礎上。作業系統專門引入程序的概念,從理論角度看,是對正在執行的程式活動規律的抽象;從實現角度看,則是一種資料結構,目的在於清晰地刻畫動態系統的內在規律,有效管理和排程進入計算機系統主儲存器執行的程式。
  • 程序(process)這個名詞最早是 1960 年在 MIT 的 MULTICS 和 IBM 公司的 TSS/360系統中提出的,直到目前對程序的定義和名稱均不統一,不同的系統中採用不同的術語名稱,例如, MIT 稱程序(process), IBM 公司稱任務(task)和 Univac 公司稱活動80(active)。可以說程序的定義多種多樣,國內學術界較為一致的看法是: 程序是一個可併發執行的具有獨立功能的程式關於某個資料集合的一次執行過程,也是作業系統進行資源分配和保護的基本單位(1978 年全國作業系統學術會議)  

2、為什麼引入程序

程式併發執行時具有如下特徵:

  • 間斷性:程式在併發執行時,由於它們共享資源或為完成同一項任務而相互合作,使在併發程式之間形成了相互制約的關係。相互制約將導致併發程式具有“執行-暫停-執行”這種間斷性活動規律。
  • 失去封閉性 :程式在併發執行時,是多個程式共享系統中的各種資源,因而這些資源的狀態將由多個程式來改變,致使程式的執行已失去了封閉性。
  • 不可再現性 :程式在併發執行時,由於失去了封閉性,也將導致失去結果的可再現性。即程式經過多次執行,雖然其各次的環境和初始條件相同,但得到的結果卻各不相同。

由於程式在併發執行時,可能會造成執行結果的不可再現,所以用“程式”這個概念已無法描述程式的併發執行,所以必須引入新的概念—程序來描述程式的併發執行,並要對程序進行必要的管理,以保證程序在併發執行時結果可再現。 

  • 一是刻畫系統的動態性,發揮系統的併發性,提高資源利用率。在多道程式環境下,程式可以併發執行,一個程式的任意兩條指令之間都可能發生隨機事件而引發程式切換。因而,每個程式的執行都可能不是連續的而是走走停停的。此外,程式的併發執行又引起了資源共享和競爭的問題,造成了各併發執行的程式間可能存在制約關係。併發執行的程式不再處在一個封閉的環境中,出現了許多新的特徵,系統需要一個能描述程式動態執行過程的單位,這個基本單位就是程序。同靜態的程式相比較,程序依賴於處理器和主儲存器資源,具有動態性和暫時性,程序隨著一個程式模組進入主儲存器並獲得一個數據塊和一個程序控制塊而建立,因等待某個事件發生或資源得不到滿足而暫停執行,隨著執行的結束退出主儲存器而消亡,從建立到消亡期間,程序處於不斷的動態變化之中。此外,由於程式的併發執行,使得處理器和 I/O 裝置、I/O 裝置和 I/O 裝置能有效地並行工作, 提高了資源的利用率和系統的效率。 由此可見,程序是併發程式設計的一種有力工具,作業系統中引入程序概念能較好地刻畫系統內部的“動態性”,發揮系統的“併發性”和提高資源的利用率。
  • 二是解決共享性,正確描述程式的執行狀態。也可以從解決“共享性”來看作業系統中引入程序概念的必要性。首先,引入“可再入”程式的概念,所謂“可再入”程式是指能被多個程式同時呼叫的程式。另一種稱“可再用”程式由於它被呼叫過程中具有自身修改,在呼叫它的程式退出以前是不允許其他程式來呼叫它的。“可再入”程式具有以下性質:它是純程式碼,即它在執行中自身不被改變;呼叫它的各程式應提供工作區,因此,可再入程式可以同時被幾個程式呼叫。

程序(Process)定義:“可併發執行的程式在一個數據集合上的執行過程”。程序具有如下特徵:

  • 非同步性:程序按各自獨立的不可預知的速度向前推進,即程序按非同步方式進行,正是這一特徵,將導致程式執行的不可再現性,因此OS必須採用某種措施來限制各程序推進序列以保證各程式間正常協調執行。
  • 結構性:程序包含了資料集合和運行於其上的程式,為了描述和記錄程序的動態變化過程使其能正確執行,還需配置一個程序控制塊,所以,每個程序至少有三要素組成:程式塊、資料塊和程序控制塊。
  • 共享性:同一程式同時運行於不同資料集合上時,構成不同的程序。或者說,多個不同的程序可以共享相同的程式,所以,程序和程式不是一一對應的。
  • 動態性:程序是程式在資料集合上的一次執行過程,是動態概念,同時,它還有生命週期,由建立而產生,由排程而執行,由撤銷而消亡;而程式是一組有序指令序列,是靜態概念,所以,程式作為一種系統資源是永久存在的。
  • 獨立性:程序既是系統中資源分配和保護的基本單位,也是系統排程的獨立單位(單執行緒程序)。凡是未建立程序的程式,都不能作為獨立單位參與執行。通常,每個程序都可以各自獨立的速度在 CPU 上推進。
  • 制約性:併發程序之間存在著制約關係,程序在進行的關鍵點上需要相互等待或互通訊息,以保證程式執行的可再現性和計算結果的唯一性。
  • 併發性:程序可以併發地執行,程序的併發效能改進資源利用率和提高系統效率。對於一個單處理器的系統來說, m 個程序 P1, P2,…, Pm 是輪流佔用處理器併發地執行。例如,可能是這樣進行的:程序 P1 執行了 nl 條指令後讓出處理器給 P2, P2 執行了 n2 條指令後讓出處理器給 P3,…, Pm 執行了 nm 條指令後讓出處理器給 P1,…。因此,程序的執行是可以被打斷的,或者說,程序執行完一條指令後在執行下一條指令前,可能被迫讓出處理器,由其他若干個程序執行若干條指令後才能再次獲得處理器而執行。  

3、程序的描述

一個程序從建立而產生至撤銷而消亡的整個生命期間,有時佔有處理器執行,有時雖可執行但分不到處理器、有時雖有空閒處理器但因等待某個事件的發生而無法執行,這一切都說明程序和程式不相同,它是活動的且有狀態變化的,這可以用一組狀態加以刻畫。為了便於管理程序,一般來說,按程序在執行過程中的不同情況至少要定義三種不同的程序狀態:  

這裡寫圖片描述

  • 執行態/執行態(Running):當一個程序在處理機上執行時,則稱該程序處於執行狀態。
  • 就緒態(Ready):一個程序獲得了除處理機外的一切所需資源,一旦得到處理機即可執行,則稱此程序處於就緒狀態。
  • 阻塞態(Blocked):(又稱掛起狀態、等待狀態):一個程序正在等待某一事件發生(例如請求I/O而等待I/O完成等)而暫時仃止執行,這時即使把處理機分配給程序也無法執行,故稱該程序處於阻塞狀態。

三個基本狀態之間可能轉換和轉換原因如下:

  • 就緒態–>執行態:當處理機空閒時,程序排程程式必將處理機分配給一個處於就緒態的程序 ,該程序便由就緒態轉換為執行態。
  • 執行態–>阻塞態:處於執行態的程序在執行過程中需要等待某一事件發生後(例如因I/O請求等待I/O完成後),才能繼續執行,則該程序放棄處理機,從執行態轉換為阻塞態。
  • 阻塞態–>就緒態:處於阻塞態的程序,若其等待的事件已經發生,於是程序由阻塞態轉換為就緒態。
  • 執行態–>就緒態:處於執行狀態的程序在其執行過程中,因分給它的處理機時間片已用完,而不得不讓出(被搶佔)處理機,於是程序由執行態轉換為就緒態。
  • 而阻塞態–>執行態和就緒態–>阻塞態這二種狀態轉換不可能發生。
  • 處於執行態程序:如系統有一個處理機,則在任何一時刻,最多隻有一個程序處於執行態。
  • 處於就緒態程序:一般處於就緒態的程序按照一定的演算法(如先來的程序排在前面,或採用優先權高的程序排在前面)排成一個就緒佇列RL。
  • 處於阻塞態程序:處於阻塞態的程序排在阻塞佇列中。由於等待事件原因不同,阻塞佇列也按事件分成幾個佇列WLi。  

一個問題:假設一個只有一個處理機的系統中,OS的程序有執行、就緒、阻塞三個基本狀態。假如某時刻該系統中有10個程序併發執行,在略去排程程式所佔用時間情況下試問  這時刻系統中處於執行態的程序數最多幾個?最少幾個  這時刻系統中處於就緒態的程序數最多幾個?最少幾個  這時刻系統中處於阻塞態的程序數最多幾個?最少幾個?  解:

因為系統中只有一個處理機,所以某時刻處於執行態的程序數最多隻有一個。而最少可能為0,此時其它10個程序一定全部排在各阻塞佇列中,在就緒佇列中沒有程序。

而某時刻處於就緒態的程序數最多隻有9個,不可能出現10個情況,因為一旦CPU有空,排程程式馬上排程,當然這是在略去排程程式排程時間時考慮,處於就緒的程序最少0個,10個程序都阻塞則就緒態的就是0個。

處於阻塞態的最多有10個,比如是個程序都是在主動的sleep睡眠,處於阻塞態的程序數最少是0個。

作業系統作為資源管理和分配程式,其本質任務是自動控制程式的執行,並滿足程序執行過程中提出的資源使用要求。因此,作業系統的核心控制結構是程序結構,資源管理的資料結構將圍繞程序結構展開。 在研究程序的控制結構之前,首先介紹一下作業系統的控制結構。為了有效的管理程序和資源,作業系統必須掌握每一個程序和資源的當前狀態。從效率出發,作業系統的控制結構及其管理方式必須是簡明有效的,通常是通過構造一組表來管理和維護程序和每一類資源的資訊。作業系統的控制表分為四類:程序控制表,儲存控制表, I/O 控制表和檔案控制表。

  • 程序控制表用來管理程序及其相關資訊。
  • 儲存控制表用來管理一級(主)儲存器和二級(輔)儲存器,主要內容包括:主儲存器的分配資訊,二級儲存器的分配資訊,儲存保護和分割槽共享資訊,虛擬儲存器管理資訊。
  • I/O 控制表用來管理計算機系統的 I/O 裝置和通道,主要內容包括: I/O 裝置和通道是否可用, I/O 裝置和通道的分配資訊, I/O 操作的狀態和進展, I/O 操作傳輸資料所在的主存區。
  • 檔案控制表用來管理檔案,主要內容包括:被開啟檔案的資訊,檔案在主儲存器和二級儲存器中的位置資訊,被開啟檔案的狀態和其他屬性資訊。

4、程序上下文

當一個程式進入計算機的主儲存器進行計算就構成了程序,主儲存器中的程序到底是如何組成的?作業系統中把程序物理實體和支援程序執行的環境合稱為程序上下文(process context) 。當系統排程新程序佔有處理器時,新老程序隨之發生上下文切換,因此,程序的執行被認為是在程序的上下文中執行的。在作業系統中,程序上下文包括三個組成部分:

  • 使用者級上下文(user -level context):由使用者程序的程式塊、使用者資料塊(含共享資料塊)和使用者堆疊組成的程序地址空間。
  • 系統級上下文(system -level context):包括程序的標識資訊、現場資訊和控制資訊,程序環境塊,以及系統堆疊等組成的程序地址空間。
  • 暫存器上下文(register context):由程式狀態字暫存器、各類控制暫存器、地址暫存器、通用暫存器、使用者棧指標等組成。  

程序是由程式、資料和程序控制塊組成。程序上下文實際上是執行活動全過程的靜態描述。具體說,程序上下文包括系統中與執行該程序有關的各種暫存器(例如:通用暫存器、程式計數器PC、程式狀態暫存器PS等)的值,程式段在經編譯之後形成的機器指令程式碼集(或稱正文段)、資料集及各種堆疊值和PCB結構。一個程序的執行是在該程序的上下文中執行,而當系統排程新程序佔有處理機時,新老程序的上下發生切換。UNIX 作業系統的程序上下文稱為程序映象。

程序的記憶體映像可以很好地說明程序的組成。簡單的說,一個程序映像(process Image) 包括:

  • 程序程式塊:即被執行的程式,規定了程序一次執行應完成的功能。通常它是純程式碼,作為一種系統資源可被多個程序共享。
  • 程序資料塊:即程式執行時加工處理物件,包括全域性變數、區域性變數和常量等的存放區以及開闢的工作區,常常為一個程序專用。
  • 系統/使用者堆疊:每一個程序都將捆綁一個系統/使用者堆疊。用來解決過程呼叫或系統呼叫時的資訊儲存和引數傳遞。
  • 程序控制塊:每一個程序都將捆綁一個程序控制塊,用來儲存程序的標誌資訊、現場資訊和控制資訊。程序建立時,建立程序控制塊;程序撤銷時,回收程序控制塊,它與程序一一對應。  

可見每個程序有四個要素組成:控制塊、程式塊、資料塊和堆疊。  

5、程序控制模組(PCB)

每一個程序都有一個也只有一個程序控制塊 PCB(Process Control Block) ,程序控制塊是作業系統用於記錄和刻畫程序狀態及有關資訊的資料結構,也是作業系統掌握程序的唯一資料結構,是作業系統控制和管理程序的主要依據。它包括了程序執行 時的情況,以及程序讓出處理器後所處的狀態、斷點等資訊。

一般說,程序控制塊包含三類資訊:

  • 標識資訊。用於唯一地標識一個程序,常常分為由使用者使用的外部識別符號和被系統使用的內部標識號。幾乎所有作業系統中程序都被賦予一個唯一的、內部使用的數值型的程序號,作業系統的其他控制表可以通過程序號來交叉引用程序控制表。常用的標識資訊包括程序識別符號、父程序的識別符號、使用者程序名、使用者組名等。
  • 現場資訊。用於保留一個程序在執行時存放在處理器現場中的各種資訊,任何一個程序在讓出處理器時必須把此時的處理器現場資訊儲存到程序控制塊中,而當該程序重新恢復執行時也應恢復處理器現場。常用的現場資訊包括:通用暫存器的內容、控制暫存器(如 PSW 暫存器)的內容、使用者堆疊指標、系統堆疊指標等。
  • 控制資訊。用於管理和排程一個程序。常用的控制資訊包括: 1)程序排程相關資訊,如程序狀態、等待事件和等待原因、程序優先順序、佇列指引元等; 2)程序組成資訊,如正文段指標、資料段指標; 3)程序間通訊資訊,如訊息佇列指標、訊號量等互斥和同步機制; 4)程序在輔儲存器內的地址; 5) CPU資源的佔用和使用資訊,如時間片餘量、程序己佔用 CPU 的時間、程序己執行的時間總和,記賬資訊; 6)程序特權資訊,如在記憶體訪問許可權和處理器狀態方面的特權; 7)資源清單,包括程序所需全部資源、已經分得的資源,如主存資源、 I/O 裝置、開啟檔案表等。  

程序控制塊是作業系統中最為重要的資料結構,每個程序控制塊包含了作業系統管理所需的所有程序資訊,程序控制塊的集合事實上定義了一個作業系統的當前狀態。程序控制塊使用權或修改權均屬於作業系統程式,包括排程程式、資源分配程式、中斷處理程式、效能監視和分析程式等。當系統建立一個程序時,就為它建立一個 PCB,當程序執行結束被撤銷時,便回收它佔用的 PCB。作業系統是根據 PCB 來對併發執行的程序進行控制和管理的,藉助於程序控制塊 PCB,程序才能被排程執行。  

  • 程序識別符號:它用於唯一地標識一個程序。它有外部識別符號(由字母組成,供使用者使用)和內部識別符號(由整陣列成,為方便系統管理而設定)二種。 
  • 程序排程資訊:它包括程序狀態(running、ready、blacked)、佇列(就緒、阻塞佇列)、佇列指標,排程引數:程序優先順序、程序已執行時間和已等待時間等。 
  • 處理機狀態資訊:它由處理機各種暫存器(通用暫存器、指令計數器、程式狀態字PSW、使用者棧指標等)的內容所組成,該類資訊使程序被中斷後重新執行時能恢復現場從斷點處繼續執行。 
  • 程序控制資訊:它包括程式和資料的地址、I/O資源清單,保證程序正常執行的同步和通訊機制等。 
  • 家族資訊:它包括該程序的父、子程序識別符號、程序的使用者主等。 

UNIX的PCB由proc和user兩個結構組成,proc常駐主存的系統區,是PCB中最基本和常用資訊,而user可根據需要換進換出。

6、程序佇列及其管理  

併發系統中同時存在許多程序,有的處於就緒態,有的處於等待態,等待原因各不相同。程序的主要特徵是由 PCB 來刻畫的,為了便於管理和排程,常常把各個程序的 PCB 用某種方法組織起來。 用得較多的是用佇列來組織 PCB, 下面先介紹這種方法。 一般說來,把處於同一狀態(例如就緒態)的所有程序控制塊連結在一起的資料結構稱為程序佇列(process queues) ,簡稱佇列。同一狀態程序的 PCB 既可按先來先到的原則排成佇列;也可以按優先數或其他原則排成佇列。對於等待態的程序佇列可以進一步細分,每一個程序按等待的原因進入相應的等待佇列,例如,如果一個程序要求使用某個裝置,而該裝置已經被佔用時,此程序就連結到與該裝置相關的等待態佇列中去。  

在一個佇列中,連結程序控制塊的方法可以是多樣的,常用的是單向連結和雙向連結。單向連結方法是在每個程序控制塊內設定一個佇列指引元,它指出在佇列中跟隨著它的下一個程序的程序控制塊內佇列指引元的位置。雙向連結方法是在每個程序控制塊內設定兩個指引元,其中一個指出佇列中該程序的上一個程序的程序控制塊內佇列指引元的位置,另一個指出佇列中該程序的下一個程序的程序控制塊的佇列指引元的位置。為了標誌和識別一個佇列,系統為每一個佇列設定一個佇列標誌,單向連結時,佇列標誌指引元指向佇列中第一個程序的佇列指引元的位置;雙向連結時,佇列標誌的後向指引元指向佇列中第一個程序的後向佇列指引元的位置;佇列標誌的前向指引元指向佇列中最後一個程序的前向佇列指引元的位置。這兩種連結方式如圖所示。  

當發生的某個事件使一個程序的狀態發生變化時,這個程序就要退出所在的某個佇列而排入到另一個佇列中去。一個程序從一個所在的佇列中退出的事件稱為出隊,相反,一個程序排入到一個指定的佇列中的事件稱為入隊。處理器排程中負責入隊和出隊工作的功能模組稱為佇列管理模組,簡稱佇列管理。下圖給出了作業系統的佇列管理和狀態轉換示意圖。

7、程序切換與模式切換

中斷是啟用作業系統的唯一方法,它暫時中止當前執行程序的執行,把處理器切換到作業系統的控制之下。而當作業系統獲得了處理器的控制權之後,它就可以實現程序切換,所以,程序切換必定在核心態而不是在使用者態下發生。當發生中斷事件,或程序執行系統呼叫後,有可能引發核心進行程序上下文切換,由於一個程序讓出處理器時,其暫存器上下文將被儲存到系統級上下文的相應的現場資訊位置,這時核心就把這些資訊壓入系統棧的一個上下文層。當核心處理中斷返回,或一個程序完成其系統呼叫返回使用者態,或核心進行上下文切換時,核心就從系統棧彈出一個上下文層(context layer)。因此,上下文的切換總會引起上下文的壓入和彈出堆疊。核心在四種情況下允許發生上下文切換:

  1. 當程序進入等待態時;
  2. 當程序完成其系統呼叫返回使用者態但不是最有資格獲得 CPU 時;
  3. 當核心完成中斷處理,程序返回使用者態但不是最有資格獲得 CPU 時;
  4. 當程序執行結束時。

做一次程序上下文切換時,即儲存老程序的狀態而裝入被保護了的新程序的狀態,以便新程序執行。程序切換的步驟如下:

  • 儲存被中斷程序的處理器現場資訊。
  • 修改被中斷程序的程序控制塊的有關資訊,如程序狀態等。
  • 把被中斷程序的程序控制塊加入有關佇列。
  • 選擇下一個佔有處理器執行的程序。
  • 修改被選中程序的程序控制塊的有關資訊。
  • 根據被選中程序設定作業系統用到的地址轉換和儲存保護資訊。
  • 根據被選中程序的資訊來恢復處理器現場。

從上面介紹的切換工作可以看出,當進行上下文切換時,核心需要儲存足夠的資訊,以便將來適當時機能夠切換回原程序,並恢復它繼續執行。類似地,當從使用者態轉到核心態時,核心保留足夠資訊以便後來能返回到使用者態,並讓程序從它的斷點繼續執行。使用者態到核心態或者核心態到使用者態的轉變是 CPU 模式的改變,而不是程序上下文切換。為了進一步說明程序的上下文切換,下面來討論模式切換。當中斷髮生的時候,暫時中斷正在執行的使用者程序,把程序從使用者狀態切換到核心狀態,去執行作業系統例行程式以獲得服務,這就是一次模式切換,注意,此時仍在該程序的上下文中執行,僅僅模式變了。核心在被中斷了的程序的上下文中對這個中斷事件作處理,即使該中斷事件可能不是此程序引起的。另一點要注意的是被中斷的程序可以是正在使用者態下執行的,也可以是正在核心態下執行的,核心都要保留足夠資訊以便在後來能恢復被中斷了的程序執行。核心在核心態下對中斷事件進行處理時,決不會再產生或排程一個特殊程序來處理中斷事件。模式切換的步驟如下:

  • 儲存被中斷程序的處理器現場資訊。
  • 根據中斷號置程式計數器。
  • 把使用者狀態切換到核心狀態,以便執行中斷處理程式。

注意模式切換不同於程序切換,它並不引起程序狀態的變化,在大多數作業系統中,它也不一定引起程序的切換,在完成了中斷呼叫之後,完全可以再通過一次逆向的模式切換來繼續執行使用者程序。顯然,有效合理地使用模式切換和程序切換有利於作業系統效率和安全性的提高。為此,大多數現代作業系統存在兩種程序:系統程序和使用者程序。它們並不是指兩個具體的程序實體,而是指一個程序的兩個側面,系統程序是在核心態下執行作業系統程式碼的程序,使用者程序在使用者態下執行使用者程式的程序。使用者程序因中斷或系統呼叫進入核心態,系統程序就開始執行,這兩個程序(使用者程序和系統程序)使用同一個PCB,所以,實質上是一個程序實體。但是這兩個程序所執行的程式不同,對映到不同實體地址空間、使用不同堆疊。一個系統程序的地址空間中包含所有的系統核心程 序和各程序的程序資料區,所以,各程序的系統程序除資料區不同外,其餘部分全相同,但各程序的使用者程序部分則各不相同。  

下圖清楚地給出了一個程序(未對換出主存)其生命週期中,可能出現的程序切換和模式切換的示意。其中,

  • 使用者態執行表示程序在使用者模式下執行;
  • 核心態執行表示程序在核心模式執行;
  • 就緒狀態表示程序處於就緒態,但未正在執行;
  • 等待狀態表示程序正處於等待態,由於發生某個事件(如等 I/O 完成)而進入此狀態。  

上面的狀態給出了描述程序的靜態觀點,事實上程序狀態是在連續動態地轉換的,狀態圖中若有箭頭指向的邊,這種狀態轉換是合法的,通常程序完成且只完成一個合法狀態轉換。任何時刻一個處理器上僅能執行一個程序,所以,至多隻有一個程序可以處在狀態(1)或狀態(2),這兩個狀態相應於使用者模式和核心模式。當系統呼叫或中斷髮生時,程序通過一個模式切換從使用者態執行轉化為核心態執行,在核心態下執行的程序是不能被搶佔的(現在比較新的核心是允許核心態搶佔的);當程序在核心模式執行時,可以繼續響應中斷,當處理完中斷或系統呼叫後,可以通過一個模式切換轉回使用者模式繼續執行,也可以根據具體情況使程序等待一個事件(狀態(4)),只有在此時核心才會允許發生程序切換,使原先執行的程序讓出處理器;當等待事件完成後,程序會從等待狀態被喚醒,進入就緒狀態(狀(3)),若被排程程式選中,程序會重新佔有處理器執行。在多道程式設計系統中,有許多程序在併發執行,如果兩個以上程序同時執行系統呼叫,並要求在核心模式下執行,則有可能破壞核心資料結構中的資訊,通過禁止任意的上下文切換和控制中斷的響應,就能保證資料的一致性和完整性。  

二、程序控制

處理器管理的一個主要工作是對程序的控制,對程序的控制包括:建立程序、阻塞程序、喚醒程序、掛起程序、啟用程序、終止程序和撤銷程序等。這些控制和管理功能是由作業系統中的原語來實現的。 原語(Primitive)是在核心態下執行、完成系統特定功能的過程。原語和機器指令類似,其特點是執行過程中不允許被中斷,是一個不可分割的基本單位,原語的執行是順序的而不可能是併發的。系統對程序的控制如不使用原語,就會造成其狀態的不確定性,從而,達不到程序控制的目的。原語和系統呼叫都使用訪管指令實現,具有相同的呼叫形式;但原語由核心來實現,而系統呼叫由系統程序或系統伺服器實現;原語不可中斷,而系統呼叫執行時允許被中斷,甚至有些作業系統中系統程序或系統伺服器乾脆在使用者態執行;通常情況下,原語提供給系統程序或系統伺服器使用(反之決不會形成呼叫關係), 系統程序或系統伺服器提供系統呼叫給系統程式(和使用者)使用,而系統程式提供高層功能給使用者使用,例如,語言編譯程式提供語句供使用者解決應用問題。下面介紹部分程序控制原語。

1、核心

  • 核心態和使用者態 :為了防止使用者應用程式訪問和/或更改重要的作業系統資料。UNIX使用兩種處理器訪問模式:核心態和使用者態(又稱管態和目態)。作業系統程式碼在核心態下執行,即在x86處理器Ring0執行,它有著最高的特權。而使用者應用程式程式碼在使用者態下執行,即在x86處理器Ring3中執行。 
  • 使用者應用程式(在Windows 2000中以使用者執行緒方式出現)執行使用者程式一般程式碼時,它是在使用者態下執行。但當程式要呼叫系統服務,例如要呼叫作業系統中負責從磁碟檔案中讀取資料的NT執行體例程時,它就要通過一條專門的指令(自陷指令/訪管指令)來完成從使用者態切換到核心態,作業系統根據該指令及有關引數,執行使用者的請求服務。在服務完成後將處理器模式切換回使用者態,並將控制返回使用者執行緒。因此使用者執行緒有時在核心態下執行,在核心態下執行的是呼叫作業系統有關功能模組的程式碼。
  • 原語是一種特殊的廣義指令,它的功能是由系統通過一段不可分割的指令操作來完成,它又稱原子操作,原語在核心態下完成。程序控制操作(建立、撤消、阻塞……)大都為原語操作。
  • 核心功能 :核心是計算機硬體上的第一層擴充軟體,它是OS中關鍵部分,它是管理控制中心。核心在核心態下執行,常駐記憶體,核心通過執行各種原語操作來實現各種控制和管理功能。

2、程序狀態的細化

“掛起”、 “啟用”操作的引入  系統管理員有時需要暫停某個程序,以便排除系統故障或暫時減輕系統負荷,使用者有時也希望暫停自己的程序以便檢查自己作業的中間結果。這就希望系統提供“掛起”操作,暫停程序執行,同是也要提供“啟用”的操作,恢復被掛起的程序。由於被掛起前程序的狀態有三種,掛起後的程序就分為二種狀態:靜止就緒態和靜止阻塞態(有的稱掛起就緒態和掛起阻塞態)。掛起前的程序就緒態和阻塞態也改為活動就緒態和活動阻塞態。 這裡寫圖片描述

  • 當程序處於執行態和活動就緒態時,執行掛起操作,程序狀態轉換為靜止就緒態。當程序處於活動阻塞態時,執行掛起操作,程序狀態轉換為靜止阻塞態。對被掛起的程序施加“啟用”操作,則處於靜止就緒的程序轉換為活動就緒態,處於靜止阻塞態的程序轉換為活動阻塞態。被掛起的處於靜止阻塞態的程序當它等待的事件發生後,它就由靜止阻塞態轉換為靜止就緒態。

3、程序控制原語

  • 建立原語(Create) 
  • 一個程序可藉助建立原語來建立一個新程序,該新程序是它的子程序,建立一個程序主要是為新程序建立一個PCB。建立原語首先從系統的PCB表中索取一個空白的PCB表目,並獲得其內部標識,然後將呼叫程序提供的引數:如外部名、正文段、資料段的首址、大小、所需資源、優先順序等填入這張空白PCB表目中。並設定新程序狀態為活動/靜止就緒態,並把該PCB插入到就緒佇列RQ中,就可進入系統併發執行。
  • 撤消原語(Destroy)/ 終止(Termination)
  • 對於樹型層次結構的程序系統撤消原語採用的策略是由父程序發出,撤消它的一個子程序及該子程序所有的子孫程序,被撤消程序的所有資源(主存、I/O資源、PCB表目)全部釋放出來歸還系統,並將它們從所有的佇列中移去。如撤消的程序正在執行,則要呼叫程序排程程式將處理器分給其它程序。
  • 阻塞原語(block) 
  • 當前程序因請求某事件而不能執行時(例如請求I/O而等待I/O完成時),該程序將呼叫阻塞原語阻塞自己,暫時放棄處理機。程序阻塞是程序自身的主動行為。阻塞過程首先立即停止原來程式的執行,把PCB中的現行狀態由執行態改為活動阻塞態,並將PCB插入到等待某事件的阻塞佇列中,最後呼叫程序排程程式進行處理機的重新分配。
  • 喚醒原語(wakeup) 
  • 當被阻塞的程序所期待的事件發生時(例如I/O完成時),則有關程序和過程(例如I/O裝置處理程式或釋放資源的程序等)呼叫wakeup原語,將阻塞的程序喚醒,將等待該事件的程序從阻塞佇列移出,插入到就緒佇列中,將該程序的PCB中現行狀態,如是活動阻塞態改為活動就緒態,如是靜止阻塞態改為靜止就緒態。
  • 掛起原語(suspend) 
  • 呼叫掛起原語的程序只能掛起它自己或它的子孫,而不能掛起別的族系的程序。掛起原語的執行過程是:檢查要掛起程序PCB的現行狀態,若正處於活動就緒態,便將它改為靜止就緒態;如是活動阻塞態則改為靜止阻塞態。如是執行態,則將它改為靜止就緒態,並呼叫程序排程程式重新分配處理機。為了方便使用者或父程序考察該程序的執行情況,需把該程序的PCB複製到記憶體指定區域。
  • 啟用原語(active) 
  • 使用者程序或父程序通過呼叫啟用原語將被掛起的程序啟用。啟用原語執行過程是:檢查被掛起程序PCB中的現行狀態,若處於靜止就緒態,則將它改為活動就緒態,若處於靜止阻塞態,則將它改為活動阻塞態。

4、程序結構和組

它由三部分組成: proc 結構、資料段和正文段,它們合稱為程序映像, UNIX 中把程序定義為映像的執行。其中, PCB由基本控制塊 proc 結構和擴充控制塊 user 結構兩部分組成。在 proc 結構裡存放著關於一個程序的最基本、最必需的資訊,因此,它常駐記憶體;在 user 結構裡存放著只有程序執行時才用到的資料和狀態資訊,為了節省記憶體空間,當程序暫時不在處理機上執行時,就把它放在磁碟上的對換區中,程序的 user 結構總和程序的資料段一起,在主存和磁碟對換區之間換進/換出。系統中維持一張名叫 proc 的結構陣列,每個表目為一個 proc 結構,供一個程序使用。建立程序時,在 proc 表中找一個空表目,以建立起相應於該程序的 proc 結構。所有程序的 proc 結構集中形成 proc 結構陣列存放在作業系統的核心資料區中。  

  • proc 結構:存放的資訊包括程序識別符號、父程序識別符號、程序使用者識別符號、程序狀態、等待的事件、指向 user 結構和程序儲存區(text/data/stack)的指標、軟中斷資訊、有關程序執行時間/核心資源使用/使用者設定示警訊號等的計時器、程序的大小、排程優先數、就緒佇列指標等。程序即使不執行,核心也需要訪問有關資訊,所以 proc結構常駐記憶體。
  • user 結構:存放的資訊包括本程序 proc 結構表項的指標、保護現場、本程序正文段/資料區/棧區長度、使用者標識、使用者組標識、使用者開啟檔案表、當前目錄和當前根、計時資訊、檔案 I/O 引數、限制欄位、錯誤碼欄位、返回值欄位和訊號處理陣列等。為了節省記憶體空間,當程序暫時不在處理器上執行時,就把它放在磁碟上的對換區中,程序的 user 結構沒有集中存放,而是分散存放在程序的資料段內,在主存和磁碟對換區之間換進/換出。程序的 user 結構和系統棧組成了系統資料區,雖然放在每個程序的資料塊內,以便於與其他資料一起換進換出記憶體,但卻與其他資料不屬於同一地址空間。系統資料區是 PCB 的一部分屬核心態空間,使用者程式不能訪問它。
  • 系統資料結構:是程序系統資料區,它位於資料段的前面,程序 proc 結構中有指標指向這個區域的首址。該區通常有 1024 個位元組,由兩塊內容組成:最前面的若干位元組為程序的擴充控制塊 user 結構,剩下的位元組為核心棧,當程序執行在核心態時,這裡是它的工作區,用來儲存過程呼叫和中斷訪問時用到的地址和引數。
  • 使用者資料區:用於存放程式執行時用到的資料,如果程序執行的程式是非共享的,那麼這個程式也放於此地。
  • 使用者棧區:當程序執行在使用者態時,這裡是它的工作區。
  • text 結構:正文段在磁碟上和主存中的位置和大小、訪問正文段程序數、在主存中訪問正文段程序數、標誌資訊、地址轉換資訊。由於共享正文段在程序映像中的特殊性,為了便於對它們的管理, UNIX 系統在記憶體中設定了一張正文段表。每一個表目都是一個 text 結構,用來記錄一個共享正文段的屬性(磁碟和主存中的位置、尺寸、共享的程序數、正文段檔案節點指標等),有時也把這種結構稱為正文段控制(資訊)塊。這是可以被多個程序共享的可重入程式和常數,如果一個程序的程式是不被共享的,那麼,它的映像中就不出現這一部分。若一個程序有共享正文段,那麼,當把該程序的非常駐記憶體部分調入記憶體時,應該關注共享正文段是否也在記憶體,如果發現不
  • 在記憶體,則要將它調入;當把該程序的非常駐記憶體部分調出記憶體時,同樣要關注它的共享正文段目前被共享的情況,只要還有一個別的共享該正文段的程序映像全部在記憶體,那麼,這個共享正文段就不得調出去。如果一個程序有共享正文段,該共享正文段在正文段表裡一定有一個 text 結構與之相對應,而在該程序的基本控制塊 proc 裡,有專門指標指向這一個 text 結構。綜上所述,在 UNIX 程序映像的三個組成部分中,proc、 user 和 text 這三個資料結構是最為重要的角色。
  • 系統區表和程序區表:用於實現地址轉換。系統區表用於記錄程序虛擬地址空間的連續區域,包括正文區、資料區、棧區等。這些區是可被共享和保護的獨立實體,並允許多個程序共享一個區。為了對區進行管理,系統區表記錄了區的型別、大小、狀態、位置、引用計數、以及相應的檔案索引節點指標。系統為每個程序建立一張程序區表 PPRT(Per Process Region Table)由儲存管理系統使用,它定義了實體地址與虛擬地址之間的對應關係,還定義了程序對儲存區域的訪問許可權。其中含有正文段、資料段和堆疊的區域表的指標和各區域的邏輯起始地址; 區域表中含有該區域屬性(正文/資料,可以共享)的資訊和頁表的指標;而每個頁表中含有相應區域的頁面在記憶體的起始地址。  

linux3.15.57核心的程序控制塊(表)


struct task_struct {
	volatile long state;	/* -1 unrunnable, 0 runnable, >0 stopped */
	void *stack;
	atomic_t usage;
	unsigned int flags;	/* per process flags, defined below */
	unsigned int ptrace;

#ifdef CONFIG_SMP
	struct llist_node wake_entry;
	int on_cpu;
	struct task_struct *last_wakee;
	unsigned long wakee_flips;
	unsigned long wakee_flip_decay_ts;

	int wake_cpu;
#endif
	int on_rq;

	int prio, static_prio, normal_prio;
	unsigned int rt_priority;
	const struct sched_class *sched_class;
	struct sched_entity se;
	struct sched_rt_entity rt;
#ifdef CONFIG_CGROUP_SCHED
	struct task_group *sched_task_group;
#endif
	struct sched_dl_entity dl;

#ifdef CONFIG_PREEMPT_NOTIFIERS
	/* list of struct preempt_notifier: */
	struct hlist_head preempt_notifiers;
#endif

#ifdef CONFIG_BLK_DEV_IO_TRACE
	unsigned int btrace_seq;
#endif

	unsigned int policy;
	int nr_cpus_allowed;
	cpumask_t cpus_allowed;

#ifdef CONFIG_PREEMPT_RCU
	int rcu_read_lock_nesting;
	char rcu_read_unlock_special;
	struct list_head rcu_node_entry;
#endif /* #ifdef CONFIG_PREEMPT_RCU */
#ifdef CONFIG_TREE_PREEMPT_RCU
	struct rcu_node *rcu_blocked_node;
#endif /* #ifdef CONFIG_TREE_PREEMPT_RCU */
#ifdef CONFIG_RCU_BOOST
	struct rt_mutex *rcu_boost_mutex;
#endif /* #ifdef CONFIG_RCU_BOOST */

#if defined(CONFIG_SCHEDSTATS) || defined(CONFIG_TASK_DELAY_ACCT)
	struct sched_info sched_info;
#endif

	struct list_head tasks;
#ifdef CONFIG_SMP
	struct plist_node pushable_tasks;
	struct rb_node pushable_dl_tasks;
#endif

	struct mm_struct *mm, *active_mm;
#ifdef CONFIG_COMPAT_BRK
	unsigned brk_randomized:1;
#endif
	/* per-thread vma caching */
	u32 vmacache_seqnum;
	struct vm_area_struct *vmacache[VMACACHE_SIZE];
#if defined(SPLIT_RSS_COUNTING)
	struct task_rss_stat	rss_stat;
#endif
/* task state */
	int exit_state;
	int exit_code, exit_signal;
	int pdeath_signal;  /*  The signal sent when the parent dies  */
	unsigned int jobctl;	/* JOBCTL_*, siglock protected */

	/* Used for emulating ABI behavior of previous Linux versions */
	unsigned int personality;

	unsigned in_execve:1;	/* Tell the LSMs that the process is doing an
				 * execve */
	unsigned in_iowait:1;

	/* Revert to default priority/policy when forking */
	unsigned sched_reset_on_fork:1;
	unsigned sched_contributes_to_load:1;

	unsigned long atomic_flags; /* Flags needing atomic access. */

	pid_t pid;
	pid_t tgid;

#ifdef CONFIG_CC_STACKPROTECTOR
	/* Canary value for the -fstack-protector gcc feature */
	unsigned long stack_canary;
#endif
	/*
	 * pointers to (original) parent process, youngest child, younger sibling,
	 * older sibling, respectively.  (p->father can be replaced with
	 * p->real_parent->pid)
	 */
	struct task_struct __rcu *real_parent; /* real parent process */
	struct task_struct __rcu *parent; /* recipient of SIGCHLD, wait4() reports */
	/*
	 * children/sibling forms the list of my natural children
	 */
	struct list_head children;	/* list of my children */
	struct list_head sibling;	/* linkage in my parent's children list */
	struct task_struct *group_leader;	/* threadgroup leader */

	/*
	 * ptraced is the list of tasks this task is using ptrace on.
	 * This includes both natural children and PTRACE_ATTACH targets.
	 * p->ptrace_entry is p's link on the p->parent->ptraced list.
	 */
	struct list_head ptraced;
	struct list_head ptrace_entry;

	/* PID/PID hash table linkage. */
	struct pid_link pids[PIDTYPE_MAX];
	struct list_head thread_group;
	struct list_head thread_node;

	struct completion *vfork_done;		/* for vfork() */
	int __user *set_child_tid;		/* CLONE_CHILD_SETTID */
	int __user *clear_child_tid;		/* CLONE_CHILD_CLEARTID */

	cputime_t utime, stime, utimescaled, stimescaled;
	cputime_t gtime;
#ifndef CONFIG_VIRT_CPU_ACCOUNTING_NATIVE
	struct cputime prev_cputime;
#endif
#ifdef CONFIG_VIRT_CPU_ACCOUNTING_GEN
	seqlock_t vtime_seqlock;
	unsigned long long vtime_snap;
	enum {
		VTIME_SLEEPING = 0,
		VTIME_USER,
		VTIME_SYS,
	} vtime_snap_whence;
#endif
	unsigned long nvcsw, nivcsw; /* context switch counts */
	struct timespec start_time; 		/* monotonic time */
	struct timespec real_start_time;	/* boot based time */
/* mm fault and swap info: this can arguably be seen as either mm-specific or thread-specific */
	unsigned long min_flt, maj_flt;

	struct task_cputime cputime_expires;
	struct list_head cpu_timers[3];

/* process credentials */
	const struct cred __rcu *ptracer_cred; /* Tracer's credentials at attach */
	const struct cred __rcu *real_cred; /* objective and real subjective task
					 * credentials (COW) */
	const struct cred __rcu *cred;	/* effective (overridable) subjective task
					 * credentials (COW) */
	char comm[TASK_COMM_LEN]; /* executable name excluding path
				     - access with [gs]et_task_comm (which lock
				       it with task_lock())
				     - initialized normally by setup_new_exec */
/* file system info */
	int link_count, total_link_count;
#ifdef CONFIG_SYSVIPC
/* ipc stuff */
	struct sysv_sem sysvsem;
#endif
#ifdef CONFIG_DETECT_HUNG_TASK
/* hung task detection */
	unsigned long last_switch_count;
#endif
/* CPU-specific state of this task */
	struct thread_struct thread;
/* filesystem information */
	struct fs_struct *fs;
/* open file information */
	struct files_struct *files;
/* namespaces */
	struct nsproxy *nsproxy;
/* signal handlers */
	struct signal_struct *signal;
	struct sighand_struct *sighand;

	sigset_t blocked, real_blocked;
	sigset_t saved_sigmask;	/* restored if set_restore_sigmask() was used */
	struct sigpending pending;

	unsigned long sas_ss_sp;
	size_t sas_ss_size;
	int (*notifier)(void *priv);
	void *notifier_data;
	sigset_t *notifier_mask;
	struct callback_head *task_works;

	struct audit_context *audit_context;
#ifdef CONFIG_AUDITSYSCALL
	kuid_t loginuid;
	unsigned int sessionid;
#endif
	struct seccomp seccomp;

/* Thread group tracking */
   	u32 parent_exec_id;
   	u32 self_exec_id;
/* Protection of (de-)allocation: mm, files, fs, tty, keyrings, mems_allowed,
 * mempolicy */
	spinlock_t alloc_lock;

	/* Protection of the PI data structures: */
	raw_spinlock_t pi_lock;

#ifdef CONFIG_RT_MUTEXES
	/* PI waiters blocked on a rt_mutex held by this task */
	struct rb_root pi_waiters;
	struct rb_node *pi_waiters_leftmost;
	/* Deadlock detection and priority inheritance handling */
	struct rt_mutex_waiter *pi_blocked_on;
	/* Top pi_waiters task */
	struct task_struct *pi_top_task;
#endif

#ifdef CONFIG_DEBUG_MUTEXES
	/* mutex deadlock detection */
	struct mutex_waiter *blocked_on;
#endif
#ifdef CONFIG_TRACE_IRQFLAGS
	unsigned int irq_events;
	unsigned long hardirq_enable_ip;
	unsigned long hardirq_disable_ip;
	unsigned int hardirq_enable_event;
	unsigned int hardirq_disable_event;
	int hardirqs_enabled;
	int hardirq_context;
	unsigned long softirq_disable_ip;
	unsigned long softirq_enable_ip;
	unsigned int softirq_disable_event;
	unsigned int softirq_enable_event;
	int softirqs_enabled;
	int softirq_context;
#endif
#ifdef CONFIG_LOCKDEP
# define MAX_LOCK_DEPTH 48UL
	u64 curr_chain_key;
	int lockdep_depth;
	unsigned int lockdep_recursion;
	struct held_lock held_locks[MAX_LOCK_DEPTH];
	gfp_t lockdep_reclaim_gfp;
#endif

/* journalling filesystem info */
	void *journal_info;

/* stacked block device info */
	struct bio_list *bio_list;

#ifdef CONFIG_BLOCK
/* stack plugging */
	struct blk_plug *plug;
#endif

/* VM state */
	struct reclaim_state *reclaim_state;

	struct backing_dev_info *backing_dev_info;

	struct io_context *io_context;

	unsigned long ptrace_message;
	siginfo_t *last_siginfo; /* For ptrace use.  */
	struct task_io_accounting ioac;
#if defined(CONFIG_TASK_XACCT)
	u64 acct_rss_mem1;	/* accumulated rss usage */
	u64 acct_vm_mem1;	/* accumulated virtual memory usage */
	cputime_t acct_timexpd;	/* stime + utime since last update */
#endif
#ifdef CONFIG_CPUSETS
	nodemask_t mems_allowed;	/* Protected by alloc_lock */
	seqcount_t mems_allowed_seq;	/* Seqence no to catch updates */
	int cpuset_mem_spread_rotor;
	int cpuset_slab_spread_rotor;
#endif
#ifdef CONFIG_CGROUPS
	/* Control Group info protected by css_set_lock */
	struct css_set __rcu *cgroups;
	/* cg_list protected by css_set_lock and tsk->alloc_lock */
	struct list_head cg_list;
#endif
#ifdef CONFIG_FUTEX
	struct robust_list_head __user *robust_list;
#ifdef CONFIG_COMPAT
	struct compat_robust_list_head __user *compat_robust_list;
#endif
	struct list_head pi_state_list;
	struct futex_pi_state *pi_state_cache;
#endif
#ifdef CONFIG_PERF_EVENTS
	struct perf_event_context *perf_event_ctxp[perf_nr_task_contexts];
	struct mutex perf_event_mutex;
	struct list_head perf_event_list;
#endif
#ifdef CONFIG_DEBUG_PREEMPT
	unsigned long preempt_disable_ip;
#endif
#ifdef CONFIG_NUMA
	struct mempolicy *mempolicy;	/* Protected by alloc_lock */
	short il_next;
	short pref_node_fork;
#endif
#ifdef CONFIG_NUMA_BALANCING
	int numa_scan_seq;
	unsigned int numa_scan_period;
	unsigned int numa_scan_period_max;
	int numa_preferred_nid;
	unsigned long numa_migrate_retry;
	u64 node_stamp;			/* migration stamp  */
	u64 last_task_numa_placement;
	u64 last_sum_exec_runtime;
	struct callback_head numa_work;

	struct list_head numa_entry;
	struct numa_group *numa_group;

	/*
	 * Exponential decaying average of faults on a per-node basis.
	 * Scheduling placement decisions are made based on the these counts.
	 * The values remain static for the duration of a PTE scan
	 */
	unsigned long *numa_faults_memory;
	unsigned long total_numa_faults;

	/*
	 * numa_faults_buffer records faults per node during the current
	 * scan window. When the scan completes, the counts in
	 * numa_faults_memory decay and these values are copied.
	 */
	unsigned long *numa_faults_buffer_memory;

	/*
	 * Track the nodes the process was running on when a NUMA hinting
	 * fault was incurred.
	 */
	unsigned long *numa_faults_cpu;
	unsigned long *numa_faults_buffer_cpu;

	/*
	 * numa_faults_locality tracks if faults recorded during the last
	 * scan window were remote/local. The task scan period is adapted
	 * based on the locality of the faults with different weights
	 * depending on whether they were shared or private faults
	 */
	unsigned long numa_faults_locality[2];

	unsigned long numa_pages_migrated;
#endif /* CONFIG_NUMA_BALANCING */

	struct rcu_head rcu;

	/*
	 * cache last used pipe for splice
	 */
	struct pipe_inode_info *splice_pipe;

	struct page_frag task_frag;

#ifdef	CONFIG_TASK_DELAY_ACCT
	struct task_delay_info *delays;
#endif
#ifdef CONFIG_FAULT_INJECTION
	int make_it_fail;
#endif
	/*
	 * when (nr_dirtied >= nr_dirtied_pause), it's time to call
	 * balance_dirty_pages() for some dirty throttling pause
	 */
	int nr_dirtied;
	int nr_dirtied_pause;
	unsigned long dirty_paused_when; /* start of a write-and-pause period */

#ifdef CONFIG_LATENCYTOP
	int latency_record_count;
	struct latency_record latency_record[LT_SAVECOUNT];
#endif
	/*
	 * time slack values; these are used to round up poll() and
	 * select() etc timeout values. These are in nanoseconds.
	 */
	unsigned long timer_slack_ns;
	unsigned long default_timer_slack_ns;

#ifdef CONFIG_FUNCTION_GRAPH_TRACER
	/* Index of current stored address in ret_stack */
	int curr_ret_stack;
	/* Stack of return addresses for return function tracing */
	struct ftrace_ret_stack	*ret_stack;
	/* time stamp for last schedule */
	unsigned long long ftrace_timestamp;
	/*
	 * Number of functions that haven't been traced
	 * because of depth overrun.
	 */
	atomic_t trace_overrun;
	/* Pause for the tracing */
	atomic_t tracing_graph_pause;
#endif
#ifdef CONFIG_TRACING
	/* state flags for use by tracers */
	unsigned long trace;
	/* bitmask and counter of trace recursion */
	unsigned long trace_recursion;
#endif /* CONFIG_TRACING */
#ifdef CONFIG_MEMCG /* memcg uses this to do batch job */
	struct memcg_batch_info {
		int do_batch;	/* incremented when batch uncharge started */
		struct mem_cgroup *memcg; /* target memcg of uncharge */
		unsigned long nr_pages;	/* uncharged usage */
		unsigned long memsw_nr_pages; /* uncharged mem+swap usage */
	} memcg_batch;
	unsigned int memcg_kmem_skip_account;
	struct memcg_oom_info {
		struct mem_cgroup *memcg;
		gfp_t gfp_mask;
		int order;
		unsigned int may_oom:1;
	} memcg_oom;
#endif
#ifdef CONFIG_UPROBES
	struct uprobe_task *utask;
#endif
#if defined(CONFIG_BCACHE) || defined(CONFIG_BCACHE_MODULE)
	unsigned int	sequential_io;
	unsigned int	sequential_io_avg;
#endif
};

此外, Linux 為程序佇列的排程定義了以下重要的全域性變數: current 當前正在執行的程序的指標,在 SMP 中則指向 CPU 組中正被排程的CPU 的當前程序。 init_task 即 0 號程序的 PCB,是程序樹的根。 *task[NR_TASKS] 程序 PCB 陣列,規定系統可同時執行的最大程序數,每個程序佔一個數組元素(元素下標不一定就是程序 pid)。task[0]必須指向 0 號程序 init-task,可以通過 tasks[]陣列遍歷所有程序的 PCB,另外,還提供了巨集 for-each-task(),它通過next-task 遍歷所有程序的 PCB。 jiffies 是 Linux 的基準時間,系統初始化時清 0,每隔定時基準時間由時鐘中斷處理程式 do-timer()增 1。 need_resched()重新排程標誌位,當需要系統排程時置位,在系統呼叫返回前或其他情況下,判別標誌位是否為 1,以決定是否schedule()進行 CPU 排程。 intr_count 記錄中斷服務程式的巢狀重數。