天天看點

system V程序間通信-消息隊列

Linux繼承了system V提供的3種通信方式,分别是消息隊列、信号量和共享記憶體。

11.1system V IPC基礎

Linux繼承了system V提供的3種通信方式,分别是消息隊列、信号量和共享記憶體。和檔案一樣,IPC在使用前必須建立,每種IPC都有特定的生産者、所有者和通路權限。使用ipcs可以檢視目前系統正在使用的IPC工具。

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      
0x00000000 294912     vicli      600        524288     2          dest         
0x00000000 1474561    vicli      600        524288     2          dest         
0x00000000 655362     vicli      600        524288     2          dest         
0x00000000 524291     vicli      600        524288     2          dest         
0x00000000 753668     vicli      600        524288     2          dest         
0x00000000 1048581    vicli      600        524288     2          dest         
0x00000000 1146886    vicli      600        524288     2          dest         
0x00000000 1310727    vicli      600        524288     2          dest         
0x00000000 1212424    vicli      600        67108864   2          dest         
0x00000000 1507337    vicli      600        4194304    2          dest         
0x00000000 1605642    vicli      600        524288     2          dest         
0x00000000 1703947    vicli      600        524288     2          dest         
0x00000000 2162700    vicli      600        2097152    2          dest         

------ Semaphore Arrays --------
key        semid      owner      perms      nsems     
           

由以上可以看出,一個IPC工具至少包含key值,ID值,擁有者,權限和使用大小等。如果需要手工删除某個IPC機制,可以使用ipcm指令。

11.1.1key值和ID值。

linux系統為每個IPC機制都配置設定了唯一的ID,所有針對該IPC機制的操作都是用該ID值。是以,通信的雙方都需要通過某個辦法來擷取ID值。建立者根據建立函數的傳回值可擷取該值,由于Linux兩個程序不能随便通路對方的空間(除非父子程序,子繼承父,實作父親向子的單向傳遞),另一程序也就不能直接擷取這一ID值。

為了解決這個問題,IPC在實作時約定使用key值作為參數建立,如果在建立時使用相同的key值将得到同一個IPC對象的ID,這樣就保證了雙方可以擷取用于傳遞資料的IPC機制的ID值。key值是一個32位整形資料。

但如果所有程式使用固定的key建立這些IPC機制有違軟體設計思想,為了盡可能與系統資訊的檔案關聯,Linux提供ftok來建立key值,在函數的參數中,需要特定的檔案作為參數。

此函數有兩個參數,pathname為路徑名,可以是特殊檔案,例如目錄檔案,也可以是目前目錄,因為目前目錄一般都存在,且不會被立即删除,第2個參數為一個int型變量。

每個檔案都有其自身的屬性,可以通過stat函數讀取,在ftok函數建立key值過程中使用了該檔案屬性的st_dev和st_ino,集體構成:

key值的第31-24,為ftok第二個參數的低八位。

key值的第23-16為該檔案的st_dev屬性的低8位。

key值的第15-0為該檔案的st_ino屬性的低16位。

是以,使用相同的檔案路徑及整數,得到的key值是唯一的,唯一的key值建立的某類IPC機制時将得到同一個IPC機制(但如果使用相同的key值分别建立一個消息隊列和一個信号量,兩者沒有關系)。

11.1.2擁有者及權限

要通路任何一個IPC工具需要對該IPC工具用有相應的權限,一個IPC工具所具有的IPC通路權限在cat /usr/include/bits/ipc.h 檔案中被定義為struct ipc_perm。

struct ipc_perm
  {
    __key_t __key;			/* Key.  */
    __uid_t uid;			/* Owner's user ID.  */
    __gid_t gid;			/* Owner's group ID.  */
    __uid_t cuid;			/* Creator's user ID.  */
    __gid_t cgid;			/* Creator's group ID.  */
    unsigned short int mode;		/* Read/write permission.  */
    unsigned short int __pad1;
    unsigned short int __seq;		/* Sequence number.  */
    unsigned short int __pad2;
    __syscall_ulong_t __glibc_reserved1;
    __syscall_ulong_t __glibc_reserved2;
  };

           

sys/types.h

sys/ipc.h

11.2消息隊列

11.2.1消息隊列IPC原理

1.消息隊列模型

消息隊列是消息的鍊式隊列,下圖為消息隊列模型,整個消息隊列有兩種類型的資料結構。

system V程式間通信-消息隊列

msqid_ds消息隊列資料結構,描述整個消息隊列的屬性,主要包括整個消息隊列的權限、擁有者、兩個重要的指針分别指向消息隊列中的第一個消息和最後一個消息。

msg消息隊列資料結構,整個消息隊列的主體,一個消息隊列中有若幹個消息,每個消息資料結構的基本成員包括消息類型、消息大小、消息内容指針和下一個消息資料結構位置。

