消息隊列(十四)

一 簡介

  消息隊列是鏈表隊列,它通過內核提供一個struct msqid_ds *msgque[MSGMNI]向量維護內核的一個消息隊列列表,因此linux系統支持的最大消息隊列數由msgque數組大小來決定,每一個msqid_ds表示一個消息隊列,並通過msqid_ds.msg_firstmsqid_ds.msg_last維護一個先進先出的msg鏈表隊列。
  當發送一個消息到該消息隊列時,把發送的消息構造成一個msg結構對象,並添加到msqid_ds.msg_firstmsqid_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值的唯一性。

  1. 通過ftok函數獲取
key_t key;
key=ftok(".","a")

該函數通過一個路徑名稱映射出一個消息隊列key(我的理解是使用路徑映射的方式比較容易獲取一個唯一的消息隊列key)

  1. 直接定義key:
#define MSG_KEY      123456

自定義key的方式要注意避免消息隊列的重複。

2 獲取或者打開一個消息隊列

  1. 使用說明
    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:小隊列以非阻塞的方式獲取(若不能獲取,立即返回錯誤)。
  2. 函數原因
    1)如果key==IPC_PRIVATE,則申請一塊內存,創建一個新的消息隊列(數據結構msqid_ds),將其初始化後加入到msgque向量表中的某個空位置處,返回標示符。
    2)在msgque向量表中找鍵值爲key的消息隊列,如果沒有找到,結果有二:
    msgflag表示不創建新的隊列,則錯誤返回。
    msgflag表示要創建新的隊列(IPC_CREAT),則創建新消息隊列,創建過程如1)。
    3)如果在msgque向量表中找到了鍵值爲key的消息隊列,則有以下情況:
    如果msgflg表示一定要創建新的消息隊列而且不允許有相同鍵值的隊列存在,則錯誤返回。
    如果找到的隊列是不能用的或已經損壞的隊列,則錯誤返回。
    認證和存取權限檢查,如果該隊列不允許msgflg要求的存取,則錯誤返回。
    正常,返回隊列的標識符。

3 發送一個消息到消息對列

  1. 使用說明:
    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:內存不夠
  2. 函數原理:
    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*

  1. 使用說明
    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:內存不夠
  2. 函數原理:
    1)計算id = (unsigned int) msqid % MSGMNI,然後根據id在msgque[MSGMNI]中查找對應的消息隊列,並進行認證檢查,合法性檢查
    2)根據msgtyp搜索消息隊列,情況有二:
    ----如果找不到所要的消息,則以可中斷等待狀態(TASK_INTERRUPTIBLE)將當前進程掛起在rwait等待隊列上
    ----如果找到所要的消息,則將消息從隊列中摘下,調整隊列msqid_ds參數,喚醒該消息隊列的wwait進程隊列上等待寫的進程,將消息內容拷貝到用戶空間的消息緩衝區msgp中,釋放內核中該消息所佔用的空間,返回

5 消息的控制

  1. 使用說明:
    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結構體指針
  2. 函數作用
    對消息隊列進行設置以及相關操作,具體操作由cmd指定。
    問題:
  3. 在消息傳遞機制中,當讀取一個消息後,消息將從隊列移去,其他進程不能讀到。若因爲接收的緩衝區太小造成消息被截斷,截斷的部分將永遠丟失。
  4. 進程必須通過帶有IPC_RMID的sys _msgctl 調用,來顯示的刪除消息隊列。如果不是這樣則消息隊列可以長久的存在。則樣就回導致系統很難判斷,消息是爲了將來進程訪問而留下來還是被無意的拋棄了,或是由於想要釋放它的進程不正常終止了,這樣導致系統無限的保存這消息。如果這種情況經常發生,這種消息資源就會用光。
  5. 總的說來,消息隊列傳遞數據時是以一種不連續消息的方式,這樣就可以更加靈活的處理數據。

四 實例

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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章