- 論壇徽章:
- 0
|
Posix線程編程指南(轉(zhuǎn)貼)
上篇是關(guān)于share memory的,下面是關(guān)于semaphore的。!
Linux環(huán)境進(jìn)程間通信(三):消息隊(duì)列
內(nèi)容:
一、消息隊(duì)列基本概念
二、操作消息隊(duì)列
三、消息隊(duì)列的限制
四、消息隊(duì)列應(yīng)用實(shí)例
小結(jié):
參考資料
關(guān)于作者
相關(guān)內(nèi)容:
Linux 環(huán)境進(jìn)程間通信(一): 管道及有名管道
Linux 環(huán)境進(jìn)程間通信(二):信號(hào)(上)
Linux 環(huán)境進(jìn)程間通信(二):信號(hào)(下)
鄭彥興 (mlinux@163.com)
國(guó)防科大攻讀博士學(xué)位
2003 年 1 月
本系列文章中的前兩部分,我們探討管道及信號(hào)兩種通信機(jī)制,本文將深入第三部分,介紹系統(tǒng) V 消息隊(duì)列及其相應(yīng) API。
消息隊(duì)列(也叫做報(bào)文隊(duì)列)能夠克服早期unix通信機(jī)制的一些缺點(diǎn)。作為早期unix通信機(jī)制之一的信號(hào)能夠傳送的信息量有限,后來(lái)雖然POSIX 1003.1b在信號(hào)的實(shí)時(shí)性方面作了拓廣,使得信號(hào)在傳遞信息量方面有了相當(dāng)程度的改進(jìn),但是信號(hào)這種通信方式更像"即時(shí)"的通信方式,它要求接受信號(hào)的進(jìn)程在某個(gè)時(shí)間范圍內(nèi)對(duì)信號(hào)做出反應(yīng),因此該信號(hào)最多在接受信號(hào)進(jìn)程的生命周期內(nèi)才有意義,信號(hào)所傳遞的信息是接近于隨進(jìn)程持續(xù)的概念(process-persistent),見(jiàn)附錄 1;管道及有名管道及有名管道則是典型的隨進(jìn)程持續(xù)IPC,并且,只能傳送無(wú)格式的字節(jié)流無(wú)疑會(huì)給應(yīng)用程序開(kāi)發(fā)帶來(lái)不便,另外,它的緩沖區(qū)大小也受到限制。
消息隊(duì)列就是一個(gè)消息的鏈表。可以把消息看作一個(gè)記錄,具有特定的格式以及特定的優(yōu)先級(jí)。對(duì)消息隊(duì)列有寫(xiě)權(quán)限的進(jìn)程可以向中按照一定的規(guī)則添加新消息;對(duì)消息隊(duì)列有讀權(quán)限的進(jìn)程則可以從消息隊(duì)列中讀走消息。消息隊(duì)列是隨內(nèi)核持續(xù)的(參見(jiàn)附錄 1)。
目前主要有兩種類型的消息隊(duì)列:POSIX消息隊(duì)列以及系統(tǒng)V消息隊(duì)列,系統(tǒng)V消息隊(duì)列目前被大量使用。考慮到程序的可移植性,新開(kāi)發(fā)的應(yīng)用程序應(yīng)盡量使用POSIX消息隊(duì)列。
在本系列專題的序(深刻理解Linux進(jìn)程間通信(IPC))中,提到對(duì)于消息隊(duì)列、信號(hào)燈、以及共享內(nèi)存區(qū)來(lái)說(shuō),有兩個(gè)實(shí)現(xiàn)版本:POSIX的以及系統(tǒng)V的。Linux內(nèi)核(內(nèi)核2.4.18)支持POSIX信號(hào)燈、POSIX共享內(nèi)存區(qū)以及POSIX消息隊(duì)列,但對(duì)于主流Linux發(fā)行版本之一redhad8.0(內(nèi)核2.4.18),還沒(méi)有提供對(duì)POSIX進(jìn)程間通信API的支持,不過(guò)應(yīng)該只是時(shí)間上的事。
因此,本文將主要介紹系統(tǒng)V消息隊(duì)列及其相應(yīng)API。在沒(méi)有聲明的情況下,以下討論中指的都是系統(tǒng)V消息隊(duì)列。
一、消息隊(duì)列基本概念
系統(tǒng)V消息隊(duì)列是隨內(nèi)核持續(xù)的,只有在內(nèi)核重起或者顯示刪除一個(gè)消息隊(duì)列時(shí),該消息隊(duì)列才會(huì)真正被刪除。因此系統(tǒng)中記錄消息隊(duì)列的數(shù)據(jù)結(jié)構(gòu)(struct ipc_ids msg_ids)位于內(nèi)核中,系統(tǒng)中的所有消息隊(duì)列都可以在結(jié)構(gòu)msg_ids中找到訪問(wèn)入口。
消息隊(duì)列就是一個(gè)消息的鏈表。每個(gè)消息隊(duì)列都有一個(gè)隊(duì)列頭,用結(jié)構(gòu)struct msg_queue來(lái)描述(參見(jiàn)附錄 2)。隊(duì)列頭中包含了該消息隊(duì)列的大量信息,包括消息隊(duì)列鍵值、用戶ID、組ID、消息隊(duì)列中消息數(shù)目等等,甚至記錄了最近對(duì)消息隊(duì)列讀寫(xiě)進(jìn)程的ID。讀者可以訪問(wèn)這些信息,也可以設(shè)置其中的某些信息。
下圖說(shuō)明了內(nèi)核與消息隊(duì)列是怎樣建立起聯(lián)系的:
其中:struct ipc_ids msg_ids是內(nèi)核中記錄消息隊(duì)列的全局?jǐn)?shù)據(jù)結(jié)構(gòu);struct msg_queue是每個(gè)消息隊(duì)列的隊(duì)列頭。
從上圖可以看出,全局?jǐn)?shù)據(jù)結(jié)構(gòu) struct ipc_ids msg_ids 可以訪問(wèn)到每個(gè)消息隊(duì)列頭的第一個(gè)成員:struct kern_ipc_perm;而每個(gè)struct kern_ipc_perm能夠與具體的消息隊(duì)列對(duì)應(yīng)起來(lái)是因?yàn)樵谠摻Y(jié)構(gòu)中,有一個(gè)key_t類型成員key,而key則唯一確定一個(gè)消息隊(duì)列。kern_ipc_perm結(jié)構(gòu)如下:
struct kern_ipc_perm{ //內(nèi)核中記錄消息隊(duì)列的全局?jǐn)?shù)據(jù)結(jié)構(gòu)msg_ids能夠訪問(wèn)到該結(jié)構(gòu);
key_t key; //該鍵值則唯一對(duì)應(yīng)一個(gè)消息隊(duì)列
uid_t uid;
gid_t gid;
uid_t cuid;
gid_t cgid;
mode_t mode;
unsigned long seq;
}
二、操作消息隊(duì)列
對(duì)消息隊(duì)列的操作無(wú)非有下面三種類型:
1、 打開(kāi)或創(chuàng)建消息隊(duì)列
消息隊(duì)列的內(nèi)核持續(xù)性要求每個(gè)消息隊(duì)列都在系統(tǒng)范圍內(nèi)對(duì)應(yīng)唯一的鍵值,所以,要獲得一個(gè)消息隊(duì)列的描述字,只需提供該消息隊(duì)列的鍵值即可;
注:消息隊(duì)列描述字是由在系統(tǒng)范圍內(nèi)唯一的鍵值生成的,而鍵值可以看作對(duì)應(yīng)系統(tǒng)內(nèi)的一條路經(jīng)。
2、 讀寫(xiě)操作
消息讀寫(xiě)操作非常簡(jiǎn)單,對(duì)開(kāi)發(fā)人員來(lái)說(shuō),每個(gè)消息都類似如下的數(shù)據(jù)結(jié)構(gòu):
struct msgbuf{
long mtype;
char mtext[1];
};
mtype成員代表消息類型,從消息隊(duì)列中讀取消息的一個(gè)重要依據(jù)就是消息的類型;mtext是消息內(nèi)容,當(dāng)然長(zhǎng)度不一定為1。因此,對(duì)于發(fā)送消息來(lái)說(shuō),首先預(yù)置一個(gè)msgbuf緩沖區(qū)并寫(xiě)入消息類型和內(nèi)容,調(diào)用相應(yīng)的發(fā)送函數(shù)即可;對(duì)讀取消息來(lái)說(shuō),首先分配這樣一個(gè)msgbuf緩沖區(qū),然后把消息讀入該緩沖區(qū)即可。
3、 獲得或設(shè)置消息隊(duì)列屬性:
消息隊(duì)列的信息基本上都保存在消息隊(duì)列頭中,因此,可以分配一個(gè)類似于消息隊(duì)列頭的結(jié)構(gòu)(struct msqid_ds,見(jiàn)附錄 2),來(lái)返回消息隊(duì)列的屬性;同樣可以設(shè)置該數(shù)據(jù)結(jié)構(gòu)。
消息隊(duì)列API
1、文件名到鍵值
#include <sys/types.h>;
#include <sys/ipc.h>;
key_t ftok (char*pathname, char proj);
它返回與路徑pathname相對(duì)應(yīng)的一個(gè)鍵值。該函數(shù)不直接對(duì)消息隊(duì)列操作,但在調(diào)用ipc(MSGGET,…)或msgget()來(lái)獲得消息隊(duì)列描述字前,往往要調(diào)用該函數(shù)。典型的調(diào)用代碼是:
key=ftok(path_ptr, 'a');
ipc_id=ipc(MSGGET, (int)key, flags,0,NULL,0);
…
2、linux為操作系統(tǒng)V進(jìn)程間通信的三種方式(消息隊(duì)列、信號(hào)燈、共享內(nèi)存區(qū))提供了一個(gè)統(tǒng)一的用戶界面:
int ipc(unsigned int call, int first, int second, int third, void *ptr, long fifth);
第一個(gè)參數(shù)指明對(duì)IPC對(duì)象的操作方式,對(duì)消息隊(duì)列而言共有四種操作:MSGSND、MSGRCV、MSGGET以及MSGCTL,分別代表向消息隊(duì)列發(fā)送消息、從消息隊(duì)列讀取消息、打開(kāi)或創(chuàng)建消息隊(duì)列、控制消息隊(duì)列;first參數(shù)代表唯一的IPC對(duì)象;下面將介紹四種操作。
int ipc(MSGGET, int first, int second, int third, void *ptr, long fifth);
與該操作對(duì)應(yīng)的系統(tǒng)V調(diào)用為:int msgget( (key_t)first,second)。
int ipc(MSGCTL, int first, int second, int third, void *ptr, long fifth)
與該操作對(duì)應(yīng)的系統(tǒng)V調(diào)用為:int msgctl( first,second, (struct msqid_ds*) ptr)。
int ipc(MSGSND, int first, int second, int third, void *ptr, long fifth);
與該操作對(duì)應(yīng)的系統(tǒng)V調(diào)用為:int msgsnd( first, (struct msgbuf*)ptr, second, third)。
int ipc(MSGRCV, int first, int second, int third, void *ptr, long fifth);
與該操作對(duì)應(yīng)的系統(tǒng)V調(diào)用為:int msgrcv( first,(struct msgbuf*)ptr, second, fifth,third),
注:本人不主張采用系統(tǒng)調(diào)用ipc(),而更傾向于采用系統(tǒng)V或者POSIX進(jìn)程間通信API。原因如下:
雖然該系統(tǒng)調(diào)用提供了統(tǒng)一的用戶界面,但正是由于這個(gè)特性,它的參數(shù)幾乎不能給出特定的實(shí)際意義(如以first、second來(lái)命名參數(shù)),在一定程度上造成開(kāi)發(fā)不便。
正如ipc手冊(cè)所說(shuō)的:ipc()是linux所特有的,編寫(xiě)程序時(shí)應(yīng)注意程序的移植性問(wèn)題;
該系統(tǒng)調(diào)用的實(shí)現(xiàn)不過(guò)是把系統(tǒng)V IPC函數(shù)進(jìn)行了封裝,沒(méi)有任何效率上的優(yōu)勢(shì);
系統(tǒng)V在IPC方面的API數(shù)量不多,形式也較簡(jiǎn)潔。
3.系統(tǒng)V消息隊(duì)列API
系統(tǒng)V消息隊(duì)列API共有四個(gè),使用時(shí)需要包括幾個(gè)頭文件:
#include <sys/types.h>;
#include <sys/ipc.h>;
#include <sys/msg.h>;
1)int msgget(key_t key, int msgflg)
參數(shù)key是一個(gè)鍵值,由ftok獲得;msgflg參數(shù)是一些標(biāo)志位。該調(diào)用返回與健值key相對(duì)應(yīng)的消息隊(duì)列描述字。
在以下兩種情況下,該調(diào)用將創(chuàng)建一個(gè)新的消息隊(duì)列:
如果沒(méi)有消息隊(duì)列與健值key相對(duì)應(yīng),并且msgflg中包含了IPC_CREAT標(biāo)志位;
key參數(shù)為IPC_PRIVATE;
參數(shù)msgflg可以為以下:IPC_CREAT、IPC_EXCL、IPC_NOWAIT或三者的或結(jié)果。
調(diào)用返回:成功返回消息隊(duì)列描述字,否則返回-1。
注:參數(shù)key設(shè)置成常數(shù)IPC_PRIVATE并不意味著其他進(jìn)程不能訪問(wèn)該消息隊(duì)列,只意味著即將創(chuàng)建新的消息隊(duì)列。
2)int msgrcv(int msqid, struct msgbuf *msgp, int msgsz, long msgtyp, int msgflg);
該系統(tǒng)調(diào)用從msgid代表的消息隊(duì)列中讀取一個(gè)消息,并把消息存儲(chǔ)在msgp指向的msgbuf結(jié)構(gòu)中。
msqid為消息隊(duì)列描述字;消息返回后存儲(chǔ)在msgp指向的地址,msgsz指定msgbuf的mtext成員的長(zhǎng)度(即消息內(nèi)容的長(zhǎng)度),msgtyp為請(qǐng)求讀取的消息類型;讀消息標(biāo)志msgflg可以為以下幾個(gè)常值的或:
IPC_NOWAIT 如果沒(méi)有滿足條件的消息,調(diào)用立即返回,此時(shí),errno=ENOMSG
IPC_EXCEPT 與msgtyp>;0配合使用,返回隊(duì)列中第一個(gè)類型不為msgtyp的消息
IPC_NOERROR 如果隊(duì)列中滿足條件的消息內(nèi)容大于所請(qǐng)求的msgsz字節(jié),則把該消息截?cái),截(cái)嗖糠謱G失。
msgrcv手冊(cè)中詳細(xì)給出了消息類型取不同值時(shí)(>;0; <0; =0),調(diào)用將返回消息隊(duì)列中的哪個(gè)消息。
msgrcv()解除阻塞的條件有三個(gè):
消息隊(duì)列中有了滿足條件的消息;
msqid代表的消息隊(duì)列被刪除;
調(diào)用msgrcv()的進(jìn)程被信號(hào)中斷;
調(diào)用返回:成功返回讀出消息的實(shí)際字節(jié)數(shù),否則返回-1。
3)int msgsnd(int msqid, struct msgbuf *msgp, int msgsz, int msgflg);
向msgid代表的消息隊(duì)列發(fā)送一個(gè)消息,即將發(fā)送的消息存儲(chǔ)在msgp指向的msgbuf結(jié)構(gòu)中,消息的大小由msgze指定。
對(duì)發(fā)送消息來(lái)說(shuō),有意義的msgflg標(biāo)志為IPC_NOWAIT,指明在消息隊(duì)列沒(méi)有足夠空間容納要發(fā)送的消息時(shí),msgsnd是否等待。造成msgsnd()等待的條件有兩種:
當(dāng)前消息的大小與當(dāng)前消息隊(duì)列中的字節(jié)數(shù)之和超過(guò)了消息隊(duì)列的總?cè)萘浚?
當(dāng)前消息隊(duì)列的消息數(shù)(單位"個(gè)")不小于消息隊(duì)列的總?cè)萘浚▎挝?quot;字節(jié)數(shù)"),此時(shí),雖然消息隊(duì)列中的消息數(shù)目很多,但基本上都只有一個(gè)字節(jié)。
msgsnd()解除阻塞的條件有三個(gè):
不滿足上述兩個(gè)條件,即消息隊(duì)列中有容納該消息的空間;
msqid代表的消息隊(duì)列被刪除;
調(diào)用msgsnd()的進(jìn)程被信號(hào)中斷;
調(diào)用返回:成功返回0,否則返回-1。
4)int msgctl(int msqid, int cmd, struct msqid_ds *buf);
該系統(tǒng)調(diào)用對(duì)由msqid標(biāo)識(shí)的消息隊(duì)列執(zhí)行cmd操作,共有三種cmd操作:IPC_STAT、IPC_SET 、IPC_RMID。
IPC_STAT:該命令用來(lái)獲取消息隊(duì)列信息,返回的信息存貯在buf指向的msqid結(jié)構(gòu)中;
IPC_SET:該命令用來(lái)設(shè)置消息隊(duì)列的屬性,要設(shè)置的屬性存儲(chǔ)在buf指向的msqid結(jié)構(gòu)中;可設(shè)置屬性包括:msg_perm.uid、msg_perm.gid、msg_perm.mode以及msg_qbytes,同時(shí),也影響msg_ctime成員。
IPC_RMID:刪除msqid標(biāo)識(shí)的消息隊(duì)列;
調(diào)用返回:成功返回0,否則返回-1。
三、消息隊(duì)列的限制
每個(gè)消息隊(duì)列的容量(所能容納的字節(jié)數(shù))都有限制,該值因系統(tǒng)不同而不同。在后面的應(yīng)用實(shí)例中,輸出了redhat 8.0的限制,結(jié)果參見(jiàn)附錄 3。
另一個(gè)限制是每個(gè)消息隊(duì)列所能容納的最大消息數(shù):在redhad 8.0中,該限制是受消息隊(duì)列容量制約的:消息個(gè)數(shù)要小于消息隊(duì)列的容量(字節(jié)數(shù))。
注:上述兩個(gè)限制是針對(duì)每個(gè)消息隊(duì)列而言的,系統(tǒng)對(duì)消息隊(duì)列的限制還有系統(tǒng)范圍內(nèi)的最大消息隊(duì)列個(gè)數(shù),以及整個(gè)系統(tǒng)范圍內(nèi)的最大消息數(shù)。一般來(lái)說(shuō),實(shí)際開(kāi)發(fā)過(guò)程中不會(huì)超過(guò)這個(gè)限制。
四、消息隊(duì)列應(yīng)用實(shí)例
消息隊(duì)列應(yīng)用相對(duì)較簡(jiǎn)單,下面實(shí)例基本上覆蓋了對(duì)消息隊(duì)列的所有操作,同時(shí),程序輸出結(jié)果有助于加深對(duì)前面所講的某些規(guī)則及消息隊(duì)列限制的理解。
#include <sys/types.h>;
#include <sys/msg.h>;
#include <unistd.h>;
void msg_stat(int,struct msqid_ds );
main()
{
int gflags,sflags,rflags;
key_t key;
int msgid;
int reval;
struct msgsbuf{
int mtype;
char mtext[1];
}msg_sbuf;
struct msgmbuf
{
int mtype;
char mtext[10];
}msg_rbuf;
struct msqid_ds msg_ginfo,msg_sinfo;
char* msgpath="/unix/msgqueue";
key=ftok(msgpath,'a');
gflags=IPC_CREAT|IPC_EXCL;
msgid=msgget(key,gflags|00666);
if(msgid==-1)
{
printf("msg create error\n" ;
return;
}
//創(chuàng)建一個(gè)消息隊(duì)列后,輸出消息隊(duì)列缺省屬性
msg_stat(msgid,msg_ginfo);
sflags=IPC_NOWAIT;
msg_sbuf.mtype=10;
msg_sbuf.mtext[0]='a';
reval=msgsnd(msgid,&msg_sbuf,sizeof(msg_sbuf.mtext),sflags);
if(reval==-1)
{
printf("message send error\n" ;
}
//發(fā)送一個(gè)消息后,輸出消息隊(duì)列屬性
msg_stat(msgid,msg_ginfo);
rflags=IPC_NOWAIT|MSG_NOERROR;
reval=msgrcv(msgid,&msg_rbuf,4,10,rflags);
if(reval==-1)
printf("read msg error\n" ;
else
printf("read from msg queue %d bytes\n",reval);
//從消息隊(duì)列中讀出消息后,輸出消息隊(duì)列屬性
msg_stat(msgid,msg_ginfo);
msg_sinfo.msg_perm.uid=8;//just a try
msg_sinfo.msg_perm.gid=8;//
msg_sinfo.msg_qbytes=16388;
//此處驗(yàn)證超級(jí)用戶可以更改消息隊(duì)列的缺省msg_qbytes
//注意這里設(shè)置的值大于缺省值
reval=msgctl(msgid,IPC_SET,&msg_sinfo);
if(reval==-1)
{
printf("msg set info error\n" ;
return;
}
msg_stat(msgid,msg_ginfo);
//驗(yàn)證設(shè)置消息隊(duì)列屬性
reval=msgctl(msgid,IPC_RMID,NULL);//刪除消息隊(duì)列
if(reval==-1)
{
printf("unlink msg queue error\n" ;
return;
}
}
void msg_stat(int msgid,struct msqid_ds msg_info)
{
int reval;
sleep(1);//只是為了后面輸出時(shí)間的方便
reval=msgctl(msgid,IPC_STAT,&msg_info);
if(reval==-1)
{
printf("get msg info error\n" ;
return;
}
printf("\n" ;
printf("current number of bytes on queue is %d\n",msg_info.msg_cbytes);
printf("number of messages in queue is %d\n",msg_info.msg_qnum);
printf("max number of bytes on queue is %d\n",msg_info.msg_qbytes);
//每個(gè)消息隊(duì)列的容量(字節(jié)數(shù))都有限制MSGMNB,值的大小因系統(tǒng)而異。在創(chuàng)建新的消息隊(duì)列時(shí),//msg_qbytes的缺省值就是MSGMNB
printf("pid of last msgsnd is %d\n",msg_info.msg_lspid);
printf("pid of last msgrcv is %d\n",msg_info.msg_lrpid);
printf("last msgsnd time is %s", ctime(&(msg_info.msg_stime)));
printf("last msgrcv time is %s", ctime(&(msg_info.msg_rtime)));
printf("last change time is %s", ctime(&(msg_info.msg_ctime)));
printf("msg uid is %d\n",msg_info.msg_perm.uid);
printf("msg gid is %d\n",msg_info.msg_perm.gid);
}
程序輸出結(jié)果見(jiàn)附錄 3。
小結(jié):
消息隊(duì)列與管道以及有名管道相比,具有更大的靈活性,首先,它提供有格式字節(jié)流,有利于減少開(kāi)發(fā)人員的工作量;其次,消息具有類型,在實(shí)際應(yīng)用中,可作為優(yōu)先級(jí)使用。這兩點(diǎn)是管道以及有名管道所不能比的。同樣,消息隊(duì)列可以在幾個(gè)進(jìn)程間復(fù)用,而不管這幾個(gè)進(jìn)程是否具有親緣關(guān)系,這一點(diǎn)與有名管道很相似;但消息隊(duì)列是隨內(nèi)核持續(xù)的,與有名管道(隨進(jìn)程持續(xù))相比,生命力更強(qiáng),應(yīng)用空間更大。
附錄 1:在參考文獻(xiàn)[1]中,給出了IPC隨進(jìn)程持續(xù)、隨內(nèi)核持續(xù)以及隨文件系統(tǒng)持續(xù)的定義:
隨進(jìn)程持續(xù):IPC一直存在到打開(kāi)IPC對(duì)象的最后一個(gè)進(jìn)程關(guān)閉該對(duì)象為止。如管道和有名管道;
隨內(nèi)核持續(xù):IPC一直持續(xù)到內(nèi)核重新自舉或者顯示刪除該對(duì)象為止。如消息隊(duì)列、信號(hào)燈以及共享內(nèi)存等;
隨文件系統(tǒng)持續(xù):IPC一直持續(xù)到顯示刪除該對(duì)象為止。
附錄 2:
結(jié)構(gòu)msg_queue用來(lái)描述消息隊(duì)列頭,存在于系統(tǒng)空間:
struct msg_queue {
struct kern_ipc_perm q_perm;
time_t q_stime; /* last msgsnd time */
time_t q_rtime; /* last msgrcv time */
time_t q_ctime; /* last change time */
unsigned long q_cbytes; /* current number of bytes on queue */
unsigned long q_qnum; /* number of messages in queue */
unsigned long q_qbytes; /* max number of bytes on queue */
pid_t q_lspid; /* pid of last msgsnd */
pid_t q_lrpid; /* last receive pid */
struct list_head q_messages;
struct list_head q_receivers;
struct list_head q_senders;
};
結(jié)構(gòu)msqid_ds用來(lái)設(shè)置或返回消息隊(duì)列的信息,存在于用戶空間;
struct msqid_ds {
struct ipc_perm msg_perm;
struct msg *msg_first; /* first message on queue,unused */
struct msg *msg_last; /* last message in queue,unused */
__kernel_time_t msg_stime; /* last msgsnd time */
__kernel_time_t msg_rtime; /* last msgrcv time */
__kernel_time_t msg_ctime; /* last change time */
unsigned long msg_lcbytes; /* Reuse junk fields for 32 bit */
unsigned long msg_lqbytes; /* ditto */
unsigned short msg_cbytes; /* current number of bytes on queue */
unsigned short msg_qnum; /* number of messages in queue */
unsigned short msg_qbytes; /* max number of bytes on queue */
__kernel_ipc_pid_t msg_lspid; /* pid of last msgsnd */
__kernel_ipc_pid_t msg_lrpid; /* last receive pid */
};
//可以看出上述兩個(gè)結(jié)構(gòu)很相似。
附錄 3:消息隊(duì)列實(shí)例輸出結(jié)果:
current number of bytes on queue is 0
number of messages in queue is 0
max number of bytes on queue is 16384
pid of last msgsnd is 0
pid of last msgrcv is 0
last msgsnd time is Thu Jan 1 08:00:00 1970
last msgrcv time is Thu Jan 1 08:00:00 1970
last change time is Sun Dec 29 18:28:20 2002
msg uid is 0
msg gid is 0
//上面剛剛創(chuàng)建一個(gè)新消息隊(duì)列時(shí)的輸出
current number of bytes on queue is 1
number of messages in queue is 1
max number of bytes on queue is 16384
pid of last msgsnd is 2510
pid of last msgrcv is 0
last msgsnd time is Sun Dec 29 18:28:21 2002
last msgrcv time is Thu Jan 1 08:00:00 1970
last change time is Sun Dec 29 18:28:20 2002
msg uid is 0
msg gid is 0
read from msg queue 1 bytes
//實(shí)際讀出的字節(jié)數(shù)
current number of bytes on queue is 0
number of messages in queue is 0
max number of bytes on queue is 16384 //每個(gè)消息隊(duì)列最大容量(字節(jié)數(shù))
pid of last msgsnd is 2510
pid of last msgrcv is 2510
last msgsnd time is Sun Dec 29 18:28:21 2002
last msgrcv time is Sun Dec 29 18:28:22 2002
last change time is Sun Dec 29 18:28:20 2002
msg uid is 0
msg gid is 0
current number of bytes on queue is 0
number of messages in queue is 0
max number of bytes on queue is 16388 //可看出超級(jí)用戶可修改消息隊(duì)列最大容量
pid of last msgsnd is 2510
pid of last msgrcv is 2510 //對(duì)操作消息隊(duì)列進(jìn)程的跟蹤
last msgsnd time is Sun Dec 29 18:28:21 2002
last msgrcv time is Sun Dec 29 18:28:22 2002
last change time is Sun Dec 29 18:28:23 2002 //msgctl()調(diào)用對(duì)msg_ctime有影響
msg uid is 8
msg gid is 8
參考文獻(xiàn):
UNIX網(wǎng)絡(luò)編程第二卷:進(jìn)程間通信,作者:W.Richard Stevens,譯者:楊繼張,清華大學(xué)出版社。對(duì)POSIX以及系統(tǒng)V消息隊(duì)列都有闡述,對(duì)Linux環(huán)境下的程序開(kāi)發(fā)有極大的啟發(fā)意義。
linux內(nèi)核源代碼情景分析(上),毛德操、胡希明著,浙江大學(xué)出版社,給出了系統(tǒng)V消息隊(duì)列相關(guān)的源代碼分析。
http://www.fanqiang.com/a4/b2/20010508/113315.html,主要闡述linux下對(duì)文件的操作,詳細(xì)介紹了對(duì)文件的存取權(quán)限位,對(duì)IPC對(duì)象的存取權(quán)限同樣具有很好的借鑒意義。
msgget、msgsnd、msgrcv、msgctl手冊(cè)
關(guān)于作者:
鄭彥興,國(guó)防科大攻讀博士學(xué)位。聯(lián)系方式: mlinux@163.com
//////////////////////////////////////////////////////////////////////////
國(guó)防科大
2003 年 4 月
一、信號(hào)燈概述
信號(hào)燈與其他進(jìn)程間通信方式不大相同,它主要提供對(duì)進(jìn)程間共享資源訪問(wèn)控制機(jī)制。相當(dāng)于內(nèi)存中的標(biāo)志,進(jìn)程可以根據(jù)它判定是否能夠訪問(wèn)某些共享資源,同時(shí),進(jìn)程也可以修改該標(biāo)志。除了用于訪問(wèn)控制外,還可用于進(jìn)程同步。信號(hào)燈有以下兩種類型:
二值信號(hào)燈:最簡(jiǎn)單的信號(hào)燈形式,信號(hào)燈的值只能取0或1,類似于互斥鎖。
注:二值信號(hào)燈能夠?qū)崿F(xiàn)互斥鎖的功能,但兩者的關(guān)注內(nèi)容不同。信號(hào)燈強(qiáng)調(diào)共享資源,只要共享資源可用,其他進(jìn)程同樣可以修改信號(hào)燈的值;互斥鎖更強(qiáng)調(diào)進(jìn)程,占用資源的進(jìn)程使用完資源后,必須由進(jìn)程本身來(lái)解鎖。
計(jì)算信號(hào)燈:信號(hào)燈的值可以取任意非負(fù)值(當(dāng)然受內(nèi)核本身的約束)。
二、Linux信號(hào)燈
linux對(duì)信號(hào)燈的支持狀況與消息隊(duì)列一樣,在red had 8.0發(fā)行版本中支持的是系統(tǒng)V的信號(hào)燈。因此,本文將主要介紹系統(tǒng)V信號(hào)燈及其相應(yīng)API。在沒(méi)有聲明的情況下,以下討論中指的都是系統(tǒng)V信號(hào)燈。
注意,通常所說(shuō)的系統(tǒng)V信號(hào)燈指的是計(jì)數(shù)信號(hào)燈集。
三、信號(hào)燈與內(nèi)核
1、系統(tǒng)V信號(hào)燈是隨內(nèi)核持續(xù)的,只有在內(nèi)核重起或者顯示刪除一個(gè)信號(hào)燈集時(shí),該信號(hào)燈集才會(huì)真正被刪除。因此系統(tǒng)中記錄信號(hào)燈的數(shù)據(jù)結(jié)構(gòu)(struct ipc_ids sem_ids)位于內(nèi)核中,系統(tǒng)中的所有信號(hào)燈都可以在結(jié)構(gòu)sem_ids中找到訪問(wèn)入口。
2、下圖說(shuō)明了內(nèi)核與信號(hào)燈是怎樣建立起聯(lián)系的:
其中:struct ipc_ids sem_ids是內(nèi)核中記錄信號(hào)燈的全局?jǐn)?shù)據(jù)結(jié)構(gòu);描述一個(gè)具體的信號(hào)燈及其相關(guān)信息。
其中,struct sem結(jié)構(gòu)如下:
struct sem{
int semval; // current value
int sempid // pid of last operation
}
從上圖可以看出,全局?jǐn)?shù)據(jù)結(jié)構(gòu)struct ipc_ids sem_ids可以訪問(wèn)到struct kern_ipc_perm的第一個(gè)成員:struct kern_ipc_perm;而每個(gè)struct kern_ipc_perm能夠與具體的信號(hào)燈對(duì)應(yīng)起來(lái)是因?yàn)樵谠摻Y(jié)構(gòu)中,有一個(gè)key_t類型成員key,而key則唯一確定一個(gè)信號(hào)燈集;同時(shí),結(jié)構(gòu)struct kern_ipc_perm的最后一個(gè)成員sem_nsems確定了該信號(hào)燈在信號(hào)燈集中的順序,這樣內(nèi)核就能夠記錄每個(gè)信號(hào)燈的信息了。kern_ipc_perm結(jié)構(gòu)參見(jiàn)《Linux環(huán)境進(jìn)程間通信(三):消息隊(duì)列》。struct sem_array見(jiàn)附錄1。
四、操作信號(hào)燈
對(duì)消息隊(duì)列的操作無(wú)非有下面三種類型:
1、 打開(kāi)或創(chuàng)建信號(hào)燈
與消息隊(duì)列的創(chuàng)建及打開(kāi)基本相同,不再詳述。
2、 信號(hào)燈值操作
linux可以增加或減小信號(hào)燈的值,相應(yīng)于對(duì)共享資源的釋放和占有。具體參見(jiàn)后面的semop系統(tǒng)調(diào)用。
3、 獲得或設(shè)置信號(hào)燈屬性:
系統(tǒng)中的每一個(gè)信號(hào)燈集都對(duì)應(yīng)一個(gè)struct sem_array結(jié)構(gòu),該結(jié)構(gòu)記錄了信號(hào)燈集的各種信息,存在于系統(tǒng)空間。為了設(shè)置、獲得該信號(hào)燈集的各種信息及屬性,在用戶空間有一個(gè)重要的聯(lián)合結(jié)構(gòu)與之對(duì)應(yīng),即union semun。
聯(lián)合semun數(shù)據(jù)結(jié)構(gòu)各成員意義參見(jiàn)附錄2
信號(hào)燈API
1、文件名到鍵值
#include <sys/types.h>;
#include <sys/ipc.h>;
key_t ftok (char*pathname, char proj);
它返回與路徑pathname相對(duì)應(yīng)的一個(gè)鍵值,具體用法請(qǐng)參考《Linux環(huán)境進(jìn)程間通信(三):消息隊(duì)列》。
2、 linux特有的ipc()調(diào)用:
int ipc(unsigned int call, int first, int second, int third, void *ptr, long fifth);
參數(shù)call取不同值時(shí),對(duì)應(yīng)信號(hào)燈的三個(gè)系統(tǒng)調(diào)用:
當(dāng)call為SEMOP時(shí),對(duì)應(yīng)int semop(int semid, struct sembuf *sops, unsigned nsops)調(diào)用;
當(dāng)call為SEMGET時(shí),對(duì)應(yīng)int semget(key_t key, int nsems, int semflg)調(diào)用;
當(dāng)call為SEMCTL時(shí),對(duì)應(yīng)int semctl(int semid,int semnum,int cmd,union semun arg)調(diào)用;
這些調(diào)用將在后面闡述。
注:本人不主張采用系統(tǒng)調(diào)用ipc(),而更傾向于采用系統(tǒng)V或者POSIX進(jìn)程間通信API。原因已在Linux環(huán)境進(jìn)程間通信(三):消息隊(duì)列中給出。
3、系統(tǒng)V信號(hào)燈API
系統(tǒng)V消息隊(duì)列API只有三個(gè),使用時(shí)需要包括幾個(gè)頭文件:
#include <sys/types.h>;
#include <sys/ipc.h>;
#include <sys/sem.h>;
1)int semget(key_t key, int nsems, int semflg)
參數(shù)key是一個(gè)鍵值,由ftok獲得,唯一標(biāo)識(shí)一個(gè)信號(hào)燈集,用法與msgget()中的key相同;參數(shù)nsems指定打開(kāi)或者新創(chuàng)建的信號(hào)燈集中將包含信號(hào)燈的數(shù)目;semflg參數(shù)是一些標(biāo)志位。參數(shù)key和semflg的取值,以及何時(shí)打開(kāi)已有信號(hào)燈集或者創(chuàng)建一個(gè)新的信號(hào)燈集與msgget()中的對(duì)應(yīng)部分相同,不再祥述。
該調(diào)用返回與健值key相對(duì)應(yīng)的信號(hào)燈集描述字。
調(diào)用返回:成功返回信號(hào)燈集描述字,否則返回-1。
注:如果key所代表的信號(hào)燈已經(jīng)存在,且semget指定了IPC_CREAT|IPC_EXCL標(biāo)志,那么即使參數(shù)nsems與原來(lái)信號(hào)燈的數(shù)目不等,返回的也是EEXIST錯(cuò)誤;如果semget只指定了IPC_CREAT標(biāo)志,那么參數(shù)nsems必須與原來(lái)的值一致,在后面程序?qū)嵗羞要進(jìn)一步說(shuō)明。
2)int semop(int semid, struct sembuf *sops, unsigned nsops);
semid是信號(hào)燈集ID,sops指向數(shù)組的每一個(gè)sembuf結(jié)構(gòu)都刻畫(huà)一個(gè)在特定信號(hào)燈上的操作。nsops為sops指向數(shù)組的大小。
sembuf結(jié)構(gòu)如下:
struct sembuf {
unsigned short sem_num; /* semaphore index in array */
short sem_op; /* semaphore operation */
short sem_flg; /* operation flags */
};
sem_num對(duì)應(yīng)信號(hào)集中的信號(hào)燈,0對(duì)應(yīng)第一個(gè)信號(hào)燈。sem_flg可取IPC_NOWAIT以及SEM_UNDO兩個(gè)標(biāo)志。如果設(shè)置了SEM_UNDO標(biāo)志,那么在進(jìn)程結(jié)束時(shí),相應(yīng)的操作將被取消,這是比較重要的一個(gè)標(biāo)志位。如果設(shè)置了該標(biāo)志位,那么在進(jìn)程沒(méi)有釋放共享資源就退出時(shí),內(nèi)核將代為釋放。如果為一個(gè)信號(hào)燈設(shè)置了該標(biāo)志,內(nèi)核都要分配一個(gè)sem_undo結(jié)構(gòu)來(lái)記錄它,為的是確保以后資源能夠安全釋放。事實(shí)上,如果進(jìn)程退出了,那么它所占用就釋放了,但信號(hào)燈值卻沒(méi)有改變,此時(shí),信號(hào)燈值反映的已經(jīng)不是資源占有的實(shí)際情況,在這種情況下,問(wèn)題的解決就靠?jī)?nèi)核來(lái)完成。這有點(diǎn)像僵尸進(jìn)程,進(jìn)程雖然退出了,資源也都釋放了,但內(nèi)核進(jìn)程表中仍然有它的記錄,此時(shí)就需要父進(jìn)程調(diào)用waitpid來(lái)解決問(wèn)題了。
sem_op的值大于0,等于0以及小于0確定了對(duì)sem_num指定的信號(hào)燈進(jìn)行的三種操作。具體請(qǐng)參考linux相應(yīng)手冊(cè)頁(yè)。
這里需要強(qiáng)調(diào)的是semop同時(shí)操作多個(gè)信號(hào)燈,在實(shí)際應(yīng)用中,對(duì)應(yīng)多種資源的申請(qǐng)或釋放。semop保證操作的原子性,這一點(diǎn)尤為重要。尤其對(duì)于多種資源的申請(qǐng)來(lái)說(shuō),要么一次性獲得所有資源,要么放棄申請(qǐng),要么在不占有任何資源情況下繼續(xù)等待,這樣,一方面避免了資源的浪費(fèi);另一方面,避免了進(jìn)程之間由于申請(qǐng)共享資源造成死鎖。
也許從實(shí)際含義上更好理解這些操作:信號(hào)燈的當(dāng)前值記錄相應(yīng)資源目前可用數(shù)目;sem_op>;0對(duì)應(yīng)相應(yīng)進(jìn)程要釋放sem_op數(shù)目的共享資源;sem_op=0可以用于對(duì)共享資源是否已用完的測(cè)試;sem_op<0相當(dāng)于進(jìn)程要申請(qǐng)-sem_op個(gè)共享資源。再聯(lián)想操作的原子性,更不難理解該系統(tǒng)調(diào)用何時(shí)正常返回,何時(shí)睡眠等待。
調(diào)用返回:成功返回0,否則返回-1。
3) int semctl(int semid,int semnum,int cmd,union semun arg)
該系統(tǒng)調(diào)用實(shí)現(xiàn)對(duì)信號(hào)燈的各種控制操作,參數(shù)semid指定信號(hào)燈集,參數(shù)cmd指定具體的操作類型;參數(shù)semnum指定對(duì)哪個(gè)信號(hào)燈操作,只對(duì)幾個(gè)特殊的cmd操作有意義;arg用于設(shè)置或返回信號(hào)燈信息。
該系統(tǒng)調(diào)用詳細(xì)信息請(qǐng)參見(jiàn)其手冊(cè)頁(yè),這里只給出參數(shù)cmd所能指定的操作。
IPC_STAT 獲取信號(hào)燈信息,信息由arg.buf返回;
IPC_SET 設(shè)置信號(hào)燈信息,待設(shè)置信息保存在arg.buf中(在manpage中給出了可以設(shè)置哪些信息);
GETALL 返回所有信號(hào)燈的值,結(jié)果保存在arg.array中,參數(shù)sennum被忽略;
GETNCNT 返回等待semnum所代表信號(hào)燈的值增加的進(jìn)程數(shù),相當(dāng)于目前有多少進(jìn)程在等待semnum代表的信號(hào)燈所代表的共享資源;
GETPID 返回最后一個(gè)對(duì)semnum所代表信號(hào)燈執(zhí)行semop操作的進(jìn)程ID;
GETVAL 返回semnum所代表信號(hào)燈的值;
GETZCNT 返回等待semnum所代表信號(hào)燈的值變成0的進(jìn)程數(shù);
SETALL 通過(guò)arg.array更新所有信號(hào)燈的值;同時(shí),更新與本信號(hào)集相關(guān)的semid_ds結(jié)構(gòu)的sem_ctime成員;
SETVAL 設(shè)置semnum所代表信號(hào)燈的值為arg.val;
調(diào)用返回:調(diào)用失敗返回-1,成功返回與cmd相關(guān):
Cmd return value
GETNCNT Semncnt
GETPID Sempid
GETVAL Semval
GETZCNT Semzcnt
五、信號(hào)燈的限制
1、 一次系統(tǒng)調(diào)用semop可同時(shí)操作的信號(hào)燈數(shù)目SEMOPM,semop中的參數(shù)nsops如果超過(guò)了這個(gè)數(shù)目,將返回E2BIG錯(cuò)誤。SEMOPM的大小特定與系統(tǒng),redhat 8.0為32。
2、 信號(hào)燈的最大數(shù)目:SEMVMX,當(dāng)設(shè)置信號(hào)燈值超過(guò)這個(gè)限制時(shí),會(huì)返回ERANGE錯(cuò)誤。在redhat 8.0中該值為32767。
3、 系統(tǒng)范圍內(nèi)信號(hào)燈集的最大數(shù)目SEMMNI以及系統(tǒng)范圍內(nèi)信號(hào)燈的最大數(shù)目SEMMNS。超過(guò)這兩個(gè)限制將返回ENOSPC錯(cuò)誤。redhat 8.0中該值為32000。
4、 每個(gè)信號(hào)燈集中的最大信號(hào)燈數(shù)目SEMMSL,redhat 8.0中為250。 SEMOPM以及SEMVMX是使用semop調(diào)用時(shí)應(yīng)該注意的;SEMMNI以及SEMMNS是調(diào)用semget時(shí)應(yīng)該注意的。SEMVMX同時(shí)也是semctl調(diào)用應(yīng)該注意的。
六、競(jìng)爭(zhēng)問(wèn)題
第一個(gè)創(chuàng)建信號(hào)燈的進(jìn)程同時(shí)也初始化信號(hào)燈,這樣,系統(tǒng)調(diào)用semget包含了兩個(gè)步驟:創(chuàng)建信號(hào)燈;初始化信號(hào)燈。由此可能導(dǎo)致一種競(jìng)爭(zhēng)狀態(tài):第一個(gè)創(chuàng)建信號(hào)燈的進(jìn)程在初始化信號(hào)燈時(shí),第二個(gè)進(jìn)程又調(diào)用semget,并且發(fā)現(xiàn)信號(hào)燈已經(jīng)存在,此時(shí),第二個(gè)進(jìn)程必須具有判斷是否有進(jìn)程正在對(duì)信號(hào)燈進(jìn)行初始化的能力。在參考文獻(xiàn)[1]中,給出了繞過(guò)這種競(jìng)爭(zhēng)狀態(tài)的方法:當(dāng)semget創(chuàng)建一個(gè)新的信號(hào)燈時(shí),信號(hào)燈結(jié)構(gòu)semid_ds的sem_otime成員初始化后的值為0。因此,第二個(gè)進(jìn)程在成功調(diào)用semget后,可再次以IPC_STAT命令調(diào)用semctl,等待sem_otime變?yōu)榉?值,此時(shí)可判斷該信號(hào)燈已經(jīng)初始化完畢。下圖描述了競(jìng)爭(zhēng)狀態(tài)產(chǎn)生及解決方法:
實(shí)際上,這種解決方法也是基于這樣一個(gè)假定:第一個(gè)創(chuàng)建信號(hào)燈的進(jìn)程必須調(diào)用semop,這樣sem_otime才能變?yōu)榉橇阒。另外,因(yàn)榈谝粋(gè)進(jìn)程可能不調(diào)用semop,或者semop操作需要很長(zhǎng)時(shí)間,第二個(gè)進(jìn)程可能無(wú)限期等待下去,或者等待很長(zhǎng)時(shí)間。
七、信號(hào)燈應(yīng)用實(shí)例
本實(shí)例有兩個(gè)目的:1、獲取各種信號(hào)燈信息;2、利用信號(hào)燈實(shí)現(xiàn)共享資源的申請(qǐng)和釋放。并在程序中給出了詳細(xì)注釋。
#include <linux/sem.h>;
#include <stdio.h>;
#include <errno.h>;
#define SEM_PATH "/unix/my_sem"
#define max_tries 3
int semid;
main()
{
int flag1,flag2,key,i,init_ok,tmperrno;
struct semid_ds sem_info;
struct seminfo sem_info2;
union semun arg; //union semun: 請(qǐng)參考附錄2
struct sembuf askfor_res, free_res;
flag1=IPC_CREAT|IPC_EXCL|00666;
flag2=IPC_CREAT|00666;
key=ftok(SEM_PATH,'a');
//error handling for ftok here;
init_ok=0;
semid=semget(key,1,flag1);//create a semaphore set that only includes one semphore.
if(semid<0)
{
tmperrno=errno;
perror("semget" ;
if(tmperrno==EEXIST)
//errno is undefined after a successful library call( including perror call) so it is saved //in tmperrno.
{
semid=semget(key,1,flag2);
//flag2 只包含了IPC_CREAT標(biāo)志, 參數(shù)nsems(這里為1)必須與原來(lái)的信號(hào)燈數(shù)目一致
arg.buf=&sem_info;
for(i=0; i<max_tries; i++)
{
if(semctl(semid, 0, IPC_STAT, arg)==-1)
{ perror("semctl error" ; i=max_tries;}
else
{
if(arg.buf->;sem_otime!=0){ i=max_tries; init_ok=1;}
else sleep(1);
}
}
if(!init_ok)
// do some initializing, here we assume that the first process that creates the sem will
// finish initialize the sem and run semop in max_tries*1 seconds. else it will not run
// semop any more.
{
arg.val=1;
if(semctl(semid,0,SETVAL,arg)==-1) perror("semctl setval error" ;
}
}
else
{perror("semget error, process exit"); exit(); }
}
else //semid>;=0; do some initializing
{
arg.val=1;
if(semctl(semid,0,SETVAL,arg)==-1)
perror("semctl setval error");
}
//get some information about the semaphore and the limit of semaphore in redhat8.0
arg.buf=&sem_info;
if(semctl(semid, 0, IPC_STAT, arg)==-1)
perror("semctl IPC STAT");
printf("owner's uid is %d\n", arg.buf->;sem_perm.uid);
printf("owner's gid is %d\n", arg.buf->;sem_perm.gid);
printf("creater's uid is %d\n", arg.buf->;sem_perm.cuid);
printf("creater's gid is %d\n", arg.buf->;sem_perm.cgid);
arg.__buf=&sem_info2;
if(semctl(semid,0,IPC_INFO,arg)==-1)
perror("semctl IPC_INFO");
printf("the number of entries in semaphore map is %d \n", arg.__buf->;semmap);
printf("max number of semaphore identifiers is %d \n", arg.__buf->;semmni);
printf("mas number of semaphores in system is %d \n", arg.__buf->;semmns);
printf("the number of undo structures system wide is %d \n", arg.__buf->;semmnu);
printf("max number of semaphores per semid is %d \n", arg.__buf->;semmsl);
printf("max number of ops per semop call is %d \n", arg.__buf->;semopm);
printf("max number of undo entries per process is %d \n", arg.__buf->;semume);
printf("the sizeof of struct sem_undo is %d \n", arg.__buf->;semusz);
printf("the maximum semaphore value is %d \n", arg.__buf->;semvmx);
//now ask for available resource:
askfor_res.sem_num=0;
askfor_res.sem_op=-1;
askfor_res.sem_flg=SEM_UNDO;
if(semop(semid,&askfor_res,1)==-1)//ask for resource
perror("semop error");
sleep(3); //do some handling on the sharing resource here, just sleep on it 3 seconds
printf("now free the resource\n");
//now free resource
free_res.sem_num=0;
free_res.sem_op=1;
free_res.sem_flg=SEM_UNDO;
if(semop(semid,&free_res,1)==-1)//free the resource.
if(errno==EIDRM)
printf("the semaphore set was removed\n");
//you can comment out the codes below to compile a different version:
if(semctl(semid, 0, IPC_RMID)==-1)
perror("semctl IPC_RMID");
else printf("remove sem ok\n");
}
注:讀者可以嘗試一下注釋掉初始化步驟,進(jìn)程在運(yùn)行時(shí)會(huì)出現(xiàn)何種情況(進(jìn)程在申請(qǐng)資源時(shí)會(huì)睡眠),同時(shí)可以像程序結(jié)尾給出的注釋那樣,把該程序編譯成兩個(gè)不同版本。下面是本程序的運(yùn)行結(jié)果(操作系統(tǒng)redhat8.0):
owner's uid is 0
owner's gid is 0
creater's uid is 0
creater's gid is 0
the number of entries in semaphore map is 32000
max number of semaphore identifiers is 128
mas number of semaphores in system is 32000
the number of undo structures system wide is 32000
max number of semaphores per semid is 250
max number of ops per semop call is 32
max number of undo entries per process is 32
the sizeof of struct sem_undo is 20
the maximum semaphore value is 32767
now free the resource
remove sem ok
Summary:信號(hào)燈與其它進(jìn)程間通信方式有所不同,它主要用于進(jìn)程間同步。通常所說(shuō)的系統(tǒng)V信號(hào)燈實(shí)際上是一個(gè)信號(hào)燈的集合,可用于多種共享資源的進(jìn)程間同步。每個(gè)信號(hào)燈都有一個(gè)值,可以用來(lái)表示當(dāng)前該信號(hào)燈代表的共享資源可用(available)數(shù)量,如果一個(gè)進(jìn)程要申請(qǐng)共享資源,那么就從信號(hào)燈值中減去要申請(qǐng)的數(shù)目,如果當(dāng)前沒(méi)有足夠的可用資源,進(jìn)程可以睡眠等待,也可以立即返回。當(dāng)進(jìn)程要申請(qǐng)多種共享資源時(shí),linux可以保證操作的原子性,即要么申請(qǐng)到所有的共享資源,要么放棄所有資源,這樣能夠保證多個(gè)進(jìn)程不會(huì)造成互鎖。Linux對(duì)信號(hào)燈有各種各樣的限制,程序中給出了輸出結(jié)果。另外,如果讀者想對(duì)信號(hào)燈作進(jìn)一步的理解,建議閱讀sem.h源代碼,該文件不長(zhǎng),但給出了信號(hào)燈相關(guān)的重要數(shù)據(jù)結(jié)構(gòu)。
附錄1: struct sem_array如下:
/*系統(tǒng)中的每個(gè)信號(hào)燈集對(duì)應(yīng)一個(gè)sem_array 結(jié)構(gòu) */
struct sem_array {
struct kern_ipc_perm sem_perm; /* permissions .. see ipc.h */
time_t sem_otime; /* last semop time */
time_t sem_ctime; /* last change time */
struct sem *sem_base; /* ptr to first semaphore in array */
struct sem_queue *sem_pending; /* pending operations to be processed */
struct sem_queue **sem_pending_last; /* last pending operation */
struct sem_undo *undo; /* undo requests on this array */
unsigned long sem_nsems; /* no. of semaphores in array */
};
其中,sem_queue結(jié)構(gòu)如下:
/* 系統(tǒng)中每個(gè)因?yàn)樾盘?hào)燈而睡眠的進(jìn)程,都對(duì)應(yīng)一個(gè)sem_queue結(jié)構(gòu)*/
struct sem_queue {
struct sem_queue * next; /* next entry in the queue */
struct sem_queue ** prev; /* previous entry in the queue, *(q->;prev) == q */
struct task_struct* sleeper; /* this process */
struct sem_undo * undo; /* undo structure */
int pid; /* process id of requesting process */
int status; /* completion status of operation */
struct sem_array * sma; /* semaphore array for operations */
int id; /* internal sem id */
struct sembuf * sops; /* array of pending operations */
int nsops; /* number of operations */
int alter; /* operation will alter semaphore */
};
附錄2:union semun是系統(tǒng)調(diào)用semctl中的重要參數(shù):
union semun {
int val; /* value for SETVAL */
struct semid_ds *buf; /* buffer for IPC_STAT & IPC_SET */
unsigned short *array; /* array for GETALL & SETALL */
struct seminfo *__buf; /* buffer for IPC_INFO */ //test!!
void *__pad;
};
struct seminfo {
int semmap;
int semmni;
int semmns;
int semmnu;
int semmsl;
int semopm;
int semume;
int semusz;
int semvmx;
int semaem;
};
參考文獻(xiàn):
[1] UNIX網(wǎng)絡(luò)編程第二卷:進(jìn)程間通信,作者:W.Richard Stevens,譯者:楊繼張,清華大學(xué)出版社。對(duì)POSIX以及系統(tǒng)V信號(hào)燈都有闡述,對(duì)Linux環(huán)境下的程序開(kāi)發(fā)有極大的啟發(fā)意義。
[2] linux內(nèi)核源代碼情景分析(上),毛德操、胡希明著,浙江大學(xué)出版社,給出了系統(tǒng)V信號(hào)燈相關(guān)的源代碼分析,尤其在闡述保證操作原子性方面,以及闡述undo標(biāo)志位時(shí),討論的很深刻。
[3]GNU/Linux編程指南,第二版,Kurt Wall等著,張輝譯
[4]semget、semop、semctl手冊(cè)
關(guān)于作者:
鄭彥興,國(guó)防科大攻讀博士學(xué)位。聯(lián)系方式: mlinux@163.com |
|