由圖可以看出,消息隊列實為一個鍊式隊列,但是,消息隊列還可以基于類型處理。是以,消息隊列的FIFO原則僅僅适用于同類型消息。在Linux作業系統中,對消息隊列進行了以下規定,不同的系統限制值可以通過msgctl使用IPC_INFO數獲得,需要強調的是,不同的Linux版本此值不一樣。

預設情況下,整個系統最多允許16個消息隊列。

每個消息隊列最大為16K。

消息隊列中每個消息最大為8K。

2.消息隊列的基本屬性

整個消息隊列基本屬性由msqid_ds資料結構定義。

通過man msgctl可以查到。

11.2.2Linux消息隊列管理

1.建立消息隊列

在使用一個消息隊列之前,需要使用msgget來建立該消息隊列。

第一個參數key由ftok建立的key值。

第二個參數msgfig的低位用來确定消息隊列的通路權限,其最終權限為目前程序的umask值與設定值perm,類似于open函數,即最終值為perm&~umask,其高位包含以下:

#define IPC_CREAT 00001000//如果key不存在,則建立,存在,傳回ID

#define IPC_EXCL 00002000//如果key存在,傳回失敗

#define IPC_NOWAIT 00004000//如果需要等待,直接傳回錯誤

2.消息隊列屬性控制

建立消息隊列後,可以對該消息隊列的基本屬性進行控制,控制消息隊列屬性的函數為msgctl。

第一個參數msqid為消息隊列辨別符,該值為msgget函數建立消息隊列的傳回值。

第二個參數cmd為執行的控制指令,即要執行的操作。包括:

#define IPC_RMID 0//删除
#define IPC_SET 1//設定ipc_perm
#define IPC_STAT 2//擷取ipe_perm參數
#define IPC_INFO 3//如ipcs,擷取限制資訊
           

IPC_STAT:讀取消息隊列屬性,取得此隊列的msqid_ds結構,并将其放在buf指向的結構中。

IPC_SET:設定消息隊列屬性,按由buf指向的結構中的值,設定此隊列相關的結構中的下列4個字段:msg_perm.uid、msg_perm.gid、msg_perm和msg_qbytes。此指令隻能由下列兩種程序執行:一種是其有效使用者id等于msg_perm.cuid或msg_perm.uid的程序,另一種是超級使用者特權。

IPC_RMID :删除消息隊列。從系統中删除該消息隊列以及仍在該隊列的所有資料,這種删除立即生效。仍在使用這一消息隊列的其他程序在他們下一次試圖對此消息隊列操作時,将出錯傳回EIDRM。此指令隻能由兩種程序執行:一種是其有效使用者id等于msg_perm.cuid或msg_perm.uid的程序,另一種是超級使用者特權。

IPC_INFO :讀取消息隊列的基本情況。

這4個選項也可用于信号量和共享記憶體。

第三個參數是一個臨時msqid_ds結構體類型的變量。用于存儲讀取的消息隊列屬性或需要修改的消息隊列屬性。

3.發送消息到消息隊列

msgsend函數将新的消息添加到消息隊列尾端。

第一個參數msqid為指定的消息隊列辨別符,即将消息添加到那個消息隊列中。

第二個參數msgp指向使用者定義的緩沖區。

struct msgbuf{
long mtype;//消息類型
char mtext[1];//消息内容,在使用時自己重新定義此結構
};
           

mtype是一個正整數,表示消息的類型,是以,接收程序可以用來進行消息選擇(消息隊列在存儲消息時是按照發送的先後順序放置的)。

mtext存儲消息内容,在使用時自己重新定義此結構。

第三個參數為接受資訊的大小,其資料類型為unsigned int ,其大小為0到系統對消息隊列的限制值。

第四個參數用來指定在達到系統為消息隊列所定的界限(如達到字數限制)時應采取的操作。

如果設定為IPC_NOWAIT,如果需要等待,則不發送消息并且調用程序立即傳回錯誤資訊EAGAIN。

如果設定為0,則阻塞程序。

成功調用後,此函數将傳回0,否則傳回-1,同時将消息隊列msqid資料結構的成員執行下列操作:

msg_qnum以1為增量遞增。

msg_lspid設定為調用程序的程序ID。

msg_stime設定為目前時間。

4.從消息隊列接收資訊

msgrcv用于從隊列中擷取消息,

此函數從msqid指定的消息隊列中讀取消息,并将其放置到由msgp指向的記憶體空間中。

第一個參數為讀的對象,即從那個消息隊列獲得資訊。

第二個參數為一個臨時資料結構,用來儲存讀取的資訊。其定義如下:

struct msgbuf{
long mtype;//消息類型
char mtext[1];//消息内容,在使用時自己重新定義此結構
};
           

mtype是接收到的消息類型。mtext為消息的内容位置。

第三個參數msgsz用于指定mtext的大小。如果收到的消息大于msgsz,并且msgflg&MSG_NOERROR為真,則将該消息截至msgsz位元組,消息截斷部分将消失。

第四個參數msgtyp用于指定請求的消息類型。

