實驗八 程式間通訊

6卡卡發表於2020-06-09
 專案  內容
 這個作業屬於哪個課程  Linux系統與應用
 這個作業的要求在哪裡  作業要求連結
 學號-姓名  17041428-朱槐健
 作業學習目標  (1)瞭解程式間通訊的常用方式
(2)掌握管道、訊息佇列、訊號量、共享記憶體實現程式間通訊的方法

 

1. 管道通訊

  • 匿名管道:

當程式使用 pipe 函式,就可以開啟位於核心中的這個特殊“檔案”。同時 pipe 函式會返回兩個描述
符,一個用於讀,一個用於寫。如果你使用 fstat 函式來測試該描述符,可以發現此檔案型別為
FIFO 。而無名管道的無名,指的就是這個虛幻的“檔案”,它沒有名字。

man 2 pipe

pipe 函式開啟的檔案描述符是通過引數(陣列)傳遞出來的,而返回值表示開啟成功(0)或失敗(-1)。
它的引數是一個大小為 2 的陣列。此陣列的第 0 個元素用來接收以讀的方式開啟的描述符,而第 1 個元素用來接收以寫的方式開啟的描述符。也就是說, pipefd[0] 是用於讀的,而 pipefd[1] 是用於寫的。
開啟了檔案描述符後,就可以使用 read(pipefd[0]) 和 write(pipefd[1]) 來讀寫資料了。

注意事項:
這兩個分別用於讀寫的描述符必須同時開啟才行,否則會出問題。

如果關閉讀 ( close(pipefd[0]) ) 端保留寫端,繼續向寫端 ( pipefd[1] ) 端寫資料( write 函式)的程式會收到 SIGPIPE 訊號。
如果關閉寫 ( close(pipefd[1]) ) 端保留讀端,繼續向讀端 ( pipefd[0] ) 端讀資料( read 函式), read 函式會返回 0.
例題:父程式 fork 出一個子程式,通過無名管道向子程式傳送字元,子程式收到資料後將字串中的
小寫字元轉換成大寫並輸出。

 

 

  •  命名管道

1)通過命令mkfifo建立管道

man mkfifo

2)通過函式mkfifo(3)建立管道

man 3 mkfifo

FIFO檔案的特性

a)檢視檔案屬性

當使用mkfifo建立hello檔案後,檢視檔案資訊如下:

某些版本的系統在 hello 檔案後面還會跟著個 | 符號,像這樣 hello |

b) 使用 cat 命令列印 hello 檔案內容

接下來你的 cat 命令被阻塞住。

開啟另一個終端,執行:

然後你會看到被阻塞的 cat 又繼續執行完畢,在螢幕列印 “hello world” 。如果你反過來執行上面兩個命令,會發現先執行的那個總是被阻塞。

c) fifo 檔案特性

根據前面兩個實驗,可以總結:
檔案屬性前面標註的檔案型別是 p ,代表管道
檔案大小是 0
fifo 檔案需要有讀寫兩端,否則在開啟 fifo 檔案時會阻塞
當然了,如果在 open 的時候,使用了非阻塞方式,肯定是不會阻塞的。特別地,如果以非阻塞寫的方式 open ,同時沒有程式為該檔案以讀的方式開啟,會導致 open 返回錯誤(-1),同時 errno 設定成ENXIO .
例題:編寫兩個程式,分別是傳送端 pipe_send 和接收端面 pipe_recv 。程式 pipe_send 從標準
輸入接收字元,併傳送到程式 pipe_recv ,同時 pipe_recv 將接收到的字元列印到螢幕。

 

  

分別開啟兩個終端,分別執行 pipe_send 和 pipe_recv :

 

現在兩個終端都處於阻塞狀態,我們在執行 pipe_send 的終端輸入資料,然後我們就可以在執行
pipe_recv 的終端看到相應的輸出:

 

可以用組合按鍵結束上述兩個程式。

2. IPC 核心物件

每個 IPC 核心物件都是位於核心空間中的一個結構體。具體的對於共享記憶體、訊息佇列和訊號量,他們在核心空間中都有對應的結構體來描述。當你使用 get 字尾建立核心物件時,核心中就會為它開闢一塊記憶體儲存它。只要你不顯式刪除該核心物件,它就永遠位於核心空間中,除非你關機重啟。

程式空間的高 1G 空間( 3GB-4GB )是核心空間,該空間中儲存了所有的 IPC 核心物件。上圖給出不同的 IPC 核心物件在記憶體中的佈局(以陣列的方式),實際作業系統的實現並不一定是陣列,也可能是連結串列或者其它資料結構等等。每個核心物件都有自己的 id 號(陣列的索引)。此 id 號可以被使用者空間使用。所以只要使用者空間知道了核心物件的 id 號,就可以操控核心物件了。
為了能夠得到核心物件的 id 號,使用者程式需要提供鍵值—— key ,它的型別是 key_t ( int 整型)。系統呼叫函式( shmget , msgget 和 semget )根據 key ,就可以查詢到你需要的核心 id號。在核心建立完成後,就已經有一個唯一的 key 值和它繫結起來了,也就是說 key 和核心物件是一一對應的關係。( key = 0 為特殊的鍵,它不能用來查詢核心物件)

