1. 程式人生 > >【Linux】程序間通訊(IPC)之共享記憶體詳解與測試用例

【Linux】程序間通訊(IPC)之共享記憶體詳解與測試用例

學習環境centos6.5 Linux核心2.6

什麼是共享記憶體

共享記憶體允許兩個或更多程序訪問同一塊記憶體。當一個程序改變了這塊記憶體中的內容的的時候,其他程序都會察覺到這個更改。

效率

因為所有程序共享同一塊記憶體,共享記憶體在各種程序間通訊方式中具有最高的效率。就像訪問程序獨有的記憶體區域一樣快,並不需要通過系統呼叫或其他需要切入核心的過程來完成。同時還能避免對資料的各種不必要的複製。

因為系統核心沒有對訪問共享記憶體的程序進行同步機制,如果有這方面的需求,比如需要不同程序程序對共享記憶體區進行讀寫操作,則必須要提供自己的同步措施,保護臨界資源。通常我們使用訊號量機制實現程序間同步與互斥。有關訊號量的知識請閱讀另一篇部落格

訊號量

原理:

這裡寫圖片描述

在Linux中,每個程序都有自己的P C B 和地址空間,並且都有一個對應的頁表,負責將程序的虛擬記憶體和實體記憶體進行對映,通過MMU來管理。共享記憶體塊,被對映到不同程序的地址空間內,從而實現了高效率的資源共享。

分配一個新的共享記憶體塊會建立新的記憶體頁面。因為所有程序都希望共享對同一塊記憶體的訪問,只應由一個程序建立一塊新的共享記憶體。再次分配一塊已經存在的記憶體塊不會建立新的頁面,會返回一個標識該記憶體塊的標示符。一個程序如需使用這個共享記憶體塊,則首先需要將它繫結到自己的地址空間中。會建立一個從程序本身虛擬地址到共享頁面的對映關係。當不需要該共享記憶體塊的時候,必須由一個且只能是一個程序負責釋放這個被共享的記憶體頁面。

如何使用

要使用一塊共享記憶體,程序必須先分配它。其他需要訪問這個共享記憶體塊的每一個程序都必須將這個共享繫結(attach)到自己的地址空間中(系統維護一個對該記憶體的引用計數器,通過ipcs -s 命令可檢視有幾個程序在使用該共享記憶體塊)
。當通訊完畢後,所有程序從共享記憶體塊脫離,由一個程序釋放該共享記憶體塊。要注意的是,所有使用者申請的共享記憶體塊最終大小都必須是向上取整為系統頁面大小的整數倍。在Linux系統中,記憶體頁面大小預設是4KB。

注意:當一個程序建立一塊共享記憶體後,該程序在主動去釋放該共享記憶體之前,被kill掉時,只會使該程序脫離(detach)該共享記憶體塊,而不會釋放該共享記憶體塊,這時候可以使用命名ipcrm -m 去釋放該資源。

相關函式

1、shmget

#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
  • 函式說明:得到一個共享記憶體識別符號或建立一個共享記憶體物件並返回共享記憶體識別符號。
  • 引數:
    • key ftok函式返回的I P C鍵值
    • size 大於0的整數,新建的共享記憶體大小,以位元組為單位,獲取已存在的共享記憶體塊識別符號時,該引數為0,
    • shmflg IPC_CREAT||IPC_EXCL 執行成功,保證返回一個新的共享記憶體識別符號,附加引數指定IPC物件儲存許可權,如|0666
  • 返回值:成功返回共享記憶體的識別符號,出錯返回-1,並設定error錯誤位。

2、shmat

#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void shmaddr, int shmflg);
  • 函式說明:連線共享記憶體識別符號為shmid的共享記憶體,連線成功後把共享記憶體區物件對映到呼叫程序的地址空間
  • 引數:
    • shmid: 共享記憶體識別符號
    • shmaddr: 指定共享記憶體出現在程序記憶體地址的什麼位置,通常指定為NULL,讓核心自己選擇一個合適的地址位置
    • shmflg: SHM_RDONLY 為只讀模式,其他引數為讀寫模式
  • 返回值:成功返回附加好的共享記憶體地址,出錯返回-1,並設定error錯誤位

