天天看點

Linux Posic消息隊列和System V消息隊列的差別

作者:嵌入式小美老師

概述

POSIX 消息隊列 API 的主要函數如下:

  • mq_open() 函數建立一個新消息隊列或打開一個既有隊列,傳回後續調用中會用到的消息隊列描述符
  • mq_send() 函數向隊列寫入一條消息
  • mq_receive() 函數從隊列中讀取一條消息
  • mq_close() 函數關閉程序之前打開的一個消息隊列
  • mq_unlink() 函數删除一個消息隊列并當所有程序關閉該隊列時對隊列程序标記以便删除

此外,POSIX 消息隊列 API 還具備一些特别的特性:

  • 每個消息隊列都有一組關聯的特性,其中一些特性可以在使用 mq_open() 建立或者打開時進行設定。擷取和修改隊列特性的工作是由 mq_getattr() 和mq_setaddr() 來完成的
  • mq_notify() 函數允許一個程序向一個隊列注冊時接收消息通知。在注冊完之後,當一條消息可用時會通過發送一個信号或者在一個單獨的線程中調用一個函數來通知程序

打開、關閉和斷開連結消息隊列

打開一個消息隊列

#include <fcntl.h>           /* For O_* constants */
#include <sys/stat.h>        /* For mode constants */
#include <mqueue.h>

mqd_t mq_open(const char *name, int oflag);
mqd_t mq_open(const char *name, int oflag, mode_t mode,struct mq_attr *attr);           
  • mq_open() 函數建立一個新消息隊列或打開一個既有隊列
  • name 參數辨別出了消息隊列
  • oflag 參數是一個位掩碼,它控制着 mq_open() 操作的各個方面,取值如下:
Linux Posic消息隊列和System V消息隊列的差別

嵌入式物聯網需要學的東西真的非常多,千萬不要學錯了路線和内容,導緻工資要不上去!

無償分享大家一個資料包,差不多150多G。裡面學習内容、面經、項目都比較新也比較全!某魚上買估計至少要好幾十。

點選這裡找小助理0元領取:加微信領取資料

Linux Posic消息隊列和System V消息隊列的差別
  • oflag 參數的其中一個用途是,确定是打開一個既有隊列還是建立和打開一個新隊列,oflag 不包含 O_CREAT 時,那麼将會打開一個既有隊列,如果 oflag 中包含了 O_CREAT 并且給定的 name 不存在時将會建立一個新的空隊列,如果oflag 同時包含了 O_CREAT 和 O_EXCL,并且給定的 name 對應的隊列已經存在,那麼 mq_open() 就會失敗
  • mode 參數是一個位掩碼,它指定了施加于新消息隊列之上的權限
  • attr 參數是一個 mq_attr 結構,它指定了新消息隊列的特性。如果 attr 為 NULL,那麼将使用實作定義的預設特性建立隊列

fork()、exec()以及程序終止對消息隊列描述符的影響

在 fork() 中子程序會接受其父程序在消息隊列描述符的副本,并且這些描述符會引用同樣的打開的消息隊列描述。子程序不會繼承父程序的任何消息通知注冊。

當一個程序執行了一個 exec() 或終止時,所有其打開的消息隊列描述符會被關閉。關閉消息隊列描述符的結果是程序在相應隊列上的消息通知注冊會被登出。

關閉一個消息隊列

#include <mqueue.h>

int mq_close(mqd_t mqdes);           
  • mq_close() 函數關閉了消息隊列描述符 mqdes

如果調用程序已經通過 mqdes 在隊列上注冊了消息通知,那麼通知注冊會自動被删除,并且另一個程序可以随後向該隊列注冊消息通知。

當程序終止或者調用 exec() 時,消息隊列描述符會被自動關閉。問問價描述符一樣,應用程式應該在不再使用消息隊列描述符的時候顯示的關閉消息隊列描述符以防止程序耗盡消息隊列描述符的情況。

與檔案上的 close() 一樣,關閉一個消息隊列并不會删除該隊列。要删除隊列必須顯示的調用 mq_unlink(),它是 unlink() 在消息隊列上的版本。

删除一個消息隊列

mq_unlink() 函數删除通過 name 辨別的消息隊列,并将隊列标記為在所有程序使用完該隊列之後銷毀該隊列。