* If msgtyp is 0, then the first message in the queue is read.
	//接收隊列中第一條消息,任意類型
   * If msgtyp is greater than 0, then the first message in the queue of type msgtyp is read, unless MSG_EXCEPT was specified in msgflg, in which case the first message in the queue of  type  not equal to msgtyp will be read.
	//接受第一條msgtyp類型的消息
   * If msgtyp is less than 0, then the first message in the queue with the lowest type less than or equal to the absolute value of msgtyp will be read.
   //接受第一條最低類型(小于或等于msgtyp)的絕對值的消息。
           

第五個參數msgflg用于指定所需類型消息不在隊列上時将要采取的操作。

如果設定IPC_NOWAIT,如果現在沒有消息,調用程序立即傳回,同時傳回-1,閉關設定errno為ENOMSG。

如果未設定IPC_NOWAIT,則阻塞調用程序,直至出現一下任何一種情況:

某一所需類型消息被放置到隊列中。

msqid從系統中删除。當該情況發生時,将errno設定為EIDRM,并傳回-1.

調用程序收到一個要捕獲的信号。在這種情況下,未收到消息,并且調用程序按signal中指定的方式恢複執行。

接受完消息成功後,該消息将自動從消息隊列中删除,并且傳回接收到的消息的大小,并将對整個消息隊列msqid資料結構的成員執行以下操作:

msg_qnum:以1為減量遞減。

msg_lrpid:設定為調用程序的程序ID。

msg_rtime:設定為目前時間。

11.2.3消息隊列應用示例

1.使用消息隊列實作實時通信

發送端:

➜  _11_systemV cat msg_sender_exp.c 
#include "stdio.h"
#include "stdlib.h"
#include "sys/ipc.h"
#include "sys/msg.h"
#include "string.h"
#include "unistd.h"

struct msgbuf{
  long type;
  char ptr[0];
};

int main(int argc, char **argv)
{
  key_t key;
  key = ftok("/home/vicli/2_linux_program/_11_systemV/", 100);
  int msgid;
  msgid = msgget(key, IPC_CREAT | 0600);
  printf("send msgid = %d\n", msgid);
  pid_t pid = 0;
  pid = fork();
  if(pid == 0){
    while(1){
      printf("pls input msg to send: ");
      char buf[128];
      fgets(buf, 128, stdin);
      struct msgbuf *ptr = malloc(sizeof(struct msgbuf) + strlen(buf) + 1);
      ptr->type = 1;
      memcpy(ptr->ptr, buf, strlen(buf)+1);
	 if(msgsnd(msgid, ptr, strlen(buf) + 1, 0) == -1){
        printf("send error\n");
    }
      free(ptr);
    }
  }
  else{
    struct msgbuf{
      long type;
      char ptr[1024];
    };
    printf("father pid = %d\n", getpid());  
    while(1){
      struct msgbuf mybuf;
      memset(&mybuf, '\0', sizeof(mybuf));
      msgrcv(msgid, &mybuf, 1024, 2, 0);
      printf("rev msg: %s\n", mybuf.ptr);
    }
  }
}

           

接收端:

➜  _11_systemV cat msg_receiver_exp.c 
#include "stdio.h"
#include "stdlib.h"
#include "sys/ipc.h"
#include "sys/msg.h"
#include "string.h"
#include "unistd.h"

struct msgbuf{
  long type;
  char ptr[0];
};

int main(int argc, char **argv)
{
  key_t key;
  key = ftok("/home/vicli/2_linux_program/_11_systemV/", 100);
  int msgid;
  msgid = msgget(key, IPC_CREAT | 0600);
  printf("send msgid = %d\n", msgid);
  pid_t pid = 1;
  pid = fork();
  if(pid == 0){
    while(1){
      printf("pls input msg to send: ");
      char buf[128];
      fgets(buf, 128, stdin);
      struct msgbuf *ptr = malloc(sizeof(struct msgbuf) + strlen(buf) + 1);
      ptr->type = 2;
      memcpy(ptr->ptr, buf, strlen(buf)+1);
	 if(msgsnd(msgid, ptr, strlen(buf) + 1, 0) == -1){
        printf("send error\n");
    }
      free(ptr);
    }
  }
  else{
    struct msgbuf{
      long type;
      char ptr[1024];
    };
    printf("father pid = %d\n", getpid());
    while(1){
      struct msgbuf mybuf;
      memset(&mybuf, '\0', sizeof(mybuf));
      msgrcv(msgid, &mybuf, 1024, 1, 0);
      printf("rev msg: %s\n", mybuf.ptr);
    }
  }
}

           

這兩個差別隻是消息類型不一樣,但是在實際測試中,遇到問題,就是消息類型定義為了int,無法通信,卡了很久,改為了long,測試通過。

經過查找資料,發現win32下目前long和int并無差別。早期差別較大,在gcc編譯下,long為8個位元組,int為4位元組。得注意。

➜  _11_systemV ./test2
long: 8
int: 4

           

繼續閱讀