程式間通訊之共享記憶體

求求求亮發表於2020-11-10

一、基本概念

   共享記憶體,即兩個或多個程式都可以訪問的同一塊記憶體空間,一個程式對這塊空間內容的修改可為其他參與通訊的程式所看到的。顯然,為了達到這個目的就需要做兩件事:

  • 一件是在記憶體劃出一塊區域來作為共享區;
  • 另一件是把這個區域對映到參與通訊的各個程式空間。

共享記憶體區是最快的IPC形式。 一旦這樣的記憶體對映到共享它的程式的地址空間,這些程式間資料傳遞不再涉及到核心,換句話說是程式不再通過執行進入核心的系統呼叫來傳遞彼此的資料。但是共享記憶體沒有提供同步機制,需自己通過其他方式實現同步機制,如訊號量。

二、實現原理

如下圖所示, 在linux中,每個程式都有屬於自己的程式控制塊(PCB)和地址空間(Addr Space),並且都有一個與之對應的頁表,負責將程式的虛擬地址與實體地址進行對映,通過記憶體管理單元(MMU)進行管理。兩個不同的虛擬地址通過頁表對映到實體地址的同一區域,它們所指向的這塊區域即為共享記憶體。 注意此處,是兩個不同 虛擬地址對映到同一 實體地址上。
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20201109214140171.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3d
不同程式通過頁表將虛擬地址對映到同一實體地址上(即共享記憶體上),因為都是同一塊實體地址,每個程式對相應的虛擬地址操作都會作用到同一塊實體地址上,所以我們能利用它進行程式間通訊。但是有一點需要注意,此方式並不是同步的,我們需要利用其它方式實現同步互斥。

三、主要流程

  1. ftok( ) 函式生成IPC鍵值
  2. shmget( ) 函式建立共享記憶體並返回共享記憶體識別符號
  3. shmat( ) 函式連線共享記憶體識別符號為標識的共享記憶體
  4. shmdt( ) 函式斷開與共享記憶體的連線,並不是刪除
  5. shmctl( ) 函式完成對共享記憶體的控制,是否刪除共享記憶體看具體引數。

1、ftok( ) 函式生成IPC鍵值

每一個共享記憶體端都有一個對應的IPC鍵值(key_t 型別),函式ftok( ) 則是把一個已存在的路徑名和一個整數識別符號轉換成一個key_t值。

函式原型

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

key_t ftok( const char * fname, int id )

引數解析
fname就是自己指定的檔案的路徑(已經存在的檔案)。
id是子序列號,雖然是int型,但是隻能使用0~255(8bits)。

如果需要使用當前目錄,則:

key_t key;
key = ftok(".", 0x01);

返回值
成功返回鍵值,失敗返回-1

2、 shmget( ) 函式建立共享記憶體並返回識別符號

通過此函式建立一個共享記憶體物件並返回共享記憶體識別符號。

函式原型

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

int shmget(key_t key, size_t size, int shmflg)

引數解析

  • key就是生成的IPC鍵值。
  • size 為新建的共享記憶體大小,以位元組為單位。
  • shmflg 為所需操作和許可權,可以用來建立一個共享記憶體並返回一個識別符號或者是獲得一個識別符號。他有兩種方式:
    ① IPC_CREAT :如果不存在與鍵值相等的共享記憶體,則新建一個共享記憶體(不是新建檔案, 只是建立共享記憶體區域),如果存在這樣的共享記憶體區域,則返回此共享記憶體的識別符號。
    ② IPC_CREAT | IPC_EXCL:如果不存在與鍵值相等的共享記憶體,則新建一個共享記憶體,如果存在這一的共享記憶體區域,則報錯。

返回值
成功返回共享記憶體識別符號,出錯返回-1,錯誤原因存於errno中。

3、 shmat( ) 函式連線共享記憶體識別符號為標識的共享記憶體

連線指定共享記憶體識別符號的共享記憶體,連線成功後把共享記憶體區物件對映到呼叫程式的地址空間,隨後可像本地空間一樣訪問。

函式原型

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

void *shmat(int shmid, const void *shmaddr, int shmflg)

引數解析

  • shmid 共享記憶體識別符號
  • shmaddr 指定共享記憶體出現在程式的什麼位置,直接指定為NULL則讓核心自己決定一個合適的位置
  • shmflg 是一個標誌位,通常選擇0, 如果選擇SHM_RDONLY為只讀模式。

