01 概述
1.1 發展
Linux程序間通信(IPC)大緻發展如下:
早期UNIX程序間通信、基于System V程序間通信、基于Socket程序間通信和Posix程序間通信。
UNIX程序間通信方式包括:管道、FIFO、信号。
System V程序間通信方式包括:System V消息隊列、System V信号量、System V共享記憶體。
Posix程序間通信包括:Posix消息隊列、Posix信号量、Posix共享記憶體。
1.2 目的
程序間通信(IPC)的目的:
1、資料傳輸:一個程序向另一個程序傳輸資料。
2、共享資料:多個程序想要操作共享資料,一個程序對共享資料的修改,其他程序應實時看到。
3、通知事件:一個程序需要向另一個程序發送消息,通知它發生了某種事件(如程序終止時需要通知父程序)。
4、資源共享:多個程序之間共享同樣的資源。為了做到這一點,需要核心提供鎖和同步機制。
5、程序控制:有些程序希望完全控制另一個程序的執行(如Debug程序),此時控制程序希望能夠攔截另一個程序的所有陷入和異常,并能夠及時知道它的狀态改變。
1.3 單機和跨主機通信
同一主機程序間的通信方式:
1、Unix程序間通信方式:PIPE、FIFO和信号
2、SystemV程序間通信方式:信号量(Semaphore)、消息隊列(Message Queue)和共享記憶體(Shared Memory)
3、讀寫鎖、互斥鎖、條件變量
4、檔案、檔案鎖
網絡主機間的通信方式:
1、RPC:Remote Protocol Call遠端過程調用(Thrift、gRPC)
2、Socket:基于TCP/IP協定
02 管道
2.1 概述
管道是Linux系統最古老的IPC方法,可以簡單地将管道了解為一個通道,它允許資料從一個程序流向另外一個程序。
2.2 特點
1、管道傳輸的是無格式位元組流
讀者從管道中讀取任意大小的資料塊,而不管寫者寫入管道的資料塊大小,即使用管道時不存在消息邊界的概念。此外,通過管道傳遞的資料是順序的,從管道中讀取出來位元組的順序與它們被寫入管道的順序是完全一樣的。
2、從管道中讀取資料
試圖從一個目前為空的管道中讀取資料将會被阻塞直到至少有一個位元組被寫入到管道為止。如果管道的寫入端被關閉了,那麼從管道中讀取資料的程序在讀完管道中剩餘的所有資料之後将會看到檔案結束。
3、管道是半雙工(單工)的(有的系統可能支援全雙工),資料隻能向一個方向流動;需要雙方通信時,需要建立起兩個管道。
4、管道的容量是有限的。管道其實是一個在核心記憶體中紅維護的緩沖區,這個緩沖區的存儲大小是有限的,一旦管道被填滿之後,繼續向該管道的寫入操作就會被阻塞直到讀者從管道中移除一些資料為止。
2.3 函數
1、管道建立/關閉
#include<unistd.h>intpipe(intfd[2]);
成功的pipe()調用會在數組fields中傳回兩個打開的檔案描述符:一個表示管道的讀取端(fields[0]),另一個表示管道的寫入端(fields[1])。
2、管道的一個常見用途是執行shell指令并讀取其輸出或者向其發送一些輸入,popen()和pclose()函數簡化了這個任務。
FILE *popen(constchar* command ,constchar* type );intpclose( FILE * stream );
popen函數建立了一個管道,然後建立了一個子程序來執行shell,而shell又建立了一個子程序來執行command字元串。mode參數是一個字元串,它确定調用程序是從管道中讀取資料(mode是r)還是将資料寫入到管道中(mode是w)。
03 信号
3.1 概述
由于管道是一種無形、無名的檔案,它就隻能通過fork的過程建立在“近親”的程序之間,而不可能成為可以在任意兩個程序之間建立通信的機制,更不可能稱為一般的、通用的程序間通信模型。是以引入有名管道FIFO,它與管道最大的差別在于在檔案系統中有名稱,并且打開方式與打開普通檔案一樣,可以實作非相關程序間的通信。
3.2 特點
有名管道也是半雙工的通信方式,但是它允許無親緣關系程序間的通信。
3.3 函數
#include<sys/types.h>#include<sys/stat.h>intmkfifo(constchar*pathname,mode_tmode);
傳回:若成功傳回0,出錯傳回-1。
FIFO相關出錯資訊:
EACCES 無存取權限
EEXIST 指定檔案不存在
ENAMETOOLONG 路徑名太長
ENOENT 包含的目錄不存在
ENOSPC 檔案系統剩餘空間不足
ENOTDIR 檔案路徑無效
EROFS 指定的檔案存在于隻讀檔案系統中
04 信号
4.1 概述
信号(signal)是Linux程序間通信的一種機制,全稱為軟中斷信号,也被稱為軟中斷。信号本質上是在軟體層次上對硬體中斷機制的一種模拟。
信号表示一種事件,也可能異步發生。如果程式并未安排怎麼處理一個特定的信号,那麼該信号出現時程式就作出一個預設的反應。标準未定義這個預設反應是什麼,但絕大多數編譯器選擇終止程式。另外,程式可以調用signal函數,或者忽略這個信号,或者設定一個信号處理函數,當信号發生時程式就調用這個函數。
與其他程序間通信方式相比,信号所能傳遞的資訊比較粗糙,隻是一個整數。但正是由于傳遞的資訊量少,信号也便于管理和使用,可以用于系統管理相關的任務,例如通知程序終結、中止或恢複等。
4.2 信号名
SIGABRT程式請求異常終止
SIGFPE發生一個算術錯誤
SIGILL檢測到非法指令
SIGSEGV檢測到對記憶體的非法通路
SIGINT收到一個互動性注意信号
SIGTERM收到一個終止程式的請求
注:前幾個都是同步的,SIGINT、SIGTERM是異步的,它們在程式外部發生,同城是由程式的使用者觸發。
4.3 函數
void (*signal(intsig , void (*func)(int)))(int);
參數:
sig:信号編号
void(*func)(int):信号處理函數
傳回值:類型是一個信号處理函數的函數指針
SIG_ERR錯誤傳回結果(void(*)(int))(-1)
SIG_IGN忽略某個信号(void(*)(int))(0)
SIG_DFL預設處理某個信号(void(*)(int))(1)
信号集操作
對信号集進行信号的清空、加入、删除等操作。
intsigemptyset(sigset_t *set);将set集合置空
intsigfillset(sigset_t *set);将所有信号加入set集合
intsigaddset(sigset_t *set,intsigno)将signo信号加入到set集合
intsigdelset(sigset_t *set,intsigno);從set集合中移除signo信号
intsigismember(constsigset_t *set,intsigno);signo判斷信号是否存在于set集合中
設定或擷取信号集的信号屏蔽字:
intsigprocmask(inthow,constsigset_t*restrictset,sigset_t*restritoset);
傳回值:成功傳回0,出錯傳回-1
SIG_BLOCK 追加屏蔽某個信号
SIG_UNBLOCK 取消屏蔽某個信号
SIG_SETMASK 設定新的屏蔽字信号集
如果屏蔽所有信号,可以進行如下設定:
sig_set_tset;
sigfillset(&set);
sigprocmask(SIG_BLOCK, &set,NULL);
等價于如下設定:
sig_set_tset;
sigfillset(&set);
sigprocmask(SIG_SETMASK, &set,NULL);
如果清空所有信号屏蔽字集合,可以進行如下設定:
sig_set_tset;
sigfillset(&set);
sigprocmask(UN_BLOCK, &set,NULL);
等價于如下設定:
sig_set_tset;
sigemptyset(&set);
sigprocmask(SIG_SETMASK, &set,NULL);
擷取目前信号屏蔽字信号集:
sig_set_tset;
sigemptyset(&set);
sigprocmask(SIG_BLOCK,NULL, &set);
05 消息隊列
5.1 概述
消息隊列是由消息的連結清單,存儲在核心中并由消息隊列辨別符辨別。消息隊列克服了信号傳遞信号量少、管道隻能承載無格式位元組流以及緩沖區大小受限等缺點。
與管道和FIFO不同,程序可以在沒有另外一個程序等待讀的情況下進行寫。另外一方面,管道和FIFO一旦相關程序都關閉并退出後,裡面的資料也就沒有了,但是對于消息隊列,一個程序往消息隊列中寫入資料後退出,另外一個程序仍然可以打開并讀取消息。消息隊列與UNIX域套接字相比,在速度上沒有多少優勢。
5.2 函數
System V定義:
#include<sys/msg.h>
intmsgget(key_tkey,intflag);
intmsgctl(intmsgid,intcmd,struct msqid_ds *buf);
intmsgsnd(intmsgqid,constvoid*ptr,size_tnbytes,intflag);
size_tmsgrcv(intmsgqid,void*ptr,size_tnbytes,longtype,intflag);
Posix定義:
#include<mqueue.h>
mqd_tmq_open(constchar*name,intoflag,);
mqd_tmq_close(mqd_tmqdes);
mqd_tmq_unlink(constchar*name);
mqd_tmq_send(mqd_tmqdes,constchar*msg_ptr,size_tmsg_len,unsignedmsg_prio);
mqd_tmq_receive(mqd_tmqdes,char*msg_ptr,size_tmsg_len,unsigned*msg_prio);
06 共享記憶體
6.1 概述
共享記憶體區域是被多個程序共享的一部分實體記憶體。如果多個程序都把該記憶體區域映射到自己的虛拟位址空間,則這些程序就都可以直接通路該共享記憶體區域,進而可以通過該區域進行通信。
共享記憶體是程序間共享資料的一種最快的方法,一個程序向共享記憶體區域寫入資料,共享這個記憶體區域的所有程序就可以立刻看到其中的内容。
6.2 函數
SYSTEM V定義:
intshmget(key_tkey,intsize,intshmflg);
void*shmat(intshmid,constvoid*shmaddr,intshmflg);
intshmdt(constvoid*shmaddr);
intshmctl(intshmid,intcmd, struct shmid_ds *buf);
POSIX定義:
intshm_open(constchar*name,intoflag,mode_tmode);
intshm_unlink(constchar*name);
intftruncate(intfd,off_tlength);
由于POSIX标準比較通用,一般建議使用該标準定義的方法集。
07 信号量
7.1 概述
信号量(semaphore),有時稱為信号燈,是多線程環境下使用的一種設施,可以用來保證兩個或多個關鍵代碼段不被并發調用(就是具有原子性的計數器)。
信号量是一個計數器,可用來控制多個程序對共享資源的通路。它常常作為一種鎖機制,防止某程序正在通路共享資源時,其他程序也通路該資源。因為,作為程序間以及同一程序内不同線程之間的同步手段。
7.2 函數
System V定義:
#incldue<sys/sem.h>
intsemget(key_tkey,intnsems,intflag);
intsemctl(intsemid,intsemnum,intcmd,...)
intsemop(intsemid,struct sembuf *semop,size_tnops);
Posix定義:
intsem_init(sem_t*sem,intpshared,unsignedintvalue);
intsem_wait(sem_t*sem);
intsem_trywait(sem_t* sem);
intsem_timedwait(sem_t*sem,conststruct timespec *abs_timeout);
intsem_post(sem_t*sem);
intsem_close(sem_t*sem);
intsem_unlink(constchar*name);
intsem_destroy(sem_t*sem);
08 記憶體映射
8.1 概述
mmap()系統調用在調用程序的虛拟位址空間中建立一個新記憶體映射,映射分為兩種:
1、檔案映射
檔案映射将一個檔案的一部分直接映射到調用程序的虛拟記憶體中,一旦一個檔案被映射之後就可以通過在相應的記憶體區域中操作位元組來通路檔案内容了。映射的分頁會在需要的時候從檔案中(自動)加載。這種映射也被稱為基于檔案的映射或記憶體映射檔案。
2、匿名映射
一個匿名映射沒有對應的檔案,相反,這種映射的分頁會被初始化為0。
8.2 函數
void*mmap(void*start,size_tlength,intprot,intflags,intfd,off_toffset);
intmunmap(void*start,size_tlength);
參數:start:映射區的開始位址。
length:映射區的長度。
prot:期望的記憶體保護标志,不能與檔案的打開模式沖突。
是以下的某個值,可以通過or運算合理地組合在一起
PROT_EXEC//頁内容可以被執行
PROT_READ//頁内容可以被讀取
PROT_WRITE//頁可以被寫入
PROT_NONE//頁不可通路flags:指定映射對象的類型,映射選項和映射頁是否可以共享。
09 RPC
遠端過程調用(Remote Protocol Call),跨主機通信。
10 套接字
網絡通信,包括UDP和TCP兩種,單機或跨主機通信。