Linux 進程間通信之共享內存實踐

這節我們就來分享一下 Linux 的最後一種進程間通信的方式:共享內存。

1、什麼是共享內存

共享內存就是兩個不相關的進程之間可以直接訪問同一段內存,共享內存在兩個正在運行的進程之間共享和傳遞數據起到了非常有效的方式。在不同的進程之間共享的內存通常安排爲同一段物理內存,進程可以將同一段共享內存連接到它們自己的地址空間中,所有進程都可以直接訪問共享內存中的地址。而如果某個進程向共享內存寫入數據,所做的改動將立即影響到可以訪問同一段共享內存的任何其他進程;其實就是映射一段能夠被其它內存所訪問到的內存,這段內存由一個進程創建,但是多個進程都可以去訪問。共享內存是最快的 IPC 方式,它是通過其它通信方式的效率不足而專門設計的。往往都是和其它通信機制配合使用,來實現進程間的同步和通信。

共享內存的使用和信號量其實也是差不多的,都是使用接口的形式,共享內存的接口比信號量的接口更加的簡單,我們一起去了解下共享內存的使用。

共享內存函數由shmget、shmat、shmdt、shmctl四個函數組成。我們下面來分析每一個函數的用法。

1.1、創建共享內存

第一個參數是共享內存段的命名,shmget 成功時返回一個關於 key 相關的標識符,用於後續的共享內存函數。當調用失敗返回 - 1。其它進程也可以通過 shmget 函數返回值訪問同一個共享內存。第二個參數是指定共享內存的容量;第三個 shmflg 是一個權限標誌,它的作用和 open 和 mode 函數都是相同的,當共享內存不存在的時候則通過 IPC_CREAT 來創建。共享內存的權限標準和文件讀寫的權限一樣。

1.2、啓動對共享內存的訪問

void *shmat(int shm_id, const void *shm_addr, int shmflg);

當我們第一次創建完共享內存時,它還不能被任何進程訪問,shmat 函數就是用來啓動對共享內存的訪問,並把共享內存連接到當前進程的地址空間。

shm_id 是由 shmget 函數返回的共享內存標識;shm_addr 指定共享內存連接到當前進程中的地址位置,通常爲空,表示讓系統來選擇共享內存的地址。最後一個參數是標誌位通常都是 0。調用成功時返回一個指向共享內存第一個字節的指針,如果調用失敗返回 - 1。

1.3、共享內存從當前內存中分離

int shmdt(const void *shmaddr);

這個函數只是從共享內存中分離而不是刪除,這一點要分清楚,對於初學者而言這裏很容易掉坑,使共享內存在當前進程中不可再用。

參數 shmaddr 是 shmat 函數返回的地址指針,調用成功時返回 0,失敗時返回 - 1。

1.4、控制共享內存

int shmctl(int shm_id, int command, struct shmid_ds *buf);

第一個參數是 shaget 函數返回的共享內存標識符;command 參數是要採取的操作,它由 IPC_STAT、IPC_SET 和 IPC_RMID 組成,分別 IPC_STAT 代表把 shmid_ds 結構中的數據設置爲共享內存的當前關聯值,即用共享內存的當前關聯值覆蓋 shmid_ds 的值;IPC_SET 代表如果進程有足夠的權限,就可以把共享內存的當前關聯值設置爲 shmid_ds 結構中給出的值;IPC_RMID 代表刪除共享內存段。第三個參數 buf 代表一個結構指針,它指向共享內存的模式或訪問權限的結構。

shmid_ds 結構至少包括以下成員:

struct shmid_ds  
{  
  uid_t shm_perm.uid;
  uid_t shm_perm.gid;
  mode_t shm_perm.mode;
};

shm_snd.c

#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define SHM_SIZE 1024

int main()
{
        int shmid;
        char *shmptr;   
        //創建共享內存
        shmid = shmget(0x66, SHM_SIZE, IPC_CREAT|0666);
        //創建失敗
        if(shmid < 0)
        {               
            perror("shmget");
            return -1 ;
        }
        //對共享內存的訪問
        shmptr = shmat(shmid, 0, 0);
         if (shmptr == (void *)-1)
        {
            perror("shmat");
            return -2 ;
        }
        // 往共享內存寫數據
        strcpy(shmptr, "shmat write ok");
        shmdt(shmptr);
        return 0 ;
}

shm_rcv.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define SHM_SIZE 1024

int main()
{
    int shmid;
    char *shmptr;
    shmid = shmget(0x66, SHM_SIZE, IPC_CREAT|0666);
    if(shmid < 0)
    {
        perror("shmget");
        return -1 ;
    }
    shmptr = shmat(shmid, 0, 0);
    if (shmptr == (void *)-1)
    {
        perror("shmat");
        return -2 ;
    }
    // 從共享內存讀數據
    printf("read:%s\n", shmptr);
    shmdt(shmptr);
    return 0 ;
}

如圖上圖所示,nattch 項下的數字爲 0 那個就是剛剛使用 shmsnd 這個可執行程序創建的一段共享內存。當然,我們還往共享內存發了shmat write ok這個字符串,下面運行 shmrcv 這個程序,看看是否能把寫進共享內存的數據讀出來。

成功讀出。同樣的,也可以刪除共享內存,如何刪除?也一樣有兩種方法。

(1) 使用 ipcrm –m shmid 可以刪除共享內存

如上圖,我們已經知道 0x66 的 shmid 爲 1835021,所以只要執行ipcrm –m 1835021命令即可刪除,如下圖所示,成功刪除。

(2) 使用 shmctl 函數寫入 IPC_RMID 指令刪除共享內存

shmrm.c

#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int main(void)
{
        int shmid ;
        //同樣,首先先打開共享內存
        shmid = shmget(0x66 , 0 , 0);
        if(-1 == shmid)
        {
                perror("open  shmkey 0x66 fail");
                return -1 ;
        }
        //成功的話,向shmctl寫入參數,IPC_RMID表示立刻刪除,後面的參數被忽略,爲0
        int ret ;
        //寫入的是參數
        ret = shmctl(shmid , IPC_RMID , NULL);
        if(ret < 0)
        {
                perror("remove shm fail");
                return -2 ;
        }
        printf("remove key:%d success ... \n" , 0x66);
        return 0 ;
}

運行結果:

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/Nlj0niBAn0yqRzhkY8hmGg