突驗 8 進程通訊

實驗八 進程間通訊數組

 

項目 內容
這個做業屬於哪一個課程 班級課程
這個做業的要求在哪裏 做業要求
姓名一學號 17041528一朱思皓
學習目標 1.瞭解進程間通訊的經常使用方式;2.掌握管道、消息隊列、信號量、共享內存實現進程間通訊的方法

 

1.舉例說明使用匿名管道進行進程通訊。數據結構

匿名管道:併發

當進程使用 pipe 函數,就能夠打開位於內核中的這個特殊「文件」。函數

同時 pipe 函數會返回兩個描述符,一個用於讀,一個用於寫。佈局

若是使用 fstat 函數來測試該描述符,能夠發現此文件類型爲FIFO 。學習

而無名管道的無名,指的就是這個虛幻的「文件」,它沒有名字。測試

操作系統

 pipe 函數打開的文件描述符是經過參數(數組)傳遞出來的,而返回值表示打開成功(0)或失敗(-1)。線程

它的參數是一個大小爲 2 的數組。3d

此數組的第 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 出一個子進程,經過無名管道向子進程發送字符,子進程收到數據後將字符串中的

小寫字符轉換成大寫並輸出。

hellopipe.c

putchar(toupper(buf[i])) //toupper把小寫字母轉換爲大寫字母

                                     //putchar函數是「字符輸出函數」,它的功能是向計算機屏幕上輸出一個字符

n = read(STDIN_FILENO, buf, 64) //讀取標準輸入到buf中,返回讀取字節數

int pipe(int pipefd[2]) //可用於建立一個管道,以實現進程間的通訊,pipefd[0] 是用於讀的,而 pipefd[1] 是用於寫的

2.舉例說明使用mkfifo命令建立命名管道以及簡單演示管道如何工做。

命名管道:

1) 經過命令 mkfifo 建立管道

man mkfifo //建立命名管道

 

2) 經過函數 mkfifo(3) 建立管道

man 3 mkfifo

 FIFO 文件的特性

a) 查看文件屬性

當使用 mkfifo 建立 hello 文件後,查看文件信息

b) 使用 cat 命令打印 hello 文件內容

接下來 cat 命令被阻塞住。

開啓另外一個終端,執行:

 而後被阻塞的 cat 又繼續執行完畢,在屏幕打印 「hello world」 。若是反過來執行上面兩個命令,會發現先執行的那個老是被阻塞。

c) fifo 文件特性

文件屬性前面標註的文件類型是 p ,表明管道文件。

文件大小是 0 ,fifo 文件須要有讀寫兩端,不然在打開 fifo 文件時會阻塞。

固然了,若是在 open 的時候,使用了非阻塞方式,確定是不會阻塞的。

特別地,若是以非阻塞寫的方式 open ,同時沒有進程爲該文件以讀的方式打開,會致使 open 返回錯誤(-1),同時 errno 設置成ENXIO .

 

echo "hello world" > hello //輸出重定向到管道文件中

prw-r--r-- //p表明管道文件

3.編寫兩個程序使用第2題中建立的管道進行通訊。

例題:編寫兩個程序,分別是發送端 pipe_send 和接收端面 pipe_recv 。程序 pipe_send 從標準

輸入接收字符,併發送到程序 pipe_recv ,同時 pipe_recv 將接收到的字符打印到屏幕。

 pipe_send.c

 pipe_recv.c

分別開啓兩個終端,分別運行 pipe_send 和 pipe_recv :

如今兩個終端都處於阻塞狀態,咱們在運行 pipe_send 的終端輸入數據,

而後就能夠在運行pipe_recv 的終端看到相應的輸出。能夠用組合按鍵結束上述兩個進程,我用的是ctrl+c。

 int fd = open("hello", O_WRONLY) //O_WRONLY 以只寫方式打開文件hello文件

write(STDOUT_FILENO, buf, n) //把buf 寫到標準輸出中

 

4.編寫兩個程序分別經過指定的鍵值建立IPC內核對象,以及獲取該指定鍵值的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 上建立共享內存。

 ipccreate.c

獲取 ipc 內核對象

程序 ipcget 用於在指定的鍵值上獲取 ipc 內核對象的 id 號。使用格式爲 ./ipcget ,好比

./ipcget 0 0x8888 表示獲取鍵值 0x8888 上的共享內存 id 號。

ipcget.c

 

key_t key = strtoll(argv[2], NULL, 16) //根據指定的進制base,將第一個參數指向的字符串轉換爲對應的整形

id = shmget(key, 0, 0) //獲取指定共享內存的key值

strcpy(buf, "share memory") //把字符串"share memory"複製到buf中

ipcs //用於查看進程間通訊

5.編寫一個程序能夠用來建立、刪除內核對象,也能夠掛接、卸載共享內存,還能夠打印、設置內核對象信息。

前面已經知道如何建立內核對象,接下來分別瞭解三種內核對象的操做:

 

man 2 shmop //shmop函數用於共享內存操做

 

man 2 shmctl //shmctl函數用於共享內存控制

例題:編寫一個程序 shmctl 能夠用來建立、刪除內核對象,也能夠掛接、卸載共享內存,還能夠打

印、設置內核對象信息。具體使用方法具體見下面的說明:

 

