QNX® Neutrino 進程間通信編程之Pipes/FIFOs/Message Queues

介紹

Interprocess Communication(IPC,進程間通信)在QNX Neutrino從一個嵌入式實時系統向一個全面的POSIX系統轉變起着至關重要的作用。IPC是將在內核中提供各種服務的進程內聚在一起的粘合劑。在QNX中,消息傳遞是IPC的主要形式,也提供了其他的形式,除非有特殊的說明,否則這些形式也都是基於本地消息傳遞而實現的。

將更高級別的 IPC 服務(如通過我們的消息傳遞實現的管道和 FIFO)與其宏內核對應物進行比較的基準測試表明性能相當。

QNX Neutrino提供以下形式的IPC:

Service: Implemented in:
Message-passing Kernel
Pules Kernel
Signals Kernel
Event Delivery External process
POSIX message queues External process
Shared memory Process manager
Pipes External process
FIFOs External process

我們基本可以認爲Pipes和Message Queues都是建立在本地消息傳遞,通過緩衝數據並處理任何其他複雜性的服務。所以我們把POSIX IPC Pipes 與 Message Queues歸爲一個篇幅介紹。

匿名管道Pipes/命名管道FIFOs

把一個進程連接到另一個進程的一個數據流稱爲一個“管道”,通常是用作把一個進程的輸出通過管道連接到另一個進程的輸入。管道本質上是內核的一塊緩存。

匿名管道Pipes

匿名管道是基於文件描述符的通信方式。實現兩個進程間的通信時必須通過fork創建子進程,實現父子進程之間的通信。

管道具有幾個影響其用途的重要特徵:

  • 管道是單向的;必須將一端指定爲讀取端,另一端指定爲寫入端。請注意,沒有限制不同的進程必須讀取和寫入管道;相反,如果一個進程寫入管道然後立即從中讀取,該進程將收到自己的消息。如果兩個進程需要來回交換消息,則應該使用兩個管道。
  • 管道是保持秩序的;從管道接收端讀取的所有數據都將與其寫入管道的順序相匹配。無法將某些數據指定爲更高優先級以確保首先讀取它。
  • 管道的容量有限,它們使用阻塞 I/O;如果管道已滿,則對管道的任何額外寫入都將阻塞該進程,直到讀取了某些數據。因此,不必擔心消息會被丟棄,但可能會出現性能延遲,因爲寫入過程無法控制何時從管道中刪除字節。
  • 管道將數據作爲非結構化字節流發送。交換的數據沒有預定義的特徵,例如可預測的消息長度。使用管道的進程必須就通信協議達成一致並適當地處理錯誤(例如,如果其中一個進程提前終止了通信)。
  • 小於 PIPE_BUF 指定大小的消息保證以原子方式發送。因此,如果兩個進程同時寫入管道,則兩條消息都會被正確寫入並且不會相互干擾。
#include <unistd.h>

int pipe( int filedes[2] );
Parameters :
fd[0] will be the fd(file descriptor) for the read end of pipe.
fd[1] will be the fd for the write end of pipe.
Returns : 0 on Success. -1 on error.

以父子進程爲例:創建一個子進程,子進程複製了父進程的描述符表,因此子進程也有描述符表,並且他們指向的是同一個管道,由於父子進程都能訪問這個管道,就可以通信。因爲管道是半雙工單向通信,因此在通信前要確定數據流向:即關閉父子進程各自一端不用的讀寫。如果一方是讀數據就關閉寫的描述符。

說明: 管道的讀寫端通過打開文件描述符來傳遞,因此要通信的兩個進程必須從他們的公共祖先那裏繼承管道的文件描述符。上面的例子是父進程把文件描述符傳給子進程之後父子進程之間通信。也可以父進程fork()兩次,把文件描述符傳給兩個子進程,然後兩個子進程之間通信。總之需要通過fork()傳遞文件描述符使兩個進程都能訪問同一個管道,他們才能通信。

//這是一個匿名管道實現:功能:從父進程寫入數據,子進程讀取數據
 #include<stdio.h>
 #include<unistd.h>
 #include<string.h>
 #include<errno.h>
 int main()
{
        int fd[2];
         //管道需要創建在創建子進程前,這樣才能複製
         if(pipe(fd)<0)
         {
                 perror("pipe errno");
                 return -1;
         }
         int pid=-1;
         pid=fork();//創建子進程,對於父進程會返回子進程id,子進程會返回0,創建失敗會返回0
         if(pid<0)
         {
                 perror("fork errno");
                 return -1;
         }
      else if(pid==0)
         {
                //子進程 讀取數據-> fd[0]
                close(fd[1]);//fd[1]是向管道寫入數據,子進程不用寫入數據,需要關閉管道寫入端
                char buff[1024]={0};
                read(fd[0],buff,1024);//如果管道沒數據會等待,然後讀取數據,默認阻塞等待直至有數據
                 printf("chlid pid=%d, buff:%s\n",(int)getpid(), buff);
                 close(fd[0]);
         }
         else
         {
                 //父進程 :寫入數據->fd[1]
                 close(fd[0]); 
                 //fd[0]是讀取數據,父進程不用讀取數據,需要關閉管道讀取端,由於父子進程相互獨立,關閉一方描述符對另一方無影響
                 printf("parent pid=%d\n", (int)getpid()) ;
                 write(fd[1],"happy day",10);
                 close(fd[1]);
         }
         return 0;
 }

編譯運行結果如下:

bspserver@ubuntu:~/workspace/pipe$ gcc -pthread parent_child.c -o parent_child
bspserver@ubuntu:~/workspace/pipe$ ./parent_child
parent pid=11734
chlid pid=11735, buff:happy day

需要注意的是:

1、如果寫端關閉,讀端未關閉。則讀端會停止阻塞,立即返回讀取字節數爲 0 。因此,可在程序中根據返回的是否是 0 來判斷寫端是否已經關閉。 2、如果讀端關閉,寫端還沒有關閉。那麼,寫端繼續寫,操作系統會給寫端進程發送一個SIGPIPE信號,終止寫端程序。

命名管道FIFOs

