1. 程式人生 > >Linux 程序與程序排程詳解

Linux 程序與程序排程詳解

1、引言

程序:是程式執行時的一個例項,可以看作充分描述程式已經執行到何種程度的資料結構的彙集。從核心觀點看,程序的目的就是擔當分配系統資源(CPU時間、記憶體等)的實體。
當一個程序建立時,它獲得一個父程序地址空間的副本。共享正文段(程式碼段),但並不執行一個父程序資料段、棧和堆的完全拷貝,而是採用寫時複製技術。
Linux使用輕量級程序對多執行緒應用程式提供更好的支援,兩個輕量級程序基本上可以共享一些資源,諸如地址空間、開啟的檔案等。

程序排程,使用者程序數程序排程一般都多於處理機數、這將導致它們互相爭奪處理機。另外,系統程序也同樣需要使用處理機。

無論是在批處理系統還是分時系統中,使用者程序數一般都多於處理機數、這將導致它們互相爭奪處理機。另外,系統程序也同樣需要使用處理機。這就要求程序排程程式按一定的策略,動態地把處理機分配給處於就緒佇列中的某一個程序,以使之執行。  

程序排程的的分級
高階、中級和低階排程作業從提交開始直到完成,往往要經歷下述三級排程:

  1. 高階排程
    (High-Level Scheduling)又稱為作業排程,它決定把後備作業調入記憶體執行;
  2. 低階排程
    (Low-Level Scheduling)又稱為程序排程,它決定把就緒佇列的某程序獲得CPU;
  3. 中級排程
    (Intermediate-Level Scheduling)又稱為在虛擬儲存器中引入,在內、外存對換區進行程序對換。

2、程序排程 - 程序排程的時機

程序排程發生在什麼時機呢?這與引起程序排程的原因以及程序排程的方式有關。
引起程序排程的原因有以下幾類,
(1)正在執行的程序執行完畢。這時,如果不選擇新的就緒程序執行,將浪費處理機資源。
(2)執行中程序自己呼叫阻塞原語將白己阻塞起來進入睡眠等狀態。
(3)執行中程序呼叫了P原語操作,從而因資源不足而被阻塞;或呼叫了v原語操作激活了等待資源的程序佇列。
(4)執行中程序提出I/O請求後被阻塞。
(5)在分時系統中時間片已經用完。
(6)在執行完系統呼叫等系統程式後返回使用者程序時,這時可看作系統程序執行完畢,從而可排程選擇一新的使用者程序執行。
以上都是在可剝奪方式下的引起程序排程的原因。在CPU執行方式是可剝奪時.還有
(7)就緒佇列中的某程序的優先順序變得高於當前執行程序的優先順序,從而也將引發程序排程。

兩種佔用CPU的方式:
可剝奪式 (可搶佔式 preemptive):就緒佇列中一旦有優先順序高於當前執行程序優先順序的程序存在時,便立即發生程序排程,轉讓處理機。
不可剝奪式 (不可搶佔式 non_preemptive):即使在就緒佇列存在有優先順序高與當前執行程序時,當前程序仍將佔用處理機知道該程序自己因呼叫原語操作或等待I/O而進入阻塞、睡眠狀態,或時間片用完時才重新發生排程讓出處理機。  

3、程序排程 - 程序排程演算法

1.先進先出演算法(FIFO):按照程序進入就緒佇列的先後次序來選擇。即每當進入程序排程,總是把就緒佇列的隊首程序投入執行。
2. 時間片輪轉演算法(RR):分時系統的一種排程演算法。輪轉的基本思想是,將CPU的處理時間劃分成一個個的時間片,就緒佇列中的程序輪流執行一個時間片。當時間片結束時,就強迫程序讓出CPU,該程序進入就緒佇列,等待下一次排程,同時,程序排程又去選擇就緒佇列中的一個程序,分配給它一個時間片,以投入執行。
3. 最高優先順序演算法(HPF):程序排程每次將處理機分配給具有最高優先順序的就緒程序。最高優先順序演算法可與不同的CPU方式結合形成可搶佔式最高優先順序演算法和不可搶佔式最高優先順序演算法。
4. 多級佇列反饋法:幾種排程演算法的結合形式多級佇列方式。

4、程序排程 - 程序的狀態

這裡寫圖片描述

task_struct中的state描述程序的當前狀態。程序的狀態一共有5種,而程序必然處於其中一種狀態:

1)TASK_RUNNING(執行)——程序是可執行的,它或者正在執行,或者在執行佇列中等待執行。這是程序在使用者空間中執行唯一可能的狀態;也可以應用到核心空間中正在執行的程序。

2)TASK_INTERRUPTIBLE(可中斷)——程序正在睡眠(也就是說它被阻塞)等待某些條件的達成。一旦這些條件達成,核心就會把程序狀態設定為執行,處於此狀態的程序也會因為接收到訊號而提前被喚醒並投入執行。

3)TASK_UNINTERRUPTIBLE(不可中斷)——除了不會因為接收到訊號而被喚醒從而投入執行外,這個狀態與可打斷狀態相同。這個狀態通常在程序必須在等待時不受干擾或等待事件很快就會發生時出現。由於處於此狀態的任務對訊號不作響應,所以較之可中斷狀態,使用得較少。

4)TASK_ZOMBIE(僵死)——該程序已經結束了,但是其父程序還沒有呼叫wait4()系統呼叫。為了父程序能夠獲知它的訊息,子程序的程序描述符仍然被保留著。一旦父程序呼叫了wait4(),程序描述符就會被釋放。

5)TASK_STOPPED(停止)——程序停止執行,程序沒有投入執行也不能投入執行。通常這種狀態發生在接收到SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU等訊號的時候。此外,在除錯期間接收到任何訊號,都會使程序進入這種狀態。

需要調整程序的狀態,最好使用set_task_state(task, state)函式,在必要的時候,它會設定記憶體屏障來強制其他處理器作重新排序(SMP)。

程序0和程序1

系統允許一個程序建立新程序,新程序即為子程序,子程序還可以建立新的子程序,形成程序樹結構模型。整個linux系統的所有程序也是一個樹形結 構。樹根是系統自動構造的,即在核心態下執行的0號程序,它是所有程序的祖先。由0號程序建立1號程序(核心態),1號負責執行核心的部分初始化工作及進 行系統配置,並建立若干個用於快取記憶體和虛擬主存管理的核心執行緒。隨後,1號程序呼叫execve()執行可執行程式init,並演變成使用者態1號程序, 即init程序。它按照配置檔案/etc/initab的要求,完成系統啟動工作,建立編號為1號、2號…的若干終端註冊程序getty。

每個getty程序設定其程序組標識號,並監視配置到系統終端的介面線路。當檢測到來自終端的連線訊號時,getty程序將通過函式execve()執行 註冊程式login,此時使用者就可輸入註冊名和密碼進入登入過程,如果成功,由login程式再通過函式execv()執行shell,該shell程序 接收getty程序的pid,取代原來的getty程序。再由shell直接或間接地產生其他程序。

上述過程可描述為:0號程序->1號核心程序->1號使用者程序(init程序)->getty程序->shell程序

注意,上述過程描述中提到:1號核心程序呼叫執行init並演變成1號使用者態程序(init程序),這裡前者是init是函式,後者是程序。兩者容易混淆,區別如下:

1.init()函式在核心態執行,是核心程式碼

2.init程序是核心啟動並執行的第一個使用者程序,執行在使用者態下。

3.一號核心程序呼叫execve()從檔案/etc/inittab中載入可執行程式init並執行,這個過程並沒有使用呼叫do_fork(),因此兩個程序都是1號程序。

  1. 程序0

    所有程序的祖先叫做程序0 ,idle 程序 或因為歷史的原因叫做swapper 程序。它是在 linux 的初始化階段從無到有的建立的一個核心執行緒。這個祖先程序使用靜態分配的資料結構。

    在多處理器系統中,每個CPU都有一個程序0,主要開啟機器電源,計算機的BIOS就啟動一個CPU,同時禁用其他CPU。執行的CPU 上的swapper程序初初始化核心資料結構,然後啟用其他的並且使用copy_process()函式建立另外的swapper程序,把0 傳遞給新建立的swapper程序作為他們程序的PID.

  1. 程序1

    由程序0建立的核心執行緒執行init() 函式,init() 一次完成核心的初始化。init()呼叫execve()系統呼叫裝入可執行程式init ,結果 ,init 核心執行緒變成一個普通的程序,且擁有自己的每個程序核心資料結構。在系統關閉之前,init 程序一直存活,因為它建立和監控在作業系統外層執行的所有程序的活動。