IPC 是進程間通訊(Interprocess Communication)的縮寫,一般指容許用戶態進程執行系列操做的一組機制:程序員
System V IPC 最初是在一個名爲 "Columbus Unix" 的開發版 Unix 變種中引入的,以後在 AT&T 的 System III 中採用。如今在大部分 Unix 系統 (包括 Linux) 中均可以找到。編程
IPC 資源包含信號量、消息隊列和共享內存三種。IPC 的數據結構是在進程請求 IPC 資源時動態建立的。每一個 IPC 資源都是持久的:除非被進程顯式地釋放,不然永遠駐留在內存中(直到系統關閉)。IPC 資源能夠由任一進程使用,包括那些不共享祖先進程所建立的資源的進程。
因爲一個進程可能須要同類型的多個 IPC 資源,所以每一個新資源都是使用一個 32 位的 IPC 關鍵字來標識的,這和系統的目錄樹中的文件路徑名相似。每一個 IPC 資源都有一個 32 位的 IPC 標識符,這與和打開文件相關的文件描述符有些相似。IPC 標識符由內核分配給 IPC 資源,在系統內部是惟一的,而 IPC 關鍵字能夠由程序員自由地選擇。
當兩個或者更多的進程要經過一個 IPC 資源進行通訊時,這些進程都要引用該資源的 IPC 標識符。數據結構
共享內存是進程間通訊的一種最基本、最快速的機制。共享內存是兩個或多個進程共享同一塊內存區域,並經過該內存區域實現數據交換的進程間通訊機制。一般是由一個進程開闢一塊共享內存區域,而後容許多個進程對此區域進行訪問。因爲不須要使用中間介質,而是數據由內存直接映射到進程空間,所以共享內存是最快速的進程間通訊機制。
使用共享內存有兩種方法:映射 /dev/mem 設備和內存映像文件。本文主要經過 demo 演示經過映射 /dev/mem 設備實現共享內存的方法。函數
共享內存的最大不足之處在於,因爲多個進程對同一塊內存區具備訪問的權限,各個進程之間的同步問題顯得尤其突出。必須控制同一時刻只有一個進程對共享內存區域寫入數據,不然將形成數據的混亂。同步控制的問題,筆者將在隨後的文章中介紹如何經過信號量解決。ui
ipc_perm 結構spa
對於每個進程間通訊機制的對象,都有一個 ipc_perm 結構與之相對應,該結構的定義以下:指針
struct ipc_perm { uid_t uid; gid_t gid; uid_t cuid; gid_t cgid; mode_t mode; ulong seq; key_t key; }
該結構用於記錄對象的各類相關信息,各個字段的具體含義以下:
uid:全部者的有效用戶 ID。
gid:全部者的有效組 ID。
cuid:建立者的有效用戶 ID。
cgid:建立者的有效組 ID。
mode:表示此對象的訪問權限。
seq:對象的應用序號。
key:對象的鍵。code
shmid_ds 結構對象
每一個共享內存都有與之相對應的 shmid_ds 結構,其定義以下:blog
struct shmid_ds { struct ipc_perm shm_perm; int shm_segsz; pid_t shm_cpid; pid_t shm_lpid; ulong shm_nattch; time_t shm_atime; time_t shm_dtiem; time_t shm_ctime; }
此機構記錄了一個共享內存的各類屬性,該結構的各個字段的含義以下:
shm_perm:對應於該共享內存的 ipc_perm 結構。
shm_segsz:以字節表示的共享內存區域的大小。
shm_lpid:最近一次調用 shmop 函數的進程 ID。
shm_cpid:建立該共享內存的進程 ID。
shm_nattch:當前使用該共享內存區域的進程數。
shm_atime:最近一次附加操做的時間。
shm_dtime:最近一次分離操做的時間。
shm_ctime:最近一次改變的時間。
建立或打開共享內存
要使用共享內存,首先要建立一個共享內存區域,建立共享內存區域的函數聲明以下:
#include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> int shmget(key_t key, size_t size, int flg);
函數 shmget 除了可用於建立一個新的共享內存外,也可用於打開一個已存在的共享內存。其中,參數 key 表示所建立或打開的共享內存的鍵。參數 size 表示共享內存區域的大小,只在建立一個新的共享內存時生效。參數 flag 表示調用函數的操做類型,也可用於設置共享內存的訪問權限。
當函數調用成功時,返回值爲共享內存的引用標識符;調用失敗時,返回值爲 -1。
附加共享內存
當一個共享內存建立或打開後,某個進程若是要使用該共享內存,則必須將這個共享內存區域附加到它的地址空間中。附加操做的函數聲明以下:
#include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> void *shmat(int shmid, const void *shmaddr, int flag);
參數 shmid 表示要附加的共享內存區域的引用標識符。參數 shmaddr 和 flag 共通決定共享內存區域要附加到的地址值。好比設置 shmaddr 爲 0 時,系統將自動查找進程地址空間,將共享內存區域附加到第一塊有效內存區域上,此時 flag 參數無效。
當函數調用成功時,返回值爲指向共享內存區域的指針;調用失敗時,返回值爲 -1。
分離共享內存
當一個進程對共享內存區域的訪問完成後,能夠調用 shmdt 函數使共享內存區域與該進程的地址空間分離,shmdt 函數的聲明以下:
#include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> int shmdt(const void *shmaddr);
此函數僅用於將共享內存區域與進程的地址空間分離,並不刪除共享內存自己。參數 shmaddr 爲指向要分離的共享內存區域的指針(就是調用 shmat 函數的返回值)。該函數調用成功時返回 0;調用失敗時返回 -1。
共享內存的控制
對共享內存區域的具體控制操做是經過函數 shmctl 來實現的,shmctl 函數的聲明以下:
#include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> int shmctl(int shmid, int cmd, struct shmid_ds *buf);
參數 shmid 爲共享內存的引用標識符。參數 cmd 表示調用該函數但願執行的操做。參數 buf 是指向 shmid_ds 結構體的指針。參數 cmd 的取值和對應的操做以下:
SHM_LOCK:將共享內存區域上鎖。
IPC_RMID:用於刪除共享內存。
IPC_SET:按參數 buf 指向的結構中的值設置該共享內存對應的 shmid_ds 結構。
IPC_STAT:用於取得該共享內存區域的 shmid_ds 結構,保存到 buf 指向的緩衝區。
SHM_UNLOCK:將上鎖的共享內存區域釋放。
下面咱們建立兩個程序 demoa 和 demob 來簡單的演示進程間如何經過共享內存通訊。其中 demoa 的代碼以下:
#include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include <stdio.h> #include <stdlib.h> #define BUF_SIZE 1024 #define MYKEY 24 int main(void) { int shmid; char *shmptr; // 建立或打開內存共享區域 if((shmid=shmget(MYKEY,BUF_SIZE,IPC_CREAT))==-1){ printf("shmget error!\n"); exit(1); } if((shmptr=shmat(shmid,0,0))==(void*)-1){ printf("shmat error!\n"); exit(1); } while(1){ // 把用戶的輸入存到共享內存區域中 printf("input:"); scanf("%s",shmptr); } exit(0); }
demoa 程序建立或打開 key 爲 24 的共享內存區域,並把用戶輸入的字符串存入這個共享內存區域。把上面的代碼保存到文件 shm_a.c 文件中,並用下面的命令編譯:
$ gcc -Wall shm_a.c -o demoa
下面是 demob 的代碼:
#include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #define BUF_SIZE 1024 #define MYKEY 24 int main(void) { int shmid; char *shmptr; // 建立或打開內存共享區域 if((shmid=shmget(MYKEY,BUF_SIZE,IPC_CREAT))==-1){ printf("shmget error!\n"); exit(1); } if((shmptr=shmat(shmid,0,0))==(void*)-1){ fprintf(stderr,"shmat error!\n"); exit(1); } while(1){ // 每隔 3 秒從共享內存中取一次數據並打印到控制檯 printf("string:%s\n",shmptr); sleep(3); } exit(0); }
demob 程序建立或打開 key 爲 24 的共享內存區域,而後每隔 3 秒從共享內存中取一次數據並打印到控制檯。這樣經過共享內存程序 demob 就能夠獲取到 demoa 程序中的數據。 把上面的代碼保存到文件 shm_b.c 文件中,並用下面的命令編譯:
$ gcc -Wall shm_b.c -o demob
接下來分別運行 demoa 和 demob,而後嘗試在 demoa 中輸入一些字符串:
demob 徹底不關心 demoa 在幹什麼,只是機械的每隔 3 秒鐘去共享內存中取一次數據,取到什麼就輸出什麼。
咱們在 demoa 和 demob 中並無經過 shmctl 函數在適當的時機刪除建立的共享內存區域,因此當程序 demoa 和 demob 退出後,咱們建立的 key 爲 24 的共享內存區域仍然駐留在系統的內存中。
Linux 系統默認自帶了一些管理 ipc 資源的基本命令,好比 ipcs、ipcmk 和 ipcrm。咱們可使用 ipcs 命令查看系統中的 ipc 資源:
$ ipcs -m
紅框中的共享內存就是咱們的 demo 程序建立的,第一列的 key 0x18 換算成十進制就是 24。
如今咱們已經不須要這個共享內存區域了,因此可使用下面的命令把它刪除掉:
$ sudo ipcrm -M 24
固然,除了刪除 ipc 資源,咱們還能夠經過 ipcmk 命令建立 ipc 資源。關於 ipcs、ipcmk 和 kpcrm 這三個命令的具體用法請參考相關的 man page,此文再也不贅述。
本文簡單的介紹了 IPC 相關的基本概念和共享內存編程中的一些結構與函數。並經過一個簡單的 demo 演示了共享內存工做的基本原理。因爲 demo 中沒有采起任何同步技術,demob 的輸出就顯得有些雜亂無章。在接下來介紹信號量的文章中,咱們會在 demo 中經過信號量來同步共享內存的訪問。
參考:
《深刻理解 Linux 內核》
《Linux 環境下 C 編程指南》