進(jìn)程間通信
進(jìn)程間通信即IPC(InerProcess Communication)
Unix ipc 已經(jīng)是而且繼續(xù)是各種進(jìn)程通信方式的統(tǒng)稱述呐,其中極少能在所有unix的實現(xiàn)中進(jìn)行移植
不管哪一種Unix實現(xiàn)都可依靠唯一一種IPC是半雙工的管道。管道把沼、FIFOs压语、流管道局荚、命令流管道二汛、消息隊列、信號量宰啦、共享存儲通常限于同一臺主機(jī)的各個進(jìn)程間的IPC鲤嫡。套接口和流谚殊,則支持不同主機(jī)上各個進(jìn)程間IPC.
管道
管道是unix IPC的最古老的形式杠步,并且所有Unix系統(tǒng)都提供此種通信機(jī)制,管道有兩種限制:1外冀、他們是半雙工的。數(shù)據(jù)只能在一個方向上流動。2辜御、他們只能在具有公共祖先的進(jìn)程之間使用
鸭你。通常,一個管道由一個進(jìn)程創(chuàng)建擒权,然后該進(jìn)程調(diào)用fork,此后父子進(jìn)程之間就可以應(yīng)用該管道袱巨。
流管道沒有第一種限制,F(xiàn)IFO和命名管道則沒有第二種限制菜拓。盡管有這兩種限制瓣窄,半雙工管道仍是最常用的IPC
管道是由pipe函數(shù)創(chuàng)建的。
int pipe(int filedes[2])
經(jīng)由參數(shù)filedes返回兩個文件描述符:filedes[0]為讀而打開纳鼎,filedes[1]為寫而打開俺夕。filedes[1]的輸出是filedes[0]的輸入。
有兩種方法來描繪一個管道贱鄙。
fstat函數(shù)對管道的每一端都返回一個FIFO類型的文件描述符劝贸,可以用S_ISFIFO宏來測試管道。
單個進(jìn)程中的管道幾乎沒有任何用處逗宁。通常映九,調(diào)用pipe的進(jìn)程接著調(diào)用fork,這樣就創(chuàng)建了從父進(jìn)程到子進(jìn)程或反之的IPC通道。
fork之后做什么取決于我們想要有的數(shù)據(jù)流的方向瞎颗。對于從父進(jìn)程到子進(jìn)程的管道件甥,父進(jìn)程關(guān)閉管道的讀端,子進(jìn)程則關(guān)閉寫端哼拔。
當(dāng)管道的一端被關(guān)閉后引有,下列規(guī)則起作用
- 當(dāng)讀一個寫端已被關(guān)閉的管道時,在所有數(shù)據(jù)都被讀取后倦逐,read返回0譬正,以指示達(dá)到了文件結(jié)束處。
- 如果寫一個讀端已經(jīng)被關(guān)閉的管道時檬姥,則產(chǎn)生SIGPIPE信號曾我。如果忽略該信號或者捕捉該信號并且從其處理程序返回,則write出錯返回健民,errno設(shè)置為EPIPE
在寫管道式抒巢,常數(shù)PIPE_BUF規(guī)定了內(nèi)核中短刀緩存器的大小。如果對管道進(jìn)行write調(diào)用秉犹,而且要求寫的字節(jié)數(shù)小于等于PIPE_BUF虐秦,則此操作不會與其他進(jìn)程對統(tǒng)一管道的write操作穿插進(jìn)行平酿。但是若有多個進(jìn)程同時寫一個管道,而且某個或某些進(jìn)程要求寫的字節(jié)數(shù)超過PIPE_BUF字節(jié)數(shù)悦陋,則數(shù)據(jù)可能會與其他寫操作的數(shù)據(jù)相穿插.
popen和pclose函數(shù)
因為常見的操作是創(chuàng)建一個連接到另一個進(jìn)程的管道,然后讀其輸出或向其發(fā)送輸入,所以標(biāo)準(zhǔn)io庫為實現(xiàn)這些操作提供了兩個函數(shù)popen和pclose筑辨。這兩個函數(shù)實現(xiàn)的操作是:創(chuàng)建一個管道俺驶,fork一個子進(jìn)程,關(guān)閉管道不使用的端棍辕,exec一個shell以執(zhí)行命令暮现,等待命令終止。
FILE *popen(const char *cmdstring, const char *type)
int pclose(FILE *fp)
函數(shù)open先執(zhí)行fork,然后調(diào)用exec執(zhí)行cmdstring楚昭,并且返回一個標(biāo)準(zhǔn)io文件指針。如果type是r,則文件指針連接到cmdstring的標(biāo)準(zhǔn)輸.出如果type是w則文件指針連接到cmdstring的標(biāo)準(zhǔn)輸入。
pclose函數(shù)關(guān)閉標(biāo)準(zhǔn)io流绅作,等待命令執(zhí)行結(jié)束营曼,然后返回shell的終止?fàn)顟B(tài)。如果shell不能被執(zhí)行尿贫,則pclose返回的終止?fàn)顟B(tài)與shell執(zhí)行exit一樣电媳。
協(xié)同進(jìn)程
unix過濾程序從標(biāo)準(zhǔn)輸入讀取數(shù)據(jù),對其進(jìn)行適當(dāng)處理后寫到標(biāo)準(zhǔn)輸出庆亡。幾個過濾進(jìn)程通常在shell管道命令中線性地連接匾乓。當(dāng)一個程序產(chǎn)生某個過濾程序的輸入,同時又讀取該過濾程序的輸出時又谋,則在過濾程序就成為協(xié)同進(jìn)程拼缝。
komshell聽歌了協(xié)同進(jìn)程。bourne shell 和 c shell并沒有提供將進(jìn)程連接起來按協(xié)同進(jìn)程方式工作的方法彰亥。協(xié)同進(jìn)程通常在shell的后臺運行咧七,其標(biāo)準(zhǔn)輸入和標(biāo)準(zhǔn)輸出通過管道連接到另一個程序。
雖然要求初始化一個協(xié)同進(jìn)程剩愧,并將其輸入和輸出連接到另一個進(jìn)程的shell語法是十分奇特的猪叙,但是協(xié)同進(jìn)程的工作方式在C程序中也是非常有用的。
popen提供連接到另一個進(jìn)程的標(biāo)準(zhǔn)輸入或標(biāo)準(zhǔn)輸出的一個單行管道仁卷,而對于協(xié)同進(jìn)程穴翩,則它有連接到另一個進(jìn)程的兩個單行管道,一個接到其標(biāo)準(zhǔn)輸入锦积,一個則來自標(biāo)準(zhǔn)輸出芒帕。我們先要將數(shù)據(jù)寫到其標(biāo)準(zhǔn)輸入,經(jīng)其處理后再從標(biāo)準(zhǔn)輸出讀取數(shù)據(jù)丰介。協(xié)同程序中如果使用標(biāo)準(zhǔn)io則由于緩存問題的存在可能導(dǎo)致問題背蟆。如果我們無法修改協(xié)同程序的相關(guān)代碼鉴分,則解決辦法是是被調(diào)用的協(xié)同程序認(rèn)為它的標(biāo)準(zhǔn)輸入和輸出被連接到一個終端。這使得協(xié)同進(jìn)程中的標(biāo)準(zhǔn)io歷程對這兩個io流進(jìn)行緩存带膀。
FIFO
FIFO被稱為命名管道志珍,管道只能由相關(guān)進(jìn)程使用,他們共同的祖先進(jìn)程創(chuàng)建了管道垛叨。但是伦糯,通過FIFO,不相關(guān)的進(jìn)程也能交換數(shù)據(jù)。
FIFO是一種文件結(jié)構(gòu)嗽元。stat結(jié)構(gòu)成員st_mode的編碼指明文件是否是FIFO類型敛纲。可以用S_ISFIFO宏對此進(jìn)行測試剂癌。
創(chuàng)建FIFO類似于創(chuàng)建文件淤翔。FIFO的路徑名存在于文件系統(tǒng)中。
int mkfifo(const char *pathname, mode_t mode)
mode 與open函數(shù)中mode相同佩谷。一旦用mkfifo創(chuàng)建了一個FIFO就可以使用open打開它旁壮。一般的文件IO函數(shù)(close、read琳要、write寡具、unlink)等都可用于FIFO
當(dāng)打開一個FIFO時,非阻塞標(biāo)志(O_NONBLOCK)產(chǎn)生下列影響
- 在一般情況總稚补,只讀打開要阻塞到某個其他進(jìn)城為寫打開此FIFO,類似童叠,為寫而打開一個FIFO要阻塞到某個其他進(jìn)程為讀而打開它。
- 如果指定了O_NONBLOCK,則只讀打開立即返回课幕。但是如果沒有進(jìn)程已經(jīng)為讀而打開一個FIFO,那么只寫打開將出錯返回厦坛,其errno實ENXIO
類似于管道,若刺耳一個尚無進(jìn)程為讀而打開的FIFO,則產(chǎn)生信號SIGPIPE乍惊。若某個FIFO的最后一個寫進(jìn)程關(guān)閉了該FIFO,則將為該FIFO的讀進(jìn)程產(chǎn)生一個文件結(jié)束的標(biāo)志杜秸。
一個給定的FIFO有多個寫進(jìn)程是常見的。這就意味著如果不希望多個進(jìn)程所寫的數(shù)據(jù)相互穿插润绎,則需要考慮原子寫操作撬碟。正如對于管道一樣,常數(shù)PIPE_BUF說明了可被原子寫到FIFO的最大數(shù)據(jù)量莉撇。
FIFO有兩種用途:
- FIFO由shell命令使用以便將數(shù)據(jù)從一條管道線傳送到另一條呢蛤,為此無需創(chuàng)建中間臨時文件。
- FIFO用于客戶機(jī)-服務(wù)器應(yīng)用程序中棍郎,以在客戶機(jī)和服務(wù)器之間傳遞數(shù)據(jù)
管道只能用于進(jìn)程間的線性連接其障,然而因為FIFO具有名字,所以它可用于非線性連接涂佃。
系統(tǒng)v ipc
標(biāo)識符和關(guān)鍵字
每個內(nèi)核中的IPC結(jié)構(gòu)(消息隊列励翼、信號量或共享存儲段)都用一個非負(fù)征地戶的標(biāo)識符加以引用蜈敢。例如,為了對一個消息隊列發(fā)送或取消息汽抚,只需知道其隊列標(biāo)識符抓狭。與文件描述符不同,IPC標(biāo)識符不是小的整數(shù)殊橙。當(dāng)一個IPC結(jié)構(gòu)被創(chuàng)建辐宾,以后又被刪除時,與這種結(jié)構(gòu)相關(guān)的標(biāo)識符連續(xù)加1膨蛮,直至達(dá)到一個整型數(shù)的最大正值,然后又回轉(zhuǎn)到0.
無論何時創(chuàng)建IPC結(jié)構(gòu)都應(yīng)制定一個關(guān)鍵字季研,關(guān)鍵字的數(shù)據(jù)類型由系統(tǒng)規(guī)定為key_t,通常在頭文件<sys.types.h>中被規(guī)定為長整型敞葛。關(guān)鍵字由內(nèi)核變換成標(biāo)識符。
有多宗方法使客戶機(jī)和服務(wù)器同在一IPC結(jié)構(gòu)上會合
- 服務(wù)器可以指定關(guān)鍵字IPC_PRIVATE 創(chuàng)建一個新的IPC結(jié)構(gòu)与涡,將返回的標(biāo)識符存放在某處以便客戶機(jī)取用惹谐。關(guān)鍵字IPC_PRIVATE保證服務(wù)器創(chuàng)建一個新的IPC結(jié)構(gòu)。這種技術(shù)的缺點是服務(wù)器要將整形標(biāo)識符寫到文件中驼卖,然后客戶機(jī)在此后又要讀文件取得此標(biāo)識符氨肌。IPC_PRIVATE關(guān)鍵字也可用于父子關(guān)系進(jìn)程。父進(jìn)程指定IPC_PRIVATE創(chuàng)建一個新的IPC結(jié)構(gòu)酌畜,所返回的標(biāo)識符在fork后可由子進(jìn)程使用怎囚。子進(jìn)程可將此標(biāo)識符作為exec函數(shù)的一個參數(shù)傳給一個新的程序。
- 在一個公用頭文件中定義一個客戶機(jī)和服務(wù)器都認(rèn)可的關(guān)鍵字桥胞。然后服務(wù)器指定此關(guān)鍵字創(chuàng)建一個新的IPC結(jié)構(gòu)恳守。這種方法的問題是該關(guān)鍵字可能已與一個IPC結(jié)構(gòu)相結(jié)合,在此情況下贩虾,get函數(shù)出錯返回催烘。服務(wù)器必須處理這一錯誤,刪除已存在的IPC結(jié)構(gòu)缎罢,然后試著再創(chuàng)建它伊群。
- 客戶機(jī)和服務(wù)器認(rèn)同一個路徑名和課題ID,然后調(diào)用函數(shù)ftok將這兩個值轉(zhuǎn)換為一個關(guān)鍵字。然后在方法2中使用此關(guān)鍵字策精。ftok提供的唯一服務(wù)就是由一個路徑和課題ID產(chǎn)生一個關(guān)鍵字舰始。因為一般來說,客戶機(jī)和服務(wù)器至少共享一個頭文件蛮寂,所以一個比較簡單的方法是避免使用ftok蔽午,而只是在該頭文件中存放一個大家都知道的關(guān)鍵字。這樣海避免了使用另一個函數(shù)酬蹋。
三個get函數(shù)(msgget,setmget,shmget)都有兩個類似的參數(shù)key和一個整型的flag及老。如若滿足下列條件抽莱,則創(chuàng)建一個新的IPC結(jié)構(gòu)(通常由服務(wù)器創(chuàng)建):
- key 是IPC_PRIVATE,或
- key當(dāng)前未與特定類型的IPC結(jié)構(gòu)結(jié)合,flag中指定了IPC_CREAT位骄恶。為訪問現(xiàn)存的隊列食铐,key必須等于創(chuàng)建該隊列時所指定的關(guān)鍵字,并且不應(yīng)指定IPC_CREATE.
注意僧鲁,為了訪問一個現(xiàn)存的隊列虐呻,決不能指定IPC_PRIVATE關(guān)鍵字。因為這是一個特殊的鍵值寞秃,它總是擁有創(chuàng)建一個新的隊列斟叼。為了訪問一個用IPC_PRIVATE關(guān)鍵字創(chuàng)建的現(xiàn)存隊列,一定要知道與隊列相結(jié)合的標(biāo)識符春寿,然后在其他的IPC調(diào)用中使用該標(biāo)識符朗涩。
如果希望創(chuàng)建一個新的IPC結(jié)構(gòu),保證不是引用具有統(tǒng)一標(biāo)識符的一個線性IPC結(jié)構(gòu)绑改,那么必須在flag中同時制定IPC_CREAT和IPC_EXCL位谢床。這樣做了以后,如果IPC結(jié)構(gòu)已經(jīng)存在就會造成出錯厘线,返回EEXIST.
許可權(quán)結(jié)構(gòu)
系統(tǒng)V ipc為每一個ipc結(jié)構(gòu)設(shè)置了一個ipc_perm結(jié)構(gòu)识腿。該結(jié)構(gòu)規(guī)定了許可權(quán)和所有者。
struct ipc_perm {
uid_t uid;
gid_t gid;
uid_t cuid;
gid_t cgid;
mode_t mode;
ulong seq;
key_t key;
}
在創(chuàng)建ipc結(jié)構(gòu)時造壮,除seq意外的所有字段都賦初值渡讼。以后可以調(diào)用msfctl,semctl或shmctl修改uid gid和 mode字段。為了改變這些值费薄,調(diào)用進(jìn)程必須是IPC結(jié)構(gòu)的創(chuàng)建者或超級用戶硝全。更改這些字段類似于對文件調(diào)用chown和chmod.
對于任何IPC結(jié)構(gòu)都不存在執(zhí)行許可權(quán)限。另外消息隊列和共享存儲使用術(shù)語讀和寫楞抡,而信號量則使用術(shù)語讀和更改伟众。
結(jié)構(gòu)限制
三種形式的系統(tǒng)V ipc都有我們可能會遇到的內(nèi)在的限制。這些限制的大多數(shù)可以通過從新配置而加以更改召廷。
有點和缺點
系統(tǒng)V ipc的主要問題是ipc結(jié)構(gòu)在系統(tǒng)范圍內(nèi)起作用凳厢。沒有訪問計數(shù)。例如竞慢,如果創(chuàng)建了儀的消息隊列先紫,在該隊列中放入了幾則消息,然后終止筹煮,但是該消息隊列及其內(nèi)容并不被刪除遮精。他們余留在系統(tǒng)中直至:有某個進(jìn)程調(diào)用msgrcv或msgctl讀消息或者刪除消息隊列,或者某個進(jìn)程執(zhí)行ipcrm命令刪除消息隊列或者由正在再啟動的系統(tǒng)刪除消息隊列。將此與管道pipe相比本冲,那么當(dāng)最后一個訪問管道的進(jìn)程終止時准脂,管道就被完全刪除了。對于FIFO而言雖然當(dāng)最后一個引用FIFO的進(jìn)程終止時其名字仍然留在系統(tǒng)中檬洞,直至顯示的刪除它,但是留在FIFO中的數(shù)據(jù)卻在此時全部刪除添怔。
系統(tǒng)v ipc的另一個問題是:這些ipc結(jié)構(gòu)并不按名字為文件系統(tǒng)所知湾戳。我們不能用文件相關(guān)的函數(shù)來存取他們或修改他們的特性。為了支持他們不得不增加了十個全新的系統(tǒng)調(diào)用广料。我們不能用ls命令看到他們砾脑,不能用rm命令刪除他們,不鞥用chmod命令更改他們的存取權(quán)艾杏。于是也不得不增加了全新的命令ipcs和ipcrm
因為這些ipc不適用文件描述符拦止,所以不能對他們使用多路轉(zhuǎn)接io函數(shù):select和poll這就使得一次使用多個ipc結(jié)構(gòu)以及用文件或設(shè)備io來使用ipc結(jié)構(gòu)很難做到。例如沒有某種形式的忙-等待循環(huán)就不能使一個服務(wù)器等待一個消息放在兩個消息隊列中的任意一個中糜颠。
因為這些形式的ipc都是限制用在單主機(jī)上,所以他們是可靠的萧求。當(dāng)消息通過網(wǎng)路傳送石其兴,丟失消息的可能性就要加以考慮。
消息隊列
消息隊列是消息的鏈接表夸政,存放在內(nèi)核中并由消息隊列標(biāo)識符標(biāo)識元旬。我們將稱消息隊列為隊列,其標(biāo)識符為隊列id守问。msgget用于創(chuàng)建一個新隊列或打開一個現(xiàn)存的隊列匀归。msgsnd用于將新消息添加到隊列尾端。每個消息包含一個正長整型類型字段耗帕,一個非負(fù)長度以及實際數(shù)據(jù)字節(jié)穆端。所有這些都在將消息添加到隊列時傳送給msgsnd.msgrcv用于從隊列中取消息。我們并不一定要以先進(jìn)先出次序取消息仿便,也可以按消息的類型字段取消息体啰。
每個隊列都有一個msqid_ds結(jié)構(gòu)與其相關(guān)。此結(jié)構(gòu)規(guī)定了隊列的當(dāng)前狀態(tài)
struct msqid_ds {
struct ipc_perm msg_perm;
struct msg *msg_first;
struct msg *msg_last;
ulong msg_cbytes;
ulong msg_qnum;
ulong msg_qbytes;
pid_t msg_lspid;
pid_t msg_lrpid;
time_t msg_stime;
time_t msg_rtime;
time_t msg_ctime;
}
兩個指針msg_first 和msg_last分別指向相應(yīng)消息在內(nèi)核中存放的位置嗽仪,所以他們對用戶進(jìn)程而言是無價值的荒勇。
影響消息隊列的系統(tǒng)限制
- MSGMAX 可發(fā)送的最長消息的字節(jié)長度
- MSGMNB 特定隊列的最大字節(jié)長度
- MSGMNI 系統(tǒng)中最大消息隊列數(shù)
- MSGTOL 系統(tǒng)中最大消息數(shù)
調(diào)用的第一個函數(shù)通常是msgget,其功能是打開一個現(xiàn)存的對壘或者創(chuàng)建一個新隊列闻坚。
int msgget(key_t key, int flag)
當(dāng)創(chuàng)建一個新的隊列時沽翔,初始化msgqid-ds結(jié)構(gòu)的下列成員
- ipc-perm 該結(jié)構(gòu)中mode按flag中的相應(yīng)許可權(quán)限位設(shè)置。
- msg_qnum,msg_lspid,msg_lrpid,msg_stime和msg_rtime都設(shè)置為0
- msg_ctime 設(shè)置為當(dāng)前時間
- msg_qbytes設(shè)置為系統(tǒng)限制值
若執(zhí)行成功則返回非負(fù)隊列id窿凤,此后此值就可被用于其他三個消息隊列函數(shù)仅偎。
msgctl函數(shù)對隊列執(zhí)行多種操作跨蟹。它以及另外兩個與信號量和共享存儲有關(guān)的函數(shù)是系統(tǒng)v ipc的類似于ioctl的函數(shù)
int msgctl(int msqid, int cmd, struct msgqid_dsbuf)
cmd參數(shù)指定對于由msqid規(guī)定的隊列要執(zhí)行的命令:
- IPC_STAT 取此隊列的msqid_ds結(jié)構(gòu),并將其存放在buf指向的結(jié)構(gòu)中哨颂。
- IPC_SET 按由buf指向的結(jié)構(gòu)中的值設(shè)置與此隊列此昂管的結(jié)構(gòu)中的下列四個字段:msg_perm.uid,msg_perm.gid,msg_perm.mode,msg_qbytes喷市。此命令你只能由下列兩種進(jìn)程執(zhí)行:一種是其有效用戶id等于msg_perm.cuid或msg_perm.uid另一種是具有超級用戶特權(quán)的進(jìn)程。只有超級用戶才能增加msg-qbytes的值
- IPC_RMID 從系統(tǒng)中刪除該消息隊列以及仍在該隊列上的所有數(shù)據(jù)威恼。這種刪除立即生效品姓。仍在使用這一消息隊列的其他進(jìn)城在他們下一次試圖對此隊列進(jìn)行操作時將出錯返回EIDRM.此命令只能由下列兩種進(jìn)程執(zhí)行:一種是其有效用戶id等于msg_perm.cuid或msg_perm.uid另一種是具有超級用戶特權(quán)內(nèi)的進(jìn)程。
這三條命令(IPC_STAT箫措、IPC_SET和IPC_RMID)也可用于信號量和共享存儲
調(diào)用msgsnd將數(shù)據(jù)放到消息隊列上
int msgsnd(int msqid, const void *ptr, size_t nbytes, int flag);
正如前面提及到的腹备,每個消息都由三個部分組成,他們是:正長整型類型字段斤蔓、非負(fù)長度以及實際數(shù)據(jù)字節(jié)植酥。消息總是放在隊列尾端。
ptr指向一個長整型數(shù)弦牡,它包含了正整形消息類型友驮,在氣候立即跟隨了消息數(shù)據(jù)。若發(fā)送的最長消息是512字節(jié)驾锰,則可以定義下列結(jié)構(gòu)
struct mymesg {
log mtype;
char mtext[512];
}
于是卸留。ptr就是一個指向mymesg結(jié)構(gòu)的指針。接收者可以使用消息類型以先進(jìn)先出的次序取消息椭豫。
flag的值可以指定為IPC_NOWAIT.這類似于文件io的非阻塞io標(biāo)志耻瑟。若消息隊列已滿(或者是隊列中的消息總數(shù)等于系統(tǒng)限制值,或隊列中的字節(jié)總數(shù)等于系統(tǒng)限制值)赏酥,則指定IPC_NOWAIT使得msgsnd立即出錯返回EAGAIN.如果沒有指定IPC_NOWAIT,則進(jìn)程阻塞直到有空間可以容納要發(fā)送的消息或從系統(tǒng)中刪除了此隊列或捕捉到一個信號喳整,并從信號處理程序返回。在第二種情況下返回EIDRM最后一種情況則返回EINTR
注意裸扶,對消息隊列刪除的處理不是很完善框都。因為對每個消息隊列并沒有設(shè)置一個引用計數(shù)器,所以刪除一個隊列使得仍在使用這一隊列的進(jìn)程在下次隊列操作時出錯返回姓言。信號量機(jī)構(gòu)也以同樣方式處理其刪除瞬项。刪除一個文件則要等到使用該文件的最后一個進(jìn)程關(guān)閉了它,才能刪除文件的內(nèi)容何荚。
msgrcv從隊列中取用消息
int msgrcv(int msqid, void *ptr, size_t nbytes, long type, int flag)
如同msgsnd一樣ptr參數(shù)指向一個長整數(shù)囱淋,跟隨其后是存放實際消息數(shù)據(jù)的緩存。nbytes說明數(shù)據(jù)緩存的長度餐塘。若返回的消息大于nbytes,而且在flag中設(shè)置了MSG_NOERROR妥衣,則該消息被截短,如果沒有設(shè)置這一標(biāo)志,而消息又太長税手,則出錯返回E2BIG
參數(shù)type使我們可以指定想要哪一種消息:
- type==0返回隊列中的第一個消息
- type>0返回隊列消息類型為type的第一個消息
- type < 0返回隊列中消息類型值小于或等于type絕對值蜂筹,而且在這種消息中其類型值又最小的消息。
非0type用于以先進(jìn)先出次序讀消息芦倒。例如若應(yīng)用程序?qū)ο①x值優(yōu)先權(quán)艺挪,那么tyoe就可以是優(yōu)先權(quán)值。如果一個消息隊列由多個客戶機(jī)和一個服務(wù)器使用兵扬,那么type字段可以用來包含客戶機(jī)進(jìn)程id.
可以指定flag值為IPC_NOWAIT,使操作不阻塞麻裳。這使得如果沒有所指定類型的消息則msgrcv出錯返回ENOMSG.如果沒有指定IPC_NOWAIT,則進(jìn)程阻塞直至有了指定類型的消息∑髦樱或系統(tǒng)中刪除了此隊列(出錯返回EDRM)或捕捉到一個信號并從信號處理程序返回(出錯返回EINTR)
消息隊列原來實施的目的是提供比一般IPC更高速的進(jìn)程通信方法津坑,但現(xiàn)在與其他形式的IPC相比,在速度方面已經(jīng)沒有什么差別了傲霸〗澹考慮到消息隊列具有的問題,我們得出的結(jié)論是在新的應(yīng)用程序中不應(yīng)當(dāng)再使用它們昙啄。
信號量
信號量與已經(jīng)介紹過的IPC機(jī)構(gòu)(管道穆役,F(xiàn)IFO以及消息隊列)不同。它是一個計數(shù)器梳凛,用于多進(jìn)程對共享數(shù)據(jù)對象的存取孵睬。為了獲得共享資源,進(jìn)程需要指向下列操作伶跷。
- 測試控制該資源的信號量
- 如果此信號量為正,則進(jìn)程可以使用該資源秘狞。進(jìn)程將信號量減1叭莫,表示它使用了一個資源單位。
- 若此信號量的值為0烁试,則進(jìn)程進(jìn)入睡眠裝填雇初,直至信號量值大于0.若進(jìn)程被喚醒后,它返回至1
當(dāng)進(jìn)程不在使用由一個信息量控制的共享資源時减响,該信號量值增至1.如果有進(jìn)程正在睡眠等待此信號量則喚醒他們靖诗。
為了正確的實現(xiàn)信息量,信號量的測試及減1操作應(yīng)當(dāng)是原子操作支示。為此刊橘,信號量通常在內(nèi)核中實現(xiàn)。
常用的信號量形式被稱為雙態(tài)信號量颂鸿。它控制單個資源促绵,其初始值為1.但是一般而言,信號量的初始值可以是任一正值,該值說明有多少個共享資源單位可供共享應(yīng)用败晴。
不幸的是浓冒,系統(tǒng)V的信號量與此相比要復(fù)雜得多。三種特性造成了這種非必要的復(fù)雜性
- 信號量并非是一個非負(fù)值尖坤,而必須將信號量定義為含有一個或者多個信號量值得集合稳懒。當(dāng)創(chuàng)建一個信號量時,要制定該集合中的各個值
- 創(chuàng)建信息量與對其賦初值分開慢味。這是一個致命的弱點场梆,因為不能原子地創(chuàng)建一個信號量集合,并且對該集合中的所有值賦初值贮缕。
- 即使沒有進(jìn)程正在使用各種形式的系統(tǒng)V IPC,它們?nèi)匀皇谴嬖诘恼廾眨圆坏貌粸檫@種程序擔(dān)心,它在終止時并沒有釋放已經(jīng)分配給它的信號量感昼。
內(nèi)核為每個信號量設(shè)置了一個semid_ds結(jié)構(gòu)
struct semid_ds {
struct ipc_perm sem_perm;
struct sem *sem_base;
ushort sem_sems;
time_t sem_otime;
time_t sem_ctime;
}
對用戶而言装哆,sem_base指針是沒有價值的,它指向內(nèi)核中sem機(jī)構(gòu)數(shù)組定嗓,該數(shù)組中包含了sem_nsems個元素蜕琴,每個元素各對應(yīng)于集合中的一個信號量值。
struct sem {
ushort semval;
pid_t sempid;
ushort semncnt;
ushort semzcnt;
}
要調(diào)用的第一個函數(shù)是semget以獲得一個信號量id
int semget(key_t key, int nsems, int flag)
如果需要創(chuàng)建一個新的集合時宵溅,對semid_ds結(jié)構(gòu)的下列成員賦初值
- 對ipc_perm結(jié)構(gòu)賦初值凌简。該結(jié)構(gòu)中的mode被設(shè)置為flag中的相應(yīng)許可權(quán)。
- sem_otime設(shè)置為0
- sem_ctime設(shè)置為當(dāng)前時間
- sem_nsems設(shè)置為nsems
nsems是該集合中的信號量數(shù)恃逻。如果是創(chuàng)建新集合雏搂,則必須制定nsems.如果引用一個現(xiàn)存的集合,則將nsems指定為0
semctl函數(shù)包含了多種信號量操作
int semctl(int semid, int semnum, int cmd, union semumarg)
最后一個參數(shù)是個union
union semun {
int val;
struct semid_ds *buf;
ushort *array;
}
cmd 參數(shù)指定下列十種命令中的一種寇损,使其在semid指定的信號量集合上執(zhí)行此命令凸郑。其中有五條命令是針對一個特定的信號量的值,他們用semnum指定該集合中的一個成員矛市。semnum值在0和nsems-1之間
- IPC_STAT 對刺激和取semid_ds結(jié)構(gòu)芙沥,并存放在由arg.buf指向的結(jié)構(gòu)中
- IPC_SET 按由arg.buf指向的機(jī)構(gòu)中的值設(shè)置與此集合相關(guān)結(jié)構(gòu)中的下列三個字段值:sem_perm.uid,sem_perm.gid,sem_perm.mode.此命令只能由下列兩種進(jìn)程執(zhí)行:一種是其有效用戶ID等于sem_perm.cuid或sem_perm.uid的進(jìn)程;另一種是具有超級用戶特權(quán)的進(jìn)程
- IPC_RMID 從系統(tǒng)中刪除該信號量浊吏。這種刪除是立即的而昨。仍在使用此信號量的其他進(jìn)程在他們下次意圖對此信號量進(jìn)行操作時,將出錯返回EIDRM.此命令只能由下列兩種進(jìn)程執(zhí)行:一種是具有有效用戶id等于sem_perm.cuid或者sem_perm.uid的進(jìn)程找田;另一種是具有超級用戶特權(quán)的進(jìn)程歌憨。
- GETVAL 返回成員semnum的semval值
- SETVAL 是指成員semnum的semval值。該值由arg.val指定
- GETPID 返回成員semnum的sempid值
- GETNCNT 返回成員semnum的semncnt值
- GETALL 取該集合中的所有信號量的值墩衙,并將它們存放在array指向的數(shù)組中躺孝。
- SETALL 按array中指向的額數(shù)組中的值設(shè)置該集合中的所有信號量的值
函數(shù)semop自動執(zhí)行信號量集合上的操作數(shù)組
int semop(int semid, struct sembug semoparray[], size_t nops)
semoparray是一個指針享扔,它指向一個信號量操作數(shù)組
struct sembuf {
ushort sem_num;
short sem_op;
short sem_flg;
}
nops規(guī)定該數(shù)組中操作的數(shù)量
對集合中每個成員的操作由相應(yīng)的sem_op規(guī)定。此值可以是負(fù)值植袍、0惧眠、正值
- 最容易處理的情況是sem_op為正。這對應(yīng)于返回進(jìn)程占用的資源于个。sem_op值加到信號量的值上氛魁。如果指定了undo標(biāo)志,則也從該進(jìn)程的此信號量調(diào)整值中減去sem_op
- 若sem_op為負(fù)厅篓,則表示要獲取由該信號量控制的資源秀存。
如若該信號量的值大于或等于sem_op的絕對值,則從信號量值中減去sem_op的絕對值羽氮。這保證信號量的結(jié)果值大于等于0.如果指定了undo標(biāo)志或链,則sem_op的絕對值也加到該進(jìn)程的此信號量的調(diào)整值上。
如果信號量值小于sem_op的絕對值档押,則
1澳盐、若指定了IPC_NOWAIT 則出錯返回EAGAIN;
2、若未指定IPC_NOWAIT ,則該信號量的semncnt值加1令宿,然后調(diào)用進(jìn)程被掛起直至a叼耙、此信號量編程大于或等于sem_op的絕對值(即某個進(jìn)程已經(jīng)釋放了某些資源)。此信號量的semncnt值減1粒没,并且從信號量值中減去sem_op的絕對值筛婉。如果指定了undo標(biāo)準(zhǔn),則sem_op的絕對值也加到該進(jìn)程此信號量調(diào)整值上癞松。b爽撒、從系統(tǒng)中刪除了此信號量。在此情況下响蓉,函數(shù)出錯返回ERMID匆浙。c、進(jìn)程捕捉到一個信號厕妖,并從信號處理程序返回,在此情況下挑庶,此信號量的semncnt值減1言秸,并且函數(shù)出錯返回EINTR - 若sem_op為0,這表示希望等待到該信號量值變成0.如果此信號量值非0則:1迎捺、若指定了IPC_NOWAIT,則出錯返回EAGAIN 2举畸、若未指定IPC_NOWAIT,則該信號量的semncnt值加1(因為將進(jìn)入睡眠狀態(tài)),然后調(diào)用進(jìn)程被掛起凳枝,直至a抄沮、此信號量值變成0.此信號量semzcnt減1(因為已經(jīng)結(jié)束等待)b跋核、從系統(tǒng)中刪除了此信號量。在此情況下函數(shù)出錯返回ERMID.c叛买、進(jìn)程捕捉到一個信號砂代,并從信號處理程序返回。在此種情況下率挣,此信號量的semzcnt值減1刻伊,并且函數(shù)出錯返回EINTR
semop具有原子性,因為它或者執(zhí)行數(shù)組中的所有操作椒功,或者一個也不做
exit時的信號量調(diào)整
正如前面提到的捶箱,如果在進(jìn)程終止時,它暫用了經(jīng)由信號量分配的資源动漾,那么就會成為一個問題丁屎。無論何時只要微信號量操作指定了SEM_UNDO標(biāo)志,然后分配資源sem_op值小于0那么內(nèi)核就會記住對于該特定信號量旱眯,分配給我們多少資源晨川。當(dāng)該進(jìn)程終止時,不論資源或者不自愿键思,內(nèi)核豆?jié){檢驗該進(jìn)程是否還有尚未處理的信號量調(diào)整值础爬,如果有,則按調(diào)整值對應(yīng)相應(yīng)量今次那個調(diào)整吼鳞。
如果用帶SETVAL或SETALL命令的semctl設(shè)置一信號量的值看蚜,則在所有進(jìn)程中,該信號量的調(diào)整值都設(shè)置為0
雖然記錄所稍慢于信號量赔桌,但如果只需鎖一個資源供炎,并且不需要使用系統(tǒng)v信號量所有的花哨功能,則寧可使用記錄鎖疾党,理由是音诫,使用簡易。進(jìn)程終止時雪位,會處理任一遺留下的鎖竭钝。