建立 IPC 核心物件

man 2 shmget

 

 man 2 msgget

man 2 semget

 

 在建立 IPC 核心物件時,使用者程式一定需要提供 key 值才行。實際上,建立 IPC 核心物件的函式和獲取核心物件 id 的函式是一樣的,都是使用 get 字尾函式。比如在鍵值 0x8888 上建立 ipc 核心物件,並獲取其 id ,應該像下面這樣:

// 在 0x8888 這個鍵上建立核心物件,許可權為 0644,如果已經存在就返回錯誤。
int id = shmget(0x8888, 4096, IPC_CREAT | IPC_EXCL | 0644); 
int id = msgget(0x8888, IPC_CREAT | IPC_EXCL | 0644); 
int id = semget(0x8888, 1, IPC_CREAT | IPC_EXCL | 0644); // 第二個參數列示建立 幾個訊號量

例題:程式 ipccreate 用於在指定的鍵值上建立 ipc 核心物件。使用格式為 ./ipccreate ,比如./ipccreate 0 0x8888 表示在鍵值 0x8888 上建立共享記憶體。

 

獲取 ipc 核心物件

程式 ipcget 用於在指定的鍵值上獲取 ipc 核心物件的 id 號。使用格式為 ./ipcget ,比如./ipcget 0 0x8888 表示獲取鍵值 0x8888 上的共享記憶體 id 號。

 

 

3. 共享記憶體

前面已經知道如何建立核心物件,接下來分別瞭解三種核心物件的操作:

man 2 shmop

man 2 shmctl

例題:編寫一個程式 shmctl 可以用來建立、刪除核心物件,也可以掛接、解除安裝共享記憶體,還可以列印、設定核心物件資訊。具體使用方法具體見下面的說明:

./shmctl -c : 建立核心物件。
./shmctl -d : 刪除核心物件。
./shmctl -v : 顯示核心物件資訊。
./shmctl -s : 設定核心物件(將許可權設定為 0600 )。
./shmctl -a : 掛接和解除安裝共享記憶體(掛接 5 秒後,再執行 shmdt ,然後退出)。

  

 

 

 

 

先在另一個終端執行 ./shmctl -a ,然後在當前終端執行 ./shmctl -v

  

4. 訊息佇列

訊息佇列本質上是位於核心空間的連結串列,連結串列的每個節點都是一條訊息。每一條訊息都有自己的消型別,訊息型別用整數來表示,而且必須大於 0.每種型別的訊息都被對應的連結串列所維護,下圖 展示了核心空間的一個訊息佇列:

其中數字 1 表示型別為 1 的訊息,數字2、3、4 類似。彩色塊表示訊息資料,它們被掛在對應型別連結串列上。值得注意的是,剛剛說過沒有訊息型別為 0 的訊息,實際上,訊息型別為 0 的連結串列記錄了所有訊息加入佇列的順序,其中紅色箭頭表示訊息加入的順序。

訊息佇列相關的函式

man 2 msgop

 

 

訊息資料格式

無論你是傳送還是接收訊息,訊息的格式都必須按照規範來。簡單的說,它一般長成下面這個樣子:

struct Msg{ 
long type; 
// 訊息型別。這個是必須的,而且值必須 > 0,這個值被系統使用 
// 訊息正文,多少位元組隨你而定
    // ... 
}

例題:程式 msg_send 和 msg_recv 分別用於向訊息佇列傳送資料和接收資料。 msg_send 程式定義了一個結構體 Msg ,訊息正文部分是結構體 Person 。該程式向訊息佇列傳送了 10 條訊息。

 

程式 msg_send 第一次執行完後,核心中的訊息佇列大概像下面這樣:

msg_recv 程式接收一個引數,表示接收哪種型別的訊息。比如 ./msg_recv 4 表示接收型別為 4 的訊息,並列印在螢幕。

 

 

先執行 ./msg_send ,再執行 ./msg_recv 。
接收所有訊息:

接收型別為 4 的訊息,這時要重新執行 ./msg_send :

接收型別小於等於 3 的所有訊息,這是不用再執行 ./msg_send :

還有一個函式來操作訊息佇列核心物件的

man 2 msgctl

 

 

5. 訊號量

設定和獲取訊號量值的函式 semctl :
man 2 semctl

 

 

請求和釋放訊號量 semop

man 2 semop

 

struct sembuf { 
unsigned short sem_num; /* semaphore number */
short sem_op; /* semaphore operation */
short sem_flg; /* operation flags */
}

例題:訊號量操作示例

 

 

 

:使用訊號量實現父子程式之間的同步,防止父子程式搶奪 CPU 。

 

 

 

這裡可以看到字元是成對出現的,如果大家修改程式把57行 sem_p(); 和64行 sem_v(); 註釋掉,在編譯執行會發現字元可能就不會成對出現了,這裡就是用訊號量來幫我們實現程式間的同步的。

相關文章