操作系統實驗一 進程管理與進程通信
一、 實驗目的
1、軟中斷通信的基本原理,
2、認識並了解進程並發執行的實質,進程的阻塞與喚醒,終止與退出的過程。
3、熟悉進程的睡眠、同步、撤消等進程控制方法。
4、分析進程競爭資源的現象,學習解決進程互斥的方法 。
5、了解什么是信號,利用信號量機制熟悉進程間
6、熟悉消息傳送的機理 ,共享存儲機制 。掌握進程的概念,明確進程的含義。
二、 實驗內容
1、編寫一段程序,使用系統調用fork( )創建兩個子進程。當此程序運行時,在系統中有一個父進程和兩個子進程並發執行,觀察實驗結果並分析原因。
2、用fork( )創建一個進程,再調用exec( ),用新的程序替換該子進程的內容,利用wait( )來控制進程執行順序,掌握進程的睡眠、同步、撤消等進程控制方法,並根據實驗結果分析原因。
3、編寫一段多進程並發運行的程序,用lockf( )來給每一個進程加鎖,以實現進程之間的互斥,觀察並分析出現的現象及原因。
4、編寫程序:用fork( )創建兩個子進程,再用系統調用signal( )讓父進程捕捉鍵盤上來的中斷信號(即按^c鍵);捕捉到中斷信號后,父進程用系統調用kill( )向兩個子進程發出信號,子進程捕捉到信號后分別輸出下列信息后終止:
Child process1 is killed by parent!
Child process2 is killed by parent!
父進程等待兩個子進程終止后,輸出如下的信息后終止:
Parent process is killed!
分析利用信號量機制中的軟中斷通信實現進程同步的機理。
5、使用系統調用msgget( ),msgsnd( ),msgrev( ),及msgctl( )編制一長度為1k的消息發送和接收的程序,並分析消息的創建、發送和接收機制及控制原理。
6、編制一長度為1k的共享存儲區發送和接收的程序,並設計對該共享存儲區進行互斥訪問及進程同步的措施,必須保證實現正確的通信。
三、實驗原理
1、進程創建與進程並發執行
Linux中,進程既是一個獨立擁有資源的基本單位,又是一個獨立調度的基本單位。一個進程實體由若干個區(段)組成,包括程序區、數據區、棧區、共享存儲區等。每個區又分為若干頁,每個進程配置有唯一的進程控制塊PCB,用於控制和管理進程。
系統為每個進程配置了一張進程區表。表中,每一項記錄一個區的起始虛地址及指向系統區表中對應的區表項。核心通過查找進程區表和系統區表,便可將區的邏輯地址變換為物理地址。
進程是進程映像的執行過程,也就是正在執行的進程實體。它由三部分組成:
(1)用戶級上、下文。主要成分是用戶程序;
(2)寄存器上、下文。由CPU中的一些寄存器的內容組成,如PC,PSW,SP及通用寄存器等;
(3)系統級上、下文。包括OS為管理進程所用的信息,有靜態和動態之分。
進程創建所涉及的系統調用:
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( )返回值的不同,執行了不同的分支語句。
2、進程的睡眠、同步、撤消等進程控制
用fork( )創建一個進程,再調用exec( )用新的程序替換該子進程的內容,然后利用wait( )來控制進程執行順序。
(1)exec( )系列
系統調用exec( )系列,也可用於新程序的運行。fork( )只是將父進程的用戶級上下文拷貝到新進程中,而exec( )系列可以將一個可執行的二進制文件覆蓋在新進程的用戶級上下文的存儲空間上,以更改新進程的用戶級上下文。exec( )系列中的系統調用都完成相同的功能,它們把一個新程序裝入內存,來改變調用進程的執行代碼,從而形成新進程。如果exec( )調用成功,調用進程將被覆蓋,然后從新程序的入口開始執行,這樣就產生了一個新進程,新進程的進程標識符id 與調用進程相同。
exec( )沒有建立一個與調用進程並發的子進程,而是用新進程取代了原來進程。所以exec( )調用成功后,沒有任何數據返回,這與fork( )不同。exec( )系列系統調用在UNIX系統庫unistd.h中,共有execl、execlp、execle、execv、execvp五個,其基本功能相同,只是以不同的方式來給出參數。
一種是直接給出參數的指針,如:
int execl(path,arg0[,arg1,...argn],0);
char *path,*arg0,*arg1,...,*argn;
另一種是給出指向參數表的指針,如:
int execv(path,argv);
char *path,*argv[ ];
具體使用可參考有關書。
(2)exec( )和fork( )聯合使用
系統調用exec和fork( )聯合使用能為程序開發提供有力支持。用fork( )建立子進程,然后在子進程中使用exec( ),這樣就實現了父進程與一個與它完全不同子進程的並發執行。
一般,wait、exec聯合使用的模型為:
int status;
............
if (fork( )= =0)
{
...........;
execl(...);
...........;
}
wait(&status);
(3)wait( )
等待子進程運行結束。如果子進程沒有完成,父進程一直等待。wait( )將調用進程掛起,直至其子進程因暫停或終止而發來軟中斷信號為止。如果在wait( )前已有子進程暫停或終止,則調用進程做適當處理后便返回。
系統調用格式:
int wait(status)
int *status;
其中,status是用戶空間的地址。它的低8位反應子進程狀態,為0表示子進程正常結束,非0則表示出現了各種各樣的問題;高8位則帶回了exit( )的返回值。exit( )返回值由系統給出。
核心對wait( )作以下處理:
1)首先查找調用進程是否有子進程,若無,則返回出錯碼;
2)若找到一處於“僵死狀態”的子進程,則將子進程的執行時間加到父進程的執行時間上,並釋放子進程的進程表項;
3)若未找到處於“僵死狀態”的子進程,則調用進程便在可被中斷的優先級上睡眠,等待其子進程發來軟中斷信號時被喚醒。
(4)exit( )
終止進程的執行。
系統調用格式:
void exit(status)
int status;
其中,status是返回給父進程的一個整數,以備查考。
為了及時回收進程所占用的資源並減少父進程的干預,UNIX/LINUX利用exit( )來實現進程的自我終止,通常父進程在創建子進程時,應在進程的末尾安排一條exit( ),使子進程自我終止。exit(0)表示進程正常終止,exit(1)表示進程運行有錯,異常終止。
如果調用進程在執行exit( )時,其父進程正在等待它的終止,則父進程可立即得到其返回的整數。核心須為exit( )完成以下操作:
1)關閉軟中斷
2)回收資源
3)寫記帳信息
4)置進程為“僵死狀態”
3、多進程通過加鎖互斥並發運行
用lockf( )來給每一個進程加鎖,以實現多進程之間的互斥。
所涉及的系統調用:lockf(files,function,size),用作鎖定文件的某些段或者整個文件。
本函數的頭文件為
#include "unistd.h"
參數定義:
int lockf(files,function,size)
int files,function;
long size;
其中:files是文件描述符;function是鎖定和解鎖:1表示鎖定,0表示解鎖。size是鎖定或解鎖的字節數,為0,表示從文件的當前位置到文件尾。
4、進程間通過信號機制實現軟中斷通信
(1)信號的基本概念
每個信號都對應一個正整數常量(稱為signal number,即信號編號。定義在系統頭文件<signal.h>中),代表同一用戶的諸進程之間傳送事先約定的信息的類型,用於通知某進程發生了某異常事件。每個進程在運行時,都要通過信號機制來檢查是否有信號到達。若有,便中斷正在執行的程序,轉向與該信號相對應的處理程序,以完成對該事件的處理;處理結束后再返回到原來的斷點繼續執行。實質上,信號機制是對中斷機制的一種模擬,故在早期的UNIX版本中又把它稱為軟中斷。
信號與中斷的相似點:
1)采用了相同的異步通信方式;
2)當檢測出有信號或中斷請求時,都暫停正在執行的程序而轉去執行相應的處理程序;
3)都在處理完畢后返回到原來的斷點;
4)對信號或中斷都可進行屏蔽。
信號與中斷的區別:
1)中斷有優先級,而信號沒有優先級,所有的信號都是平等的;
2)信號處理程序是在用戶態下運行的,而中斷處理程序是在核心態下運行;
(3)中斷響應是及時的,而信號響應通常都有較大的時間延遲。
信號機制具有以下三方面的功能:
1)發送信號。發送信號的程序用系統調用kill( )實現;
2)預置對信號的處理方式。接收信號的程序用signal( )來實現對處理方式的預置;
3)收受信號的進程按事先的規定完成對相應事件的處理。
(2)信號的發送
信號的發送,是指由發送進程把信號送到指定進程的信號域的某一位上。如果目標進程正在一個可被中斷的優先級上睡眠,核心便將它喚醒,發送進程就此結束。一個進程可能在其信號域中有多個位被置位,代表有多種類型的信號到達,但對於一類信號,進程卻只能記住其中的某一個。
進程用kill( )向一個進程或一組進程發送一個信號。
(3)對信號的處理
當一個進程要進入或退出一個低優先級睡眠狀態時,或一個進程即將從核心態返回用戶態時,核心都要檢查該進程是否已收到軟中斷。當進程處於核心態時,即使收到軟中斷也不予理睬;只有當它返回到用戶態后,才處理軟中斷信號。對軟中斷信號的處理分三種情況進行:
1)如果進程收到的軟中斷是一個已決定要忽略的信號(function=1),進程不做任何處理便立即返回;
2)進程收到軟中斷后便退出(function=0);
3)執行用戶設置的軟中斷處理程序。
(4)所涉及的中斷調用
(1)kill( )
系統調用格式:int kill(pid,sig)
參數定義:int pid,sig;
其中,pid是一個或一組進程的標識符,參數sig是要發送的軟中斷信號。
1)pid>0時,核心將信號發送給進程pid。
2)pid=0時,核心將信號發送給與發送進程同組的所有進程。
3)pid=-1時,核心將信號發送給所有用戶標識符真正等於發送進程的有效用戶標識號的進程。
(2)signal( )
預置對信號的處理方式,允許調用進程控制軟中斷信號。
系統調用格式
signal(sig,function)
頭文件為
#include <signal.h>
參數定義
signal(sig,function)
int sig;
void (*func) ( )
其中sig用於指定信號的類型,sig為0則表示沒有收到任何信號,余者如下表:
值 |
名 字 |
說 明 |
01 |
SIGHUP |
掛起(hangup) |
02 |
SIGINT |
中斷,當用戶從鍵盤按^c鍵或^break鍵時 |
03 |
SIGQUIT |
退出,當用戶從鍵盤按quit鍵時 |
04 |
SIGILL |
非法指令 |
05 |
SIGTRAP |
跟蹤陷阱(trace trap),啟動進程,跟蹤代碼的執行 |
06 |
SIGIOT |
IOT指令 |
07 |
SIGEMT |
EMT指令 |
08 |
SIGFPE |
浮點運算溢出 |
09 |
SIGKILL |
殺死、終止進程 |
10 |
SIGBUS |
總線錯誤 |
11 |
SIGSEGV |
段違例(segmentation violation),進程試圖去訪問其虛地址空間以外的位置 |
12 |
SIGSYS |
系統調用中參數錯,如系統調用號非法 |
13 |
SIGPIPE |
向某個非讀管道中寫入數據 |
14 |
SIGALRM |
鬧鍾。當某進程希望在某時間后接收信號時發此信號 |
15 |
SIGTERM |
軟件終止(software termination) |
16 |
SIGUSR1 |
用戶自定義信號1 |
17 |
SIGUSR2 |
用戶自定義信號2 |
18 |
SIGCLD |
某個子進程死 |
19 |
SIGPWR |
電源故障 |
function:在該進程中的一個函數地址,在核心返回用戶態時,它以軟中斷信號的序號作為參數調用該函數,對除了信號SIGKILL,SIGTRAP和SIGPWR以外的信號,核心自動地重新設置軟中斷信號處理程序的值為SIG_DFL,一個進程不能捕獲SIGKILL信號。
function 的解釋如下:
1)function=1時,進程對sig類信號不予理睬,亦即屏蔽了該類信號;
2)function=0時,缺省值,進程在收到sig信號后應終止自己;
3)function為非0,非1類整數時,function的值即作為信號處理程序的指針。
5、消息的發送與接收
使用系統調用msgget( ),msgsnd( ),msgrev( ),及msgctl( )編制一長度為1k的消息發送和接收的程序。
消息(message)是一個格式化的可變長的信息單元。消息機制允許由一個進程給其它任意的進程發送一個消息。當一個進程收到多個消息時,可將它們排成一個消息隊列。消息使用二種重要的數據結構:一是消息首部,其中記錄了一些與消息有關的信息,如消息數據的字節數;二個消息隊列頭表,其每一表項是作為一個消息隊列的消息頭,記錄了消息隊列的有關信息。
(1)消息機制的數據結構
(1)消息首部
記錄一些與消息有關的信息,如消息的類型、大小、指向消息數據區的指針、消息隊列的鏈接指針等。
(2)消息隊列頭表
其每一項作為一個消息隊列的消息頭,記錄了消息隊列的有關信息如指向消息隊列中第一個消息和指向最后一個消息的指針、隊列中消息的數目、隊列中消息數據的總字節數、隊列所允許消息數據的最大字節總數,還有最近一次執行發送操作的進程標識符和時間、最近一次執行接收操作的進程標識符和時間等。
(3) 消息隊列的描述符
UNIX中,每一個消息隊列都有一個稱為關鍵字(key)的名字,是由用戶指定的;消息隊列有一消息隊列描述符,其作用與用戶文件描述符一樣,也是為了方便用戶和系統對消息隊列的訪問。
涉及的系統調用
(1) msgget( )
創建一個消息,獲得一個消息的描述符。核心將搜索消息隊列頭表,確定是否有指定名字的消息隊列。若無,核心將分配一新的消息隊列頭,並對它進行初始化,然后給用戶返回一個消息隊列描述符,否則它只是檢查消息隊列的許可權便返回。
系統調用格式:
msgqid=msgget(key,flag)
該函數使用頭文件如下:
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
參數定義
int msgget(key,flag)
key_t key;
int flag;
其中:
key是用戶指定的消息隊列的名字;flag是用戶設置的標志和訪問方式。如 IPC_CREAT |0400 是否該隊列已被創建。無則創建,是則打開;
IPC_EXCL |0400 是否該隊列的創建應是互斥的。
msgqid 是該系統調用返回的描述符,失敗則返回-1。
(2) msgsnd()
發送一消息。向指定的消息隊列發送一個消息,並將該消息鏈接到該消息隊列的尾部。
系統調用格式:
msgsnd(msgqid,msgp,size,flag)
該函數使用頭文件如下:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
參數定義:
int msgsnd(msgqid,msgp,size,flag)
I int msgqid,size,flag;
struct msgbuf * msgp;
其中msgqid是返回消息隊列的描述符;msgp是指向用戶消息緩沖區的一個結構體指針。緩沖區中包括消息類型和消息正文,即
{
long mtype; /*消息類型*/
char mtext[ ]; /*消息的文本*/
}
size指示由msgp指向的數據結構中字符數組的長度;即消息的長度。這個數組的最大值由MSG-MAX( )系統可調用參數來確定。flag規定當核心用盡內部緩沖空間時應執行的動作:進程是等待,還是立即返回。若在標志flag中未設置IPC_NOWAIT位,則當該消息隊列中的字節數超過最大值時,或系統范圍的消息數超過某一最大值時,調用msgsnd進程睡眠。若是設置IPC_NOWAIT,則在此情況下,msgsnd立即返回。
對於msgsnd( ),核心須完成以下工作:
1)對消息隊列的描述符和許可權及消息長度等進行檢查。若合法才繼續執行,否則返回;
2)核心為消息分配消息數據區。將用戶消息緩沖區中的消息正文,拷貝到消息數據區;
3)分配消息首部,並將它鏈入消息隊列的末尾。在消息首部中須填寫消息類型、消息大小和指向消息數據區的指針等數據;
4)修改消息隊列頭中的數據,如隊列中的消息數、字節總數等。最后,喚醒等待消息的進程。
(3) msgrcv( )
接受一消息。從指定的消息隊列中接收指定類型的消息。
系統調用格式:
msgrcv(msgqid,msgp,size,type,flag)
本函數使用的頭文件如下:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
參數定義:
int msgrcv(msgqid,msgp,size,type,flag)
int msgqid,size,flag;
struct msgbuf *msgp;
long type;
其中,msgqid,msgp,size,flag與msgsnd中的對應參數相似,type是規定要讀的消息類型,flag規定倘若該隊列無消息,核心應做的操作。如此時設置了IPC_NOWAIT標志,則立即返回,若在flag中設置了MS_NOERROR,且所接收的消息大於size,則核心截斷所接收的消息。
對於msgrcv系統調用,核心須完成下述工作:
1)對消息隊列的描述符和許可權等進行檢查。若合法,就往下執行;否則返回;
2)根據type的不同分成三種情況處理:
type=0,接收該隊列的第一個消息,並將它返回給調用者;
type為正整數,接收類型type的第一個消息;
type為負整數,接收小於等於type絕對值的最低類型的第一個消息。
3)當所返回消息大小等於或小於用戶的請求時,核心便將消息正文拷貝到用戶區,並從消息隊列中刪除此消息,然后喚醒睡眠的發送進程。但如果消息長度比用戶要求的大時,則做出錯返回。
(4) msgctl( )
消息隊列的操縱。讀取消息隊列的狀態信息並進行修改,如查詢消息隊列描述符、修改它的許可權及刪除該隊列等。
系統調用格式:
msgctl(msgqid,cmd,buf);
本函數使用的頭文件如下:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
參數定義:
int msgctl(msgqid,cmd,buf);
int msgqid,cmd;
struct msgqid_ds *buf;
其中,函數調用成功時返回0,不成功則返回-1。buf是用戶緩沖區地址,供用戶存放控制參數和查詢結果;cmd是規定的命令。命令可分三類:
1)IPC_STAT。查詢有關消息隊列情況的命令。如查詢隊列中的消息數目、隊列中的最大字節數、最后一個發送消息的進程標識符、發送時間等;
2)IPC_SET。按buf指向的結構中的值,設置和改變有關消息隊列屬性的命令。如改變消息隊列的用戶標識符、消息隊列的許可權等;
3)IPC_RMID。消除消息隊列的標識符。
msgqid_ds 結構定義如下:
struct msgqid_ds
{ struct ipc_perm msg_perm; /*許可權結構*/
short pad1[7]; /*由系統使用*/
ushort msg_qnum; /*隊列上消息數*/
ushort msg_qbytes; /*隊列上最大字節數*/
ushort msg_lspid; /*最后發送消息的PID*/
ushort msg_lrpid; /*最后接收消息的PID*/
time_t msg_stime; /*最后發送消息的時間*/
time_t msg_rtime; /*最后接收消息的時間*/
time_t msg_ctime; /*最后更改時間*/
};
struct ipc_perm
{ ushort uid; /*當前用戶*/
ushort gid; /*當前進程組*/
ushort cuid; /*創建用戶*/
ushort cgid; /*創建進程組*/
ushort mode; /*存取許可權*/
{ short pid1; long pad2;} /*由系統使用*/
}
6、進程的共享存儲區通信
編制一長度為1k的共享存儲區發送和接收的程序。
(1)共享存儲區機制的概念
共享存儲區(Share Memory)是UNIX系統中通信速度最高的一種通信機制。該機制可使若干進程共享主存中的某一個區域,且使該區域出現(映射)在多個進程的虛地址空間中。另一方面,一個進程的虛地址空間中又可連接多個共享存儲區,每個共享存儲區都有自己的名字。當進程間欲利用共享存儲區進行通信時,必須先在主存中建立一共享存儲區,然后將它附接到自己的虛地址空間上。此后,進程對該區的訪問操作,與對其虛地址空間的其它部分的操作完全相同。進程之間便可通過對共享存儲區中數據的讀、寫來進行直接通信。圖示列出二個進程通過共享一個共享存儲區來進行通信的例子。其中,進程A將建立的共享存儲區附接到自己的AA’區域,進程B將它附接到自己的BB’區域。
應當指出,共享存儲區機制只為進程提供了用於實現通信的共享存儲區和對共享存儲區進行操作的手段,然而並未提供對該區進行互斥訪問及進程同步的措施。因而當用戶需要使用該機制時,必須自己設置同步和互斥措施才能保證實現正確的通信。
(2)涉及的系統調用
1)shmget( )
創建、獲得一個共享存儲區。
系統調用格式:
shmid=shmget(key,size,flag)
該函數使用頭文件如下:
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
參數定義
int shmget(key,size,flag);
key_t key;
int size,flag;
其中,key是共享存儲區的名字;size是其大小(以字節計);flag是用戶設置的標志,如IPC_CREAT。IPC_CREAT表示若系統中尚無指名的共享存儲區,則由核心建立一個共享存儲區;若系統中已有共享存儲區,便忽略IPC_CREAT。
附:
操作允許權 八進制數
用戶可讀 00400
用戶可寫 00200
小組可讀 00040
小組可寫 00020
其它可讀 00004
其它可寫 00002
控制命令 值
IPC_CREAT 0001000
IPC_EXCL 0002000
例:shmid=shmget(key,size,(IPC_CREAT|0400))
創建一個關鍵字為key,長度為size的共享存儲區
2)shmat( )
共享存儲區的附接。從邏輯上將一個共享存儲區附接到進程的虛擬地址空間上。
系統調用格式:
virtaddr=shmat(shmid,addr,flag)
該函數使用頭文件如下:
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
參數定義
char *shmat(shmid,addr,flag);
int shmid,flag;
char * addr;
其中,shmid是共享存儲區的標識符;addr是用戶給定的,將共享存儲區附接到進程的虛地址空間;flag規定共享存儲區的讀、寫權限,以及系統是否應對用戶規定的地址做舍入操作。其值為SHM_RDONLY時,表示只能讀;其值為0時,表示可讀、可寫;其值為SHM_RND(取整)時,表示操作系統在必要時舍去這個地址。該系統調用的返回值是共享存儲區所附接到的進程虛地址viraddr。
3)shmdt( )
把一個共享存儲區從指定進程的虛地址空間斷開。
系統調用格式:
shmdt(addr)
該函數使用頭文件如下:
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
參數定義
int shmdt(addr);
char addr;
其中,addr是要斷開連接的虛地址,亦即以前由連接的系統調用shmat( )所返回的虛地址。調用成功時,返回0值,調用不成功,返回-1。
4)shmctl( )
共享存儲區的控制,對其狀態信息進行讀取和修改。
系統調用格式:
shmctl(shmid,cmd,buf)
該函數使用頭文件如下:
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
參數定義
int shmctl(shmid,cmd,buf);
int shmid,cmd;
struct shmid_ds *buf;
其中,buf是用戶緩沖區地址,cmd是操作命令。命令可分為多種類型:
第一種:用於查詢有關共享存儲區的情況。如其長度、當前連接的進程數、共享區的創建者標識符等;
第二種:用於設置或改變共享存儲區的屬性。如共享存儲區的許可權、當前連接的進程計數等;
第三種:對共享存儲區的加鎖和解鎖命令;
第四種:刪除共享存儲區標識符等。
上述的查詢是將shmid所指示的數據結構中的有關成員,放入所指示的緩沖區中;而設置是用由buf所指示的緩沖區內容來設置由shmid所指示的數據結構中的相應成員。
四、實驗中用到的系統調用函數(包括實驗原理中介紹的和自己采用的),自己采用的系統調用函數要按照指導書中的格式說明進行介紹。
fork, exec, wait, exit, getpid, sleep, lockf, kill, signal, read, write, msgget, msgsnd, msgrcv, msgctl,shmget, shmat, shmdt, shmctl。
五、實驗步驟
1、編寫程序,讓父進程創建兩個子進程,三個進程並發執行,父進程循環輸出“創建父進程”五次,子進程1循環輸出“創建子進程1”五次,子進程2循環輸出“創建子進程2”五次。多次執行程序,觀察輸出結果。
2.父進程創建一個子進程,直到子進程創建成功,若此時是父進程正在運行,則調用wait()使父進程進入等待,將cpu讓給子進程,子進程獲得cpu后開始運行,用預先寫好的新程序science2裝入子進程運行的地址,即用science2程序的內容代替子進程的內容。當子進程執行結束再轉父進程。
3.父進程創建兩個子進程,若是父進程運行,則輸出“創建父進程”,若子進程運行,則輸出“創建子進程1”或者“創建子進程2”,分別用加鎖和不加鎖的程序進行測試。
4.用fork( )創建兩個子進程,再用系統調用signal( )讓父進程捕捉鍵盤上來的中斷信號(即按^c鍵);捕捉到中斷信號后,父進程用系統調用kill( )向兩個子進程發出信號,子進程捕捉到信號后分別輸出下列信息后終止:
Child process1 is killed by parent!
Child process2 is killed by parent!
父進程等待兩個子進程終止后,輸出如下的信息后終止:
Parent process is killed!
5.在兩個終端上創建兩個進程,分別是client負責發送消息,server負責接受消息。
6.開辟一個共享資源區,創建兩個子進程server和client,子進程client監聽共享資源區情況,如果資源區被server修改,client即可從就緒態轉變為運行態。
六、實驗結果分析(截屏的實驗結果,與實驗結果對應的實驗分析)
1、
程序並發運行。由於調用fork()創建兩個子進程並發運行,三個進程交替輸出,很明顯可以看到程序並發性。
2:
未加wait()函數同步:
未加wait()同步時父進程總是沒有等待子進程結束后自己就結束了。
加了wait()函數同步后:父進程等待子進程實現同步
3:
在實驗1的代碼基礎上為三個進程加鎖:可以看到加鎖后三個進程在進入加鎖區之前會並發執行,但同一時間總是只有一個進程在執行輸出。原因是使用了lockf()函數為進程代碼加鎖,一個進程在使用CPU資源時另外的進程如果想獲取CPU資源就會返回錯誤或者進入等待狀態,直到某一子進程執行完畢釋放CPU資源。
4:
父進程接受到鍵盤上發送的^c中斷信號后向子進程發出kill()信號,子進程收到kill()后打印指定語句。
5:
運行server.c:
運行client.c向服務器server發送消息:
返回server端查看消息:
從實驗中,一運行client.c控制台上server端和client端幾乎同時出現全部語句,但按理論來說應該是client發送一條消息,server接收一條消息,兩端交替在控制台進行輸出,但由於電腦運行速度實在太快才導致這種現象。但只要在client端輸出語句后加上sleep(2)讓其睡眠一下后就可在控制台中看到兩端交替輸出的現象。
6:
程序6每次執行時在輸出(client) sent和(server) received過程中都存在微乎其微的延遲,結合資料總結得出,程序代碼中client()函數printf消息后沒有任何通知服務器server端的操作,而且此刻client()仍占用系統CPU,server端等到系統調度調用時才占用CPU進行應答,所以造成延遲,同樣,server端完成應答后也同樣占用CPU直到系統調度轉換到client端。
七、思考題
1、進程創建與進程並發執行
(1)系統是怎樣創建進程的?
系統首先為進程申請空白的PCB,再為新進程分配所需資源,初始化進程PCB,初始化結束后再將新進程插入就緒隊列,等待CPU執行。
(2)當首次調用新創建進程時,其入口在哪里?
fork()在子進程和父進程的返回值不同,在子進程中返回0,在父進程中返回子進程的PID。fork()系統調用子進程創建成功后會與父進程執行相同的代碼,此時子進程也同時繼承了父進程的程序指針,子進程從fork()后的語句開始執行,即新創建進程的入口。
(3)利用strace 和ltrace -f -i -S ./executable-file-name查看程序執行過程,並分析原因,畫出進程家族樹。
2、進程的睡眠、同步、撤消等進程控制
(1)可執行文件加載時進行了哪些處理?
可執行文件加載時執行execl系統調用裝入新程序science2,子進程fork后的代碼分支被science2的程序代碼替代,開始執行science2.c的內容。
(2)什么是進程同步?wait( )是如何實現進程同步的?
進程同步是指多個彼此依賴的相關進程在執行次序上進行協調,以使並發執行的主進程之間有效的共享資源和相互合作,從而使程序的執行具有可再現性。
Wait首先程序在調用fork()創建了一個子進程后,馬上調用wait(),使父進程在子進程調用之前一直處於睡眠狀態,這樣使子進程先運行,子進程運行exec()裝入命令后調用wait(NULL),使子進程和父進程並發執行,實現了進程同步。
(3)wait( )和exit()是如何控制實驗結果的隨機性的?
在代碼設計中我使用了execl()函數后調用science2的命令,輸出“當前成功調用execl()創建新程序”,執行完execl()函數后,子進程調用exit()函數,退出當前進程,在不使用wait()函數時,子進程和父進程的執行順序具有一定隨機性。但從執行結果中可以發現,加入wait()函數后父進程總是在子進程執行完畢后才執行,所以在控制台輸出結果中最后輸出的總是父進程。通過wait()和exit()的聯合調用控制實驗結果的隨機性。
3、多進程通過加鎖互斥並發運行
(1)進程加鎖和未上鎖的輸出結果相同嗎? 為什么?
對比實驗3與實驗1的輸出結果,完全不同。實驗1程序沒有上鎖,實驗3程序是實驗1的加鎖版本。未上鎖版本中三個進程並發運行,三個語句交互輸出。加鎖版本中三個進程同為並發運行但每個進程運行時不會被其他進程打斷,等到進程執行完畢時下一個進程才運行。因為在在代碼塊使用了lockf()系統函數為代碼塊加鎖,控制對鎖定進程的訪問,此時試圖獲得CPU資源的其他進程將返回錯誤或者進入等待,直到CPU資源被釋放為止。
4、進程間通過信號機制實現軟中斷通信
(1)為了得到實驗內容要求的結果,需要用到哪些系統調用函數來實現及進程間的通信控制和同步?
signal()、kill()、fork()、exit()、wait()
(2)kill( )和signal( )函數在信號通信中的作用是什么?如果分別注釋掉它們,結果會如何?
注釋掉kill():無論執行何種操作都不會引發進程中止。signal從控制台接受^C信號,調用預先寫好的killchilds函數,函數中原本應向子進程發送信號的kill函數被注釋,子進程接收不到信號,無法做出回應,程序陷入死鎖無法退出。
注釋掉signal():程序只有在輸入中斷信號^C時才會退出。
5、消息的發送與接收
(1)為了便於操作和觀察結果,需要編制幾個程序分別用於消息的發送與接收?
需要編制兩個程序client.c和server.c分別用於消息的發送與接收。
(2)這些程序如何進行編輯、編譯和執行?為什么?
使用系統自帶的文本編輯器在控制台分別輸入gedit client.c和gedit server.c創建並編輯c代碼文件。編輯完成后執行gcc client.c -o client和gcc server.c -o server編譯文件,再先執行./server打開服務端,再執行./client。
(3)如何實現消息的發送與接收的同步?
創建兩個終端,先執行./server創建服務端,再在另一個終端執行./client創建客戶端向server端發送消息,server端接受到消息后輸出信息。
6、進程的共享存儲區通信
(1)為了便於操作和觀察結果,需要如何合理設計程序來實現子進程間的共享存儲區通信?
開辟一個共享資源區,創建兩個子進程server和client,子進程client監聽共享資源區情況,如果資源區被server修改,client即可從就緒態轉變為運行態。
(2)比較消息通信和共享存儲區通信這兩種進程通信機制的性能和優缺點。
答:
(1)消息隊列的建立比共享區的設立消耗的資源少。前者只是一個軟件上設定的問題,后者需要對硬件的操作,實現內存的映像,當然控制起來比前者復雜。如果每次都重新進行隊列或共享的建立,共享區的設立沒有什么優勢。
(2)當消息隊列和共享區建立好后,共享區的數據傳輸,受到了系統硬件的支持,不耗費多余的資源;而消息傳遞,由軟件進行控制和實現,需要消耗一定的cpu的資源。從這個意義上講,共享區更適合頻繁和大量的數據傳輸。
(3)消息的傳遞,自身就帶有同步的控制。當等到消息的時候,進程進入睡眠狀態,不再消耗cpu資源。而共享隊列如果不借助其他機制進行同步,接收數據的一方必須進行不斷的查詢,白白浪費了大量的cpu資源。可見,消息方式的使用更加靈活。
以上為網上總結。我自我覺得總結得沒有這段話好且全面,所以在此決定將這段話放上來當做對本思考題的回答。
八、實驗數據及源代碼(學生必須提交自己設計的程序源代碼,並有注釋,源代碼電子版也一並提交),包括思考題的程序。
程序完整代碼請轉至個人GitHub倉庫(如果喜歡,麻煩點個star✨謝謝~)
結語:隨筆僅供參考,千萬不要照抄哦,我相信你可以的~!