命名管道:文件系統可見,是一個特殊類型(管道類型)文件,命名管道可以應用於同一主機上任意進程間通信。

在管道中,只有具有血緣關係的進程才能進行通信,對於後來的命名管道,就解決了這個問題,FIFO不同於匿名管道之處在於它提供了一個路徑名與之關聯,以FIFO的文件形式存儲在文件系統中。有名管道是一個設備文件,因此即使進程與創建FIFO的進程不存在親緣關係,只要可以訪問該路徑,就能通過FIFO相互通信。值得注意的是,FIFO(first input first output)總是按照先進先出的原則工作,第一個被寫入的數據首先從管道中讀取。

FIFO 通過將文件名附加到管道來工作。出於這個原因,FIFO 也稱爲命名管道,而不是前面討論的匿名管道。 FIFO 由一個調用 mkfifo() 的進程創建。創建後,任何進程(具有正確的訪問權限)都可以通過對關聯的文件名調用 open() 來訪問 FIFO。一旦進程打開了文件,它們就可以使用標準的 read() 和 write() 函數進行通信。

命名管道的創建:

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);

服務端進程代碼如下:

/*
 *
 *       server.c: Write strings in POSIX FIFOs to file
 *                 (Server process)
 */

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
//
int main()
{
    umask(0);//將權限清0
    if(mkfifo("./mypipe",0666|S_IFIFO) < 0){//創建管道
        perror("mkfifo");
        return 1;
    }
    int fd = open("./mypipe",O_RDONLY);//打開管道
    if(fd < 0){
        perror("open");
        return 2;
    }
    char buf[1024];
    while(1){
        buf[0] = 0;
        printf("please waiting...\n");
        ssize_t s = read(fd,buf,sizeof(buf)-1);
 
        if(s > 0){
            buf[s-1] = 0;//過濾\n
            printf("Server:%s\n",buf);
        }else if(s == 0){//當客戶端退出時,read返回0
            printf("client quit, Exit\n");
            break;
        }
    }
    close(fd);
    return 0;
}

客戶端進程代碼如下:

/*
 *
 *       client.c: Write strings for printing in POSIX FIFOs object
 *                 
 */
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
int main()
{
    int fd = open("./mypipe",O_WRONLY);//打開管道
    if(fd < 0){
        perror("open");
        return 1;
    }
    char buf[1024];
    while(1){
        printf("Client:");
        fflush(stdout);
        ssize_t s = read(0,buf,sizeof(buf)-1);//向管道文件中寫數據
        if(s > 0){
            buf[s] = 0;//以字符串的形式寫
            write(fd,buf,strlen(buf));
        }
    }
    close(fd);
    return 0;
}

編譯運行結果如下:

服務器端

bspserver@ubuntu:~/workspace/pipe$ gcc server.c -o server
bspserver@ubuntu:~/workspace/pipe$  ./server
please waiting...
Server:hello
please waiting...
Server:world
please waiting...
client quit, Exit
bspserver@ubuntu:~/workspace/pipe$ 

客戶端

bspserver@ubuntu:~/workspace/pipe$ gcc client.c -o client
Client:hello
Client:world
Client:^C
bspserver@ubuntu:~/workspace/pipe$ 

管道與shell命令實例

在shell中輸入命令:ls -l | grep Q,我們知道ls命令(其實也是一個進程)會把當前目錄中的文件都列出來,但是它不會直接輸出,而是把本來要輸出到屏幕上的數據通過管道輸出到grep這個進程中,作爲grep這個進程的輸入,然後這個進程對輸入的信息進行篩選,把存在Q的信息的字符串(以行爲單位)打印在屏幕上。

bspserver@ubuntu:~/workspace$ ls -l | grep Q
drwxrwxr-x  5 bspserver bspserver      4096 Nov 29 17:34 QNX_BCD
drwxrwxr-x  5 bspserver bspserver      4096 Nov 29 00:58 QNX_HMI
drwxrwxr-x  5 bspserver bspserver      4096 Nov 23 02:47 SDP_QNX660
drwxrwxr-x  5 bspserver bspserver      4096 Nov 29 01:02 SDP_QNX70

管道最常見的用途之一是將命令行上的多個命令鏈接在一起。

例如,考慮以下命令行:ls -l | sort -n -k 5 | tail -n 1 | awk '{print $NF}'