#include <mqueue.h>

int mq_unlink(const char *name);           

描述符和消息隊列之間的關系

消息隊列是一個程序級别的句柄,它引用了系統層面的打開的消息隊列描述符中的一個條目,而該條目引用了一個消息隊列對象,如下圖:

Linux Posic消息隊列和System V消息隊列的差別
  • 一個打開的消息隊列描述擁有一組關聯的标記。SUSv3 隻規定了一種這樣的标記,即 NONBLOCK,它确定了 IO 是否是非阻塞的
  • 兩個程序能夠持有引用同一個打開的消息隊列描述的消息隊列描述符。當一個程序在打開了一個消息隊列之後調用 fork() 時就會發生這樣的情況。這些描述符會共享 O_NONBLOCK 标記的狀态
  • 兩個程序能夠持有引用不同消息隊列描述(它們引用了同一個消息隊列)的打開的消息隊列描述(如程序 A 中的描述符 z 和程序 B 中的描述符 y 都引用了/mq-r)。當兩個程序分别使用 mq_open() 打開同一個隊列時就會發生這種情況

消息隊列特性

mq_open()、mq_getattr() 以及 mq_setattr() 函數都會接收一個參數,它是一個指向 mq_attr 結構的指針。這個結構是在 <mqueue.h> 中進行定義的,其形式如下:

struct mq_attr
{
    long mq_flags;/* Message queue flags.  */
    long mq_maxmsg;/* Maximum number of messages.  */
    long mq_msgsize;/* Maximum message size.  */
    long mq_curmsgs;/* Number of messages currently queued.  */
};           

在建立隊列時設定消息隊列特性

在使用 mq_open() 建立消息隊列時可以通過下列 mq_attr 字段來确定隊列的特性:

  • mq_maxmsg 字段定義了使用 mq_send() 向消息隊列添加消息的數量上限,其取值必須大于0
  • mq_msgsize 字段定義了加入消息隊列的每條消息的大小的上限,其取值必須大于 0

核心根據這兩個值來确定消息隊列所需的最大記憶體量。

mq_maxmsg 和 mq_msgsize 特性是在消息隊列被建立時就确定下來的,并且之後也無法修改這兩個特性。

擷取消息隊列特性

#include <mqueue.h>

int mq_getattr(mqd_t mqdes, struct mq_attr *attr);           
  • mq_getattr() 函數傳回一個包含與描述符 mqdes 相關聯的消息隊列描述和消息隊列的相關資訊的 mq_attr 結構

除了上面已經介紹的 mq_maxmsg 和 mq_msgsize 字段之外,attr 指向的傳回結構中還包含下列字段:

  • mq_flags :其取值隻有一個 O_NONBLOCK。這個标記是根據 mq_open() 的 oflag 參數來初始化的,并且使用 mq_setattr() 可以修改這個标記
  • mq_curmsgs:這個目前位于隊列中的消息數。這個資訊在 mq_getattr() 傳回時可能已經發生了改變,前提是存在其他程序從隊列中讀取消息或向隊列寫入消息

修改消息隊列特性

#include <mqueue.h>

int mq_setattr(mqd_t mqdes, const struct mq_attr *newattr,struct mq_attr *oldattr);           
  • mq_setattr() 函數設定與消息隊列描述符 mqdes 相關聯的消息隊列描述的特性,并可選地傳回與消息隊列有關的資訊
  • mq_setattr() 函數執行下列任務:
    • 它使用 newattr 指向的 mq_attr 結構中的 mq_flags 字段來修改與描述符 mqdes 相關聯的消息隊列描述的标記
    • 如果 oldattr 不為 NULL,那麼就傳回一個包含之前的消息隊列描述标記和消息隊列特性的 mq_attr 結構(即與 mq_getattr()執行的任務一樣)

SUSv3 規定使用 mq_setattr() 能夠修改的唯一特性是 O_NONBLOCK 标記的狀态。如下代碼可以啟用 O_NONBLOCK:

if(mq_getattr(mqd, &attr) == -1){
errExit("mq_getattr");
}
attr.mq_flags != O_NONBLOCK;
if(mq_setattr(mqd, &attr, NULL) == -1){
errExit("mq_setattr()");
}           

交換消息

發送消息

#include <mqueue.h>

