1. 程式人生 > >作業系統之程序與程序控制

作業系統之程序與程序控制

一、程序概念

引子 程式執行在併發環境中的問題

(1)執行過程不確定
(2)結果不可再現

1.程序定義

  程序是程式在某個資料集合上的一次執行活動。資料集合是指軟硬體環境,多個程序共存或共享的環境。

2.程序的特徵

(1)動態性
  程序是程式的一次執行過程,動態產生且動態消亡;
(2)併發性
  程序同其他程序一起向前推進;
(3)非同步性
  程序按照各自的速度向前推進(每一個程序按照自定邏輯,不考慮其他程序的執行,各自佔用CPU);
(4)獨立性
  程序是系統分配資源和排程CPU的單位(但是有了執行緒後,作業系統排程CPU的單位就變成了執行緒)。

3.程序與程式的區別

(1)程序是動態的:程式的一次執行過程;
(2)程式是靜態的:一組指令的有序集合;
(3)程序是暫存的:在記憶體中短期駐留;
(4)程式是長存的:可以在儲存介質上長期儲存;
(5)一個程式可能有多個程序。

4.程序的分類

(1)按照使用資源的許可權進行分類
  ①系統程序:系統核心相關的程序;
  ②使用者程序:運行於使用者態的程序。
(2)按照對CPU的依賴性進行分類
  ①CPU型程序:主要用於計算;
  ②I/O型程序:主要用於I/O操作。

二、程序狀態

1.程序的狀態

(1)執行狀態(Running)
  程序已經佔用CPU,在CPU上執行。
(2)就緒狀態(Ready)
  具備執行條件但是由於沒有CPU可用,所以暫時不能執行。
(3)阻塞狀態(Block)也叫等待狀態(Wait)
  由於等待某項服務完成或者等待某個訊號而不能執行的狀態,比如等待系統呼叫,I/O操作等等。

2.程序的三態模型

 

(1)就緒->執行:程序排程;
(2)執行->就緒:時間片到或者被強佔;
(3)執行->阻塞:請求服務後等待響應,或者等待某個訊號的到來;
(4)阻塞->就緒:請求的服務已經完成,或者等待的訊號已經到來。

3.程序的五態模型

 

(1)新建狀態
  使用者向系統提交程式後,在程序建立之前的過程。
(2)終止狀態
  程序撤出系統的過程。
4.Linux中的狀態定義
(1)可執行態
  ①就緒:在就緒佇列中等待排程;
  ②執行:正在執行。
(2)阻塞(等待)態
  ①淺度(可中斷)阻塞:能被其他程序的訊號或者時鐘喚醒;
  ②深度(不可中斷)阻塞:不能被其他程序通過訊號和時鐘喚醒,一般是用來請求檔案服務,I/O服務,系統服務。
(3)僵死態
  程序終止執行,釋放大部分資源。
(4)掛起態
  程序被掛起,暫停。可用於除錯程序。

三、程序控制塊(Process Control Block,PCB)

1.程序控制塊的定義

(1)描述程序狀態、資源、與相關程序關係的資料結構;
(2)PCB是程序的標誌。對於作業系統來說,它通過PCB來感知和管理程序;
(3)程序建立時會建立PCB,程序撤出時會銷燬PCB。

2.PCB中的基本成員

(1)name(ID):程序名稱(識別符號,0~32768的正整數);
(2)status:狀態;
(3)next_pcb:指向下一個PCB的指標;
(4)start_addr:程式地址;
(5)p_priority:優先順序;
(6)cpu status:現場保留區(堆疊);
(7)comminfo:程序通訊;
(8)processfamily:家族;
(9)own resource:資源。
  不同的作業系統PCB成員會有所區別,但是一些基本成員都是相同的,可能只是命名上的區別而已。程序控制塊應包含以下三類資訊:標識資訊、現場資訊、控制資訊。

3.Linux中的程序控制塊

Linux的程序控制塊一般包含如下資訊:
(1)程序狀態;
(2)排程資訊;
(3)識別符號;
(4)內部程序通訊資訊;
(5)連結資訊;
(6)時間和計時器;
(7)檔案系統;
(8)虛擬記憶體資訊;
(9)處理器資訊。

4.程序的切換

(1)程序的上下文

  Context,指程序執行環境,CPU環境(比如各個暫存器的取值)等等。程序的上下文由三部分組成:使用者級上下文(程式、資料、共享儲存區、使用者棧,它們佔用程序的虛擬地址空間)、暫存器上下文(由各個暫存器組成)、系統級上下文(PCB、核心棧等)。
(2)程序切換過程
  ①程序被從堆疊排程到CPU執行;
  ②程序被從CPU排程到堆疊暫停執行。

四、程序控制的概念

1.程序控制的概念

  在程序生存期間,對其全部行為的控制。典型的控制行為有建立程序、阻塞程序、喚醒程序、撤銷程序。

2.程序控制行為之一——程序建立

(1)功能
  建立一個具有指定標識的程序。
(2)引數
  程序標識、優先順序、程序起始地址、CPU初始狀態、資源需求等等。