返回值
成功返回已經對映到共享記憶體的虛擬地址的起始地址,直接對其操作即可。
出錯返回-1,錯誤原因存於errno中

完成此操作獲取共享記憶體地址後,我們可以對其進行讀寫等操作。

4、shmdt( ) 函式斷開與共享記憶體的連線

與shmat函式相反,是用來斷開與共享記憶體附加點的地址,禁止本程式訪問此片共享記憶體。
函式原型

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

int shmdt(const void *shmaddr)

引數解析
shmaddr: 連線的共享記憶體的起始地址。(虛擬地址

注意:此函式不是刪除共享記憶體,而是在不用後,將其與當前程式分離。

5、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中
    ② IPC_SET: 改變共享記憶體的狀態,把buf所指的shmid_ds結構中的uid、gid、mode複製到共享記憶體的shmid_ds結構內
    ③ IPC_RMID: 刪除這片共享記憶體(常用
  • buf 為共享記憶體管理結構體。

返回值

成功返回0,出錯返回-1,錯誤原因存在errno中。

四、例程

本例程分為一個寫端和一個讀端,寫端改變溫度值,讀端讀取溫度值。

1、寫端

寫端每隔2秒將溫度值+10,然後轉化為字串,寫入共享記憶體中。

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>


#define PATH    "/tmp/share"
#define FTOK_ID     0X15


int main(int argc, char *argv[])
{
    key_t               key = -1;
    char                *addr = NULL;
    char                w_buff[128];
    int                 rv = -1;
    float               temperature = 0;

    while(1)
    {

        /***************************************
         *    1 生成IPC鍵值
         ***************************************/
        key  =ftok(PATH, FTOK_ID);
        if(key < 0)
        {
            printf("%d: Create the ftok faliure, because of %s\n", __LINE__, strerror(errno));
            return -1;
        }

        /****************************************
         *    2 建立共享記憶體並返回識別符號
         ****************************************/
        rv = shmget(key,  256, IPC_CREAT|0666);
        if(rv < 0)
        {
            printf("%d: Create the share memery faliure, because of %s\n", __LINE__, strerror(errno));
            return -2;
        }

        /****************************************
         *    3 連線共享記憶體 
         ****************************************/
        addr = shmat(rv, NULL, 0);
        if(addr == NULL)
        {
            printf("%d: Can not get share memery addr, because of %s\n", __LINE__, strerror(errno));
            return -3;
        }

        memset(w_buff, 0, sizeof(w_buff));
        printf("\nStart...\n");
        temperature += 10;
        sprintf(w_buff, "%f c", temperature);

        memcpy(addr, &w_buff, sizeof(w_buff));

        /*****************************************
         *   4 斷開與共享記憶體的連線
         *****************************************/
        shmdt(addr);
        printf("\nEnd...\n");
    
        sleep(2);
            
    }

    /*********************************************
     *     5 刪除共享記憶體 
     *********************************************/
    shmctl(rv, IPC_RMID, NULL);
    
    return 0;
}

在這裡插入圖片描述

2、讀端

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>


#define PATH    "/tmp/share"
#define FTOK_ID     0X15


int main(int argc, char *argv[])
{
    key_t               key = -1; 
    char                *addr = NULL;
    int                 rv = -1; 

    while(1)
    {   
        key  =ftok(PATH, FTOK_ID);
        if(key < 0)
        {   
            printf("%d: Create the ftok faliure, because of %s\n", __LINE__, strerror(errno));
            return -1; 
        }   
        rv = shmget(key,  256, IPC_CREAT|0666);
        if(rv < 0)
        {   
            printf("%d: Create the share memery faliure, because of %s\n", __LINE__, strerror(errno));
            return -2;
        }

        addr = shmat(rv, NULL, 0);
        if(addr == NULL)
        {
            printf("%d: Can not get share memery addr, because of %s\n", __LINE__, strerror(errno));
            return -3;
        }

        printf("get temperature is %s\n", (char*) addr);
        shmdt(addr);

        sleep(1);

    }
    shmctl(rv, IPC_RMID, NULL);

    return 0;

}

在這裡插入圖片描述

相關文章