? 消息隊列概念
Linux系統中消息隊列(Message Queue)是進程間通信的一種方式,這種通信機制的好處是可以傳輸指定類型(用戶可以自行定義)的數據,相同類型的數據根據到達順序在隊列中進行排隊。
當然,不同類型的數據不能處于同一個隊列中,也就是說系統中可能存在多個消息隊列,每個消息隊列中的數據類型都是不同的,所以用戶打算讀取消息隊列中的數據時也需要指定數據類型,才可以從存儲該類型數據的消息隊列中讀取有效數據。
思考:既然Linux系統中可能存在多條消息隊列,那操作系統是如何管理的以及進程如何選擇某個消息隊列來發送消息?
回答:Linux系統每個創建的消息隊列都具有一個唯一的鍵值key,進程可以通過指定消息隊列的鍵值來向消息隊列發送數據。Linux系統中提供了一個shell命令:ipcs -a來查看系統中所有的IPC對象的信息。
? 創建消息隊列
思考:既然當前的Linux系統中不存在消息隊列,那用戶如何創建一個消息隊列供進程訪問?
回答:Linux系統提供了一個名稱叫做msgget()的函數接口,用戶利用該接口可以創建或者打開一個消息隊列,同樣Linux系統中提供了一個shell命令:ipcmk 可以用于創建IPC對象。
(1) ipcmk命令
(2) msgget()函數
(3) 函數參數
msgget函數有兩個參數,第一個參數需要傳入一個key_t類型的值,該值指的是要創建的消息隊列的key鍵值,key也可以稱為密鑰。 鍵值類型key_t其實在內核源碼中指的是int類型,如下圖:
msgget()函數的第二個參數指的是創建消息隊列的標志,其中IPC_CREAT指的是如果消息隊列不存在則創建,IPC_EXCL指的是如果消息隊列存在則表示函數調用失敗。
另外,也可以指定消息隊列的權限,權限的結構和open函數的mode類型,采用八進制表示,只不過消息隊列不需要設置執行權限,所以權限設置為0644即可。
(4) 返回結果
msgget()函數調用成功,則返回消息隊列的標識符,如果調用失敗,則返回-1并設置錯誤碼。
思考:可以看到調用msgget()函數需要傳入一個鍵值key,這個鍵值key是否可以由用戶指定?
回答:可以由用戶指定,但是不建議這樣操作,因為可能有多個進程都會創建消息隊列,如果某兩個進程指定的鍵值key相同,則會創建失敗,所以應該由系統生成唯一的鍵值key。
Linux系統中提供了一個名稱叫做ftok()的函數接口,利用該接口可以生成IPC對象的鍵值key,該接口的使用規則如下:
可以看到,ftok()函數可以把一個指定路徑的文件和一個指定的項目id轉換為一個system-V IPC對象使用的鍵值key。
(1) 函數參數
ftok()函數的第一個參數指的是系統中已經存在并且可以訪問的一個文件的路徑,用戶可以指定一個文件,但是該文件必須存在且可以被訪問,其實就是為了得到文件的屬性信息中的inode編號和設備編號,因為Linux系統中一個文件的inode編號是唯一的。
ftok()函數的第二個參數指的是項目ID,這個可以由用戶進行指定,雖然參數proj_id的類型是整型int,但是只會用到最低8it,所以這個參數的范圍其實是1~255,因為這個參數的值必須是非0值。
(2) 返回結果
ftok()函數的返回值:當函數調用成功后,則返回生成的鍵值key,如果調用失敗,則返回-1。
(3) 注意事項
在man手冊中提到ftok()函數生成的鍵值key的組成:proj_id的低8位+ 設備編號的低8位+ inode編號的低16位。
練習:設計程序,在Linux系統中創建一個消息隊列,并測試消息隊列的鍵值key的組成是否正確。提示:可以通過stat()函數獲取文件的屬性信息。
? 訪問消息隊列
思考:用戶如果創建了一個消息隊列,那進程如何向消息隊列收發數據,數據類型如何指定?
回答:Linux系統中提供了一個名稱叫做msgsnd()的函數接口,用戶利用該函數可以向指定的消息隊列發送消息,同時提供了一個名稱叫做msgrcv()的函數接口,用戶利用該接口可以從指定的消息隊列中讀取消息。
(1) 發送消息
可以知道,用戶創建的消息隊列是有默認容量的,默認容量是16384字節,可以通過msg.h得到,用戶在向消息隊列寫入數據的時候要考慮消息隊列的容量。
msgsnd函數的第一個參數msgid指的是消息隊列的標識符,該標識符可以通過msgget函數得到。
msgsnd函數的第二個參數msgp指的是一個指向struct msgbuf類型的結構體指針,該結構體中有兩個成員,其中一個成員mtype指的是消息類型,必須是一個大于0的正整數,另一個成員mtext指的是消息正文,類型可以是數組或者其他結構。
msgsnd函數的第三個參數msgsz指的是消息正文的大小,按字節計算,當然msgsz的值必須是非負整數,可以設置為0,表示消息正文的長度為0。
msgsnd函數的第四個參數msgflg指的是消息隊列的標志,如果該標志設置為IPC_NOWAIT,則表示不阻塞,此時如果待寫入的消息的長度大于消息隊列剩余空間,則直接返回并報錯。
注意:消息隊列默認的屬性是阻塞的,也就是當待寫入的消息的長度大于消息隊列剩余空間時,默認阻塞,直到消息隊列的容量足夠容納時會解除阻塞。
(2) 讀取消息
第一個參數:msgqid指的是MSG對象的標識符ID,MSG標識符可以通過msgget()函數獲取。
第二個參數:msgp指的是存放消息的緩存地址,該地址下存儲的是struct msgbuf結構體。
第三個參數:msgsz指的是存放消息的緩存的大小,按照字節計算,如果消息正文的大小大于用戶設置的緩存大小,則根據msgflg是否為MSG_NOERROR進行判斷,如果msgflg設置為MSG_NOERROR ,則可以讀取對應字節的消息,如果msgflg沒有設置,則無法讀取消息并報錯。
第四個參數:msgtyp指的是要接收消息的類型,在調用msgsnd函數時構造的消息結構體中有該成員的值。
-
等于0:指的是不區分類型,直接讀取MSG中的第一個消息。
-
大于0:讀取類型為指定msgtyp的第一個消息(若msgflg被配置了MSG_EXCEPT則讀取除了類型為msgtyp的第一個消息)。
-
小于0:讀取類型小于等于msgtyp絕對值的第一個具有最小類型的消息。例如當MSG對象中有類型為3、1、5類型消息若干條,當msgtyp為-3時,類型為3的第一個消息將被讀取。
第五個參數:msgflg指的是接收消息選項,如果msgflg設置為0,指的是默認接收模式,在MSG中無指定類型消息時阻塞。
- IPC_NOWAIT :指的是非阻塞接收模式,當MSG中沒有指定類型消息時直接退出函數
- MSG_EXCEPT :指的是讀取除msgtyp之外的第一個消息。
- MSG_NOERROR:如果待讀取的消息尺寸比msgsz大只返回msgsz部分,其余部分丟棄
? 控制消息隊列
IPC對象是一種持久性資源,如果沒有明確的刪除掉IPC對象,則IPC對象是不會自動從內存中消失的。用戶除了可以使用命令的方式刪除,也可以使用函數來刪除。
Linux系統中提供了一個名稱叫做msgctl的函數接口,用戶可以利用該函數實現獲取消息隊列的屬性信息、設置消息隊列的屬性信息、刪除消息隊列等操作。
- 指令刪除
- 函數刪除