./shmctl -c : 建立內核對象。

./shmctl -d : 刪除內核對象。

./shmctl -v : 顯示內核對象信息。

./shmctl -s : 設置內核對象(將權限設置爲 0600 )。

./shmctl -a : 掛接和卸載共享內存(掛接 5 秒後,再執行 shmdt ,而後退出)。

shmctl.c

先在另外一個終端執行 ./shmctl -a ,而後在當前終端執行 ./shmctl -v (注意手速,5秒內要搞定)。

再在另外一個終端 ./shmctl -a 執行完,而後在當前終端執行 ./shmctl -v 。

struct shmid_ds shmid //定義shmid爲shmid_ds類型的結構體

ASSERT(shmctl(id, IPC_STAT, &shmid)) //將獲得的共享內存的狀態存在shmid結構體中

./shmctl -s mode 0600 //修改內核對象的權限

6.編寫兩程序分別用於向消息隊列發送數據和接收數據。msg_send 程序定義了一個結構體 Msg,消息正文部分是結構體 Person。該程序向消息隊列發送了 10 條消息。

消息隊列本質上是位於內核空間的鏈表,鏈表的每一個節點都是一條消息。每一條消息都有本身的消息類

型,消息類型用整數來表示,並且必須大於 0.每種類型的消息都被對應的鏈表所維護,下圖 展現了內

核空間的一個消息隊列:

 

其中數字 1 表示類型爲 1 的消息,數字二、三、4 相似。彩色塊表示消息數據,它們被掛在對應類型的鏈

表上。值得注意的是,剛剛說過沒有消息類型爲 0 的消息,實際上,消息類型爲 0 的鏈表記錄了全部消

息加入隊列的順序,其中紅色箭頭表示消息加入的順序。

 

消息隊列相關的函數

man 2 msgop //msgop函數用於消息隊列的操做

消息數據格式

不管你是發送仍是接收消息,消息的格式都必須按照規範來。簡單的說,它通常長成下面這個樣子:

 

struct Msg

{

 long type; // 消息類型。這個是必須的,並且值必須 > 0,這個值被系統使用

            // 消息正文,多少字節隨你而定 

            // ... 

}

例題:程序 msg_send 和 msg_recv 分別用於向消息隊列發送數據和接收數據。 msg_send 程序定義了

一個結構體 Msg ,消息正文部分是結構體 Person 。該程序向消息隊列發送了 10 條消息。

 

 msg_send.c

程序 msg_send 第一次運行完後,內核中的消息隊列大概像下面這樣:

 

msg_recv 程序接收一個參數,表示接收哪一種類型的消息。

好比 ./msg_recv 4 表示接收類型爲 4 的消息,並打印在屏幕。

 

msg_recv.c

 

 

先運行 ./msg_send ,再運行 ./msg_recv 。

接收全部消息:

 接收類型爲 4 的消息,這時要從新運行 ./msg_send ,接收類型小於等於 3 的全部消息,這是不用再運行 ./msg_send 。

還有一個函數來操做消息隊列內核對象的:

 man 2 msgctl //msgctl函數用於消息隊列的控制

 

ASSERT(msgget, id) //ASSERT函數是若是它的條件返回錯誤,則終止程序執行

long type = atol(argv[1]) //atol函數的做用是將一個字符串轉化爲長整型數據

int res = msgsnd(id, &msg[i], sizeof(Person), 0) //msgsnd函數是用來向消息隊列發送消息的

 

7.編寫程序舉例說明信號量如何操做。

設置和獲取信號量值的函數 semctl :

 

man 2 semctl //semctl函數用於信號量數組的控制

請求和釋放信號量 semop

 

man 2 semop //semop函數用於信號量數組的操做

 

 

struct sembuf 

  unsigned short sem_num; /* semaphore number */

  short sem_op; /* semaphore operation */ 

  short sem_flg; /* operation flags */ 

}

例題:信號量操做

 

semop.c

 

 

semctl(id, 3, GETALL, vals) //GETALL將全部信號的值存入vals數組中

struct sembuf op1 = {0, -2, 0} //第1個0表示信號量集合中的第一個信號,-2表示操做信號量值減2,第2個0表示設置信號量默認操做

semop(id, &op1, 1) //op1指向進行操做的信號量集數組首地址,1是進行操做信號量的個數

8.編寫程序使用信號量實現父子進程之間的同步,防止父子進程搶奪CPU。

例題:使用信號量實現父子進程之間的同步,防止父子進程搶奪 CPU 。

 

mysem.c

 

 

 

這裏能夠看到字符是成對出現的,修改程序把65行 sem_p(); 和72行 sem_v(); 註釋掉,在編譯

運行會發現字符可能就不會成對出現了,這裏就是用信號量來幫咱們實現進程間的同步的。

 

 

 

#include<sys/types.h> 

#include<unistd.h> 

#include<sys/wait.h> //添加頭文件,解除警告

struct sembuf op = {0,-1,0} //第1個0表示信號量集合中的第一個信號,-1表示操做信號量值減1,第2個0表示設置信號量默認操做

if(semop(semid,&op,1) == -1) //請求一個資源

sleep(rand()%4) //rand()%4 表明產生0-3之間的隨機數,阻塞線程

fflush(stdout) //刷新流

相關文章
相關標籤/搜索