1. 程式人生 > 其它 >《作業系統原理》實驗報告三

《作業系統原理》實驗報告三

技術標籤:實驗報告linux作業系統多程序多執行緒

一、實驗目的
(1)理解執行緒/程序的通訊機制和程式設計;
(2)理解執行緒/程序的死鎖概念和如何解決死鎖

二、實驗內容
(1)在 Ubuntu 或 Fedora 環境建立一對父子程序,使用共享記憶體的方式實現程序間的通訊。父程序提供資料(1-100,遞增),子程序讀出來並顯示。
(2)(考慮訊號通訊機制)在 Ubuntu 或 Fedora 環境建立父子 2 個程序 A,B。程序 A 不斷獲取使用者從鍵盤輸入的字串或整數,通過訊號機制傳給程序 B。如果輸入的是字串,程序 B 將其打印出來;如果輸入的是整數,程序 B 將其累加起來,並輸出該數和累加和。當累加和大於 100 時結束子程序,子程序輸出“My work done!”後結束,然後父程序也結束。

(3)在 windows 環境使用建立一對父子程序,使用管道(pipe)的方式實現程序間的通訊。父程序提供資料(1-100,遞增),子程序讀出來並顯示。
(4)(考慮匿名管道通訊)在 windows 環境下建立將 CMD 控制檯程式封裝為標準的 windows 視窗程式。
(5)在 windows 環境下,利用高階語言程式設計環境(限定為 VS 環境或 VC 環境或QT)呼叫 CreateThread 函式哲學家就餐問題的演示。要求:(1)提供死鎖的解法和非死鎖的解法;(2)有圖形介面直觀顯示哲學家取筷子,吃飯,放筷子,思考等狀態。(3)為增強結果的隨機性,各個狀態之間的維持時間採用隨機時間,例如100ms-500ms 之間。
:[1,3,4]中任意 1 題和第2,5 題,共計 3 道題

三、實驗過程
(一)實驗步驟
1)使用共享記憶體實現程序間的通訊
1. 何為共享記憶體?
共享記憶體就是允許兩個不相關的程序訪問同一個邏輯記憶體。不同程序之間共享的記憶體通常安排為同一段實體記憶體。程序可以將同一段共享記憶體連線到它們自己的地址空間中,所有程序都可以訪問共享記憶體中的地址
2.重要函式介紹:
宣告在標頭檔案 sys/shm.h 中
shmget函式:該函式用來建立共享記憶體
int shmget(key_t key, size_t size, int shmflg);
key_t key: 它有效地為共享記憶體段命名,shmget函式成功時返回一個與key相關的共享記憶體識別符號(非負整數)。呼叫失敗返回-1.

size_t size: size以位元組為單位指定需要共享的記憶體容量
int shmflg: 共享記憶體的許可權標誌
shmat函式: shmat函式的作用就是用來啟動對該共享記憶體的訪問,並把共享記憶體連線到當前程序的地址空間
void *shmat(int shm_id, const void *shm_addr, int shmflg);
shm_id: shm_id是由shmget函式返回的共享記憶體標識。
shm_addr: shm_addr指定共享記憶體連線到當前程序中的地址位置,通常為空,表示讓系統來選擇共享記憶體的地址。
shmflg: shm_flg是一組標誌位,通常為0。
shmdt函式: 該函式用於將共享記憶體從當前程序中分離,使該共享記憶體對當前程序不再可用。
int shmdt(const void *shmaddr);
引數shmaddr: 是shmat函式返回的地址指標,呼叫成功時返回0,失敗時返回-1
shmctl函式: 與訊號量的semctl函式一樣,用來控制共享記憶體
int shmctl(int shm_id, int command, struct shmid_ds *buf);
shm_id: 是shmget函式返回的共享記憶體識別符號。
command: 是要採取的操作,它可以取下面的三個值 :
IPC_STAT: 把shmid_ds結構中的資料設定為共享記憶體的當前關聯值,即用共享記憶體的當前關聯值覆蓋shmid_ds的值。
IPC_SET:如果程序有足夠的許可權,就把共享記憶體的當前關聯值設定為shmid_ds結構中給出的值
IPC_RMID: 刪除共享記憶體段
buf: 是一個結構指標,它指向共享記憶體模式和訪問許可權的結構。
1.

	if (pid < 0)    
2.	   {    
3.	       //printf('子程序建立失敗');    
4.	       fprintf(stderr, "can't fork ,error %d\n", errno);    
5.	       exit(1);    
6.	   }    
7.	   else if (pid == 0) //子程序    
8.	   {    
9.	       while (cRunning) //讀取共享記憶體中的資料    
10.	       {    
11.	           //沒有程序向共享記憶體定資料有資料可讀取    
12.	           if (shared->written != 0)    
13.	           {    
14.	               int i = 0;    
15.	               while (i < 100)    
16.	               {    
17.	                   printf("child  read: %d\n", shared->text[i]);    
18.	                   i++;    
19.	               }    
20.	               //讀取完資料,設定written使共享記憶體段可寫    
21.	               shared->written = 0;    
22.	               //退出迴圈(程式)    
23.	               cRunning = 0;    
24.	           }    
25.	           else //有其他程序在寫資料,不能讀取資料    
26.	               sleep(1);    
27.	       }    
28.	   }    
29.	   else //父程序    
30.	   {    
31.	       int i = 1;    
32.	       while (pRunning) //向共享記憶體中寫資料    
33.	       {    
34.	           //資料還沒有被讀取,則等待資料被讀取,不能向共享記憶體中寫入文字    
35.	           while (shared->written == 1)    
36.	           {    
37.	               sleep(1);    
38.	           }    
39.	           //向共享記憶體中寫入資料    
40.	           shared->text[i - 1] = i;    
41.	           printf("parent write: %d\n", shared->text[i - 1]);    
42.	           i++;    
43.	           if (i > 100)    
44.	           {    
45.	               //寫完資料,設定written使共享記憶體段可讀,退出迴圈(程式)    
46.	               shared->written = 1;    
47.	               pRunning = 0;    
48.	           }    
49.	       }    
50.	   }   

