訊息佇列
在UNIX的SystemV版本,AT&T引進了三種新形式的IPC功能(訊息佇列、訊號量、以及共用記憶體)。但BSD版本的UNIX使用套介面作為主要的IPC形式。Linux系統同時支援這兩個版本。
系統調用msgget()
如果希望建立一個新的訊息佇列,或者希望存取一個已經存在的訊息佇列,你可以使用系統調用msgget()。
系統調用:msgget();
原型:int msgget(key_t key, int msgflg);
傳回值:如果成功,返回訊息佇列標識符
如果失敗,則返回-1:errno=EACCESS(許可權不允許)
EEXIST(隊列已經存在,無法建立)
EIDRM(隊列標誌為刪除)
ENOENT(隊列不存在)
ENOMEM(建立隊列時記憶體不夠)
ENOSPC(超出最大隊列限制)
系統調用msgget()中的第一個參數是關鍵字值(通常是由ftok() 返回的)。然後此關鍵字值將會和其他已經存在於系統核心中的關鍵字值比較。這時,開啟和存取操作是和參數msgflg中的內容相關的。
IPC_CREAT如果核心中沒有此隊列,則建立它。
IPC_EXCL當和IPC_CREAT一起使用時,如果隊列已經存在,則失敗。
如果單獨使用IPC_CREAT,則msgget()要麼返回一個新建立的訊息佇列的標識符,要麼返回具有相同關鍵字值的隊列的標識符。如果IPC_EXCL和IPC_CREAT一起使用,則msgget()要麼建立一個新的訊息佇列,要麼如果隊列已經存在則返回一個失敗值-1。IPC_EXCL單獨使用是沒有用處的。
下面看一個開啟和建立一個訊息佇列的例子:
int open_queue(key_t keyval)
{
intqid;
if((qid=msgget (keyval, IPC_CREAT|0660))==-1)
{
return(-1);
}
return(qid);
}
系統調用msgsnd()
一旦我們得到了隊列標識符,我們就可以在隊列上執行我們希望的操作了。如果想要往隊列中發送一條訊息,你可以使用系統調用msgsnd():
系統調用:msgsnd();
原型:int msgsnd(int msqid,struct msgbuf*msgp,int msgsz,int msgflg);
傳回值:如果成功,0。
如果失敗,-1:errno=EAGAIN(隊列已滿,並且使用了IPC_NOWAIT)
EACCES(沒有寫的許可權)
EFAULT(msgp地址無效)
EIDRM(訊息佇列已經刪除)
EINTR(當等待寫操作時,收到一個訊號)
EINVAL(無效的訊息佇列標識符,非正數的訊息類型,或
者無效的訊息長度)
ENOMEM(沒有足夠的記憶體複製訊息緩衝區)
系統調用msgsnd()的第一個參數是訊息佇列標識符,它是由系統調用msgget返回的。第二個參數是msgp,是指向訊息緩衝區的指標。參數msgsz中包含的是訊息的位元組大小,但不包括訊息類型的長度(4個位元組)。
參數msgflg可以設定為0(此時為忽略此參數),或者使用IPC_NOWAIT。
如果訊息佇列已滿,那麼此訊息則不會寫入到訊息佇列中,控制將返回到調用進程中。如果沒有指明,調用進程將會掛起,直到訊息可以寫入到隊列中。
下面是一個發送訊息的程式:
int send_message(int qid, struct mymsgbuf *qbuf)
{
intresult,length;
/*The length is essentially the size of the structure minus sizeof(mtype)*/
length=sizeof(structmymsgbuf)-sizeof(long);
if((result = msgsnd (qid, qbuf, length, 0))==-1)
{
return(-1);
}
return(result);
}
系統調用:msgrcv();
原型:int msgrcv(intmsqid,structmsgbuf*msgp,intmsgsz,longmtype,intmsgflg);
傳回值:如果成功,則返回複製到訊息緩衝區的位元組數。
如果失敗,則返回-1:errno=E2BIG(訊息的長度大於msgsz,沒有MSG_NOERROR)
EACCES(沒有讀的許可權)
EFAULT(msgp指向的地址是無效的)
EIDRM(隊列已經被刪除)
EINTR(被訊號中斷)
EINVAL(msgqid無效,或者msgsz小於0)
ENOMSG(使用IPC_NOWAIT,同時隊列中的訊息無法滿足要求)
第一個參數用來指定將要讀取訊息的隊列 。第二個參數代表要儲存訊息的訊息緩衝區 的地址。第三個參數是訊息緩衝區的長度,不包括mtype的長度,它可以按照如下的方法計算:
msgsz=sizeof(struct mymsgbuf)-sizeof(long);
第四個參數是要從訊息佇列中讀取的訊息的類型。如果此參數的值為0,那麼隊列中最長時間的一條訊息將返回,而不論其類型是什麼。
如果調用中使用了IPC_NOWAIT作為標誌,那麼當沒有資料可以使用時,調用將把ENOMSG返回到調用進程中。否則,調用進程將會掛起,直到隊列中的一條訊息滿足msgrcv()的參數要求。如果當用戶端等待一條訊息的時候隊列為空白,將會返回EIDRM。如果進程在等待訊息的過程中捕捉到一個訊號,則返回EINTR。
下面就是一個從隊列中讀取訊息的程式:
int read_message(int qid,long type,struct mymsgbuf*qbuf)
{
intresult,length;
/*The length is essentially the size of the structure minus sizeof(mtype)*/
length=sizeof(struct mymsgbuf)-sizeof(long);
if((result=msgrcv(qid,qbuf,length,type,0))==-1)
{
return(-1);
}
return(result);
}
在成功地讀取了一條訊息以後,隊列中的這條訊息的入口將被刪除 。
參數msgflg中的MSG_NOERROR位提供一種額外的用途。如果訊息的實際長度大於msgsz,同時使用了MSG_NOERROR,那麼訊息將會被截斷,只有與msgsz長度相等的訊息返回。一般情況下,系統調用msgrcv()會返回-1,而這條訊息將會繼續儲存在隊列中。我們可以利用這個特點編製一個程式,利用這個程式可以查看訊息佇列的情況,看看符合我們條件的訊息是否已經到來:
int peek_message(int qid,long type)
{
int result,length;
if((result=msgrcv(qid,NULL,0,type,IPC_NOWAIT))==-1)
{
if(errno==E2BIG)
return(TRUE);
}
return(FALSE);
}
在上面的程式中,我們忽略了緩衝區的地址和長度。這樣,系統調用將會失敗。儘管如此,我們可以檢查返回的E2BIG值,它說明合格訊息確實存在。
系統調用msgctl()
下面我們繼續討論如何使用一個給定的訊息佇列的內部資料結構。我們可以使用系統調用msgctl ( )來控制對訊息佇列的操作。
系統調用: msgctl( ) ;
調用原型: int msgctl ( int msgqid, int cmd, struct msqid_ds *buf );
傳回值: 0 ,如果成功。
- 1,如果失敗:errno = EACCES (沒有讀的許可權同時cmd 是IPC_STAT )
EFAULT (buf 指向的地址無效)
EIDRM (在讀取中隊列被刪除)
EINVAL (msgqid無效, 或者msgsz 小於0 )
EPERM (IPC_SET或者IPC_RMID 命令被使用,但調用程式沒有寫的許可權)
下面我們看一下可以使用的幾個命令:
IPC_STAT
讀取訊息佇列的資料結構msqid_ds,並將其儲存在b u f指定的地址中。
IPC_SET
設定訊息佇列的資料結構msqid_ds中的ipc_perm元素的值。這個值取自buf參數。
IPC_RMID
從系統核心中移走訊息佇列。
我們在前面討論過了訊息佇列的資料結構(msqid_ds)。系統核心中為系統中的每一個訊息佇列儲存一個此資料結構的執行個體。通過使用IPC_STAT命令,我們可以得到一個此資料結構的副本。下面的程式就是實現此函數的過程:
int get_queue_ds( int qid, struct msgqid_ds *qbuf )
{
if( msgctl( qid, IPC_STAT, qbuf) == -1)
{
return(-1);
}
return(0);
}
如果不能複製內部緩衝區,調用進程將返回-1。如果調用成功,則返回0。緩衝區中應該包括訊息佇列中的資料結構。
訊息佇列中的資料結構中唯一可以改動的元素就是ipc_perm。它包括隊列的存取許可權和關於隊列建立者和擁有者的資訊。你可以改變使用者的id、使用者的組id以及訊息佇列的存取許可權。
下面是一個修改隊列存模數式的程式:
int change_queue_mode(int qid, char *mode )
{
struct msqid_ds tmpbuf;
/* Retrieve a current copy of the internal data structure */
get_queue_ds( qid, &tmpbuf);
/* Change the permissions using an old trick */
sscanf(mode, "%ho", &tmpbuf.msg_perm.mode);
/* Update the internal data structure */
if( msgctl( qid, IPC_SET, &tmpbuf) == -1)
{
return(-1);
}
return(
}
我們通過調用get_queue_ds來讀取隊列的內部資料結構。然後,我們調用sscanf( )修改資料結構msg_perm中的mode 成員的值。但直到調用msgctl()時,許可權的改變才真正完成。在這裡msgctl()使用的是IPC_SET命令。
最後,我們使用系統調用msgctl ( )中的IPC_RMID命令刪除訊息佇列:
int remove_queue(int qid )
{
if( msgctl( qid, IPC_RMID, 0) == -1)
{
return(-1);
}
return(0);
}
};