bspserver@ubuntu:~/workspace$ ls -l
total 545464
drwxrwxr-x 14 bspserver bspserver      4096 Dec  8 02:48 04_Source_BareMetal
drwxrwxr-x  5 bspserver bspserver      4096 Nov 23 02:48 audio_driver
drwxrwxr-x  8 bspserver bspserver      4096 Nov 24 21:04 devs-fpdlink
drwxrwxr-x 10 bspserver bspserver      4096 Nov 23 22:14 esp-idf
-rw-rw-r--  1 bspserver bspserver 240473071 Nov 23 22:08 esp-idf-master.tar.gz
drwxrwxr-x 19 bspserver bspserver      4096 Dec 20 18:05 FFmpeg
-rw-rw-r--  1 bspserver bspserver 317400777 Dec 20 18:02 FFmpeg.tar.gz
drwxrwxr-x  2 bspserver bspserver      4096 Dec 19 18:51 github
drwxrwxr-x  6 bspserver bspserver      4096 Dec 17 01:41 linux_training
drwxrwxr-x  2 bspserver bspserver      4096 Nov 26 01:49 pcan_pytest
drwxrwxr-x  7 bspserver bspserver      4096 Dec 13 21:36 POSIX-threads-programming-tutorials-master
-rw-r--r--  1 root      root         612759 Dec 13 02:53 POSIX-threads-programming-tutorials-master.tar.gz
drwxrwxr-x  5 bspserver bspserver      4096 Nov 29 17:34 QNX_BCD
drwxrwxr-x  5 bspserver bspserver      4096 Nov 29 00:58 QNX_HMI
drwxrwxr-x  5 bspserver bspserver      4096 Nov 23 02:47 SDP_QNX660
drwxrwxr-x  5 bspserver bspserver      4096 Nov 29 01:02 SDP_QNX700
drwxrwxr-x  5 bspserver bspserver      4096 Nov 24 21:13 Upgrade_HAL
bspserver@ubuntu:~/workspace$ ls -l | sort -n -k 5
total 545464
drwxrwxr-x 10 bspserver bspserver      4096 Nov 23 22:14 esp-idf
drwxrwxr-x 14 bspserver bspserver      4096 Dec  8 02:48 04_Source_BareMetal
drwxrwxr-x 19 bspserver bspserver      4096 Dec 20 18:05 FFmpeg
drwxrwxr-x  2 bspserver bspserver      4096 Dec 19 18:51 github
drwxrwxr-x  2 bspserver bspserver      4096 Nov 26 01:49 pcan_pytest
drwxrwxr-x  5 bspserver bspserver      4096 Nov 23 02:47 SDP_QNX660
drwxrwxr-x  5 bspserver bspserver      4096 Nov 23 02:48 audio_driver
drwxrwxr-x  5 bspserver bspserver      4096 Nov 24 21:13 Upgrade_HAL
drwxrwxr-x  5 bspserver bspserver      4096 Nov 29 00:58 QNX_HMI
drwxrwxr-x  5 bspserver bspserver      4096 Nov 29 01:02 SDP_QNX700
drwxrwxr-x  5 bspserver bspserver      4096 Nov 29 17:34 QNX_BCD
drwxrwxr-x  6 bspserver bspserver      4096 Dec 17 01:41 linux_training
drwxrwxr-x  7 bspserver bspserver      4096 Dec 13 21:36 POSIX-threads-programming-tutorials-master
drwxrwxr-x  8 bspserver bspserver      4096 Nov 24 21:04 devs-fpdlink
-rw-r--r--  1 root      root         612759 Dec 13 02:53 POSIX-threads-programming-tutorials-master.tar.gz
-rw-rw-r--  1 bspserver bspserver 240473071 Nov 23 22:08 esp-idf-master.tar.gz
-rw-rw-r--  1 bspserver bspserver 317400777 Dec 20 18:02 FFmpeg.tar.gz
bspserver@ubuntu:~/workspace$ ls -l | sort -n -k 5 |  tail -n 1 
-rw-rw-r--  1 bspserver bspserver 317400777 Dec 20 18:02 FFmpeg.tar.gz
bspserver@ubuntu:~/workspace$ ls -l | sort -n -k 5 | tail -n 1 | awk '{print $NF}'
FFmpeg.tar.gz
bspserver@ubuntu:~/workspace$ 

此命令行創建四個鏈接在一起的進程。 首先,ls 命令打印出文件列表及其詳細信息。 此列表作爲輸入發送到sort,排序基於第 5 個字段(文件大小)進行數字排序。 tail進程然後抓取最後一行,這是最大文件的行。 最後,awk 將打印該行的最後一個字段,即最大文件的文件名。

命名管道和匿名管道區別和聯繫:

1.區別:匿名管道用int pipe(int pipefd[2]); 創建並打開匿名管道返回描述符 命名管道用mkfifo或者 int mkfifo(const char *pathname, mode_t mode);創建,並沒有打開,如果打開需要open; 匿名管道是具有親緣關係進程間通信的媒介,而命名管道作爲同一主機任意進程間通信的媒介; 匿名管道不可見文件系統,命名管道可見於文件系統,是一個特殊類型(管道類型)文件。 2.聯繫:匿名管道和命名管道都是內核的一塊緩衝區,並且都是單向通信;另外當命名管道打開(open)後,所有特性和匿名管道一樣(上文匿名管道讀寫規則與管道特性):兩者自帶同步(臨界資源訪問的時序性)與互斥(臨界資源同一時間的唯一訪問性),管道生命週期隨進程退出而結束。

消息隊列 POSIX message queues

POSIX通過message queues定義一組非阻塞的消息傳遞機制。消息隊列爲命名對象,針對這些對象可以進行讀取和寫入,作爲離散消息的優先級隊列,消息隊列具有比管道更多的結構,爲應用程序提供了更多的通信控制。QNX Neutrino內核不包含message queues,它的實現在內核之外。 QNX Neutrino提供了兩種message queues的實現:

  • mqueue,使用mqueue資源管理的傳統實現
  • mq,使用mq服務和非同步消息的替代實現

QNX消息機制與POSIX的Message queues有一個根本的區別:,QNX的消息機制通過內存拷貝來實現消息的傳遞;而POSIX的消息隊列通過將消息進行存取來實現消息的傳遞。QNX的消息機制比POSIX的消息隊列效率更高,但有時爲了POSIX的靈活,需要適當的犧牲一點效率。

消息隊列與文件類似,操作的接口相近。

Function Description
mq_open() Open a message queue
mq_close() Close a message queue
mq_unlink() Remove a message queue
mq_send() Add a message to the message queue
mq_receive() Receive a message from the message queue
mq_notify() Tell the calling process that a message is available on a message queue
mq_setattr() Set message queue attributes
mq_getattr() Get message queue attributes
  • mq_open: 創建一個新的消息隊列或打開一個已存在的消息的隊列。
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);

返回值:成功,消息隊列描述符;失敗,-1

消息隊列描述符用作其餘消息隊列函數(mq_unlink除外)的第一個參數。

name規則:必須以一個斜槓符打頭,並且不能再包含任何其他斜槓符

oflag:O_RDONLY、O_WRONLY、O_RDWR三者之一,按位或上O_CREAT、O_EXCL

mode:S_ISRUSR、S_ISWUSR、S_ISRGRP、S_ISWGRP、S_ISROTH、S_ISWOTH

attr

struct mq_attr

{

​ long mq_flags;//阻塞標誌, 0或O_NONBLOCK

​ long mq_maxmsg;//最大消息數

​ long mq_msgsize;//每個消息最大大小

​ long mq_curmsgs;//當前消息數

};

  • mq_close: 關閉消息隊列。
  • mq_unlink: 從系統中刪除消息隊列。
int mq_close(mqd_t mqd);
int mq_unlink(const char *name)

//關閉已打開的消息隊列

int mq_close(mqd_t mqd);

返回值:成功,0;出錯,-1

