由于每個進程都有自己獨立的地址空間,不像線程共享同一地址空間,進程之間的通信需要特定的機制。
單個進程內部的模塊(如函數)之間的通信相對簡單,可以通過全局變量、參數傳遞等方式實現(xiàn),但進程間通信相對復雜,因為不同進程彼此隔離,不共享內存。
雖然大多數普通應用程序是單進程、多線程的,不需要進程間通信的復雜性,但在一些復雜或大型應用中,尤其是服務器或圖形用戶界面(GUI)程序,可能會使用多進程架構來提升性能或簡化設計。
這種情況下,IPC 機制就變得非常重要。
1、進程間通信機制
linux 系統(tǒng)繼承了 unix 系統(tǒng)的豐富 IPC 機制,并對其進行了擴展和改進。
UNIX 系統(tǒng)的進程間通信大致可以分為兩大流派:
System V IPC(由 AT&T 貝爾實驗室主導發(fā)展):改進了 UNIX 早期的進程間通信手段,形成了適用于單臺計算機的 IPC 機制,主要包括信號量、消息隊列和共享內存。BSD 套接字通信(由加州大學伯克利分校主導發(fā)展):BSD 在網絡通信和分布式系統(tǒng)方面做出了重要貢獻,跳出了進程間通信局限于單個計算機的限制,形成了基于套接字(Socket)的通信機制,廣泛用于網絡應用程序。
Linux 將這兩大體系都繼承了下來,并在此基礎上發(fā)展了 POSIX IPC。
POSIX(Portable Operating System Interface for Unix)標準化了多種 UNIX 系統(tǒng)功能,包括進程間通信,彌補了 System V IPC 的一些不足。

1.1、UNIX IPC
UNIX 傳統(tǒng)的 IPC 機制包括管道、FIFO 和信號,這些機制最早由 UNIX 系統(tǒng)引入,適用于簡單的單機進程間通信。
管道(Pipe):一種單向、半雙工的通信機制,通常用于父子進程間的數據傳遞。父進程可以寫入數據,子進程可以讀取。FIFO(命名管道):類似于管道,但通過文件系統(tǒng)實現(xiàn),任何進程都可以通過路徑訪問該管道,實現(xiàn)雙向通信。信號(signal):信號是一種用于進程間異步通知的機制,可以用于進程之間的簡單通信或事件通知,例如 SIGINT(Ctrl+C 發(fā)送的中斷信號)。1.2、System V IPC
System V IPC 是 UNIX 的增強版本,主要包括信號量、消息隊列和共享內存,適合需要更復雜的進程同步與數據共享的場景。
信號量(Semaphore):用于進程間的同步,通常用于控制對共享資源的訪問。信號量用于防止多個進程同時訪問同一資源,避免資源爭用問題。消息隊列(Message Queue):允許進程以消息的形式發(fā)送和接收數據。消息隊列是一種先進先出(FIFO)的結構,支持不同類型的消息,使得進程可以基于消息類型進行處理。共享內存(Shared Memory):進程之間共享同一塊內存區(qū)域,允許它們直接讀寫數據。這是最有效的 IPC 方式,因為數據不需要在進程之間復制。1.3、POSIX IPC
POSIX IPC 是 System V IPC 的改進版本,旨在解決 System V IPC 在靈活性和可移植性上的一些不足。
POSIX 標準為 UNIX 系統(tǒng)間的兼容性提供了統(tǒng)一的接口,使得程序可以更方便地在不同的 UNIX 系統(tǒng)間移植。
POSIX 信號量:與 System V 信號量類似,用于進程同步,但提供了更靈活的接口和更強的實時性支持。POSIX 消息隊列:改進了 System V 消息隊列,允許指定消息的優(yōu)先級,并提供更簡單的接口。POSIX 共享內存:與 System V 共享內存類似,但具有更好的兼容性和可移植性,接口設計更加現(xiàn)代化。1.4、套接字(Socket)通信
套接字是一種既可以用于本地進程間通信,也可以用于網絡通信的機制,支持雙向數據傳輸。
基于套接字的 IPC 可以實現(xiàn)非常靈活的通信模式,例如客戶端-服務器架構,適合在多臺計算機之間傳遞數據。
各類 IPC 機制的對比和應用場景:

2、管道(Pipe)
管道是一種半雙工(單向)的通信方式,通常用于父子進程之間的通信。一個進程可以向管道寫入數據,另一個進程從管道讀取數據。
Linux 提供了無名管道和命名管道兩種類型。
無名管道(Anonymous Pipe):只能在具有親緣關系的進程間使用,比如父進程和子進程。命名管道(Named Pipe 或 FIFO):通過文件系統(tǒng)中的路徑來創(chuàng)建,任意進程都可以訪問。
示例:
代碼語言:JavaScript代碼運行次數:0運行復制
int main() { int fd[2]; pipe(fd); // 創(chuàng)建無名管道 if (fork() == 0) { // 子進程 close(fd[0]); // 關閉讀取端 write(fd[1], "Hello, parent!", 15); close(fd[1]); } else { // 父進程 char buffer[20]; close(fd[1]); // 關閉寫入端 read(fd[0], buffer, sizeof(buffer)); printf("Received: %sn", buffer); close(fd[0]); } return 0;}
3、消息隊列(Message Queue)
消息隊列是一種先進先出的隊列,允許進程以消息的形式發(fā)送和接收數據。
消息隊列可以支持多種類型的消息,通過消息類型實現(xiàn)多種目的的通信。
示例:進程A可以向隊列發(fā)送一個帶有特定類型的消息,而進程B可以根據消息類型進行處理。
代碼語言:javascript代碼運行次數:0運行復制
struct msgbuf { long mtype; char mtext[100];};int main() { key_t key = ftok("msgqueue", 65); int msgid = msgget(key, 0666 | IPC_CREAT); struct msgbuf message; message.mtype = 1; // 消息類型 snprintf(message.mtext, sizeof(message.mtext), "Hello Message Queue"); msgsnd(msgid, &message, sizeof(message.mtext), 0); return 0;}
4、共享內存(Shared Memory)
共享內存是最快的 IPC 機制之一,因為進程之間直接訪問同一塊內存區(qū)域,而不需要拷貝數據。
通常使用 shmget()、shmat() 和 shmdt() 函數進行共享內存的創(chuàng)建和訪問。
示例:
代碼語言:javascript代碼運行次數:0運行復制
int main() { key_t key = ftok("shmfile",65); int shmid = shmget(key, 1024, 0666|IPC_CREAT); char *str = (char*) shmat(shmid, (void*)0, 0); strcpy(str, "Hello Shared Memory"); printf("Data written in memory: %sn", str); shmdt(str); return 0;}
5、信號量(Semaphore)
信號量是一種用于進程同步的機制,通常用于控制多個進程對共享資源的訪問。
嵌入式系統(tǒng)中,信號量通常用來避免多個進程同時訪問同一資源,防止數據競爭。
示例:信號量可以通過 semget() 和 semop() 函數來操作,用于鎖定或解鎖資源。
代碼語言:javascript代碼運行次數:0運行復制
int main() { key_t key = ftok("semfile",65); int semid = semget(key, 1, 0666 | IPC_CREAT); struct sembuf sem_lock = {0, -1, 0}; // 減1操作 struct sembuf sem_unlock = {0, 1, 0}; // 加1操作 semop(semid, &sem_lock, 1); // 上鎖 printf("Critical sectionn"); semop(semid, &sem_unlock, 1); // 解鎖 return 0;}
6、套接字(Socket)
套接字不僅支持本地進程間通信,還可以用于網絡通信。
基于套接字的 IPC 支持雙向通信,比較靈活,適合嵌入式系統(tǒng)中進程之間需要頻繁且復雜的數據交互的情況。
示例:
代碼語言:javascript代碼運行次數:0運行復制
int main() { int sv[2]; socketpair(AF_UNIX, SOCK_STREAM, 0, sv); if (fork() == 0) { // 子進程 close(sv[0]); write(sv[1], "Hello from child", 16); close(sv[1]); } else { // 父進程 char buffer[20]; close(sv[1]); read(sv[0], buffer, sizeof(buffer)); printf("Received: %sn", buffer); close(sv[0]); } return 0;}
7、信號(Signal)
信號是用來通知進程發(fā)生某種事件的機制。進程可以捕獲、忽略或處理信號,典型的信號包括 SIGINT(中斷信號)和 SIGKILL(殺死進程信號)。
示例:處理 SIGINT 信號(Ctrl+C)。
代碼語言:javascript代碼運行次數:0運行復制
void sigint_handler(int sig) { printf("Caught signal %dn", sig);}int main() { signal(SIGINT, sigint_handler); while (1) { printf("Running...n"); sleep(1); } return 0;}
進程間通信的機制多種多樣,選擇合適的方式取決于應用場景的需求。