int mq_send(mqd_t mqdes, const char *msg_ptr,size_t msg_len, unsigned int msg_prio);           
  • mq_send() 函數将位于 msg_ptr 指向的緩沖區中的消息添加到描述符 mqdes 所引用的消息隊列中
  • msg_len 參數指定了 msg_ptr 指向的消息的長度,其值必須小于或者等于隊列的 mq_msgsize 特性,否則 mq_send() 就會傳回EMSGSIZE 錯誤。長度為零的消息是允許的
  • 每個隊列都擁有一個用非負整數表示的優先級,它通過 msq_prio 指定。0表示優先級最低,數值越大優先級越高。當一個消息被添加到隊列中時,他會被放置在隊列中具有相同優先級的所有消息之後。如果一個應用程式無需使用消息優先級,那麼隻需要将msg_prio 指定為 0 即可
  • SUSv3 允許一個實作為消息優先級規定一個上限,這可以通過定義常量 MQ_PRIO_MAX 或通過規定 sysconf(_SC_MQ_PRIO_MAX) 的傳回值來完成
  • 如果消息隊列已經滿了(即已經達到了隊列的 mq_maxmsg 限制),那麼後續的 mq_send() 調用會阻塞直到隊列中存在可用空間為止或者在 O_NONBLOCK 标記起作用時立即失敗并傳回 EAGAIN 錯誤

接收消息

#include <mqueue.h>

ssize_t mq_receive(mqd_t mqdes, char *msg_ptr,size_t msg_len, unsigned int *msg_prio);           
  • mq_receive() 從 mqdes 引用的消息隊列中删除一條優先級最高、存在時間最長的消息并将删除的消息放置在 msq_ptr 指向對的緩沖區
  • 調用者通過 msq_len 指定 msg_ptr 指向的緩沖區中可用位元組數
  • 不管消息的實際大小是什麼,msg_len(即 msg_ptr 指向的緩沖區的大小)必須要大于或等于隊列的 mq_msgsize 特性,否則 mq_receive() 就會失敗并傳回 EMSGSIZE 錯誤
  • 如果 msg_prio 不為 NULL,那麼接收到的消息的優先級會被複制到 msg_prio 指向的位置處
  • 如果消息隊列目前為空,那麼 mq_receive() 會阻塞直到存在可用的消息或在 O_NONBLOCK 标記起作用時會立即失敗并傳回 EAGAIN 錯誤

在發送和接收消息時設定逾時時間

#include <time.h>
#include <mqueue.h>

int mq_timedsend(mqd_t mqdes, const char *msg_ptr,size_t msg_len, unsigned int msg_prio,const struct timespec *abs_timeout);

ssize_t mq_timedreceive(mqd_t mqdes, char *msg_ptr,size_t msg_len, unsigned int *msg_prio,const struct timespec *abs_timeout);           
  • mq_timedsend() 和 mq_timedreceive() 函數與 mq_send() 和 mq_receive() 幾乎是完全一樣的,它們之間唯一的差别在于如果操作無法立即被執行,并且該消息隊列描述上的 O_NONBLOCK 标記不起作用,那麼 abs_timeout 參數就會為調用阻塞的時間指定一個上限
  • 如果 mq_timedsend() 或 mq_timedreceive() 調用因逾時而無法完成操作,那麼調用就會失敗并傳回 ETIMEDOUT 錯誤
  • 在 Linux 上将 abs_timeout 指定為 NULL 表示永遠不會逾時,但這種行為并沒有在 SUSv3中得到規定,是以可移植的應用程式不應該依賴這種行為

消息通知

POSIX 消息隊列差別于 System V 消息隊列的一個特性是 POSIX 消息隊列能夠接收之前為空的隊列上有可用消息的異步通知(即隊列從空變成了非空)。這個特性意味着已經無需指向一個阻塞的調用或者将消息隊列描述符标記為非阻塞并在隊列上定期指向 mq_receive() 消息了,程序可以選擇通過信号的形式或者通過在一個單獨的線程中調用一個函數的形式來接受通知。

mq_notify() 函數注冊調用程序在一條消息進入描述符 mqdes 引用的空隊列時接收通知:

#include <mqueue.h>

