Linux進程間通信一 System V 共享內存簡介與示例

目錄

1. System V共享內存簡介

2. API介紹

2.0 key_t和標識符

2.1  創建system v共享內存

2.2 映射共享內存並使用

2.3 取消共享內存映射

2.4 控制共享內存

3. 實例

3.1 共享內存寫示例

3.2 共享內存讀示例

4. 運行

5. 注意事項

6. 參考資料


1. System V共享內存簡介

System V IPC通常指的是以下三種:

  1. System V 消息隊列(message queues)
  2. System V 信號量(semaphores)
  3. System V 共享內存(shared memory)

System V IPC最早是在上世紀70年代末由貝爾實驗室開發出來,三種IPC在實現架構、使用方式上有很多相同之處,比如內核實現裏共用了ipc_perm權限管理結構體,使用方式上都包括基本的xxxget,xxxctl等等。今天主要介紹下System V 共享內存的使用方式。共享內存作爲IPC通信中最快的一種,之所以快是因爲一旦創建成功,後續操作無需內核拷貝數據以及系統調用,通信雙方可以直接操作共享內存來通信。因爲開發出來比較早,所以存在一些缺陷,所以就有了Posix IPC的產生,不過這是後話,在此不提。首先看下共享內存的接口。

2. API介紹

2.0 key_t和標識符

System V IPC對象通過標識符來區分,該標誌符具有系統唯一性,是操作系統內的全局變量。生成SystemV IPC對象的Get函數需要一個key_t參數,這是一個整型變量,用於對應一個標識符。該標識符通常有三種方法生成:

  1. 使用隨機固定值
  2. 使用IPC_PRIVATE,這表示每次生成IPC對象的時候都會創建一個新的標識符,因此不相干的進程之間無法知道標識符ID,因此通常只用於父子進程,子進程繼承父進程的IPC標識符,因而可以通信。
  3. 使用ftok函數生成key_t,該函數組合路徑的屬性和傳入的proj_id拼出一個key_t,實際使用的時候路徑通常都是可執行文件路徑。
#include <sys/types.h>
#include <sys/ipc.h>


/**
* @brief 根據路徑屬性和proj_id創建key_t
*
* @params pathname 路徑名,文件必須存在且可訪問
* @params proj_id 自定義ID,只使用低8位(不能爲0)
*
* @returns 成功返回key_t,失敗返回-1
*/

key_t ftok(const char *pathname, int proj_id);

 

2.1  創建system v共享內存

可以使用ipcs | ipcrm | ipcmk 查看、刪除和創建共享內存

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

/**
* @brief 創建共享內存ID
*
* @params key 與shm_id關聯的key,三種生成方式,包括IPC_PRIVATE,注意key和shm_id不是綁定關係,相同的key產生的標識符不一定相同。
* @params size 共享內存的大小,必須是正整數。
* @params shmflg 標誌位和權限控制標誌位,可以多個用or運算。IPC_CREAT、 IPC_EXCL
*
* @returns 成功返回shmid,失敗返回-1
*/

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

根據標誌位的不同,有如下幾種情況:

oflag參數 key不存在 key已經存在
無特殊標誌位 返回-1 返回標識符
IPC_CREAT 返回新建標識符 返回已有標識符
IPC_CREAT|IPC_EXCL 返回新建標識符 返回-1

 

2.2 映射共享內存並使用

使用共享內存前需要將其映射到經常自身地址空間,之後就可以像malloc分配的內存一樣使用了

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

/**
* @brief 映射共享內存到進程指定地址
*
* @params shmid 共享內存標識符
* @params shmaddr 映射的地址,爲NULL則內核自動選擇合適的地址,非空並且設置SHM_RND標誌位,則自動對齊,如果沒有此標誌位並且不對齊,報錯。
* @params shmflg 可以設置SHM_RND、SHM_REMAP,SHM_RDONLY
* 如果映射的地址已經有了映射,可以設置SHM_REMAP標誌位重新映射,否則報錯。
* 如果地址不對齊,可以設置SHM_RND標誌位,此時內核自動找到符合符合要求的地址,否則報錯。
*
* @returns 成功返回映射地址,可以像malloc返回的地址那樣操作,失敗返回 (void *) -1 
*/

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

2.3 取消共享內存映射

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

/**
* @brief 取消共享內存映射
*
* @params shmaddr shmat返回的共享內存地址
*
* 只是減少共享內存的引用計數並未實際刪除共享內存
*
* @returns 成功返回0,失敗返回 -1 
*/

int shmdt(const void *shmaddr);

2.4 控制共享內存

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

