目錄
前言
正文:
1消息隊列
1.1什么是消息隊列??
1.2消息隊列的數據結構?
1.3消息隊列的相關接口?
1.3.1創建?
1.3.2釋放?
1.3.3發送?
1.3.4接收?
1.4消息隊列補充
2.信號量?
2.1什么是信號量?
2.2互斥相關概念?
2.3信號量的數據結構
2.4信號量相關接口?
2.4.1創建?
2.4.2釋放
2.4.3操作
2.5信號量補充
3.深入理解 System V通信方式?
總結:
前言
在 System V 通信標準中,還有一種通信方式:消息隊列,以及一種實現互斥的工具:信號量;隨著時代的發展,這些陳舊的標準都已經較少使用了,但作為 IPC 中的經典知識,我們可以對其做一個簡單了解,擴展 IPC 的知識棧,尤其是 信號量,可以通過它,為以后多線程學習中 POSIX 信號量的學習做鋪墊
正文:
1消息隊列
1.1什么是消息隊列??
?消息隊列(Message Queuing
)是一種比較特殊的通信方式,它不同于管道與共享內存那樣借助一塊空間進行數據讀寫,而是?在系統中創建了一個隊列,這個隊列的節點就是數據塊,包含類型和信息
- 假設現在進程 A、B 想要通過消息隊列進行通信,首先創建一個消息隊列
- 然后進程 A 將自己想要發送給進程 B 的信息打包成數據塊(其中包括發送方的信息),將數據塊添加至消息隊列隊尾處
- 進程 B 同樣也可以向消息隊列中添加數據塊,同時也會從消息隊列中捕獲其他進程的數據塊,解析后進行讀取,這樣就完成了通信
?
?
遍歷消息隊列時,存數據塊?還是?取數據塊?取決于?數據塊中的類型?type
注意:?消息隊列跟共享內存一樣,是由操作系統創建的,其生命周期不隨進程,因此在使用結束后需要刪除
下面有關于消息隊列詳解的文章
?
- 《什么是消息隊列》
- 《消息隊列詳解》
1.2消息隊列的數據結構?
同屬于?System V
?標準,消息隊列也有屬于自己的數據結構
注:msg
?表示?消息隊列
struct msqid_ds
{struct ipc_perm msg_perm; /* Ownership and permissions */time_t msg_stime; /* Time of last msgsnd(2) */time_t msg_rtime; /* Time of last msgrcv(2) */time_t msg_ctime; /* Time of last change */unsigned long __msg_cbytes; /* Current number of bytes in queue (nonstandard) */msgqnum_t msg_qnum; /* Current number of messages in queue */msglen_t msg_qbytes; /* Maximum number of bytes allowed in queue */pid_t msg_lspid; /* PID of last msgsnd(2) */pid_t msg_lrpid; /* PID of last msgrcv(2) */
};
?和?共享內存?一樣,其中?struct ipc_perm
?中存儲了?消息隊列的基本信息,具體包含內容如下:
struct ipc_perm
{key_t __key; /* Key supplied to msgget(2) */uid_t uid; /* Effective UID of owner */gid_t gid; /* Effective GID of owner */uid_t cuid; /* Effective UID of creator */gid_t cgid; /* Effective GID of creator */unsigned short mode; /* Permissions */unsigned short __seq; /* Sequence number */
};
可以通過?man msgctl
?查看函數使用手冊,其中就包含了?消息隊列?的數據結構信息
1.3消息隊列的相關接口?
?論標準的重要性,消息隊列的大小接口風格與共享內存一致,都是出自?System V
?標準
1.3.1創建?
?使用?msgget
?函數創建?消息隊列
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>int msgget(key_t key, int msgflg);
關于?msgget
?函數
?
與?共享內存?的?shmget
?可以說是十分相似了,關于?ftok
?函數計算?key
?值,這里就不再闡述,可以在這篇文章中學習 《Linux進程間通信【共享內存】》
簡單使用函數?msgget
?創建?消息隊列,并使用?ipcs -q
?指令查看資源情況
?
#include <iostream>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>using namespace std;int main()
{//創建消息隊列int n = msgget(ftok("./", 668), IPC_CREAT | IPC_EXCL | 0666);if(n == -1){cerr << "msgget fail!" << endl;exit(1);}return 0;
}
程序運行后,創建出了一個?msqid
?為?0
?的消息隊列
因為此時并?沒有使用消息隊列進行通信,所以已使用字節?used-bytes
?和 消息數?messages
?都是?0
注意:
- 消息隊列在創建時,也需要指定創建方式:
IPC_CREAT
、IPC_EXCL
、權限
?等信息 - 消息隊列創建后,
msqid
也是隨機生成的,大概率每次都不一樣 - 消息隊列生命周期也是隨操作系統的,并不會因進程的結束而釋放
1.3.2釋放?
?
消息隊列也有兩種釋放方式:通過指令釋放、通過函數釋放
釋放指令:ipcrm -q msqid
?釋放消息隊列,其他?System V
?通信資源也可以這樣釋放
ipcrm -m shmid
?釋放共享內存ipcrm -s semid
?釋放信號量集
?
釋放函數:msgctl(msqid, IPC_RMID, NULL)
?釋放指定的消息隊列,跟?shmctl
?刪除共享內存一樣
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>int msgctl(int msqid, int cmd, struct msqid_ds *buf);
?關于?msgctl
?函數
?簡單回顧下參數2部分可傳遞參數:
?
IPC_RMID
?表示刪除共享內存IPC_STAT
?用于獲取或設置所控制共享內存的數據結構IPC_SET
?在進程有足夠權限的前提下,將共享內存的當前關聯值設置為?buf
?數據結構中的值
同樣的,消息隊列 = 消息隊列的內核數據結構(struct msqid_ds
) + 真正開辟的空間
1.3.3發送?
?利用消息隊列發送信息,即?將信息打包成數據塊,入隊尾,所使用函數為?msgsnd
?
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
?關于?msgsnd
?函數
?
?參數2 表示待發送的數據塊,這顯然是一個結構體類型,需要自己定義,結構如下:
?
struct msgbuf
{long mtype; /* message type, must be > 0 */char mtext[1]; /* message data */
};
mtype
?就是傳說中數據塊類型,據發送方而設定;mtex
?是一個比較特殊的東西:柔性數組,其中存儲待發送的?信息,因為是?柔性數組,所以可以根據?信息?的大小靈活調整數組的大小
1.3.4接收?
?消息發送后,總得接收吧,既然發送是往隊尾中添加數據塊,那么接收就是?從隊頭中取數據塊,假設所取數據塊為自己發送的,那么就不進行操作,其他情況則取出數據塊,使用?msgrcv
?函數接收信息
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
關于?msgrcv
?函數
同樣的,接收的數據結構如下所示,也包含了?類型?和?柔性數組
?
struct msgbuf
{long mtype; /* message type, must be > 0 */char mtext[1]; /* message data */
};
1.4消息隊列補充
?System V
?版的 消息隊列 使用起來比較麻煩,并且過于陳舊,現在已經較少使用了,所以我們不必對其進行深究,知道個大概就行了,如果實際中真遇到了,再查文檔也不遲
2.信號量?
2.1什么是信號量?
?信號量(semaphore
)一種特殊的工具,主要用于實現?同步和互斥
信號量?又稱?信號燈,是各大高校《操作系統》課程中老師提及的高頻知識點,往往伴隨著?P、V
?操作出現,但大多數老師都只是提及了基本概念,并未對?信號量?的本質及使用場景作出詳細講解
在正式學習?信號量?相關知識前,需要先簡單了解下?互斥相關四個概念,為后續?多線程中信號量的學習作鋪墊(重點)
2.2互斥相關概念?
1、并發
?是指系統中同時存在多個獨立的活動單元
- 比如在多線程中,多個執行流可以同時執行代碼,可能訪問同一份共享資源
2、互斥
?是指同一時刻只允許一個活動單元使用共享資源
- 即在任何一個時刻,都只允許一個執行流進行共享資源的訪問(可以通過加鎖實現)
3、臨界資源
?與?臨界區
,多執行流環境中的共享資源就是?臨界資源
,涉及?臨界資源
?操作的代碼區間即?臨界區
- 在多線程環境中,全局變量就是?
臨界資源
,對全局變量的修改、訪問代碼屬于?臨界區
4、原子性
:只允許存在 成功 和 失敗 兩種狀態
- 比如對變量的修改,要么修改成功,要么修改失敗,不會存在修改一半被切走的狀態
?所以?互斥?是為了解決?臨界資源 在多執行流環境中的并發訪問問題,需要借助?互斥鎖 或 信號量?等工具實現?原子操作,實現?互斥
?
?
關于互斥鎖(mutex
) 的相關知識在?多線程?中介紹,現在先來學習?信號量,搞清楚它是如何實現?互斥?的
2.3信號量的數據結構
?下面來看看?信號量?的數據結構,通過?man semctl
?進行查看
?sem
?表示?信號量
struct semid_ds
{struct ipc_perm sem_perm; /* Ownership and permissions */time_t sem_otime; /* Last semop time */time_t sem_ctime; /* Last change time */unsigned long sem_nsems; /* No. of semaphores in set */
};
System V
?家族基本規矩,struct ipc_perm
?中存儲了?信號量的基本信息,具體包含內容如下:
?
struct ipc_perm
{key_t __key; /* Key supplied to semget(2) */uid_t uid; /* Effective UID of owner */gid_t gid; /* Effective GID of owner */uid_t cuid; /* Effective UID of creator */gid_t cgid; /* Effective GID of creator */unsigned short mode; /* Permissions */unsigned short __seq; /* Sequence number */
};
?顯然,無論是 共享內存、消息隊列、信號量,它們的 ipc_perm 結構體中的內容都是一模一樣的,結構上的統一可以帶來管理上的便利,具體原因可以接著往下看
2.4信號量相關接口?
2.4.1創建?
?信號量的申請比較特殊,一次可以申請多個信息量,官方稱此為?信號量集,所使用函數為?semget
?
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>int semget(key_t key, int nsems, int semflg);
關于?semget
?函數
除了參數2,其他基本與另外倆兄弟一模一樣,實際傳遞時,一般傳?1
,表示只創建一個?信號量
使用函數創建?信號量集,并通過指令?ipcs -s
?查看創建的?信號量集?信息
#include <iostream>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>using namespace std;int main()
{//創建一個信號量int n = semget(ftok("./", 668), 1, IPC_CREAT | IPC_EXCL | 0666);if(n == -1){cerr << "semget fail!" << endl;exit(1);}return 0;
}
程序運行后,創建了一個?信號量集,nsems
?為?1
,表示在當前?信號量集?中只有一個?信號量
- 信號量集在創建時,也需要指定創建方式:
IPC_CREAT
、IPC_EXCL
、權限
?等信息 - 信號量集創建后,
semid
也是隨機生成的,大概率每次都不一樣 - 信號量集生命周期也是隨操作系統的,并不會因進程的結束而釋放
2.4.2釋放
老方法: 指令釋放:直接通過指令?ipcrm -s semid
?釋放信號量集(略)
通過函數釋放:semctl(semid, semnum, IPC_RMID)
,信號量中的控制函數有一點不一樣
?
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>int semctl(int semid, int semnum, int cmd, ...);
?關于?semctl
?函數
注意:
- 參數2 表示信號量集中的某個信號量編號,從?
1
?開始編號 - 參數3 中可傳遞的動作與共享內存、消息隊列一致
- 參數4 就像?
printf
?和?scanf
?中最后一個參數一樣,可以靈活使用
2.4.3操作
信號量的操縱比較ex,也比較麻煩,所以僅作了解即可
使用?semop
?函數對?信號量?進行諸如?+1
、-1
?的基本操作
?
#include <sys/types.h>#include <sys/ipc.h>#include <sys/sem.h>int semop(int semid, struct sembuf *sops, unsigned nsops);
?關于?semop
?函數
重點在于參數2,這是一個結構體,具體成員如下:
unsigned short sem_num; /* semaphore number */
short sem_op; /* semaphore operation */
short sem_flg; /* operation flags */
其中包含信號量編號、操作等信息,需要我們自己設計出一個結構體,然后傳給?semop
?函數使用
可以簡單理解為:sem_op
?就是要進行的操作,如果將?sem_op
?設為?-1
,表示信號量?-1
(申請),同理?+1
?表示信號量?+1
(歸還)
2.5信號量補充
?
信號量 是實現 互斥 的其中一種方法,具體表現為:資源申請,計數器 -1,資源歸還,計數器 +1,只有在計數器不為 0 的情況下,才能進行資源申請,可以設計 二元信號量 實現 互斥
System V 中的 信號量 操作比較麻煩,但 信號量 的思想還是值得一學的,等后面學習 多線程 時,也會使用 POSIX 中的 信號量 實現 互斥,相比之下,POSIX 版的信號量操作要簡單得多,同時應用也更為廣泛
因為 信號量 需要被多個獨立進程看到,所以 信號量 本身也是 臨界資源,不過它是 原子 的,所以可以用于 互斥
多個獨立進程看到同一份資源,這就是 IPC 的目標,所以 信號量 被劃分至進程間通信中
?
3.深入理解 System V通信方式?
?不難發現,共享內存、消息隊列、信號量的數據結構基本一致,并且都有同一個成員?struct ipc_perm
,所以實際對于?操作系統?來說,對?System V
?中各種方式的描述管理只需要這樣做
- 將 共享內存、消息隊列、信號量對象描述后,統一存入數組中
- 再進行指定對象創建時,只需要根據 ipc_id_arr[n]->__key 進行比對,即可當前對象是否被創建!
- 因為 struct shmid_ds 與 struct ipc_perm shm_perm 的地址一致(其他對象也一樣),所以可以對當前位置的指針進行強轉:((struct shmid_ds)ipc_id_arr[0]) 即可訪問 shmid_ds 中的成員,這不就是多態中的虛表嗎?
這樣一來,操作系統可以只根據一個地址,靈活訪問 兩個結構體中的內容,比如 struct ipc_perm shm_perm 和 struct shmid_ds,并且操作系統還把多種不同的對象,描述融合入了一個 ipc_id_arr 指針數組中,真正做到了 高效管理
注:默認 ipc_id_arr[n] 訪問的是 struct ipc_perm 中的成員
注:上述圖示只是一個草圖,目的是為了輔助理解原理,并非操作系統中真實樣貌
操作系統在進行比較判斷時,如何判斷類型呢?
這就是操作系統設計的巧妙之處了,ipc_id_arr 沒那么簡單,它會存儲對象的相應類型信息
通過下標(id) 訪問對象,這與文件系統中的機制不謀而合,不過實現上略有差異,間接導致 System V 的管理系統被邊緣化(歷史選擇了文件系統)
shmid、msqid 和 semid 都是 ipc_id_arr 的下標,為什么值很大呢?
在進行查找時,會將這些 id % 數組大小 進行轉換,確保不會發生越界,事實上,這個值與開機時間有關,開機越長,值越大,當然到了一定程度后,會重新輪回
總結:
上面就是關于信號量部分的內容,有個大概了解就行。
?