int mq_notify(mqd_t mqdes, const struct sigevent *sevp);           
  • 在任意時刻都隻有一個程序(“注冊程序”)能夠向一個特定的消息隊列注冊接收通知。如果一個消息隊列上已經存在注冊線程了,那麼後續在該隊列上的注冊請求将會失敗(mq_notify() 傳回 EBUSY 錯誤)
  • 隻有當一條新消息進入空隊列時才會發送通知。如果隊列不空就不會發送通知消息
  • 當向注冊程序發送一個通知之後就會删除注冊消息,之後任何程序就能夠向隊列注冊接收通知了。換句話說,隻要一個程序想要持續的接收通知,那麼它就必須要在每次接收通知之後再次調用 mq_notify() 注冊自己
  • 注冊程序隻有在目前不存在其他在該隊列上調用 mq_receive() 而發生阻塞的程序時才會收到通知。如果其他程序在 mq_receive()調用中被阻塞了,那麼該程序會讀取消息,注冊程序會保持注冊狀态
  • 一個程序可以通過在調用 mq_notify() 時傳入一個值為 NULL 的 notification 參數來撤銷自己在消息通知上的注冊資訊

sigevent 中與 mq_notify() 相關的字段:

union sigval{
int sival_int;
void *sival_ptr;
};