struct shmid_ds {
    struct ipc_perm shm_perm;    /* 所有權及權限 */
    size_t          shm_segsz;   /* 共享內存大小 (bytes) */
    time_t          shm_atime;   /* 上次shmat時間 */
    time_t          shm_dtime;   /* 上次shmdt時間 */
    time_t          shm_ctime;   /* 上次修改時間 */
    pid_t           shm_cpid;    /* 創建進程皮帶 */
    pid_t           shm_lpid;    /* PID of last shmat(2)/shmdt(2) */
    shmatt_t        shm_nattch;  /* 當前shmat進程數量 */
    ...
};

/**
* @brief 取消共享內存映射
*
* @params shmid 共享內存標識符
* @params cmd 控制命令,IPC_STAT, IPC_SET, IPC_RMID, SHM_LOCK, SHM_UNLOCK
* @params buf 緩存
* IPC_STAT 獲取對應共享內存信息,存到buf緩存裏。
* IPC_SET 只能修改shm_perm裏面的uid,gid及mode
* IPC_RMID 刪除共享內存,只有引用計數爲0才真正刪除
* SHM_LOCK 阻止共享內存被替換出去
* SHM_UNLOCK 和SHM_LOCK相反
*
* @returns 成功返回0,失敗返回 -1
*/

int shmctl(int shmid, int cmd, struct shmid_ds *buf);

3. 實例

3.1 共享內存寫示例

/*
**  Name: shm_write.c
**  Desc: 共享內存寫端
**  Author: masonf
**  Date: 2020-06-02
*/


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

// 消息結構體
typedef struct{
    char content[128];
} msg;

main(int argc, char** argv)
{
    int shm_id,i;
    key_t key;
    msg *pMsg;

    if (argc < 2)
    {
        printf("invalid argument\n");
        return ;
    }

    // key簡單寫死
    key = 9999;

    // 創建共享內存
    shm_id=shmget(key,4096,IPC_CREAT);
    if(shm_id == -1)
    {
        perror("shmget error");
        return;
    }

    // 映射共享內存到進程地址空間
    pMsg=(msg*)shmat(shm_id, NULL, 0);
    if (pMsg == (void *)-1)
    {
        perror("shmat error");
        return ;
    }

    // 寫數據到共享內存
    memcpy(pMsg->content, argv[1], strlen(argv[1]));

    // 取消共享內存映射,並沒有刪除共享內存
    if(shmdt(pMsg) == -1)
    {
        perror(" detach error ");
    }

    return ;
}

3.2 共享內存讀示例

/*
**  Name: shm_read.c
**  Desc: 共享內存讀端
**  Author: masonf
**  Date: 2020-06-02
*/

#include <sys/ipc.h>
#include <stdio.h>
#include <sys/shm.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
typedef struct{
    char content[128];
} msg;

void main(int argc, char** argv)
{
    int shm_id,i;
    key_t key;
    msg *pMsg = NULL;

    // 使用固定值
    key = 9999;

    // 存在則直接返回共享內存
    shm_id = shmget(key, 4096, IPC_CREAT);    
    if(shm_id == -1)
    {
        perror("shmget error");
        return;
    }

    // 映射共享內存到進程自身地址空間
    pMsg = (msg*)shmat(shm_id,NULL,0);
    if (pMsg == NULL)
    {
        printf("shmat error, errno:%d", errno);
        return ;
    }

    printf("Get Msg from shm_id:%s\n", pMsg->content);

    // 取消共享內存映射
    if(shmdt(pMsg) == -1)
    {
        perror(" detach error ");
    }

    return;
}

4. 運行

5. 注意事項

  1. 當多個進程同時讀寫共享內存存在競態衝突,需要用戶自己做好互斥,可以使用信號量等機制。
  2. 共享內存具有內核持久性,除非手動調用shmctl刪除,否則會一直存在,直到系統重啓。
  3. fork後執行exec,則共享內存會自動解除映射,detach
  4. 創建共享內存有一些限制。
  • shmmax  單個共享內存塊最大字節數
  • shmmnb  共享內存空間最小字節數
  • shmmni  系統最多可以創建的共享內存數量
  • shmseg  單個進程最多可以映射的共享內存數量
  • shmall   系統可供使用的共享內存大小

6. 參考資料

1. https://linux.die.net/man/2/shmctl

https://linux.die.net/man/2/shmget

2. 《Linux環境編程 從應用到內核》

3. 《Unix網絡編程 卷二 進程間通信》

 

================================================================================================

Linux應用程序、內核、驅動、後臺開發交流討論羣(745510310),感興趣的同學可以加羣討論、交流、資料查找等,前進的道路上,你不是一個人奧^_^。...
 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章