System V IPC 之共享內存

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:將上鎖的共享內存區域釋放。

進程間經過共享內存通訊的 demo

下面咱們建立兩個程序 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 秒鐘去共享內存中取一次數據,取到什麼就輸出什麼。

管理 ipc 資源的基本命令

咱們在 demoa 和 demob 中並無經過 shmctl 函數在適當的時機刪除建立的共享內存區域,因此當程序 demoa 和 demob 退出後,咱們建立的 key 爲 24 的共享內存區域仍然駐留在系統的內存中。
Linux 系統默認自帶了一些管理 ipc 資源的基本命令,好比 ipcsipcmk 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 編程指南》

相關文章
相關標籤/搜索