一個進程終止時,它的所有打開的消息隊列都關閉,如同調用了mq_close。

//刪除消息隊列的name

*int mq_unlink(const char name);

返回值:成功,0;出錯,-1

每個消息隊列有一個保存其當前打開的描述符數的引用計數,只有當引用計數爲0時,才刪除該消息隊列。mq_unlink和mq_close都會讓引用數減一

  • mq_send:向消息隊列中寫入一條消息。
int mq_send(mqd_t mqd, const char *ptr, size_t len, unsigned int prio);

返回值:成功,0;出錯,-1

說明:優先級prio要小於MQ_PRIO_MAX(此值最少爲32)。

  • mq_receive:從消息隊列中讀取一條消息。
ssize_t mq_receive(mqd_t mqd, char *ptr, size_t len, unsigned int *prio);

  • 返回值:成功,消息中字節數;出錯,-1

說明:消息內存的長度len,最小要等於mq_msgsize。mq_receive總是返回消息隊列中最高優先級的最早消息。

  • mq_getattr:用於獲取當前消息隊列的屬性。
  • mq_setattr:用於設置當前消息隊列的屬性。
int mq_getattr(mqd_t mqd, struct mq_attr *attr);
int mq_setattr(mqd_t mqd, const struct mq_attr *attr, struct mq_attr *oattr);

返回值:成功,0;出錯,-1

消息隊列先前屬性返回到oattr中

消息隊列有4個屬性,如下:

struct mq_attr { long mq_flags;//阻塞標誌, 0或O_NONBLOCK long mq_maxmsg;//最大消息數 long mq_msgsize;//每個消息最大大小 long mq_curmsgs;//當前消息數 }; 其中,mq_setattr只能設置mq_flags屬性; mq_open只能設置mq_maxmsg和mq_msgsize屬性,並且兩個必須要同時設置; mq_getattr返回全部4個屬性。

服務端進程代碼如下:

#include <stdio.h>
#include <mqueue.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <string.h>
 
#define MAXSIZE     10   //定義buf大小
#define BUFFER      8192 //定義Msg大小
 
struct MsgType{
    int len;
    char buf[MAXSIZE];
    char x;
    short y;
};
 
int main(int argc, char **argv)
{
    /*消息隊列*/
    mqd_t msgq_id;
    struct MsgType msg;
    unsigned int prio = 1;
    unsigned int send_size = BUFFER;
    struct mq_attr msgq_attr;
    const char *file = "/posix";
    /*mq_open() for creating a new queue (using default attributes) */
    /*mq_open() 創建一個新的 POSIX 消息隊列或打開一個存在的隊列*/
    msgq_id = mq_open(file, O_RDWR | O_CREAT, S_IRWXU | S_IRWXG, NULL);
    if(msgq_id == (mqd_t)-1)
    {
        perror("mq_open");
        exit(1);
    }
    /* getting the attributes from the queue        --  mq_getattr() */
    if(mq_getattr(msgq_id, &msgq_attr) == -1)
    {
        perror("mq_getattr");
        exit(1);
    }
    printf("Queue \"%s\":\n\t- stores at most %ld messages\n\t- \
        large at most %ld bytes each\n\t- currently holds %ld messages\n",
        file, msgq_attr.mq_maxmsg, msgq_attr.mq_msgsize, msgq_attr.mq_curmsgs);
    /*setting the attributes of the queue        --  mq_setattr() */
    /*mq_setattr() 設置消息隊列的屬性,設置時使用由 newattr 指針指向的 mq_attr 結構的信息。*/
    /*屬性中只有標誌 mq_flasgs 裏的 O_NONBLOCK 標誌可以更改,其它在 newattr 內的域都被忽略 */
    if(mq_setattr(msgq_id, &msgq_attr, NULL) == -1)
    {
        perror("mq_setattr");
        exit(1);
    }  
    int i = 0;
    while(i < atoi(argv[1]))
    {
        msg.len = i;
        memset(msg.buf, 0, MAXSIZE);
        sprintf(msg.buf, "0x%x", i);
        msg.x = (char)(i + 'a');
        msg.y = (short)(i + 100);
        printf("msg.len = %d, msg.buf = %s, msg.x = %c, msg.y = %d\n", msg.len, msg.buf, msg.x, msg.y);
        /*sending the message      --  mq_send() */
        /*mq_send() 把 msg_ptr 指向的消息加入由 mqdes 引用的消息隊列裏。*/
        /*參數 msg_len 指定消息 msg_ptr 的長度:這個長度必須小於或等於隊列 mq_msgsize 屬性的值。零長度的消息是允許。*/
        if(mq_send(msgq_id, (char*)&msg, sizeof(struct MsgType), prio) == -1)
        {
            perror("mq_send");
            exit(1);
        }
        i++;
        sleep(1);
    }
    msgq_attr.mq_curmsgs = msgq_attr.mq_maxmsg;
    while(msgq_attr.mq_curmsgs)
    {
        /* getting the attributes from the queue        --  mq_getattr() */
        if(mq_getattr(msgq_id, &msgq_attr) == -1)
        {
            perror("mq_getattr");
            exit(1);
        }
        sleep(1); //等待消費者進程退出
        printf("currently holds %ld messages\n", msgq_attr.mq_curmsgs);
    }
    /*closing the queue        -- mq_close() */
    /*mq_close() 關閉消息隊列描述符 mqdes。如果調用進程在消息隊列 mqdes 綁定了通知請求,*/
    /*那麼這個請求被刪除,此後其它進程就可以綁定通知請求到此消息隊列。*/
    if(mq_close(msgq_id) == -1)
    {
        perror("mq_close");
        exit(1);
    }
    /*mq_unlink() 刪除名爲 name 的消息隊列。消息隊列名將被直接刪除。*/
    /*消息隊列本身在所有引用這個隊列的描述符被關閉時銷燬。*/
    if(mq_unlink(file) == -1)
    {
        perror("mq_unlink");
        exit(1);
    }
    return 0;
}

客戶端進程代碼如下:

#include <stdio.h>
#include <mqueue.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <string.h>
 
#define MAXSIZE     10   //定義buf大小
#define BUFFER      8192 //定義Msg大小
 
struct MsgType{
    int len;
    char buf[MAXSIZE];
    char x;
    short y;
};
 
int main(int argc, char **argv)
{
    /*消息隊列*/
    mqd_t msgq_id;
    struct MsgType msg;
    unsigned int sender;
    struct mq_attr msgq_attr;
    unsigned int recv_size = BUFFER;
    const char *file = "/posix";
 
    /* mq_open() for opening an existing queue */
    msgq_id = mq_open(file, O_RDWR);
    if(msgq_id == (mqd_t)-1)
    {
        perror("mq_open");
        exit(1);
    }
    /* getting the attributes from the queue        --  mq_getattr() */
    if(mq_getattr(msgq_id, &msgq_attr) == -1)
    {
        perror("mq_getattr");
        exit(1);
    }
    printf("Queue \"%s\":\n\t- stores at most %ld messages\n\t- \
        large at most %ld bytes each\n\t- currently holds %ld messages\n",
        file, msgq_attr.mq_maxmsg, msgq_attr.mq_msgsize, msgq_attr.mq_curmsgs);
 
    if(recv_size < msgq_attr.mq_msgsize)
        recv_size = msgq_attr.mq_msgsize;
 
    int i = 0;
    while(i < atoi(argv[1])) //運行一個consumenr,爲 10 ,同時運行兩個consumer進程,爲 5 
    {
        msg.len = -1;
        memset(msg.buf, 0, MAXSIZE);
        msg.x = ' ';
        msg.y = -1;
        /* getting a message */
        /*mq_receive() 從由描述符 mqdes 引用的隊列時刪除優先級最高的最老的消息,並把放置到 msg_ptr 的緩存區內。*/
        /*參數 msg_len 指定緩衝區 msg_ptr 的大小:它必須大於隊列的 mq_msgsize 屬性(參數 mq_getattr)。*/
        /*如果 prio 不是 NULL,那麼它指向的內存用於返回收到消息相關的優先級。*/
        if (mq_receive(msgq_id, (char*)&msg, recv_size, &sender) == -1)
        {
            perror("mq_receive");
            exit(1);
        }
        printf("msg.len = %d, msg.buf = %s, msg.x = %c, msg.y = %d\n", msg.len, msg.buf, msg.x, msg.y);
        i++;
        sleep(2);
    }
 
    if(mq_close(msgq_id) == -1)
    {
        perror("mq_close");
        exit(1);
    }
    return 0;
}

代碼編譯運行結果如下:

bspserver@ubuntu:~/workspace/posix_message_queue$ gcc -pthread client.c -lrt -o client
bspserver@ubuntu:~/workspace/posix_message_queue$ gcc -pthread server.c -lrt -o server
bspserver@ubuntu:~/workspace/linux_training/kernel/process-courses/posix_message_queue$ ./server 5
Queue "/posix":
	- stores at most 10 messages
	-         large at most 8192 bytes each
	- currently holds 0 messages
msg.len = 0, msg.buf = 0x0, msg.x = a, msg.y = 100
msg.len = 1, msg.buf = 0x1, msg.x = b, msg.y = 101
msg.len = 2, msg.buf = 0x2, msg.x = c, msg.y = 102
msg.len = 3, msg.buf = 0x3, msg.x = d, msg.y = 103
msg.len = 4, msg.buf = 0x4, msg.x = e, msg.y = 104
currently holds 5 messages
currently holds 5 messages
currently holds 4 messages
currently holds 4 messages
currently holds 3 messages
currently holds 3 messages
currently holds 2 messages
currently holds 2 messages
currently holds 1 messages
currently holds 1 messages
currently holds 0 messages
# 再看一個終端查看消息隊列的屬性
bspserver@ubuntu:~$ ls /dev/mqueue/ -al
total 0
drwxrwxrwt  2 root      root        60 Dec 20 23:11 .
drwxr-xr-x 20 root      root      4160 Dec 20 17:55 ..
-rwxrwx---  1 bspserver bspserver   80 Dec 20 23:11 posix
bspserver@ubuntu:~$ cat /dev/mqueue/posix 
QSIZE:100        NOTIFY:0     SIGNO:0     NOTIFY_PID:0     
bspserver@ubuntu:~$ ls /proc/sys/fs/mqueue/
msg_default      msg_max    msgsize_default  msgsize_max      queues_max       
bspserver@ubuntu:~$ cat /proc/sys/fs/mqueue/*
10
10
8192
8192
256
bspserver@ubuntu:~$ 
# 再開一個終端運行客戶端進程
bspserver@ubuntu:~/workspace/posix_message_queue$ ./client 5
Queue "/posix":
	- stores at most 10 messages
	-         large at most 8192 bytes each
	- currently holds 5 messages
msg.len = 0, msg.buf = 0x0, msg.x = a, msg.y = 100
msg.len = 1, msg.buf = 0x1, msg.x = b, msg.y = 101
msg.len = 2, msg.buf = 0x2, msg.x = c, msg.y = 102
msg.len = 3, msg.buf = 0x3, msg.x = d, msg.y = 103
msg.len = 4, msg.buf = 0x4, msg.x = e, msg.y = 104

運行一個server進程,爲 20 ,同時運行兩個client進程,爲 5, 還有一個client進程爲10的情況。 當發送的總消息個數大於mqueue最大個數時,消息隊列會一直等待到隊列不爲滿的時候再往隊列裏發數據。

bspserver@ubuntu:~/workspace/posix_message_queue$ ./server 20
Queue "/posix":
	- stores at most 10 messages
	-         large at most 8192 bytes each
	- currently holds 0 messages
msg.len = 0, msg.buf = 0x0, msg.x = a, msg.y = 100
msg.len = 1, msg.buf = 0x1, msg.x = b, msg.y = 101
msg.len = 2, msg.buf = 0x2, msg.x = c, msg.y = 102
msg.len = 3, msg.buf = 0x3, msg.x = d, msg.y = 103
msg.len = 4, msg.buf = 0x4, msg.x = e, msg.y = 104
msg.len = 5, msg.buf = 0x5, msg.x = f, msg.y = 105
msg.len = 6, msg.buf = 0x6, msg.x = g, msg.y = 106
msg.len = 7, msg.buf = 0x7, msg.x = h, msg.y = 107
msg.len = 8, msg.buf = 0x8, msg.x = i, msg.y = 108
msg.len = 9, msg.buf = 0x9, msg.x = j, msg.y = 109
# 如果沒有進程從mqueue裏取數據,此時mqueue爲滿。mq_send 無法再往mqueue發送數據。
msg.len = 10, msg.buf = 0xa, msg.x = k, msg.y = 110 
msg.len = 11, msg.buf = 0xb, msg.x = l, msg.y = 111
msg.len = 12, msg.buf = 0xc, msg.x = m, msg.y = 112
msg.len = 13, msg.buf = 0xd, msg.x = n, msg.y = 113
msg.len = 14, msg.buf = 0xe, msg.x = o, msg.y = 114
msg.len = 15, msg.buf = 0xf, msg.x = p, msg.y = 115
msg.len = 16, msg.buf = 0x10, msg.x = q, msg.y = 116
msg.len = 17, msg.buf = 0x11, msg.x = r, msg.y = 117
msg.len = 18, msg.buf = 0x12, msg.x = s, msg.y = 118
msg.len = 19, msg.buf = 0x13, msg.x = t, msg.y = 119
currently holds 5 messages
currently holds 4 messages
currently holds 3 messages
currently holds 2 messages
currently holds 1 messages
currently holds 1 messages
currently holds 0 messages
# 三個client進程分別取出數據
bspserver@ubuntu:~/workspace/posix_message_queue$ ./client 5 
Queue "/posix":
	- stores at most 10 messages
	-         large at most 8192 bytes each
	- currently holds 9 messages
msg.len = 6, msg.buf = 0x6, msg.x = g, msg.y = 106
msg.len = 9, msg.buf = 0x9, msg.x = j, msg.y = 109
msg.len = 12, msg.buf = 0xc, msg.x = m, msg.y = 112
msg.len = 15, msg.buf = 0xf, msg.x = p, msg.y = 115
msg.len = 17, msg.buf = 0x11, msg.x = r, msg.y = 117

bspserver@ubuntu:~/workspace/posix_message_queue$ ./client 5
Queue "/posix":
	- stores at most 10 messages
	-         large at most 8192 bytes each
	- currently holds 10 messages
msg.len = 3, msg.buf = 0x3, msg.x = d, msg.y = 103
msg.len = 5, msg.buf = 0x5, msg.x = f, msg.y = 105
msg.len = 8, msg.buf = 0x8, msg.x = i, msg.y = 108
msg.len = 11, msg.buf = 0xb, msg.x = l, msg.y = 111
msg.len = 14, msg.buf = 0xe, msg.x = o, msg.y = 114

bspserver@ubuntu:~/workspace/posix_message_queue$ ./client 10
Queue "/posix":
	- stores at most 10 messages
	-         large at most 8192 bytes each
	- currently holds 10 messages
msg.len = 0, msg.buf = 0x0, msg.x = a, msg.y = 100
msg.len = 1, msg.buf = 0x1, msg.x = b, msg.y = 101
msg.len = 2, msg.buf = 0x2, msg.x = c, msg.y = 102
msg.len = 4, msg.buf = 0x4, msg.x = e, msg.y = 104
msg.len = 7, msg.buf = 0x7, msg.x = h, msg.y = 107
msg.len = 10, msg.buf = 0xa, msg.x = k, msg.y = 110
msg.len = 13, msg.buf = 0xd, msg.x = n, msg.y = 113
msg.len = 16, msg.buf = 0x10, msg.x = q, msg.y = 116
msg.len = 18, msg.buf = 0x12, msg.x = s, msg.y = 118
msg.len = 19, msg.buf = 0x13, msg.x = t, msg.y = 119

Posix消息隊列容許 異步事件通知,以告知何時有一個消息放置到某個空消息隊列中,這種通知有兩種方式可以選擇:

  • 產生一個信號
  • 創建一個線程來執行一個指定的函數

信號實例

帶信號通知的客戶端進程代碼如下:

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <mqueue.h>
 
#define MAXSIZE     10   //定義buf大小
#define BUFFER      8192 //定義Msg大小
 
struct MsgType{
    int len;
    char buf[MAXSIZE];
    char x;
    short y;
};
 
int main(int argc, char **argv){
    /*消息隊列*/
    mqd_t msgq_id;
    struct MsgType msg;
    unsigned int prio = 1;
    struct mq_attr msgq_attr;
    const char *file = "/posix";
    printf("\r\n");
    printf("(CLIENT) My PID: %d\n\n", getpid());
    /*mq_open() for creating a new queue (using default attributes) */
    /*mq_open() 創建一個新的 POSIX 消息隊列或打開一個存在的隊列*/
    msgq_id = mq_open(file, O_RDWR | O_CREAT, 0777, NULL);
    if(msgq_id == (mqd_t)-1)
    {
        perror("mq_open");
        exit(1);
    }
    /* getting the attributes from the queue        --  mq_getattr() */
    if(mq_getattr(msgq_id, &msgq_attr) == -1)
    {
        perror("mq_getattr");
        exit(1);
    }
    printf("Queue \"%s\":\n\t- stores at most %ld messages\n\t- \
        large at most %ld bytes each\n\t- currently holds %ld messages\n",
        file, msgq_attr.mq_maxmsg, msgq_attr.mq_msgsize, msgq_attr.mq_curmsgs);
    int i = 0;
    while(i < atoi(argv[1]))
    {
        msg.len = i;
        memset(msg.buf, 0, MAXSIZE);
        sprintf(msg.buf, "0x%x", i);
        msg.x = (char)(i + 'a');
        msg.y = (short)(i + 100);
        //printf("msg.len = %d, msg.buf = %s, msg.x = %c, msg.y = %d\n", msg.len, msg.buf, msg.x, msg.y);
        /*sending the message      --  mq_send() */
        /*mq_send() 把 msg_ptr 指向的消息加入由 mqdes 引用的消息隊列裏。*/
        /*參數 msg_len 指定消息 msg_ptr 的長度:這個長度必須小於或等於隊列 mq_msgsize 屬性的值。零長度的消息是允許。*/
        if(mq_send(msgq_id, (char*)&msg, sizeof(struct MsgType), prio) == -1)
        {
            perror("mq_send");
            exit(1);
        }
        i++;
        sleep(1);  
    }
    msgq_attr.mq_curmsgs = msgq_attr.mq_maxmsg;
    while(msgq_attr.mq_curmsgs)
    {
        /* getting the attributes from the queue        --  mq_getattr() */
        if(mq_getattr(msgq_id, &msgq_attr) == -1)
        {
            perror("mq_getattr");
            exit(1);
        }
        sleep(1); //等待消費者進程退出
        printf("currently holds %ld messages\n", msgq_attr.mq_curmsgs);
    }
    return 0;
}

