一 簡介
消息隊列是鏈表隊列,它通過內核提供一個struct msqid_ds *msgque[MSGMNI]
向量維護內核的一個消息隊列列表,因此linux系統支持的最大消息隊列數由msgque
數組大小來決定,每一個msqid_ds
表示一個消息隊列,並通過msqid_ds.msg_first
、msqid_ds.msg_last
維護一個先進先出的msg
鏈表隊列。
當發送一個消息到該消息隊列時,把發送的消息構造成一個msg
結構對象,並添加到msqid_ds.msg_first
、msqid_ds.msg_last
維護的鏈表隊列,同樣,接收消息的時候也是從msg
鏈表隊列尾部查找到一個msg_type
匹配的msg
節點,從鏈表隊列中刪除該msg
節點,並修改msqid_ds
結構對象的數據。Linux的消息隊列(queue),它有消息隊列標識符(queue ID)。msgget
創建一個新隊列或打開一個存在的隊列;msgsnd
向隊列末端添加一條新消息;msgrcv
從隊列中取消息,取消息是不一定遵循先進先出的,也可以按消息的類型字段取消息。
二 消息隊列的數據結構
msg.c
中包含的函數模塊。
有關進程間通信資源的屬性:
鍵(key):一個由用戶提供的整數,用來標誌某個消息。
創建者(creator):創建這個消息的進程的用戶ID(UID)和組ID(GID)。
所有者(owner):消息所有者的UID和GID。資源創建時,資源的創建者就是資源的所有者。資源的創建者進程、當前的所有者進程和超級用戶具有改變資源所有者的權力。
1 struct ipc_perm
struct ipc_perm{
key_t key; //整型,0表示private,非0表示public
ushort uid; //資源擁有者的有效標識
ushort gid; //資源擁有者所在組的有效標識
ushort cuid; //資源創建者的有效標識
ushort cgid; //資源創建者所在組的有效標識
ushort mode; //訪問模式
ushort seq; //序列號,計算標識符
};
系統在創建消息隊列的同時設定了訪問權限,並返回一個標識。進程通信時必須先傳遞該標識,待函數ipcperms()
確認權限後纔可以訪問通信資源。訪問權限由ipc_perm
結構描述。通過key可以得到引用標識,從而訪問通信資源。Key爲public,則任何進程都可以通過key得到引用標識。
2 msg結構用來存放消息的有關信息
struct msg{
struct msg *msg_next; //消息隊列中的下一個
long msg_type; //消息的類型
char *msg_spot; //存放消息內容的地址
time_t msg_time; //消息發送的時間
short msg_ts; //消息的長度
message; /*消息體*/
};
3 存放消息的信息
struct msgbuf{
long mtype; //消息的類型
char mtext[1]; //消息的內容
};
4 每一個msqid_ds結構代表一個消息隊列, 是進程讀寫的信息的存儲空間
static struct msqid_ds *msgque[MSGMNI];
定義了一個消息隊列數組msgque, 數組的元素類指向msqid_ds 結構的指針。消息在隊列中是按到來的順序維護。進程讀消息時,這些消息按FIFO從隊列中移去。
struct msqid_ds{
struct ipc_perm msg_perm; //權限
struct msg *msg_first; //指向消息隊列的第一條消息
struct msg *msg_last; //指向消息隊列的最後一條消息
time_t msg_stime; // 最後發送時間
time_t msg_rtime; //最後接收時間
time_t msg_ctime; //最後修改時間
struct wait_queue *wwait; //寫消息進程的等待隊列
struct wait_queue *rwait; //讀消息進程的等待隊列
ushort msg_cbytes; //隊列中消息的字節數
ushort msg_qnum; //隊列中的消息數
ushort msg_qbytes; //隊列中消息的最大字節數
ushort msg_lspid; // 最後一個發送消息的進程的標識號
ushort msg_lrpid; //最後一個接收消息的進程的標識號
};
三 消息隊列的使用
1 消息隊列Key的獲取:
在程序中若要使用消息隊列,必須要能知道消息隊列key,因爲應用進程無法直接訪問內核消息隊列中的數據結構,因此需要一個消息隊列的標識,讓應用進程知道當前操作的是哪個消息隊列,同時也要保證每個消息隊列key值的唯一性。
- 通過ftok函數獲取
key_t key;
key=ftok(".","a")
該函數通過一個路徑名稱映射出一個消息隊列key(我的理解是使用路徑映射的方式比較容易獲取一個唯一的消息隊列key)
- 直接定義key:
#define MSG_KEY 123456
自定義key的方式要注意避免消息隊列的重複。
2 獲取或者打開一個消息隊列
- 使用說明
qid=msgget(key_t key, int msgflag)
–key: 消息隊列key
–msgflag:
IPC_PRIVATE:創建一個該進程獨佔的消息隊列,其它進程不能訪問該消息隊列;
IPC_CREAT:若消息隊列不存在,創建一個新的消息隊列,若消息隊列存在,返回存在的消息隊列;
IPC_CREAT | IPC_EXCL: IPC_EXCL標誌本身沒有多大意義,與IPC_CREAT一起使用,保證只創建新的消息隊列,若對應key的消息隊列已經存在,則返回錯誤;
IPC_NOWAIT:小隊列以非阻塞的方式獲取(若不能獲取,立即返回錯誤)。 - 函數原因
1)如果key==IPC_PRIVATE,則申請一塊內存,創建一個新的消息隊列(數據結構msqid_ds),將其初始化後加入到msgque向量表中的某個空位置處,返回標示符。
2)在msgque向量表中找鍵值爲key的消息隊列,如果沒有找到,結果有二:
msgflag表示不創建新的隊列,則錯誤返回。
msgflag表示要創建新的隊列(IPC_CREAT),則創建新消息隊列,創建過程如1)。
3)如果在msgque向量表中找到了鍵值爲key的消息隊列,則有以下情況:
如果msgflg表示一定要創建新的消息隊列而且不允許有相同鍵值的隊列存在,則錯誤返回。
如果找到的隊列是不能用的或已經損壞的隊列,則錯誤返回。
認證和存取權限檢查,如果該隊列不允許msgflg要求的存取,則錯誤返回。
正常,返回隊列的標識符。
3 發送一個消息到消息對列
- 使用說明:
int msgsnd (int msqid, struct msgbuf *msgp, size_t msgsz, int msgflg)
------msqid爲消息隊列的qid
------msgp對應消息內容結構體指針
------msgsz消息的大小即msgp指針指向的消息結構體的大小
------msgflg消息標誌
0:忽略該標誌位,以阻塞的方式發送消息到消息隊列
IPC_NOWAIT:以非阻塞的方式發送消息,若消息隊列滿,函數立即返回。
------返回:
0: 成功
-1:非阻塞方式訪問滿消息隊列返回
EACCES:沒有該消息隊列寫權限
EFAULT:消息隊列地址無法獲取
EIDRM:消息隊列已經被刪除
EINTR:消息隊列等待寫入的時候被中斷
ENOMEM:內存不夠 - 函數原理:
1)計算id = (unsigned int) msqid % MSGMNI,然後根據id在linux系統消息隊列向量msgque[MSGMNI]中查找對應的消息隊列,並進行認證檢查,合法性檢查
2)如果隊列已滿,以可中斷等待狀態(TASK_INTERRUPTIBLE)將當前進程掛起在wwait等待隊列(發送消息等待隊列)上(msgflag==0)。
3)否則 根據msgbuf的大小申請一塊空間,並在其上創建一個消息數據結構struct msg(內核空間),將消息緩衝區中的消息內容拷貝到該內存塊中消息頭的後面(從用戶空間拷貝到內核空間)。
4)將消息數據結構加入到消息隊列的隊尾,修改隊列msqid_ds的相應參數。
5)喚醒在該消息隊列的rwait進程隊列(讀等待進程隊列)上等待讀的進程,並返回。
4 從消息隊列接收一個消息到msgbuf*
- 使用說明
int msgrcv (int msqid, struct msgbuf *msgp, size_t msgsz,long msgtyp, int msgflg)
----msqid爲消息隊列的qid
----msgp是接收到的消息將要存放的緩衝區
----msgsz是消息的大小
----msgtyp是期望接收的消息類型
----msgflg是標誌
0:表示忽略
IPC_NOWAIT:如果消息隊列爲空,不阻塞等待,返回一個ENOMSG
----返回
0:成功
-1:消息長度大於msgsz
EACCES:沒有該消息隊列讀權限
EFAULT:消息隊列地址無法獲取
EIDRM:消息隊列已經被刪除
EINTR:消息隊列等待寫入的時候被中斷
ENOMEM:內存不夠 - 函數原理:
1)計算id = (unsigned int) msqid % MSGMNI,然後根據id在msgque[MSGMNI]中查找對應的消息隊列,並進行認證檢查,合法性檢查
2)根據msgtyp搜索消息隊列,情況有二:
----如果找不到所要的消息,則以可中斷等待狀態(TASK_INTERRUPTIBLE)將當前進程掛起在rwait等待隊列上
----如果找到所要的消息,則將消息從隊列中摘下,調整隊列msqid_ds參數,喚醒該消息隊列的wwait進程隊列上等待寫的進程,將消息內容拷貝到用戶空間的消息緩衝區msgp中,釋放內核中該消息所佔用的空間,返回
5 消息的控制
- 使用說明:
int msgctl (int msqid, int cmd, struct msqid_ds *buf)
------msqid:爲消息隊列的qid
------cmd:爲該函數要對消息隊列執行的操作
IPC_STAT:取出消息隊列的msqid_ds結構體並將參數存入buf所指向的msqid_ds結構對象中
IPC_SET:設定消息隊列的msqid_ds 數據中的msg_perm 成員。設定的值由buf 指向的msqid_ds
結構給出。
IPC_EMID:將隊列從系統內核中刪除。
----buf:消息隊列msqid_ds結構體指針 - 函數作用
對消息隊列進行設置以及相關操作,具體操作由cmd指定。
問題: - 在消息傳遞機制中,當讀取一個消息後,消息將從隊列移去,其他進程不能讀到。若因爲接收的緩衝區太小造成消息被截斷,截斷的部分將永遠丟失。
- 進程必須通過帶有IPC_RMID的sys _msgctl 調用,來顯示的刪除消息隊列。如果不是這樣則消息隊列可以長久的存在。則樣就回導致系統很難判斷,消息是爲了將來進程訪問而留下來還是被無意的拋棄了,或是由於想要釋放它的進程不正常終止了,這樣導致系統無限的保存這消息。如果這種情況經常發生,這種消息資源就會用光。
- 總的說來,消息隊列傳遞數據時是以一種不連續消息的方式,這樣就可以更加靈活的處理數據。
四 實例
read.c
#include <unistd.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define IPC_KEY 1024
#define MAXLINE 80
void if_error(int stat_code, char* err_msg)
{
if (stat_code < 0) {
perror(err_msg);
exit(errno);
}
}
struct msg_ds
{
char data[MAXLINE];
};
int main(int argc, char **argv)
{
int msq_id, rd_n;
struct msg_ds msg;
msq_id = msgget(IPC_KEY, IPC_CREAT);
if_error(msq_id, "msgget");
while (1) {
/* recv msg */
rd_n = msgrcv(msq_id, (void*)&msg, MAXLINE, 0, 0);
if_error(rd_n, "msgrcv");
msg.data[rd_n] = '\0';
/* exit msg */
if (strncmp(msg.data, "exit", 4) == 0) break;
printf("recv msg: %s\n", msg.data);
}
msgctl(msq_id, IPC_RMID, 0);
return 0;
}
write.c
#include <unistd.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define IPC_KEY 1024
#define MAXLINE 80
void if_error(int stat_code, char* err_msg)
{
if (stat_code < 0) {
perror(err_msg);
exit(errno);
}
}
struct msg_ds
{
char data[MAXLINE];
};
int main(int argc, char **argv)
{
int msq_id, wr_n;
struct msg_ds msg;
msq_id = msgget(IPC_KEY, IPC_CREAT);
if_error(msq_id, "msgget");
while (1) {
/* send msg */
printf("send msg: ");
fgets(msg.data, MAXLINE, stdin);
msg.data[strlen(msg.data)-1] = '\0';
wr_n = msgsnd(msq_id, &msg, MAXLINE, 0);
if_error(wr_n, "msgsnd");
/* exit msg */
if (strncmp(msg.data, "exit", 4) == 0) break;
}
msgctl(msq_id, IPC_RMID, 0);
return 0;
}