(3)建立程序的過程
  ①建立一個空白PCB;
  ②獲得並賦予程序識別符號ID;
  ③為程序分配空間;
  ④初始化PCB;
  ⑤把程序插入到相應的程序佇列,一般放到就緒佇列中。

3.程序控制行為之二——程序撤銷

(1)功能
  撤銷一個指定的程序,收回程序所佔用的資源,撤銷該程序的PCB。
(2)程序撤銷的時機/時間
  ①正常結束;
  ②異常結束;
  ③外界因素。
(3)引數
  被撤銷的程序名(ID)。
(4)程序撤銷的過程
  ①在PCB佇列中找到要撤銷程序的PCB;
  ②獲取該程序的狀態;
  ③若該程序處於執行態,則立即終止該程序(先檢查該程序是否有子程序,若有子程序,則先撤銷子程序,遞迴執行此操作);
  ④釋放程序佔有的資源;
  ⑤將程序的PCB從PCB佇列中移除。

4.程序控制行為之三——程序阻塞

(1)功能
  阻塞程序的執行。
(2)阻塞的時機/事件
  ①請求系統服務:作業系統不能立即滿足程序的請求,導致程序不能滿足執行條件;
  ②啟動某種操作:程序啟動某操作,阻塞等待該操作完成(比如進行I/O操作);
  ③新資料尚未到達:該程序要獲得另外一個程序的中間結果,該程序需要等待另一個程序運算結束;
  ④程序正常結束:程序完成任務後,自我阻塞,等待新任務到達。
(3)引數
  阻塞原因。
(4)程序阻塞的實現
  ①停止執行;
  ②將PCB“執行態”改為“阻塞態”;
  ③出入相應原因的阻塞佇列;
  ④轉到排程程式(把系統控制權交給排程模組)。

5.程序控制行為之四——程序喚醒

(1)功能
  喚醒處於阻塞佇列中的某個程序。
(2)引起喚醒的時機/事件
  ①系統服務滿足程序要求;
  ②I/O事件完成;
  ③新資料到達;
  ④程序向系統或I/O提出新請求(服務)。
(3)引數
  程序ID。

6.程序控制原語

  由若干指令構成的具有特定功能的函式,具有原子性,其操作不可分割。以上四種程序控制行為(建立、撤銷、阻塞、喚醒)都被封裝為原語,以保證其執行的完整性。

五、Linux下的程序控制

1.建立程序

  建立程序的函式原型是pid_t fork(void);
  pid_t是一個整數型別,即fork()函式會返回新程序的ID號(0~32768的整數)。
  例如:pid_t pid = fork();

注意:
(1)新程序是當前程序的子程序。
(2)父程序和子程序
  ①父程序:fork()的呼叫者;
  ②子程序:新建的程序。
(3)子程序是父程序的複製(相同的程式碼,相同的資料,相同的堆疊),除了ID號和時間資訊外,兩者完全相同。
(4)子程序和父程序可以併發執行。

請問下面的程式會輸出什麼結果?

//檔名為test.c
int main(void)
{
    fork();
    printf("Hello World!\n");

    return 0;
}

 

答案:

  

  一個是子程序輸出的,一個是父程序輸出的。

請問下面的程式會輸出什麼結果?

//檔名為fork_2.c
int main(void)
{
    pid_t pid;
    pid = fork();
    
    if(pid == 0)
    {
        printf("pid == 0\n");
    }
    else
    {
        printf("pid != 0\n");
    }

    return 0;
}

 

答案:
  

  為什麼if和else兩個分支都被執行了呢?

  因為fork()函式執行後,建立了一個新的程序,子程序是父程序的複製,所以相同的程式碼會執行兩次。在子程序中fork()函式會返回0,在父程序中,fork()函式會返回子程序的id號。所以在子程序中,會執行if分支,在父程序中,會執行else分支。但誰先誰後不確定。

2.fork()函式的執行步驟

  由於子程序是父程序的複製,所以子程序中也會有建立子程序的語句,如果不加以限制,就會形成遞迴建立,但實際上並不是這樣的。
  實際流程是:父程序建立了子程序後,子程序中“建立程序”語句及其前面的語句都不再執行。併發執行的是fork()語句後的語句。

   

 

在linux的原始碼中我們可以找到fork函式:

//linux核心中的fork函式
...
copy_files(clone_flags,p);    //克隆檔案
copy_fs(clone_flags,p);    //克隆檔案系統
copy_mm(clone_flags,p);    //克隆記憶體資訊
...

  我們可以看到有三條語句,用於拷貝程序的所有資訊,這也解釋了為什麼說子程序是父程序的複製。

3.子程序如何執行與父程序不同的功能。

  Linux中 ,init程序(初始化程序)是所有其他程序的父程序,那麼是不是就說所有的程序都執行與init程序相同的功能呢?並不是。Linux中某些子程序和父程序的執行並不是完全相同的。它是如何做到的呢?這是因為exec函式簇的存在,它是若干函式的集合。其功能是讓子程序具有和父程序完全不同的新功能。