struct sigevent{
int sigev_notify;
int sigev_signo;
union sigval sifev_value;
void (*sigev_notify_function)(union sigval);
void *sigev_notify_attributes;
};           
  • sigev_notify 字段将會被設定成下列值中的一個
    • SIGEV_NONE:注冊這個程序接收通知,但當一條消息進入空隊列時不通知該程序。與往常一樣,當新消息進入空隊列之後注冊資訊會被删除
    • SIGEV_SIGNAL:通過生成一個在 sigev_signo 字段中指定的信号來通知程序。如果 sigev_signo 是一個實時信号,那麼sigev_value 字段将會指定信号都帶的資料。通過傳入信号處理器的 siginfo_t 結構中的 si_value 字段或通過調用 sigwaitinfo() 或 sigtimedwait() 傳回值能夠取得這部分資料。siginfo_t 結構中的下列字段也會被填充:si_code,其值為 SI_MESGQ;si_signo,其值是信号編号;si_pid,其值是發送消息的程序的程序 ID;以及 si_uid,其值是發送消息的程序的真實使用者 ID。(si_pid 和 si_uid 字段在其他大多數實作上不會被設定
    • SIGEV_THREAD:通過調用在 sigev_notify_function 中指定的函數來通知程序,就像是在一個新線程中啟動該函數一樣。sigev_notify_attributes 字段可以為 NULL 或是一個指向定義了線程的特性的 pthread_ attr_t 結構的指針。sigev_value 中指定的聯合 sigval 值将會作為參數傳入這個函數

Linux 特有的特性

POSIX 消息隊列在 Linux 上的實作提供了一些非标準的卻相當有用的特性。

通過指令行顯示和删除消息隊列對象

POSIX IPC 對象被實作成了虛拟檔案系統中的檔案,并且可以使用 ls 和 rm 來列出和删除這些檔案。為列出和删除 POSIX 消息隊列就必須要使用乤指令來将消息隊列挂載到檔案系統中:

mount  -t mqueue source target           

source 可以是任意一個名字(通常将其指定為字元串 none),其唯一的意義是它将出現在 /proc/mounts 中并且 mount 和 df 指令會顯示出這個名字。target 是消息隊列檔案系統的挂載點。

下面的 shell 會話顯示了如何挂載消息隊列檔案系統和顯示其内容。首先為檔案系統建立一個挂載點并挂載它:

$ su

$ mkdir /dev/mqueue
$ mount -t mqueue node /dev/mqueue
$ exit
$ cat /proc/mounts | grep mqueue
node /dev/mqueue mqueue rw,relatime 0 0
$ ls -ld /dev/mqueue
drwxr-xr-x 2 root root 0 Aug  9 21:28 /dev/mqueue           

擷取消息隊列的相關資訊

cat /dev/mqueue/mq
QSIZE:7NOTIFY:0 SIGNO:0NOTIFY_PID:0           

QSIZE 字段的值為隊列中所有資料的總位元組數,剩下的字段則與消息通知相關,如果 NOTIFY_PID 為非零,那麼程序 ID 為該值得程序已經向該隊列注冊接收消息通知剩下的字段則這種通知相關的資訊。

  • NOTIFY 是一個與其中一個 sigev_notify 常量對應的值:
    • 0 表示 SIGEV_SIGNAL
    • 1 表示 SIGEV_NONE
    • 2 表示 SIGEV_THREAD
  • 如果通知方式是 SIGEV_SIGNAL,那麼 SIGNO 字段指出了哪個信号會用來分發消息通知

使用另一種 I/O 模型操作消息隊列

在 Linux 中,消息隊列描述符實際上是一個檔案描述符,是以可以使用 IO 多路複用系統調用(select() 和 poll())或 epoll() API 來監控這個檔案描述符。

消息隊列限制

SUSv3 為 POSIX 消息隊列定義了兩個限制:

  • MQ_PRIO_MAX:定義了一條消息的最大優先級
  • MQ_OPEN_MAX:一個實作可以定義這個限制來指明一個程序最多能打開的消息隊列數量。SUSv3 要求這個限制最小為_POSIX_MQ_OPEN_MAX(8)。Linux 并沒有定義這個限制,相反,由于 Linux 将消息隊列描述符實作成了檔案描述符,是以适用于檔案描述符的限制将适用于消息隊列描述符。(換句話說,在 Linux 上,每個程序以及系統所能打開的檔案描述符的數量限制實際上會應用于檔案描述符數量和消息隊列描述符數量之和。)

下面這三個檔案位于 /proc/sys/fs/mqueue 目錄中:

  • msg_max:這個限制為新消息隊列的 mq_maxmsg 特性的取值規定了一個上限,預設值是 10,最小值是1,最大值由核心常量 HARD_MSGMAX 定義
  • msgsize_max:這個限制為非特權程序建立的新消息隊列的 mq_msgsize 特性的取值規定了一個上限(即使用 mq_open() 建立隊列時 attr.mq_msgsize 字段的上限值 ),預設值是 8192最小值是 128,當一個非特權程序 CAP_SYS_RESOURCE 調用 mq_open() 時會忽略這個限制
  • queues_max:這是一個系統級别的限制,它規定了系統上最多能夠建立的消息隊列的數量,一旦達到這個限制,就隻有特權程序 CAP_SYS_RESOURCE 才能夠建立新隊列,預設值是 256,取值範圍是 [0,INT_MAX]

POSIX 和 System V 消息隊列比較

POSIX 消息隊列與 System V 消息隊列的相似之處在于資料的交換機關是整個消息,但它們之間存在一些顯著的差異:

  • POSIX 消息隊列是引用計數的。隻有當所有目前使用隊列的程序都關閉了之後才會對隊列程序标記以便删除
  • 每個 System V 消息都有一個整數類型,并且通過 msgrcv() 可以以各種方式選擇消息。而 POSIX 消息由一個管理的優先級,并且消息之間是嚴格按照優先級順序排隊(以及接收)的
  • POSIX 消息隊列提供了一個特性允許在隊列中的一條消息可用時異步的通知程序
  • POSIX 消息通知特性運作一個程序能夠在一條消息進入空隊列時異步通知信号或者線程的執行個體化來接受通知
  • 在Linux上可以使用 poll、select、epoll 來監聽 POSIX 消息隊列。System V 消息沒有這個特性

但與 System V 消息隊列相比,POSIX 消息隊列也具備以下劣勢:

  • POSIX 消息隊列的可移植性稍差
  • 與 POSIX 消息隊列嚴格按照優先級排序相比,System V 消息隊列能夠根據類型來選擇消息的功能的靈活性更強。

POSIX 消息隊列支援是一個通過 CONFIG_POSIX_MQUEUE 選項配置的可選核心元件。

往期精彩

linux驅動開發中與裝置樹相關的6種debug方法

嵌入式Linux QT開發之如何實作擷取磁盤空間大小的應用邏輯

嵌入式Linux MIPI接口LCD調試-關于DRM顯示與應用調試的幹貨濃縮

基于瑞芯微RV1109 Linux觸摸屏GT911驅動調試心得(二)-裝置樹刷廠商給的觸摸屏固件

轉載自:嵌入式應用研究院

文章來源于Linux Posic消息隊列和System V消息隊列的差別

原文連結:https://mp.weixin.qq.com/s/j3IgF68NAZB3vhrSbmHuzQ