3、shmdt

#include <sys/types.h>
#include <sys/shm.h>
void *shmdt(const void* shmaddr);
  • 函式說明:與shmat函式相反,是用來斷開與共享記憶體附加點的地址,禁止本程序訪問此片共享記憶體,需要注意的是,該函式並不刪除所指定的共享記憶體區,而是將之前用shmat函式連線好的共享記憶體區脫離目前的程序
  • 引數:shmddr 連線共享記憶體的起始地址
  • 返回值:成功返回0,出錯返回-1,並設定error。

4、shmctl

#include <sys/types.h>
#Include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds* buf);
  • 函式說明:控制共享記憶體塊
  • 引數:
    • shmid:共享記憶體識別符號
    • cmd:
      • IPC_STAT:得到共享記憶體的狀態,把共享記憶體的shmid_ds結構賦值到buf所指向的buf中
      • IPC_SET:改變共享記憶體的狀態,把buf所指向的shmid_ds結構中的uid、gid、mode賦值到共享記憶體的shmid_ds結構內
      • IPC_RMID:刪除這塊共享記憶體
    • buf:共享記憶體管理結構體
  • 返回值:成功返回0,出錯返回-1,並設定error錯誤位。

程式碼演示

目的闡述:程序server 建立一塊大小為4096kb的共享記憶體,然後將程序attach到該共享記憶體塊上,並執行寫操作,從’a’開始寫,每寫一次sleep(1);而client程序每隔一秒從該共享記憶體塊讀取一個並列印。

comm.h

#ifndef _COMM_H_
#define _COMM_H_

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

#define PATHNAME "." // ftok函式 生成key使用
#define PROJ_ID 66 // ftok 函式生成key使用

int create_shm( int size);//  分配指定大小的共享記憶體塊
int destroy_shm( int shmid); // 釋放指定id的共享記憶體塊
int get_shmid(); // 獲取已經存在的共享記憶體塊


#endif /*_COMM_H_*/

comm.c

#include "comm.h"

// 
static int comm_shm(int size, int shmflag)
{
    key_t key = ftok(PATHNAME, PROJ_ID); // 獲取key 
    if(key < 0){
        perror("ftok");
        return -1;
    }
    int shmid = shmget(key,  size, shmflag);
    if(shmid < 0){
        perror("shmget");
        return -2;
    }

    return shmid;
}

int create_shm( int size)
{
    return comm_shm(size, IPC_CREAT|IPC_EXCL|0666);
}
int get_shmid()
{
    return comm_shm(0, IPC_CREAT);
}

int destroy_shm(int shmid)
{
    if( shmctl( shmid, IPC_RMID, NULL) < 0)
    {
        perror("shmctl");
        return -1;
    }
    return 0;
}

server.c

#include "comm.h"

int main()
{
    int shmid = create_shm(4096);// 建立共享記憶體塊
    char *buf;
    int i = 0;
        buf = shmat(shmid,NULL, 0 );

    while( i < 4096)
    {
        buf[i] = 'a'+i ;
        i++;
        sleep(1); 
        if(i == 26)
            break; // 讓程式結束,去釋放該共享記憶體
    }
        destroy_shm(shmid);
    return 0;
}

client.c

#include "comm.h"

int main()
{
    int shmid = get_shmid(4096);
    char *buf;
    int index = 0;
        buf = shmat(shmid,NULL, 0 );

    while( index < 4096)
    {
        printf("%s\n", buf);
        sleep(1);
        index++;
        if( index == 27)
            break; // 讓程式結束
    }
    return 0;
}

截圖演示:
先啟動服務端,我們可以執行命令ipcs -m 檢視分配的該記憶體塊:
這裡寫圖片描述

可以發現倒數第二列,表示為1,表示該程序被attach到該記憶體塊,等啟動client程序後該值變為2

這裡寫圖片描述

在後臺執行server程序,啟動server程序後
這裡寫圖片描述

等兩個程序結束後再次使用ipcs -m 命令,發現已經看不到之前申請的那塊共享記憶體。