消息隊列亦稱報文隊列,也叫做信箱。意思是說,這種通信機制傳遞的數據具有某種結構,而不是簡單的字節流。消息隊列的工作機制如下所示:
消息的結構
用戶空間的消息緩衝區
爲在進程與內核之間傳遞消息,無論發送進程還是接收進程,都需要在進程空間中用消息緩衝區來暫存消息。該消息緩衝區的結構定義如下:
struct msgbuf {
long mtype; /* 消息的類型 */
char mtext[1]; /* 消息正文 */
};
從這個緩衝區的定義可以看到,消息通信的最大特點在於,發送進程可以在域mtype中定義消息的類型,這樣就爲接收進程提供了一個方便,即接收進程可以根據mtype來判斷隊列中的一個消息是否爲它所等待的消息,從而使接收進程可以有選擇地進行接收。
域mtext[]爲存放消息正文的數組,發送進程可以根據消息的大小定義該數組的長度。
內核空間的消息結構
爲便於內核對消息的維護和管理,以及要將大型消息分頁存放,所以內存中用於消息通信的數據結構要比進程消息緩衝區的結構稍微複雜一些。內核空間消息結構分爲首頁結構和一般頁結構。其首頁結構msg_msg的定義如下:
struct msg_msg {
struct list_head m_list;
long m_type; //消息結構
int m_ts; /* 消息文本大小 */
struct msg_msgseg* next; //下一個消息片段頁
void *security;
//下面是可能存在的消息內容
};
當進程發送消息時,負責發送消息的系統調用會把進程空間的msgbuf中的消息正文複製到內核空間消息首頁結構msg_msg的後面。
- 當消息的大小加上msg_msg結構的大小小於一個頁面時,消息正文緊跟着msg_msg結構的後面存放;
- 當消息的大小加上msg_msg結構的大小大於一個頁面時,則需要將消息分段存放。即消息正文的開頭部分存放在首頁結構msg_msg中,剩餘部分則分別存放在多個一般頁結構msg_msgseg中,然後把首頁和一般頁按邏輯順序用指針next連接成鏈表形成一個消息。
一般頁結構msg_msgseg的定義如下:
struct msg_msgseg {
struct msg_msgseg* next;
/* the next part of the message follows immediately */
};
一個大型消息的結構如下所示:
消息隊列的結構
每個消息隊列都有一個msg_queue結構類型的隊列頭。msg_queue結構的定義如下:
struct msg_queue {
struct kern_ipc_perm q_perm;
time_t q_stime; /* 最後發送消息時間 */
time_t q_rtime; /* 最後接收消息時間 */
time_t q_ctime; /* 最後改變時間 */
unsigned long q_cbytes; /* 當前隊列字節數 */
unsigned long q_qnum; /* 當前隊列消息數 */
unsigned long q_qbytes; /* 隊列的最大字節數 */
pid_t q_lspid; /* 最後發送消息進程PID */
pid_t q_lrpid; /* 最後接受消息進程PID */
struct list_head q_messages; //消息隊列
struct list_head q_receivers; //接收信號進程等待隊列
struct list_head q_senders; //發送消息進程等待隊列
};
消息隊列的結構圖如下所示:
結構msg_queue中還分別記錄了接收和發送進程的等待隊列。每個正在等待接收進程的描述結構如下:
struct msg_receiver {
struct list_head r_list;
struct task_struct *r_tsk; //進程控制塊指針
int r_mode; //讀方式
long r_msgtype; //讀的消息類型
long r_maxsize; //讀的消息最大尺寸
struct msg_msg *volatile r_msg; //消息指針
};
每個正在等待發送進程的描述結構如下:
struct msg_sender
{
struct list_head list;
struct task_struct *tsk; //進程控制塊指針
};
與共享內存的管理方式一樣,Linux把所有消息隊列都組織在一個數組中。在文件linux/ipc/util.h如下:
struct ipc_id_ary
{
int size;
struct kern_ipc_perm *p[0]; //存放段描述結構的數組
};
同時,在文件linux/ipc/msg.c中也定義了一個全局變量msg_ids來管理消息隊列:
static struct ipc_ids msg_ids;
這個變量的數據類型爲ipc_ids結構。在文件linux/ipc/util.h中聲明的ipc_ids結構代碼如下:
struct ipc_ids {
int in_use;
unsigned short seq;
unsigned short seq_max;
struct rw_semaphore rw_mutex;
struct idr ipcs_idr;
struct ipc_id_ary *entries; //指向struct ipc_id_ary的指針
};
消息隊列的整體結構如下所示:
消息隊列的創建與打開
頭文件:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
消息隊列的內核持續性要求每個消息隊列都在系統範圍內對應唯一的鍵值,所以,要獲得一個消息隊列的描述符,只需提供該消息隊列的鍵值即可。
消息隊列描述字是由在系統範圍內唯一的鍵值生成的,而鍵值可以看作對應系統內的一條路徑。
進程可以通過調用函數msgget()來創建一個消息隊列。msgget()對應的系統調用如下:
int msgget(key_t key, int msgflg);
asmlinkage long sys_msgget(key_t key, int msgflg);
其中,參數key是用戶給定的鍵值。如果該值爲0,系統會爲進程創建一個進程自用的消息隊列,即供其自發自收;否則創建或打開一個消息隊列。參數msgflg是該函數的功能標誌。
消息隊列的讀寫
消息讀寫操作非常簡單,對於開發人員來說,每個消息都類似於如下的數據結構,即需要用戶聲明一個數據結構:
struct msgbuf {
long mtype; /* 消息的類型 */
char mtext[1]; /* 消息正文 */
};
其中,mtype成員代表消息類型,從消息隊列中讀取消息的一個重要依據就是消息的類型;mtext是消息內容,當然長度不一定爲1。
對於發送消息來說,首先預置一個msgbuf緩衝區並寫入消息類型和內容,調用相應的發送函數即可;對於讀取消息來說,首先分配這樣一個msgbuf緩衝區,然後把消息讀入該緩衝區即可。
int msgsnd(int msqid, struct msgbuf * msgp, int msgsz, int msgflg);
向消息隊列發送一條消息。其中,msqid爲已打開的消息隊列ID;msgp爲存放消息的結構;msgsz爲消息數據的長度;msgflg爲發送標誌。有意義的msgflg標誌爲IPC_NOWAIT,指明在消息隊列沒有足夠空間容納要發送的消息時,msgsnd是否等待。
int msgrcv(int msqid, struct msgbuf * msgp, int msgsz, long msgtyp, int msgflg);
從msqid代表的消息隊列中讀取一個消息,並把消息存放在msgp指向的msgbuf結構中。在成功地讀取了一條消息後,隊列中的這條消息將被刪除。
操作成功時返回0,失敗返回-1。
例子:父進程向消息隊列發送消息,子進程從消息隊列接收消息。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
int pid, msqid;
struct msgbuf
{
long mtype;
char mtext[20];
}send_buf, receive_buf;
if ((msqid = msgget(IPC_PRIVARE, 0700)) < 0) { //創建消息隊列
printf("msgget建立消息隊列失敗");
exit(1);
}else
printf("msgget建立消息隊列成功");
if ((pid = fork()) < 0) { //創建子進程
printf("fork()函數調用失敗");
exit(2);
}else if (pid > 0) { //父進程,發送消息到消息隊列
send_buf.mtype = 1;
strcpy(send_buf.mtext, "Hello World");
printf("發送到消息隊列的信息內容爲:%s", send_buf.mtext);
if (msgsnd(msqid, &send_buf, 20, IPC_NOWAIT) < 0) { //發送消息到消息隊列
printf("消息發送失敗");
exit(3);
}else
printf("消息發送成功");
sleep(2);
exit(0);
}else { //子進程,從消息隊列中接收消息
sleep(2);
int infolen;
if ((infolen = msgrcv(msqid, &receive_buf, 20, 0, IPC_NOWAIT)) < 0) { //從消息隊列中接收消息
printf("消息讀取錯誤");
exit(4);
}else
printf("消息讀取成功");
if ((msgctl(msqid, IPC_RWID, NULL)) < 0) { //刪除消息隊列中的消息
printf("消息刪除錯誤");
exit(5);
}
else
printf("消息刪除成功");
exit(0);
}
return 0;
}