帶信號通知的服務端進程代碼如下:

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <mqueue.h>
 
#define MAXSIZE     10   //定義buf大小
#define BUFFER      8192 //定義Msg大小
 
struct MsgType{
    int len;
    char buf[MAXSIZE];
    char x;
    short y;
};
mqd_t msgq_id;   
void message_callback(int signum, siginfo_t* info, void* context);
 
int main(int argc, char **argv){
 
    struct sigevent se;
    struct sigaction sa;
    printf("\r\n");
    printf("(SERVER) My PID: %d\n\n", getpid());
     
    msgq_id = mq_open("/posix", O_RDWR | O_CREAT, 0777, NULL);
    for(;;)
    {
        se.sigev_notify = SIGEV_SIGNAL;
        se.sigev_signo = SIGUSR1;
        mq_notify(msgq_id, &se);
         
        sa.sa_flags = SA_SIGINFO;
        sa.sa_sigaction = &message_callback;
        sigemptyset(&(sa.sa_mask));
        if(sigaction(SIGUSR1, &sa, NULL) == -1){
            perror("sigaction");
            exit(1);
        }
        pause();
    }
    mq_close(msgq_id);
    return 0;
}
 
void message_callback(int signum, siginfo_t* info, void* context){
    struct MsgType msg;
    unsigned int sender;
    unsigned int recv_size = BUFFER;
     
    if(signum == SIGUSR1){
        if(info->si_code == SI_MESGQ){
            msg.len = -1;
            memset(msg.buf, 0, MAXSIZE);
            msg.x = ' ';
            msg.y = -1;   
             
            printf("Message received from PID %d:\n", info->si_pid);
            /* getting a message */
            /*mq_receive() 從由描述符 mqdes 引用的隊列時刪除優先級最高的最老的消息,並把放置到 msg_ptr 的緩存區內。*/
            /*參數 msg_len 指定緩衝區 msg_ptr 的大小:它必須大於隊列的 mq_msgsize 屬性(參數 mq_getattr)。*/
            /*如果 prio 不是 NULL,那麼它指向的內存用於返回收到消息相關的優先級。*/
            if (mq_receive(msgq_id, (char*)&msg, recv_size, &sender) == -1)
            {
                perror("mq_receive");
                exit(1);
            }
            printf("msg.len = %d, msg.buf = %s, msg.x = %c, msg.y = %d\n", msg.len, msg.buf, msg.x, msg.y);
        }
    }
}