2)使用訊號通訊機制實現程序間的通訊
程序可以通過呼叫kill函式向包括它本身在內的其他程序傳送一個訊號。如果程式沒有許可權,kill函式會呼叫失敗,失敗的常見原因是目標程序由另一個使用者所擁有,這個函式跟shell同名命令kill的功能完全一樣,定義如下:
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
kill函式中,pid表示要傳送訊號到達的目標程序的程序id,sig為傳送的訊號值。
成功時,返回0。失敗時,返回-1,並設定 errno變數
由於訊號通訊不能傳遞資料,故結合管道通訊
管道如何實現程序間的通訊
(1)父程序建立管道,得到兩個⽂件描述符指向管道的兩端
(2)父程序fork出子程序,⼦程序也有兩個⽂件描述符指向同⼀管道
(3)父程序關閉fd[0],子程序關閉fd[1],即⽗程序關閉管道讀端,⼦程序關閉管道寫端(因為管道只支援單向通訊)。⽗程序可以往管道⾥寫,⼦程序可以從管道⾥讀,管道是⽤環形佇列實現的,資料從寫端流⼊從讀端流出,這樣就實現了程序間通訊。
在這裡插入圖片描述

3)哲學家就餐問題演示
筷子編號: 0-4 (哲學家左手邊的筷子與自己編號相同)
int S[5]={1,1,1, 1,1}; //訊號量: i號筷子是否可用: 0不可用,1可用
UINT Philosopher (int i)//執行緒函式,i是哲學家的編號
{ while (TRUE){
思考;
休息;
P(S[i]); //取左手邊的筷子
P(S[(i+4) % 5]); //取右手邊的筷子
吃飯; //正在用 兩隻筷子…
V(S[(i+4) % 5]); //放下右手邊的筷子
V(S[i); //放下左手邊的筷子}
}
核心程式碼:
//建立5個訊號量
for(int i=0;i<5;i++){
S[i] = CreateSemaphore(NULL ,1 ,1,name[i]);
}
//開啟訊號量:
HANDLE right,left;
left =OpenSemaphore(SEMAPHORE_ALL_ACCESS,FALSE,name[id]);
right=OpenSemaphore(SEMAPHORE_ALL_ACCESS,FALSE,name[(id+4)%5]);
WaitForSingleObject(left,INFINITE); //p操作
ReleaseSemaphore(left,1,NULL); //v操作
(二)解決錯誤和優化
1. 編譯錯誤,不能從const char *轉換為LPCWSTR。原因:因為你的程式在UNICODE(寬位元組)字符集下執行,如果呼叫了 MessageBox ,實際上呼叫的是 MessageBoxW 函式;如果你的程式在 ANSI 字符集執行,呼叫 MessageBox ,就相當於調essageBoxA;其中 MessageBoxW 支援 UNICODE;MessageBoxA 支援ANSI;UNICODE與ANSI 有什麼區別呢?簡單的說,UNICODE版的字元比ANSI 的記憶體佔用大,比如:Win32程式中出現的標準定義 char 佔一個位元組,而 char 的UNICODE版被定義成這樣:typedef unsigned short wchar_t ;佔2個位元組。所以有字元做引數的函式相應也用兩個版本了。
解決:專案選單——專案屬性(最後一個)——配置屬性——常規——專案預設值——字符集,將使用Unicode字符集改為未設定即可。
2.特殊語法錯誤,linux c之提示format‘%d’ expects argument of type ‘int’, but argument 2 has type ‘long int’ 。解決辦法md,m為指定的輸出欄位的寬度。如果資料的位數小於m,則左端補以空格,若大於m,則按實際位數輸出。%ld(%mld 也可),輸出長整型資料。最後 printf(“data is %ld”, data)解決。
總結:%md,m為指定的輸出欄位的寬度。如果資料的位數小於m,則左端補以空格,若大於m,則按實際位數輸出。%ld(%mld 也可),輸出長整型資料。u格式符,用來輸出unsigned型資料,無符號數,以十進位制數形式輸出。格式:%u,%mu,%lu都可
3.特殊語法錯誤,變數重複申明。錯誤分析:變數“xxxx”在同一作用域中定義了多次。檢查“xxxx”的每一次定義,只保留一個,或者更改變數名。
4. test.c:59:5: warning: incompatible implicit declaration of built-in function ‘memset’ [enabled by default],新增標頭檔案: #include<string.h>解決。

四、實驗結果
1)使用共享記憶體實現程序間的通訊
如下圖所示
在這裡插入圖片描述
在這裡插入圖片描述

2)使用共享記憶體實現程序間的通訊
在這裡插入圖片描述

3)哲學家就餐問題演示
在這裡插入圖片描述
在這裡插入圖片描述

五、體會
通過本次實驗,我對作業系統執行緒/程序的通訊機制和程式設計、作業系統執行緒/程序的死鎖概念有了更深刻的理解,掌握瞭如何解決死鎖,對作業系統的功能與原理有了進一步的瞭解。