[轉(zhuǎn)]linux進(jìn)程間通信(IPC)機(jī)制總結(jié)
在linux下的多個(gè)進(jìn)程間的通信機(jī)制叫做IPC(Inter-Process Communication),它是多個(gè)進(jìn)程之間相互溝通的一種方法。在linux下有多種進(jìn)程間通信的方法:半雙工管道、命名管道、消息隊(duì)列、信號(hào)、信號(hào)量、共享內(nèi)存、內(nèi)存映射文件,套接字等等。使用這些機(jī)制可以為linux下的網(wǎng)絡(luò)服務(wù)器開(kāi)發(fā)提供靈活而又堅(jiān)固的框架。
1.?管道 (PIPE)
管道實(shí)際是用于進(jìn)程間通信的一段共享內(nèi)存,創(chuàng)建管道的進(jìn)程稱為管道服務(wù)器,連接到一個(gè)管道的進(jìn)程為管道客戶機(jī)。一個(gè)進(jìn)程在向管道寫(xiě)入數(shù)據(jù)后,另一進(jìn)程就可以從管道的另一端將其讀取出來(lái)。
管道的特點(diǎn):
1、管道是半雙工的,數(shù)據(jù)只能向一個(gè)方向流動(dòng);需要雙方通信時(shí),需要建立起兩個(gè)管道;
2、只能用于父子進(jìn)程或者兄弟進(jìn)程之間(具有親緣關(guān)系的進(jìn)程)。比如fork或exec創(chuàng)建的新進(jìn)程,在使用exec創(chuàng)建新進(jìn)程時(shí),需要將管道的文件描述符作為參數(shù)傳遞給exec創(chuàng)建的新進(jìn)程。當(dāng)父進(jìn)程與使用fork創(chuàng)建的子進(jìn)程直接通信時(shí),發(fā)送數(shù)據(jù)的進(jìn)程關(guān)閉讀端,接受數(shù)據(jù)的進(jìn)程關(guān)閉寫(xiě)端。
3、單獨(dú)構(gòu)成一種獨(dú)立的文件系統(tǒng):管道對(duì)于管道兩端的進(jìn)程而言,就是一個(gè)文件,但它不是普通的文件,它不屬于某種文件系統(tǒng),而是自立門戶,單獨(dú)構(gòu)成一種文件系統(tǒng),并且只存在與內(nèi)存中。
4、數(shù)據(jù)的讀出和寫(xiě)入:一個(gè)進(jìn)程向管道中寫(xiě)的內(nèi)容被管道另一端的進(jìn)程讀出。寫(xiě)入的內(nèi)容每次都添加在管道緩沖區(qū)的末尾,并且每次都是從緩沖區(qū)的頭部讀出數(shù)據(jù)。
管道的實(shí)現(xiàn)機(jī)制:
管道是由內(nèi)核管理的一個(gè)緩沖區(qū),相當(dāng)于我們放入內(nèi)存中的一個(gè)紙條。管道的一端連接一個(gè)進(jìn)程的輸出。這個(gè)進(jìn)程會(huì)向管道中放入信息。管道的另一端連接一個(gè)進(jìn)程的輸入,這個(gè)進(jìn)程取出被放入管道的信息。一個(gè)緩沖區(qū)不需要很大,它被設(shè)計(jì)成為環(huán)形的數(shù)據(jù)結(jié)構(gòu),以便管道可以被循環(huán)利用。當(dāng)管道中沒(méi)有信息的話,從管道中讀取的進(jìn)程會(huì)等待,直到另一端的進(jìn)程放入信息。當(dāng)管道被放滿信息的時(shí)候,嘗試放入信息的進(jìn)程會(huì)等待,直到另一端的進(jìn)程取出信息。當(dāng)兩個(gè)進(jìn)程都終結(jié)的時(shí)候,管道也自動(dòng)消失。
管道只能在本地計(jì)算機(jī)中使用,而不可用于網(wǎng)絡(luò)間的通信。
pipe函數(shù)原型:
#include?
int?pipe(int?file_descriptor[2]);//建立管道,該函數(shù)在數(shù)組上填上兩個(gè)新的文件描述符后返回0,失敗返回-1。
eg.int?fd[2]
int?result?=?pipe(fd);
通過(guò)使用底層的read和write調(diào)用來(lái)訪問(wèn)數(shù)據(jù)。?向file_descriptor[1]寫(xiě)數(shù)據(jù),從file_descriptor[0]中讀數(shù)據(jù)。寫(xiě)入與讀取的順序原則是先進(jìn)先出。
管道讀寫(xiě)規(guī)則
當(dāng)沒(méi)有數(shù)據(jù)可讀時(shí)
O_NONBLOCK?disable:read調(diào)用阻塞,即進(jìn)程暫停執(zhí)行,一直等到有數(shù)據(jù)來(lái)到為止。
O_NONBLOCK?enable:read調(diào)用返回-1,errno值為EAGAIN。
當(dāng)管道滿的時(shí)候
O_NONBLOCK?disable:?write調(diào)用阻塞,直到有進(jìn)程讀走數(shù)據(jù)
O_NONBLOCK?enable:調(diào)用返回-1,errno值為EAGAIN
如果所有管道寫(xiě)端對(duì)應(yīng)的文件描述符被關(guān)閉,則read返回0
如果所有管道讀端對(duì)應(yīng)的文件描述符被關(guān)閉,則write操作會(huì)產(chǎn)生信號(hào)SIGPIPE
當(dāng)要寫(xiě)入的數(shù)據(jù)量不大于PIPE_BUF(Posix.1要求PIPE_BUF至少512字節(jié))時(shí),linux將保證寫(xiě)入的原子性。
當(dāng)要寫(xiě)入的數(shù)據(jù)量大于PIPE_BUF時(shí),linux將不再保證寫(xiě)入的原子性。
2.?命名管道(FIFO)
命名管道是一種特殊類型的文件,它在系統(tǒng)中以文件形式存在。這樣克服了管道的弊端,他可以允許沒(méi)有親緣關(guān)系的進(jìn)程間通信。
創(chuàng)建管道的兩個(gè)系統(tǒng)調(diào)用原型:
[cpp]?copy
#include?
#include?
int?mkfifo(const?char?*filename,mode_t?mode);?//建立一個(gè)名字為filename的命名管道,參數(shù)mode為該文件的權(quán)限(mode%~umask),若成功則返回0,否則返回-1,錯(cuò)誤原因存于errno中。
eg.mkfifo(?"/tmp/cmd_pipe",?S_IFIFO?|?0666?);
具體操作方法只要?jiǎng)?chuàng)建了一個(gè)命名管道然后就可以使用open、read、write等系統(tǒng)調(diào)用來(lái)操作。創(chuàng)建可以手工創(chuàng)建或者程序中創(chuàng)建。
[cpp]?copy
int?mknod(const?char?*path,?mode_t?mode,?dev_t?dev);?//第一個(gè)參數(shù)表示你要?jiǎng)?chuàng)建的文件的名稱,第二個(gè)參數(shù)表示文件類型,第三個(gè)參數(shù)表示該文件對(duì)應(yīng)的設(shè)備文件的設(shè)備號(hào)。只有當(dāng)文件類型為?S_IFCHR?或?S_IFBLK?的時(shí)候該文件才有設(shè)備號(hào),創(chuàng)建普通文件時(shí)傳入0即可。
eg.mknod(FIFO_FILE,S_IFIFO|0666,0);
管道和命名管道的區(qū)別:
對(duì)于命名管道FIFO來(lái)說(shuō),IO操作和普通管道IO操作基本一樣,但是兩者有一個(gè)主要的區(qū)別,在命名管道中,管道可以是事先已經(jīng)創(chuàng)建好的,比如我們?cè)诿钚邢聢?zhí)行
mkfifo myfifo
就是創(chuàng)建一個(gè)命名通道,我們必須用open函數(shù)來(lái)顯示地建立連接到管道的通道,而在管道中,管道已經(jīng)在主進(jìn)程里創(chuàng)建好了,然后在fork時(shí)直接復(fù)制相關(guān)數(shù)據(jù)或者是用exec創(chuàng)建的新進(jìn)程時(shí)把管道的文件描述符當(dāng)參數(shù)傳遞進(jìn)去。
一般來(lái)說(shuō)FIFO和PIPE一樣總是處于阻塞狀態(tài)。也就是說(shuō)如果命名管道FIFO打開(kāi)時(shí)設(shè)置了讀權(quán)限,則讀進(jìn)程將一直阻塞,一直到其他進(jìn)程打開(kāi)該FIFO并向管道寫(xiě)入數(shù)據(jù)。這個(gè)阻塞動(dòng)作反過(guò)來(lái)也是成立的。如果不希望命名管道操作的時(shí)候發(fā)生阻塞,可以在open的時(shí)候使用O_NONBLOCK標(biāo)志,以關(guān)閉默認(rèn)的阻塞操作。
3.?信號(hào) (signal)
信號(hào)機(jī)制是unix系統(tǒng)中最為古老的進(jìn)程之間的通信機(jī)制,用于一個(gè)或幾個(gè)進(jìn)程之間傳遞異步信號(hào)。信號(hào)可以有各種異步事件產(chǎn)生,比如鍵盤(pán)中斷等。shell也可以使用信號(hào)將作業(yè)控制命令傳遞給它的子進(jìn)程。
在此列出幾個(gè)簡(jiǎn)單使用方法定義:
[cpp]?copy
#include?
#include?
void?(*signal(int?sig,void?(*func)(int)))(int);?//用于截取系統(tǒng)信號(hào),第一個(gè)參數(shù)為信號(hào),第二個(gè)參數(shù)為對(duì)此信號(hào)掛接用戶自己的處理函數(shù)指針。返回值為以前信號(hào)處理程序的指針。
eg.int?ret?=?signal(SIGSTOP,?sig_handle);
由于signal不夠健壯,推薦使用sigaction函數(shù)。
[cpp]?copy
int?kill(pid_t?pid,int?sig);?//kill函數(shù)向進(jìn)程號(hào)為pid的進(jìn)程發(fā)送信號(hào),信號(hào)值為sig。當(dāng)pid為0時(shí),向當(dāng)前系統(tǒng)的所有進(jìn)程發(fā)送信號(hào)sig。
int?raise(int?sig);//向當(dāng)前進(jìn)程中自舉一個(gè)信號(hào)sig,?即向當(dāng)前進(jìn)程發(fā)送信號(hào)。
#include?
unsigned?int?alarm(unsigned?int?seconds);?//alarm()用來(lái)設(shè)置信號(hào)SIGALRM在經(jīng)過(guò)參數(shù)seconds指定的秒數(shù)后傳送給目前的進(jìn)程。如果參數(shù)seconds為0,則之前設(shè)置的鬧鐘會(huì)被取消,并將剩下的時(shí)間返回。使用alarm函數(shù)的時(shí)候要注意alarm函數(shù)的覆蓋性,即在一個(gè)進(jìn)程中采用一次alarm函數(shù)則該進(jìn)程之前的alarm函數(shù)將失效。
int?pause(void);?//使調(diào)用進(jìn)程(或線程)睡眠狀態(tài),直到接收到信號(hào),要么終止,或?qū)е滤{(diào)用一個(gè)信號(hào)捕獲函數(shù)。
4.?消息隊(duì)列(Message queues)
消息隊(duì)列是內(nèi)核地址空間中的內(nèi)部鏈表,通過(guò)linux內(nèi)核在各個(gè)進(jìn)程直接傳遞內(nèi)容,消息順序地發(fā)送到消息隊(duì)列中,并以幾種不同的方式從隊(duì)列中獲得,每個(gè)消息隊(duì)列可以用IPC標(biāo)識(shí)符唯一地進(jìn)行識(shí)別。內(nèi)核中的消息隊(duì)列是通過(guò)IPC的標(biāo)識(shí)符來(lái)區(qū)別,不同的消息隊(duì)列直接是相互獨(dú)立的。每個(gè)消息隊(duì)列中的消息,又構(gòu)成一個(gè)獨(dú)立的鏈表。
消息隊(duì)列克服了信號(hào)承載信息量少,管道只能承載無(wú)格式字符流。
消息隊(duì)列頭文件:
[cpp]?copy
#include?
#include?
#include?
1、消息緩沖區(qū)結(jié)構(gòu):
[cpp]?copy
struct?msgbuf{
long?mtype;
char?mtext[1];//柔性數(shù)組
}
在結(jié)構(gòu)中有兩個(gè)成員,mtype為消息類型,用戶可以給某個(gè)消息設(shè)定一個(gè)類型,可以在消息隊(duì)列中正確地發(fā)送和接受自己的消息。mtext為消息數(shù)據(jù),采用柔性數(shù)組,用戶可以重新定義msgbuf結(jié)構(gòu)。例如:
[cpp]?copy
struct?msgbuf{
long?mtype;
char?mtext[1];//柔性數(shù)組
}
當(dāng)然用戶不可隨意定義msgbuf結(jié)構(gòu),因?yàn)樵趌inux中消息的大小是有限制的,在linux/msg.h中定義如下:
#define MSGMAX 8192
消息總的大小不能超過(guò)8192個(gè)字節(jié),包括mtype成員(4個(gè)字節(jié))。
2、msqid_ds內(nèi)核數(shù)據(jù)結(jié)構(gòu)。
[cpp]?copy
Linux內(nèi)核中,每個(gè)消息隊(duì)列都維護(hù)一個(gè)結(jié)構(gòu)體,此結(jié)構(gòu)體保存著消息隊(duì)列當(dāng)前狀態(tài)信息,該結(jié)構(gòu)體在頭文件linux/msg.h中定義。
3、ipc_perm內(nèi)核數(shù)據(jù)結(jié)構(gòu)
[cpp]?copy
struct?ipc_perm{
key_t?key;
uid_t?uid;
gid_t?gid;
.......
};
結(jié)構(gòu)體ipc_perm保存著消息隊(duì)列的一些重要的信息,比如說(shuō)消息隊(duì)列關(guān)聯(lián)的鍵值,消息隊(duì)列的用戶id組id等。它定義在頭文件linux/ipc.h中。
常用函數(shù):
系統(tǒng)建立IPC通訊?(消息隊(duì)列、信號(hào)量和共享內(nèi)存)?時(shí)必須指定一個(gè)ID值。通常情況下,該id值通過(guò)ftok函數(shù)得到。
[cpp]?copy
key_t?ftok(?const?char?*?fname,?int?id?);//參數(shù)一為目錄名稱,?參數(shù)二為id。如指定文件的索引節(jié)點(diǎn)號(hào)為65538,換算成16進(jìn)制為0x010002,而你指定的ID值為38,換算成16進(jìn)制為0x26,則最后的key_t返回值為0x26010002。
eg.key_t?key?=?key?=ftok(".",?1);
int?msgget(key_t?key,int?msgflag);?//msgget用來(lái)創(chuàng)建和訪問(wèn)一個(gè)消息隊(duì)列。程序必須提供一個(gè)鍵值來(lái)命名特定的消息隊(duì)列。
eg.int?msg_id?=?msgget(key,?IPC_CREATE?|?IPC_EXCL?|?0x0666);//根據(jù)關(guān)鍵字創(chuàng)建一個(gè)新的隊(duì)列(IPC_CREATE),如果隊(duì)列存在則出錯(cuò)(IPC_EXCL),擁有對(duì)文件的讀寫(xiě)執(zhí)行權(quán)限(0666)。
int?msgsnd(int?msgid,const?void?*msgptr,size_t?msg_sz,int?msgflg);?//msgsnd函數(shù)允許我們把一條消息添加到消息隊(duì)列中。msgptr只想準(zhǔn)備發(fā)送消息的指針,指針結(jié)構(gòu)體必須以一個(gè)長(zhǎng)整型變量開(kāi)始。
eg.struct?msgmbuf{
int?mtype;
char?mtext[10];
};
struct?msgmbuf?msg_mbuf;
msg_mbuf.mtype?=?10;//消息大小10字節(jié)
memcpy(msg_mbuf.mtext,?"測(cè)試消息",?sizeof("測(cè)試消息"));
int?ret?=?msgsnd(msg_id,?&msg_mbuf,?sizeof("測(cè)試消息"),?IPC_NOWAIT);
int?msgrcv(int?msgid,?void?*msgptr,?size_t?msg_sz,?long?int?msgtype,?int?msgflg);?//msgrcv可以通過(guò)msqid對(duì)指定消息隊(duì)列進(jìn)行接收操作。第二個(gè)參數(shù)為消息緩沖區(qū)變量地址,第三個(gè)參數(shù)為消息緩沖區(qū)結(jié)構(gòu)大小,但是不包括mtype成員長(zhǎng)度,第四個(gè)參數(shù)為mtype指定從隊(duì)列中獲取的消息類型。
eg.int?ret?=?msgrcv(msg_id,?&msg_mbuf,?10,?10,?IPC_NOWAIT?|?MSG_NOERROR);
int?msgctl(int?msqid,int?cmd,struct?msqid_ds?*buf);?//msgctl函數(shù)主要是一些控制如刪除消息隊(duì)列等操作。?cmd值如下:
IPC_STAT:獲取隊(duì)列的msgid_ds結(jié)構(gòu),并把它存到buf指向的地址。
IPC_SET:將隊(duì)列的msgid_ds設(shè)置為buf指向的msgid_ds。
IPC_RMID:內(nèi)核刪除消息隊(duì)列,最后一項(xiàng)填NULL,?執(zhí)行操作后,內(nèi)核會(huì)把消息隊(duì)列從系統(tǒng)中刪除。
消息隊(duì)列的本質(zhì)
Linux的消息隊(duì)列(queue)實(shí)質(zhì)上是一個(gè)鏈表,它有消息隊(duì)列標(biāo)識(shí)符(queue?ID)。?msgget創(chuàng)建一個(gè)新隊(duì)列或打開(kāi)一個(gè)存在的隊(duì)列;msgsnd向隊(duì)列末端添加一條新消息;msgrcv從隊(duì)列中取消息,?取消息是不一定遵循先進(jìn)先出的,?也可以按消息的類型字段取消息。
消息隊(duì)列與命名管道的比較
消息隊(duì)列跟命名管道有不少的相同之處,通過(guò)與命名管道一樣,消息隊(duì)列進(jìn)行通信的進(jìn)程可以是不相關(guān)的進(jìn)程,同時(shí)它們都是通過(guò)發(fā)送和接收的方式來(lái)傳遞數(shù)據(jù)的。在命名管道中,發(fā)送數(shù)據(jù)用write,接收數(shù)據(jù)用read,則在消息隊(duì)列中,發(fā)送數(shù)據(jù)用msgsnd,接收數(shù)據(jù)用msgrcv。而且它們對(duì)每個(gè)數(shù)據(jù)都有一個(gè)最大長(zhǎng)度的限制。
與命名管道相比,消息隊(duì)列的優(yōu)勢(shì)在于,1、消息隊(duì)列也可以獨(dú)立于發(fā)送和接收進(jìn)程而存在,從而消除了在同步命名管道的打開(kāi)和關(guān)閉時(shí)可能產(chǎn)生的困難。2、同時(shí)通過(guò)發(fā)送消息還可以避免命名管道的同步和阻塞問(wèn)題,不需要由進(jìn)程自己來(lái)提供同步方法。3、接收程序可以通過(guò)消息類型有選擇地接收數(shù)據(jù),而不是像命名管道中那樣,只能默認(rèn)地接收。
5.?信號(hào)量(Semaphore)
信號(hào)量是一種計(jì)數(shù)器,用于控制對(duì)多個(gè)進(jìn)程共享的資源進(jìn)行的訪問(wèn)。它們常常被用作一個(gè)鎖機(jī)制,在某個(gè)進(jìn)程正在對(duì)特定的資源進(jìn)行操作時(shí),信號(hào)量可以防止另一個(gè)進(jìn)程去訪問(wèn)它。
信號(hào)量是特殊的變量,它只取正整數(shù)值并且只允許對(duì)這個(gè)值進(jìn)行兩種操作:等待(wait)和信號(hào)(signal)。(P、V操作,P用于等待,V用于信號(hào))
p(sv):如果sv的值大于0,就給它減1;如果它的值等于0,就掛起該進(jìn)程的執(zhí)行
V(sv):如果有其他進(jìn)程因等待sv而被掛起,就讓它恢復(fù)運(yùn)行;如果沒(méi)有其他進(jìn)程因等待sv而掛起,則給它加1
簡(jiǎn)單理解就是P相當(dāng)于申請(qǐng)資源,V相當(dāng)于釋放資源
信號(hào)量頭文件:
[cpp]?copy
#include?
#include?
#include?
內(nèi)核為每個(gè)信號(hào)量集合都維護(hù)一個(gè)semid_ds結(jié)構(gòu):
[cpp]?copy
struct?semid_ds{
struct?ipc_perm?sem_perm;
unsigned?short?sem_nsems;
time_t?sem_otime;
time_t?sem_ctime;
...
}
信號(hào)量數(shù)據(jù)結(jié)構(gòu):
[cpp]?copy
union?semun{
int?val;
struct?semid_ds?*buf;
unsigned?short?*array;
struct?seminfo?*__buf;
}
信號(hào)量操作sembuf結(jié)構(gòu):
[cpp]?copy
struct?sembuf{
ushort?sem_num;//信號(hào)量的編號(hào)
short?sem_op;//信號(hào)量的操作。如果為正,則從信號(hào)量中加上一個(gè)值,如果為負(fù),則從信號(hào)量中減掉一個(gè)值,如果為0,則將進(jìn)程設(shè)置為睡眠狀態(tài),直到信號(hào)量的值為0為止。
short?sem_flg;//信號(hào)的操作標(biāo)志,一般為IPC_NOWAIT。
}
常用函數(shù):
[cpp]?copy
int?semget(key_t?key,?int?num_sems,?int?sem_flags);?//semget函數(shù)用于創(chuàng)建一個(gè)新的信號(hào)量集合?,?或者訪問(wèn)一個(gè)現(xiàn)有的集合(不同進(jìn)程只要key值相同即可訪問(wèn)同一信號(hào)量集合)。第一個(gè)參數(shù)key是ftok生成的鍵值,第二個(gè)參數(shù)num_sems可以指定在新的集合應(yīng)該創(chuàng)建的信號(hào)量的數(shù)目,第三個(gè)參數(shù)sem_flags是打開(kāi)信號(hào)量的方式。
eg.int?semid?=?semget(key,?0,?IPC_CREATE?|?IPC_EXCL?|?0666);//第三個(gè)參數(shù)參考消息隊(duì)列int?msgget(key_t?key,int?msgflag);第二個(gè)參數(shù)。
int?semop(int?sem_id,?struct?sembuf?*sem_ops,?size_t?num_sem_ops);?//semop函數(shù)用于改變信號(hào)量的值。第二個(gè)參數(shù)是要在信號(hào)集合上執(zhí)行操作的一個(gè)數(shù)組,第三個(gè)參數(shù)是該數(shù)組操作的個(gè)數(shù)?。
eg.struct?sembuf?sops?=?{0,?+1,?IPC_NOWAIT};//對(duì)索引值為0的信號(hào)量加一。
semop(semid,?&sops,?1);//以上功能執(zhí)行的次數(shù)為一次。
int?semctl(int?sem_id,?int?sem_num,?int?command,...);?//semctl函數(shù)用于信號(hào)量集合執(zhí)行控制操作,初始化信號(hào)量的值,刪除一個(gè)信號(hào)量等。?類似于調(diào)用msgctl(),?msgctl()是用于消息隊(duì)列上的操作。第一個(gè)參數(shù)是指定的信號(hào)量集合(semget的返回值),第二個(gè)參數(shù)是要執(zhí)行操作的信號(hào)量在集合中的索引值(例如集合中第一個(gè)信號(hào)量下標(biāo)為0),第三個(gè)command參數(shù)代表要在集合上執(zhí)行的命令。
IPC_STAT:獲取某個(gè)集合的semid_ds結(jié)構(gòu),并把它存儲(chǔ)到semun聯(lián)合體的buf參數(shù)指向的地址。
IPC_SET:將某個(gè)集合的semid_ds結(jié)構(gòu)的ipc_perm成員的值。該命令所取的值是從semun聯(lián)合體的buf參數(shù)中取到。
IPC_RMID:內(nèi)核刪除該信號(hào)量集合。
GETVAL:返回集合中某個(gè)信號(hào)量的值。
SETVAL:把集合中單個(gè)信號(hào)量的值設(shè)置成為聯(lián)合體val成員的值。
6.?共享內(nèi)存(Share Memory)
共享內(nèi)存是在多個(gè)進(jìn)程之間共享內(nèi)存區(qū)域的一種進(jìn)程間的通信方式,由IPC為進(jìn)程創(chuàng)建的一個(gè)特殊地址范圍,它將出現(xiàn)在該進(jìn)程的地址空間(這里的地址空間具體是哪個(gè)地方?)中。其他進(jìn)程可以將同一段共享內(nèi)存連接到自己的地址空間中。所有進(jìn)程都可以訪問(wèn)共享內(nèi)存中的地址,就好像它們是malloc分配的一樣。如果一個(gè)進(jìn)程向共享內(nèi)存中寫(xiě)入了數(shù)據(jù),所做的改動(dòng)將立刻被其他進(jìn)程看到。
共享內(nèi)存是IPC最快捷的方式,因?yàn)楣蚕韮?nèi)存方式的通信沒(méi)有中間過(guò)程,而管道、消息隊(duì)列等方式則是需要將數(shù)據(jù)通過(guò)中間機(jī)制進(jìn)行轉(zhuǎn)換。共享內(nèi)存方式直接將某段內(nèi)存段進(jìn)行映射,多個(gè)進(jìn)程間的共享內(nèi)存是同一塊的物理空間,僅僅映射到各進(jìn)程的地址不同而已,因此不需要進(jìn)行復(fù)制,可以直接使用此段空間。
注意:共享內(nèi)存本身并沒(méi)有同步機(jī)制,需要程序員自己控制。
共享內(nèi)存頭文件:
[cpp]?copy
#include?
#include?
#include?
結(jié)構(gòu)shmid_ds結(jié)構(gòu)體(是不是很眼熟,看消息隊(duì)列的msgid_ds結(jié)構(gòu)體):
[cpp]?copy
strcut?shmid_ds{
struct?ipc_perm????shm_perm;
size_t????shm_segsz;
time_t????shm_atime;
time_t????shm_dtime;
......
}
共享內(nèi)存函數(shù)定義:
[cpp]?copy
int?shmget(key_t?key,size_t?size,int?shmflg);??//shmget函數(shù)用來(lái)創(chuàng)建一個(gè)新的共享內(nèi)存段,?或者訪問(wèn)一個(gè)現(xiàn)有的共享內(nèi)存段(不同進(jìn)程只要key值相同即可訪問(wèn)同一共享內(nèi)存段)。第一個(gè)參數(shù)key是ftok生成的鍵值,第二個(gè)參數(shù)size為共享內(nèi)存的大小,第三個(gè)參數(shù)sem_flags是打開(kāi)共享內(nèi)存的方式。
eg.int?shmid?=?shmget(key,?1024,?IPC_CREATE?|?IPC_EXCL?|?0666);//第三個(gè)參數(shù)參考消息隊(duì)列int?msgget(key_t?key,int?msgflag);
void?*shmat(int?shm_id,const?void?*shm_addr,int?shmflg);?//shmat函數(shù)通過(guò)shm_id將共享內(nèi)存連接到進(jìn)程的地址空間中。第二個(gè)參數(shù)可以由用戶指定共享內(nèi)存映射到進(jìn)程空間的地址,shm_addr如果為0,則由內(nèi)核試著查找一個(gè)未映射的區(qū)域。返回值為共享內(nèi)存映射的地址。
eg.char?*shms?=?(char?*)shmat(shmid,?0,?0);//shmid由shmget獲得
int?shmdt(const?void?*shm_addr);?//shmdt函數(shù)將共享內(nèi)存從當(dāng)前進(jìn)程中分離。?參數(shù)為共享內(nèi)存映射的地址。
eg.shmdt(shms);
int?shmctl(int?shm_id,int?cmd,struct?shmid_ds?*buf);//shmctl函數(shù)是控制函數(shù),使用方法和消息隊(duì)列msgctl()函數(shù)調(diào)用完全類似。參數(shù)一shm_id是共享內(nèi)存的句柄,cmd是向共享內(nèi)存發(fā)送的命令,最后一個(gè)參數(shù)buf是向共享內(nèi)存發(fā)送命令的參數(shù)。
消息隊(duì)列、信號(hào)量以及共享內(nèi)存的相似之處:
它們被統(tǒng)稱為XSI?IPC,它們?cè)趦?nèi)核中有相似的IPC結(jié)構(gòu)(消息隊(duì)列的msgid_ds,信號(hào)量的semid_ds,共享內(nèi)存的shmid_ds),而且都用一個(gè)非負(fù)整數(shù)的標(biāo)識(shí)符加以引用(消息隊(duì)列的msg_id,信號(hào)量的sem_id,共享內(nèi)存的shm_id,分別通過(guò)msgget、semget以及shmget獲得),標(biāo)志符是IPC對(duì)象的內(nèi)部名,每個(gè)IPC對(duì)象都有一個(gè)鍵(key_t?key)相關(guān)聯(lián),將這個(gè)鍵作為該對(duì)象的外部名。
XSI?IPC和PIPE、FIFO的區(qū)別:
1、XSI?IPC的IPC結(jié)構(gòu)是在系統(tǒng)范圍內(nèi)起作用,沒(méi)用使用引用計(jì)數(shù)。如果一個(gè)進(jìn)程創(chuàng)建一個(gè)消息隊(duì)列,并在消息隊(duì)列中放入幾個(gè)消息,進(jìn)程終止后,即使現(xiàn)在已經(jīng)沒(méi)有程序使用該消息隊(duì)列,消息隊(duì)列及其內(nèi)容依然保留。而PIPE在最后一個(gè)引用管道的進(jìn)程終止時(shí),管道就被完全刪除了。對(duì)于FIFO最后一個(gè)引用FIFO的進(jìn)程終止時(shí),雖然FIFO還在系統(tǒng),但是其中的內(nèi)容會(huì)被刪除。
2、和PIPE、FIFO不一樣,XSI?IPC不使用文件描述符,所以不能用ls查看IPC對(duì)象,不能用rm命令刪除,不能用chmod命令刪除它們的訪問(wèn)權(quán)限。只能使用ipcs和ipcrm來(lái)查看可以刪除它們。
7.?內(nèi)存映射(Memory Map)
內(nèi)存映射文件,是由一個(gè)文件到一塊內(nèi)存的映射。內(nèi)存映射文件與虛擬內(nèi)存有些類似,通過(guò)內(nèi)存映射文件可以保留一個(gè)地址的區(qū)域,
同時(shí)將物理存儲(chǔ)器提交給此區(qū)域,內(nèi)存文件映射的物理存儲(chǔ)器來(lái)自一個(gè)已經(jīng)存在于磁盤(pán)上的文件,而且在對(duì)該文件進(jìn)行操作之前必須首先對(duì)文件進(jìn)行映射。使用內(nèi)存映射文件處理存儲(chǔ)于磁盤(pán)上的文件時(shí),將不必再對(duì)文件執(zhí)行I/O操作。每一個(gè)使用該機(jī)制的進(jìn)程通過(guò)把同一個(gè)共享的文件映射到自己的進(jìn)程地址空間來(lái)實(shí)現(xiàn)多個(gè)進(jìn)程間的通信(這里類似于共享內(nèi)存,只要有一個(gè)進(jìn)程對(duì)這塊映射文件的內(nèi)存進(jìn)行操作,其他進(jìn)程也能夠馬上看到)。
使用內(nèi)存映射文件不僅可以實(shí)現(xiàn)多個(gè)進(jìn)程間的通信,還可以用于處理大文件提高效率。因?yàn)槲覀兤胀ǖ淖龇ㄊ前汛疟P(pán)上的文件先拷貝到內(nèi)核空間的一個(gè)緩沖區(qū)再拷貝到用戶空間(內(nèi)存),用戶修改后再將這些數(shù)據(jù)拷貝到緩沖區(qū)再拷貝到磁盤(pán)文件,一共四次拷貝。如果文件數(shù)據(jù)量很大,拷貝的開(kāi)銷是非常大的。那么問(wèn)題來(lái)了,系統(tǒng)在在進(jìn)行內(nèi)存映射文件就不需要數(shù)據(jù)拷貝?mmap()確實(shí)沒(méi)有進(jìn)行數(shù)據(jù)拷貝,真正的拷貝是在在缺頁(yè)中斷處理時(shí)進(jìn)行的,由于mmap()將文件直接映射到用戶空間,所以中斷處理函數(shù)根據(jù)這個(gè)映射關(guān)系,直接將文件從硬盤(pán)拷貝到用戶空間,所以只進(jìn)行一次數(shù)據(jù)拷貝。效率高于read/write。
內(nèi)存映射頭文件:
[cpp]?copy
#include?
void?*mmap(void*start,size_t?length,int?prot,int?flags,int?fd,off_t?offset);?//mmap函數(shù)將一個(gè)文件或者其它對(duì)象映射進(jìn)內(nèi)存。?第一個(gè)參數(shù)為映射區(qū)的開(kāi)始地址,設(shè)置為0表示由系統(tǒng)決定映射區(qū)的起始地址,第二個(gè)參數(shù)為映射的長(zhǎng)度,第三個(gè)參數(shù)為期望的內(nèi)存保護(hù)標(biāo)志,第四個(gè)參數(shù)是指定映射對(duì)象的類型,第五個(gè)參數(shù)為文件描述符(指明要映射的文件),第六個(gè)參數(shù)是被映射對(duì)象內(nèi)容的起點(diǎn)。成功返回被映射區(qū)的指針,失敗返回MAP_FAILED[其值為(void?*)-1]。
int?munmap(void*?start,size_t?length);?//munmap函數(shù)用來(lái)取消參數(shù)start所指的映射內(nèi)存起始地址,參數(shù)length則是欲取消的內(nèi)存大小。如果解除映射成功則返回0,否則返回-1,錯(cuò)誤原因存于errno中錯(cuò)誤代碼EINVAL。
int?msync(void?*addr,size_t?len,int?flags);?//msync函數(shù)實(shí)現(xiàn)磁盤(pán)文件內(nèi)容和共享內(nèi)存取內(nèi)容一致,即同步。第一個(gè)參數(shù)為文件映射到進(jìn)程空間的地址,第二個(gè)參數(shù)為映射空間的大小,第三個(gè)參數(shù)為刷新的參數(shù)設(shè)置。
共享內(nèi)存和內(nèi)存映射文件的區(qū)別:
內(nèi)存映射文件是利用虛擬內(nèi)存把文件映射到進(jìn)程的地址空間中去,在此之后進(jìn)程操作文件,就像操作進(jìn)程空間里的地址一樣了,比如使用c語(yǔ)言的memcpy等內(nèi)存操作的函數(shù)。這種方法能夠很好的應(yīng)用在需要頻繁處理一個(gè)文件或者是一個(gè)大文件的場(chǎng)合,這種方式處理IO效率比普通IO效率要高
共享內(nèi)存是內(nèi)存映射文件的一種特殊情況,內(nèi)存映射的是一塊內(nèi)存,而非磁盤(pán)上的文件。共享內(nèi)存的主語(yǔ)是進(jìn)程(Process),操作系統(tǒng)默認(rèn)會(huì)給每一個(gè)進(jìn)程分配一個(gè)內(nèi)存空間,每一個(gè)進(jìn)程只允許訪問(wèn)操作系統(tǒng)分配給它的哪一段內(nèi)存,而不能訪問(wèn)其他進(jìn)程的。而有時(shí)候需要在不同進(jìn)程之間訪問(wèn)同一段內(nèi)存,怎么辦呢?操作系統(tǒng)給出了 創(chuàng)建訪問(wèn)共享內(nèi)存的API,需要共享內(nèi)存的進(jìn)程可以通過(guò)這一組定義好的API來(lái)訪問(wèn)多個(gè)進(jìn)程之間共有的內(nèi)存,各個(gè)進(jìn)程訪問(wèn)這一段內(nèi)存就像訪問(wèn)一個(gè)硬盤(pán)上的文件一樣。
內(nèi)存映射文件與虛擬內(nèi)存的區(qū)別和聯(lián)系:
內(nèi)存映射文件和虛擬內(nèi)存都是操作系統(tǒng)內(nèi)存管理的重要部分,兩者有相似點(diǎn)也有不同點(diǎn)。
聯(lián)系:虛擬內(nèi)存和內(nèi)存映射都是將一部分內(nèi)容加載到內(nèi)存,另一部放在磁盤(pán)上的一種機(jī)制。對(duì)于用戶而言都是透明的。
區(qū)別:虛擬內(nèi)存是硬盤(pán)的一部分,是內(nèi)存和硬盤(pán)的數(shù)據(jù)交換區(qū),許多程序運(yùn)行過(guò)程中把暫時(shí)不用的程序數(shù)據(jù)放入這塊虛擬內(nèi)存,節(jié)約內(nèi)存資源。內(nèi)存映射是一個(gè)文件到一塊內(nèi)存的映射,這樣程序通過(guò)內(nèi)存指針就可以對(duì)文件進(jìn)行訪問(wèn)。
虛擬內(nèi)存的硬件基礎(chǔ)是分頁(yè)機(jī)制。另外一個(gè)基礎(chǔ)就是局部性原理(時(shí)間局部性和空間局部性),這樣就可以將程序的一部分裝入內(nèi)存,其余部分留在外存,當(dāng)訪問(wèn)信息不存在,再將所需數(shù)據(jù)調(diào)入內(nèi)存。而內(nèi)存映射文件并不是局部性,而是使虛擬地址空間的某個(gè)區(qū)域銀蛇磁盤(pán)的全部或部分內(nèi)容,通過(guò)該區(qū)域?qū)Ρ挥成涞拇疟P(pán)文件進(jìn)行訪問(wèn),不必進(jìn)行文件I/O也不需要對(duì)文件內(nèi)容進(jìn)行緩沖處理。
8.?套接字
套接字機(jī)制不但可以單機(jī)的不同進(jìn)程通信,而且使得跨網(wǎng)機(jī)器間進(jìn)程可以通信。
套接字的創(chuàng)建和使用與管道是有區(qū)別的,套接字明確地將客戶端與服務(wù)器區(qū)分開(kāi)來(lái),可以實(shí)現(xiàn)多個(gè)客戶端連到同一服務(wù)器。
服務(wù)器套接字連接過(guò)程描述:
首先,服務(wù)器應(yīng)用程序用socket創(chuàng)建一個(gè)套接字,它是系統(tǒng)分配服務(wù)器進(jìn)程的類似文件描述符的資源。?接著,服務(wù)器調(diào)用bind給套接字命名。這個(gè)名字是一個(gè)標(biāo)示符,它允許linux將進(jìn)入的針對(duì)特定端口的連接轉(zhuǎn)到正確的服務(wù)器進(jìn)程。?然后,系統(tǒng)調(diào)用listen函數(shù)開(kāi)始接聽(tīng),等待客戶端連接。listen創(chuàng)建一個(gè)隊(duì)列并將其用于存放來(lái)自客戶端的進(jìn)入連接。?當(dāng)客戶端調(diào)用connect請(qǐng)求連接時(shí),服務(wù)器調(diào)用accept接受客戶端連接,accept此時(shí)會(huì)創(chuàng)建一個(gè)新套接字,用于與這個(gè)客戶端進(jìn)行通信。
客戶端套接字連接過(guò)程描述:
客戶端首先調(diào)用socket創(chuàng)建一個(gè)未命名套接字,讓后將服務(wù)器的命名套接字作為地址來(lái)調(diào)用connect與服務(wù)器建立連接。
只要雙方連接建立成功,我們就可以像操作底層文件一樣來(lái)操作socket套接字實(shí)現(xiàn)通信。
幾個(gè)基礎(chǔ)函數(shù)定義:
[cpp]?copy
#include?
#include?
int?socket(it?domain,int?type,int?protocal);
int?bind(int?socket,const?struct?sockaddr?*address,size_t?address_len);
int?listen(int?socket,int?backlog);
int?accept(int?socket,struct?sockaddr?*address,size_t?*address_len);
int?connect(int?socket,const?struct?sockaddr?*addrsss,size_t?address_len);
詳細(xì)請(qǐng)看:http://blog.csdn.net/a987073381/article/details/51869000
還記得消息隊(duì)列中的msgbuf結(jié)構(gòu)嗎?在socket編程中也同樣適用,在socket編程中,一個(gè)服務(wù)可以接受多個(gè)客戶端的連接,可以為每個(gè)客戶端設(shè)定一個(gè)消息類型,服務(wù)器和客戶端直接的通信可以通過(guò)此消息類型來(lái)發(fā)送和接受消息,而且多個(gè)客戶端之間也可以通過(guò)消息類型來(lái)區(qū)分。
參考:
http://www.cnblogs.com/mickole/p/3192210.html
http://blog.csdn.net/nodeathphoenix/article/details/23284157
http://blog.csdn.net/ljianhui/article/details/10287879
http://www.cnblogs.com/kunhu/p/3608589.html
http://blog.csdn.net/kobejayandy/article/details/18863543
http://www.cnblogs.com/lbsx/archive/2009/08/03/1537698.html
http://baike.baidu.com/link?url=ak6DtjdUQddNHUW0zOP1Qy1UrIX0zJoLG77RCoRsfhNu7O2H1JfcYPVjUKuLTzuAm7g0HAxE0OVqRODOWYwr7_
http://blog.csdn.net/hongchangfirst/article/details/11599369
http://blog.sina.com.cn/s/blog_4eee98350100abbr.html
出處:http://blog.csdn.net/a987073381/article/details/52006729
嵌入式 任務(wù)調(diào)度 Linux
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。