代碼編譯運行結果如下:

bspserver@ubuntu:~/workspace/posix_message_queue$ ./mq_notify_signal_server 20  & 
[1] 12735
bspserver@ubuntu:~/workspace/posix_message_queue$ 
(SERVER) My PID: 12735

./mq_notify_signal_client 5 &
[2] 12736
bspserver@ubuntu:~/workspace/posix_message_queue$ 
(CLIENT) My PID: 12736

Queue "/posix":
	- stores at most 10 messages
	-         large at most 8192 bytes each
	- currently holds 0 messages
Message received from PID 12736:
msg.len = 0, msg.buf = 0x0, msg.x = a, msg.y = 100
./mq_notify_signal_client 5 &Message received from PID 12736:
msg.len = 1, msg.buf = 0x1, msg.x = b, msg.y = 101

[3] 12737
bspserver@ubuntu:~/workspace/posix_message_queue$ 
(CLIENT) My PID: 12737

Queue "/posix":
	- stores at most 10 messages
	-         large at most 8192 bytes each
	- currently holds 0 messages
Message received from PID 12737:
msg.len = 0, msg.buf = 0x0, msg.x = a, msg.y = 100
Message received from PID 12736:
msg.len = 2, msg.buf = 0x2, msg.x = c, msg.y = 102
Message received from PID 12737:
msg.len = 1, msg.buf = 0x1, msg.x = b, msg.y = 101
Message received from PID 12736:
msg.len = 3, msg.buf = 0x3, msg.x = d, msg.y = 103
Message received from PID 12737:
msg.len = 2, msg.buf = 0x2, msg.x = c, msg.y = 102
Message received from PID 12736:
msg.len = 4, msg.buf = 0x4, msg.x = e, msg.y = 104
Message received from PID 12737:
msg.len = 3, msg.buf = 0x3, msg.x = d, msg.y = 103
Message received from PID 12737:
msg.len = 4, msg.buf = 0x4, msg.x = e, msg.y = 104
currently holds 0 messages
currently holds 0 messages

[2]-  Done                    ./mq_notify_signal_client 5
[3]+  Done                    ./mq_notify_signal_client 5
bspserver@ubuntu:~/workspace/linux_training/kernel/process-courses/posix_message_queue$ 

指定的函數mq_notify實例

這種通知通過調用mq_notify建立

#include <mqueue.h>
int mq_notify(mqd_t mqdes, const struct sigevent* notification);

