在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ù)器開發(fā)提供靈活而又堅(jiān)固的框架。
1. 管道 (PIPE) 管道實(shí)際是用于進(jìn)程間通信的一段共享內(nèi)存,創(chuàng)建管道的進(jìn)程稱為管道服務(wù)器,連接到一個(gè)管道的進(jìn)程為管道客戶機(jī)。一個(gè)進(jìn)程在向管道寫入數(shù)據(jù)后,另一進(jìn)程就可以從管道的另一端將其讀取出來。
管道的特點(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)閉寫端。
3、單獨(dú)構(gòu)成一種獨(dú)立的文件系統(tǒng):管道對(duì)于管道兩端的進(jìn)程而言,就是一個(gè)文件,但它不是普通的文件,它不屬于某種文件系統(tǒng),而是自立門戶,單獨(dú)構(gòu)成一種文件系統(tǒng),并且只存在與內(nèi)存中。
4、數(shù)據(jù)的讀出和寫入:一個(gè)進(jìn)程向管道中寫的內(nèi)容被管道另一端的進(jìn)程讀出。寫入的內(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)管道中沒有信息的話,從管道中讀取的進(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);
通過使用底層的read和write調(diào)用來訪問數(shù)據(jù)。 向
file_descriptor[1]寫數(shù)據(jù),從
file_descriptor[0]中
讀數(shù)據(jù)。寫入與讀取的順序原則是
先進(jìn)先出。
管道讀寫規(guī)則
當(dāng)沒有數(shù)據(jù)可讀時(shí)
O_NONBLOCK disable:read調(diào)用阻塞,即進(jìn)程暫停執(zhí)行,一直等到有數(shù)據(jù)來到為止。
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
如果所有管道寫端對(duì)應(yīng)的文件描述符被關(guān)閉,則read返回0
如果所有管道讀端對(duì)應(yīng)的文件描述符被關(guān)閉,則write操作會(huì)產(chǎn)生信號(hào)SIGPIPE
當(dāng)要寫入的數(shù)據(jù)量不大于PIPE_BUF(Posix.1要求PIPE_BUF至少512字節(jié))時(shí),linux將保證寫入的原子性。
當(dāng)要寫入的數(shù)據(jù)量大于PIPE_BUF時(shí),linux將不再保證寫入的原子性。
2. 命名管道(FIFO)命名管道是一種特殊類型的文件,它在系統(tǒng)中以文件形式存在。這樣克服了管道的弊端,他可以
允許沒有親緣關(guān)系的進(jìn)程間通信。
創(chuàng)建管道的兩個(gè)系統(tǒng)調(diào)用原型:#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)用來操作。創(chuàng)建可以手工創(chuàng)建或者程序中創(chuàng)建。
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來說,IO操作和普通管道IO操作基本一樣,但是兩者有一個(gè)主要的區(qū)別,在命名管道中,管道可以是事先已經(jīng)創(chuàng)建好的,比如我們?cè)诿钚邢聢?zhí)行
mkfifo myfifo
就是創(chuàng)建一個(gè)命名通道,我們必須用open函數(shù)來顯示地建立連接到管道的通道,而在管道中,管道已經(jīng)在主進(jìn)程里創(chuàng)建好了,然后在fork時(shí)直接復(fù)制相關(guān)數(shù)據(jù)或者是用exec創(chuàng)建的新進(jìn)程時(shí)把管道的文件描述符當(dāng)參數(shù)傳遞進(jìn)去。
一般來說FIFO和PIPE一樣總是處于阻塞狀態(tài)。也就是說如果命名管道FIFO打開時(shí)設(shè)置了讀權(quán)限,則讀進(jìn)程將一直阻塞,一直到其他進(jìn)程打開該FIFO并向管道寫入數(shù)據(jù)。這個(gè)阻塞動(dòng)作反過來也是成立的。如果不希望命名管道操作的時(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)生,比如鍵盤中斷等。shell也可以使用信號(hào)將作業(yè)控制命令傳遞給它的子進(jìn)程。
在此列出幾個(gè)簡(jiǎn)單使用方法定義:
#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ù)。
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()用來設(shè)置信號(hào)SIGALRM在經(jīng)過參數(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)部鏈表,通過linux內(nèi)核在各個(gè)進(jìn)程直接傳遞內(nèi)容,消息順序地發(fā)送到消息隊(duì)列中,并以幾種不同的方式從隊(duì)列中獲得,每個(gè)消息隊(duì)列可以用IPC標(biāo)識(shí)符唯一地進(jìn)行識(shí)別。內(nèi)核中的消息隊(duì)列是通過IPC的標(biāo)識(shí)符來區(qū)別,不同的消息隊(duì)列直接是相互獨(dú)立的。每個(gè)消息隊(duì)列中的消息,又構(gòu)成一個(gè)獨(dú)立的鏈表。
消息隊(duì)列克服了信號(hào)承載信息量少,管道只能承載無格式字符流。
消息隊(duì)列頭文件:
#include #include #include
1、消息緩沖區(qū)結(jié)構(gòu):
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)。例如:
struct msgbuf{ long mtype; char mtext[1];//柔性數(shù)組}
當(dāng)然用戶不可隨意定義msgbuf結(jié)構(gòu),因?yàn)樵趌inux中消息的大小是有限制的,在linux/msg.h中定義如下:
#define MSGMAX 8192
消息總的大小不能超過8192個(gè)字節(jié),包括mtype成員(4個(gè)字節(jié))。
2、msqid_ds內(nèi)核數(shù)據(jù)結(jié)構(gòu)。
struct msgid_ds{
struct ipc_perm msg_perm{
time_t msg_stime;
time_t msg_rtime;
time_t msg_ctime;
unsigned long _msg_cbuyes;
..........
};
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)
struct ipc_perm{ key_t key; uid_t uid; gid_t gid; .......};
結(jié)構(gòu)體ipc_perm保存著消息隊(duì)列的一些重要的信息,比如說消息隊(duì)列關(guān)聯(lián)的鍵值,消息隊(duì)列的用戶id組id等。它定義在頭文件linux/ipc.h中。
常用函數(shù):
系統(tǒng)建立IPC通訊 (消息隊(duì)列、信號(hào)量和共享內(nèi)存) 時(shí)必須指定一個(gè)ID值。通常情況下,該id值通過ftok函數(shù)得到。
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用來創(chuàng)建和訪問一個(gè)消息隊(duì)列。程序必須提供一個(gè)鍵值來命名特定的消息隊(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ì)文件的讀寫執(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)整型變量開始。 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可以通過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ì)列或打開一個(gè)存在的隊(duì)列;msgsnd向隊(duì)列末端添加一條新消息;msgrcv從隊(duì)列中取消息, 取消息是不一定遵循先進(jìn)先出的, 也可以按消息的類型字段取消息。
消息隊(duì)列與命名管道的比較
消息隊(duì)列跟命名管道有不少的相同之處,通過與命名管道一樣,消息隊(duì)列進(jìn)行通信的進(jìn)程可以是不相關(guān)的進(jìn)程,同時(shí)它們都是通過發(fā)送和接收的方式來傳遞數(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)程而存在,從而消除了在同步命名管道的打開和關(guān)閉時(shí)可能產(chǎn)生的困難。2、同時(shí)通過發(fā)送消息還可以避免命名管道的同步和阻塞問題,不需要由進(jìn)程自己來提供同步方法。3、接收程序可以通過消息類型有選擇地接收數(shù)據(jù),而不是像命名管道中那樣,只能默認(rèn)地接收。
5. 信號(hào)量(Semaphore)信號(hào)量是一種計(jì)數(shù)器,用于控制對(duì)多個(gè)進(jìn)程共享的資源進(jìn)行的訪問。它們常常被用作一個(gè)鎖機(jī)制,在某個(gè)進(jìn)程正在對(duì)特定的資源進(jìn)行操作時(shí),信號(hào)量可以防止另一個(gè)進(jì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)行;如果沒有其他進(jìn)程因等待sv而掛起,則給它加1
簡(jiǎn)單理解就是P相當(dāng)于申請(qǐng)資源,V相當(dāng)于釋放資源
信號(hào)量頭文件:
#include #include #include
內(nèi)核為每個(gè)信號(hào)量集合都維護(hù)一個(gè)semid_ds結(jié)構(gòu):
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):
union semun{ int val; struct semid_ds *buf; unsigned short *array; struct seminfo *__buf;}
信號(hào)量操作sembuf結(jié)構(gòu):
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ù):
int semget(key_t key, int num_sems, int sem_flags); //semget函數(shù)用于創(chuàng)建一個(gè)新的信號(hào)量集合 , 或者訪問一個(gè)現(xiàn)有的集合(不同進(jìn)程只要key值相同即可訪問同一信號(hào)量集合)。第一個(gè)參數(shù)key是ftok生成的鍵值,第二個(gè)參數(shù)num_sems可以指定在新的集合應(yīng)該創(chuàng)建的信號(hào)量的數(shù)目,第三個(gè)參數(shù)sem_flags是打開信號(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)程都可以訪問共享內(nèi)存中的地址,就好像它們是malloc分配的一樣。如果一個(gè)進(jìn)程向共享內(nèi)存中寫入了數(shù)據(jù),所做的改動(dòng)將立刻被其他進(jìn)程看到。
共享內(nèi)存是
IPC最快捷的方式,因?yàn)楣蚕韮?nèi)存方式的通信沒有中間過程,而管道、消息隊(duì)列等方式則是需要將數(shù)據(jù)通過中間機(jī)制進(jìn)行轉(zhuǎn)換。共享內(nèi)存方式直接將某段內(nèi)存段進(jìn)行映射,多個(gè)進(jìn)程間的共享內(nèi)存是同一塊的物理空間,僅僅映射到各進(jìn)程的地址不同而已,因此不需要進(jìn)行復(fù)制,可以直接使用此段空間。
注意:共享內(nèi)存本身并沒有同步機(jī)制,需要程序員自己控制。 共享內(nèi)存頭文件:#include #include #include
結(jié)構(gòu)shmid_ds結(jié)構(gòu)體(是不是很眼熟,看消息隊(duì)列的msgid_ds結(jié)構(gòu)體):
strcut shmid_ds{ struct ipc_perm shm_perm; size_t shm_segsz; time_t shm_atime; time_t shm_dtime; ......}
共享內(nèi)存函數(shù)定義:
int shmget(key_t key,size_t size,int shmflg); //shmget函數(shù)用來創(chuàng)建一個(gè)新的共享內(nèi)存段, 或者訪問一個(gè)現(xiàn)有的共享內(nèi)存段(不同進(jìn)程只要key值相同即可訪問同一共享內(nèi)存段)。第一個(gè)參數(shù)key是ftok生成的鍵值,第二個(gè)參數(shù)size為共享內(nèi)存的大小,第三個(gè)參數(shù)sem_flags是打開共享內(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ù)通過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,分別通過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)起作用,沒用使用引用計(jì)數(shù)。如果一個(gè)進(jìn)程創(chuàng)建一個(gè)消息隊(duì)列,并在消息隊(duì)列中放入幾個(gè)消息,進(jìn)程終止后,即使現(xiàn)在已經(jīng)沒有程序使用該消息隊(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命令刪除它們的訪問權(quán)限。只能使用ipcs和ipcrm來查看可以刪除它們。
7. 內(nèi)存映射(Memory Map)
內(nèi)存映射文件,是由一個(gè)文件到一塊內(nèi)存的映射。內(nèi)存映射文件與虛擬內(nèi)存有些類似,通過內(nèi)存映射文件可以保留一個(gè)地址的區(qū)域,
同時(shí)將物理存儲(chǔ)器提交給此區(qū)域,內(nèi)存文件映射的物理存儲(chǔ)器來自一個(gè)已經(jīng)存在于磁盤上的文件,而且在對(duì)該文件進(jìn)行操作之前必須首先對(duì)文件進(jìn)行映射。使用內(nèi)存映射文件處理存儲(chǔ)于磁盤上的文件時(shí),將不必再對(duì)文件執(zhí)行I/O操作。每一個(gè)使用該機(jī)制的進(jìn)程通過把同一個(gè)共享的文件映射到自己的進(jìn)程地址空間來實(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)槲覀兤胀ǖ淖龇ㄊ?span>把磁盤上的文件先拷貝到內(nèi)核空間的一個(gè)緩沖區(qū)再拷貝到用戶空間(內(nèi)存),用戶修改后再將這些數(shù)據(jù)拷貝到緩沖區(qū)再拷貝到磁盤文件,一共四次拷貝。如果文件數(shù)據(jù)量很大,拷貝的開銷是非常大的。那么問題來了,系統(tǒng)在在進(jìn)行內(nèi)存映射文件就不需要數(shù)據(jù)拷貝?mmap()確實(shí)沒有進(jìn)行數(shù)據(jù)拷貝,真正的拷貝是在在缺頁中斷處理時(shí)進(jìn)行的,由于mmap()將文件直接映射到用戶空間,所以中斷處理函數(shù)根據(jù)這個(gè)映射關(guān)系,直接將文件從硬盤拷貝到用戶空間,所以只進(jìn)行一次數(shù)據(jù)拷貝。效率高于read/write。
內(nèi)存映射頭文件:
#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ū)的開始地址,設(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ù)用來取消參數(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)磁盤文件內(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語言的memcpy等內(nèi)存操作的函數(shù)。這種方法能夠很好的應(yīng)用在需要頻繁處理一個(gè)文件或者是一個(gè)大文件的場(chǎng)合,這種方式處理IO效率比普通IO效率要高
共享內(nèi)存是內(nèi)存映射文件的一種特殊情況,內(nèi)存映射的是一塊內(nèi)存,而非磁盤上的文件。共享內(nèi)存的主語是進(jìn)程(Process),操作系統(tǒng)默認(rèn)會(huì)給每一個(gè)進(jìn)程分配一個(gè)內(nèi)存空間,每一個(gè)進(jìn)程只允許訪問操作系統(tǒng)分配給它的哪一段內(nèi)存,而不能訪問其他進(jìn)程的。而有時(shí)候需要在不同進(jìn)程之間訪問同一段內(nèi)存,怎么辦呢?操作系統(tǒng)給出了 創(chuàng)建訪問共享內(nèi)存的API,需要共享內(nèi)存的進(jìn)程可以通過這一組定義好的API來訪問多個(gè)進(jìn)程之間共有的內(nèi)存,各個(gè)進(jìn)程訪問這一段內(nèi)存就像訪問一個(gè)硬盤上的文件一樣。
內(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)存,另一部放在磁盤上的一種機(jī)制。對(duì)于用戶而言都是透明的。
區(qū)別:虛擬內(nèi)存是硬盤的一部分,是內(nèi)存和硬盤的數(shù)據(jù)交換區(qū),許多程序運(yùn)行過程中把暫時(shí)不用的程序數(shù)據(jù)放入這塊虛擬內(nèi)存,節(jié)約內(nèi)存資源。內(nèi)存映射是一個(gè)文件到一塊內(nèi)存的映射,這樣程序通過內(nèi)存指針就可以對(duì)文件進(jìn)行訪問。
虛擬內(nèi)存的硬件基礎(chǔ)是分頁機(jī)制。另外一個(gè)基礎(chǔ)就是局部性原理(時(shí)間局部性和空間局部性),這樣就可以將程序的一部分裝入內(nèi)存,其余部分留在外存,當(dāng)訪問信息不存在,再將所需數(shù)據(jù)調(diào)入內(nèi)存。而內(nèi)存映射文件并不是局部性,而是使虛擬地址空間的某個(gè)區(qū)域銀蛇磁盤的全部或部分內(nèi)容,通過該區(qū)域?qū)Ρ挥成涞拇疟P文件進(jì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ū)分開來,可以實(shí)現(xiàn)多個(gè)客戶端連到同一服務(wù)器。
服務(wù)器套接字連接過程描述:
首先,服務(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ù)開始接聽,等待客戶端連接。listen創(chuàng)建一個(gè)隊(duì)列并將其用于存放來自客戶端的進(jìn)入連接。 當(dāng)客戶端調(diào)用connect請(qǐng)求連接時(shí),服務(wù)器調(diào)用accept接受客戶端連接,accept此時(shí)會(huì)創(chuàng)建一個(gè)新套接字,用于與這個(gè)客戶端進(jìn)行通信。
客戶端套接字連接過程描述:
客戶端首先調(diào)用socket創(chuàng)建一個(gè)未命名套接字,讓后將服務(wù)器的命名套接字作為地址來調(diào)用connect與服務(wù)器建立連接。
只要雙方連接建立成功,我們就可以像操作底層文件一樣來操作socket套接字實(shí)現(xiàn)通信。
幾個(gè)基礎(chǔ)函數(shù)定義:
#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ù)器和客戶端直接的通信可以通過此消息類型來發(fā)送和接受消息,而且多個(gè)客戶端之間也可以通過消息類型來區(qū)分。
參考:
《linux網(wǎng)絡(luò)編程》
《unix環(huán)境高級(jí)編程》