作業系統實驗一程序管理
一 程序的建立實驗
實驗目的
1、掌握程序的概念,明確程序的含義
2、認識並瞭解併發執行的實質
實驗內容
1、編寫一段程式,使用系統呼叫fork( )建立兩個子程序。當此程式執行時,在系統中有一個父程序和兩個子程序活動。讓每一個程序在螢幕上顯示一個字元:父程序顯示'a',子程序分別顯示字元'b'和字元'c'。試觀察記錄螢幕上的顯示結果,並分析原因。
2、修改上述程式,每一個程序迴圈顯示一句話。子程序顯示'daughter …'及'son ……',父程序顯示 'parent ……',觀察結果,分析原因。
實驗準備
(1)閱讀LINUX的fork.c原始碼檔案,分析程序的建立過程。
(2)閱讀LINUX的sched.c原始碼檔案,加深對程序管理概念的認識。
實驗指導
一、程序
UNIX中,程序既是一個獨立擁有資源的基本單位,又是一個獨立排程的基本單位。一個程序實體由若干個區(段)組成,包括程式區、資料區、棧區、共享儲存區等。每個區又分為若干頁,每個程序配置有唯一的程序控制塊PCB,用於控制和管理程序。
PCB的資料結構如下:
1、程序表項(Process Table Entry)。包括一些最常用的核心資料:
程序識別符號PID、使用者識別符號UID、程序狀態、事件描述符、程序和U區在記憶體或外存的地址、軟中斷訊號、計時域、程序的大小、偏置值nice、指向就緒佇列中下一個PCB的指標P_Link、指向U區程序正文、資料及棧在記憶體區域的指標。
2、U區(U Area)。用於存放程序表項的一些擴充資訊。
每一個程序都有一個私用的U區,其中含有:程序表項指標、真正使用者識別符號u-ruid(read user ID)、有效使用者識別符號u-euid(effective user ID)、使用者檔案描述符表、計時器、內部I/O引數、限制欄位、差錯欄位、返回值、訊號處理陣列。
由於UNIX系統採用段頁式儲存管理,為了把段的起始虛地址變換為段在系統中的實體地址,便於實現區的共享,所以還有:
3、系統區表項。以存放各個段在物理儲存器中的位置等資訊。
系統把一個程序的虛地址空間劃分為若干個連續的邏輯區,有正文區、資料區、棧區等。這些區是可被共享和保護的獨立實體,多個程序可共享一個區。為了對區進行管理,核心中設定一個系統區表,各表項中記錄了以下有關描述活動區的資訊:
區的型別和大小、區的狀態、區在物理儲存器中的位置、引用計數、指向檔案索引結點的指標。
4、程序區表
系統為每個程序配置了一張程序區表。表中,每一項記錄一個區的起始虛地址及指向系統區表中對應的區表項。核心通過查詢程序區表和系統區表,便可將區的邏輯地址變換為實體地址。
二、程序映像
UNIX系統中,程序是程序映像的執行過程,也就是正在執行的程序實體。它由三部分組成:
1、使用者級上、下文。主要成分是使用者程式;
2、暫存器上、下文。由CPU中的一些暫存器的內容組成,如PC,PSW,SP及通用暫存器等;
3、系統級上、下文。包括OS為管理程序所用的資訊,有靜態和動態之分。
三、所涉及的系統呼叫
1、fork( )
建立一個新程序。
系統呼叫格式:
pid=fork( )
引數定義:
int fork( )
fork( )返回值意義如下:
0:在子程序中,pid變數儲存的fork( )返回值為0,表示當前程序是子程序。
>0:在父程序中,pid變數儲存的fork( )返回值為子程序的id值(程序唯一識別符號)。
-1:建立失敗。
如果fork( )呼叫成功,它向父程序返回子程序的PID,並向子程序返回0,即fork( )被呼叫了一次,但返回了兩次。此時OS在記憶體中建立一個新程序,所建的新程序是呼叫fork( )父程序(parent process)的副本,稱為子程序(child process)。子程序繼承了父程序的許多特性,並具有與父程序完全相同的使用者級上下文。父程序與子程序併發執行。
核心為fork( )完成以下操作:
(1)為新程序分配一程序表項和程序識別符號
進入fork( )後,核心檢查系統是否有足夠的資源來建立一個新程序。若資源不足,則fork( )系統呼叫失敗;否則,核心為新程序分配一程序表項和唯一的程序識別符號。
(2)檢查同時執行的程序數目
超過預先規定的最大數目時,fork( )系統呼叫失敗。
(3)拷貝程序表項中的資料
將父程序的當前目錄和所有已開啟的資料拷貝到子程序表項中,並置程序的狀態為“建立”狀態。
(4)子程序繼承父程序的所有檔案
對父程序當前目錄和所有已開啟的檔案表項中的引用計數加1。
(5)為子程序建立程序上、下文
程序建立結束,設子程序狀態為“記憶體中就緒”並返回子程序的識別符號。
(6)子程序執行
雖然父程序與子程序程式完全相同,但每個程序都有自己的程式計數器PC(注意子程序的PC開始位置),然後根據pid變數儲存的fork( )返回值的不同,執行了不同的分支語句。
四、參考程式
1、
#include <stdio.h>
main( )
{
int p1,p2;
while((p1=fork( ))= = -1); /*建立子程序p1*/
if (p1= =0) putchar('b');
else
{
while((p2=fork( ))= = -1); /*建立子程序p2*/
if(p2= =0) putchar('c');
else putchar('a');
}
}
2、
#include <stdio.h>
main( )
{
int p1,p2,i;
while((p1=fork( ))= = -1); /*建立子程序p1*/
if (p1= =0)
for(i=0;i<10;i++)
printf("daughter %d/n",i);
else
{
while((p2=fork( ))= = -1); /*建立子程序p2*/
if(p2= =0)
for(i=0;i<10;i++)
printf("son %d/n",i);
else
for(i=0;i<10;i++)
printf("parent %d/n",i);
}
}
五、執行結果
1、bca,bac, abc ,……都有可能。
2、parent…
son…
daughter..
daughter..
或 parent…
son…
parent…
daughter…等
六、分析原因
除strace 外,也可用ltrace -f -i -S ./executable-file-name檢視以上程式執行過程。
1、從程序併發執行來看,各種情況都有可能。上面的三個程序沒有同步措施,所以父程序與子程序的輸出內容會疊加在一起。輸出次序帶有隨機性。
2、由於函式printf( )在輸出字串時不會被中斷,因此,字串內部字元順序輸出不變。但由於程序併發執行的排程順序和父子程序搶佔處理機問題,輸出字串的順序和先後隨著執行的不同而發生變化。這與列印單字元的結果相同。
補充:程序樹
在UNIX系統中,只有0程序是在系統引導時被建立的,在系統初啟時由0程序建立1程序,以後0程序變成對換程序,1程序成為系統中的始祖程序。UNIX利用fork( )為每個終端建立一個子程序為使用者服務,如等待使用者登入、執行SHELL命令解釋程式等,每個終端程序又可利用fork( )來建立其子程序,從而形成一棵程序樹。可以說,系統中除0程序外的所有程序都是用fork( )建立的。
七、思考題
(1)系統是怎樣建立程序的?
(2)當首次呼叫新建立程序時,其入口在哪裡?