作業系統之程序與程序控制
一、程序概念
引子 程式執行在併發環境中的問題
(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函式簇的存在,它是若干函式的集合。其功能是讓子程序具有和父程序完全不同的新功能。