該函數爲指定隊列建立或刪除異步事件通知

  1. 如果notification參數爲非空,那麼當前進程希望在有一個消息到達所指定的先前爲空的對列時得到通知。
  2. 如果notification參數爲空,而且當前進程被註冊爲接收指定隊列的通知,那麼已存在的註冊將被撤銷。
  3. 任意時刻只有一個進程可以被註冊爲接收某個給定隊列的通知。
  4. 當有一個消息到達先前爲空的消息隊列,而且已有一個進程被註冊爲接收該隊列的通知時,只有在沒有任何線程阻塞在該隊列的mq_receive調用中的前提下,通知纔會發出。即說明,在mq_receive調用中的阻塞比任何通知的註冊都優先。
  5. 當前通知被髮送給它的註冊進程時,其註冊將被撤銷。該進程必須再次調用mq_notify以重新註冊。
union sigval
{
   int  sival_int;  /*整數值*/
   void *sival_ptr; /*指針值*/
};
struct sigevent
{
   int sigev_notify; /*通知類型:SIGEV_NONE、SIGEV_SIGNAL、SIGEV_THREAD*/
   int sigev_signo; /*信號值*/
   union sigval sigev_value; /*傳遞給信號處理函數或線程的信號值*/
   void (*sigev_notify_function)(union sigval); /*線程處理函數*/
   pthread_attr_t *sigev_notify_attributes; /*線程屬性*/
};
sigev_notify 的取值有3個:
SIGEV_NONE:消息到達不會發出通知
SIGEV_SIGNAL:以信號方式發送通知,當設置此選項時,sigev_signo 設置信號的編號,且只有當信號爲實時信號時纔可以通過sigev_value順帶數據,參考這裏。
SIGEV_THREAD:以線程方式通知,當設置此選項時,sigev_notify_function 即一個函數指針,sigev_notify_attributes 即線程的屬性

#include <stdio.h>
#include <mqueue.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <time.h>
#include <string.h>

#define MAXSIZE     10   //定義buf大小
#define BUFFER      8192 //定義Msg大小

mqd_t mqdes;
struct MsgType{
    int len;
    char buf[MAXSIZE];
    char x;
    short y;
};

static void                     /* Thread start function */
message_callback(union sigval sv)
{
    struct mq_attr attr;
	struct MsgType msg;
    unsigned int recv_size = BUFFER;	
    ssize_t nr;
    mqd_t mqdes = *((mqd_t *) sv.sival_ptr);


    /* Determine maximum msg size; allocate buffer to receive msg */


    if (mq_getattr(mqdes, &attr) == -1) {
        perror("mq_getattr");
        exit(EXIT_FAILURE);
    }

	msg.len = -1;
	memset(msg.buf, 0, MAXSIZE);
	msg.x = ' ';
	msg.y = -1;	

	/* getting a message */
	/*mq_receive() 從由描述符 mqdes 引用的隊列時刪除優先級最高的最老的消息,並把放置到 msg_ptr 的緩存區內。*/
	/*參數 msg_len 指定緩衝區 msg_ptr 的大小:它必須大於隊列的 mq_msgsize 屬性(參數 mq_getattr)。*/ 
	/*如果 prio 不是 NULL,那麼它指向的內存用於返回收到消息相關的優先級。*/
	if (mq_receive(mqdes, (char*)&msg, attr.mq_msgsize, NULL) == -1) 
	{
		perror("mq_receive");
		exit(1);
	}
	printf("mq_receive: thread_id = %d, msg.len = %d, msg.buf = %s, msg.x = %c, msg.y = %d\n",getpid(), msg.len, msg.buf, msg.x, msg.y);
    exit(EXIT_SUCCESS);         /* Terminate the process */
}


int
main(int argc, char *argv[])
{
    struct sigevent sev;	
    struct MsgType msg;	
    mqd_t mqdes = mq_open("/posix", O_CREAT | O_RDWR, 0777, NULL);
    if (mqdes == (mqd_t) -1) {
        perror("mq_open");
        exit(EXIT_FAILURE);
    }

	bzero(&sev, sizeof(sev));
    sev.sigev_notify = SIGEV_THREAD;
    sev.sigev_notify_function = message_callback;
    sev.sigev_notify_attributes = NULL;
    sev.sigev_value.sival_ptr = &mqdes;   /* Arg. to thread func. */

	msg.len = atoi(argv[1]);
	memset(msg.buf, 0, MAXSIZE);
	sprintf(msg.buf, "0x%x", atoi(argv[1]));
	msg.x = (char)(atoi(argv[1]) + 'a');
	msg.y = (short)(atoi(argv[1]) + 100);
	printf("mq_send: thread_id = %d, msg.len = %d, msg.buf = %s, msg.x = %c, msg.y = %d\n",getpid(), msg.len, msg.buf, msg.x, msg.y);
	if (mq_notify(mqdes, &sev) == -1) {
		perror("mq_notify");
		exit(EXIT_FAILURE);
	}
	sleep(1);
	/*sending the message      --  mq_send() */
	/*mq_send() 把 msg_ptr 指向的消息加入由 mqdes 引用的消息隊列裏。*/
	/*參數 msg_len 指定消息 msg_ptr 的長度:這個長度必須小於或等於隊列 mq_msgsize 屬性的值。零長度的消息是允許。*/
	if(mq_send(mqdes, (char*)&msg, sizeof(struct MsgType), 1) == -1)
	{
		perror("mq_send");
		exit(1);
	}
	pause();    /* Process will be terminated by thread function */	

    exit(EXIT_SUCCESS);	
}

代碼編譯運行結果如下:

bspserver@ubuntu:~/workspace/posix_message_queue$ gcc -pthread  mq_notify_thread.c -o mq_notify_thread -lrt
bspserver@ubuntu:~/workspace/posix_message_queue$ ./mq_notify_thread 20
mq_send: thread_id = 12904, msg.len = 20, msg.buf = 0x14, msg.x = u, msg.y = 120
mq_receive: thread_id = 12904, msg.len = 20, msg.buf = 0x14, msg.x = u, msg.y = 120
bspserver@ubuntu:~/workspace/posix_message_queue$ 

參考文獻:

Programming with POSIX Threads
Pipe redirection in Linux: Named and Unnamed Pipes (linuxhandbook.com)
Pipes and FIFOs — Computer Systems Fundamentals
CMP320 Operating Systems Lecture 07, 08 Operating System Concepts
POSIX message queues in Linux - SoftPrayog
System Architecture - Interprocess Communication (IPC)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章