從 0 開始學(xué)習(xí) Linux 系列之「22.共享內(nèi)存 Shared Memory」

共享內(nèi)存

版權(quán)聲明:本文為 cdeveloper 原創(chuàng)文章,可以隨意轉(zhuǎn)載,但必須在明確位置注明出處!

共享內(nèi)存 Shared Memory

這次我們來學(xué)習(xí)在 Linux 中最快的一種 IPC 方式:共享內(nèi)存 Shared Memory,它的基本原理是:內(nèi)核開辟一片內(nèi)存區(qū)域,然后多個用戶進(jìn)程可以將這片區(qū)域映射到它們自己的地址空間中進(jìn)行讀寫。為什么這種方式最快?因為數(shù)據(jù)不需要在進(jìn)程之間復(fù)制,只要一個進(jìn)程寫入數(shù)據(jù),另一個進(jìn)程就能馬上讀取數(shù)據(jù)了,但是讀取和寫入必須同步。

共享內(nèi)存屬性

我們可以在終端中使用 ipcs -m 查看系統(tǒng)當(dāng)前開辟的共享內(nèi)存:

ipcs -m

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status 
0x00000000 104529920  orange     600        2097152    2          dest
0x00000000 688129     orange     600        524288     2          dest
0x00000000 113672194  orange     600        524288     2          dest
...

可以看到列出了共享內(nèi)存的一些屬性:地址,id,擁有者等等。

共享內(nèi)存在哪里?

進(jìn)程可以將共享內(nèi)存映射到自己的地址空間中,如下:

進(jìn)程地址空間

可以看到中間有一個內(nèi)存映射區(qū)域,進(jìn)程把內(nèi)核的共享內(nèi)存映射到這個地方,模型如下:

共享內(nèi)存

進(jìn)程可以通過共享內(nèi)存的 API 獲得內(nèi)核所管理的共享內(nèi)存的一份映射,例如直接同步讀寫自己的映射文件就可以實現(xiàn)進(jìn)程 A 和 B 之間的通信了,非常簡單。

共享內(nèi)存的操作

共享內(nèi)存使用起來比較簡單,但是需要注意必須保持不同進(jìn)程的讀寫同步,可以使用信號量或者互斥鎖等,但是本篇文章主要介紹共享內(nèi)存,不會涉及同步相關(guān)的操作,下面的例子主要介紹共享內(nèi)存相關(guān)的 API 操作,當(dāng)你熟悉了基本的用法,在以后學(xué)習(xí)同步時可以加上同步機(jī)制來保證讀寫正確。

共享內(nèi)存(SHM)的操作主要分為下面 4 個步驟:

  1. 創(chuàng)建或獲取 SHM
  2. 映射 SHM 到進(jìn)程地址空間
  3. 操作映射后的區(qū)域,即讀寫
  4. 關(guān)閉進(jìn)程地址空間的映射區(qū)域

分別來了解這些操作的 API。

1. 創(chuàng)建:shmget

創(chuàng)建 SHM 調(diào)用 shmget

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

/*
 * key:SHM 標(biāo)識
 * size:SHM 大小
 * shmflg:創(chuàng)建或得到的屬性,例如 IPC_CREAT
 * return:成功返回 shmid,失敗返回 -1,并設(shè)置 erron
 */
int shmget(key_t key, size_t size, int shmflg);

2. 映射:shmat

shmat 將由 shmget 返回的 shmid 標(biāo)識的 SHM 映射到進(jìn)程的地址空間:

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

/*
 * shmid:SHM ID
 * shmaddr:SHM 內(nèi)存地址
 * shmflg:SHM 權(quán)限
 * return:成功返回 SHM 的地址,失敗返回 (void *) -1,并設(shè)置 erron
 */
void *shmat(int shmid, const void *shmaddr, int shmflg);

其中 shmaddr 參數(shù)主要有 2 種情況:

  1. shmaddr = NULL:系統(tǒng)選擇一塊合適的內(nèi)存地址作為映射的起始地址
  2. shmaddr != NULL:用戶自己地址,但是該地址需要符合一定的條件,詳情參考 man shmat

3. 關(guān)閉映射:shmdt

shmdt 解除當(dāng)前進(jìn)程映射的 SHM:

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

/*
 * shmaddr:已經(jīng)映射的 SHM 地址
 * return:成功返回 0,失敗返回 -1,并設(shè)置 erron
 */
int shmdt(const void *shmaddr);

例子:write_shm.c,read_shm.c

我們先創(chuàng)建一個片 SHM,然后寫入內(nèi)容:

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

int main() {
    // 1. 創(chuàng)建 SHM
    int shm_id = shmget(13, 2048, IPC_CREAT | 0666);
    if (shm_id != -1) {
        // 2. 映射 SHM
        void* shm = shmat(shm_id, NULL, 0);
        if (shm != (void*)-1) {
            // 3. 寫 SHM
            char str[] = "I'm share memory";
            memcpy(shm, str, strlen(str) + 1);
            // 4. 關(guān)閉 SHM
            shmdt(shm);
        } else {
            perror("shmat:");
        }
    } else {
        perror("shmget:");
    }
    return 0;
}

我們再寫一個程序來映射 SHM 并讀取其中的內(nèi)容:

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

int main() { 
    // 1. 獲取 SHM
    int shm_id = shmget(13, 2048, IPC_CREAT | 0666);
    
    if (shm_id != -1) {
        // 2. 映射 SHM
        void* shm = shmat(shm_id, NULL, 0);
        if (shm != (void*)-1) {
            // 3. 讀取 SHM
            char str[50] = { 0 };
            memcpy(str, shm, strlen("I'm share memory"));
            printf("shm = %s\n", (char *)shm); 
            // 4. 關(guān)閉 SHM
            shmdt(shm);
        } else {
            perror("shmat:");
        }
    } else {
        perror("shmget:");
    }
    if (0 == shmctl(shm_id, IPC_RMID))
        printf("delete shm success.\n");
    return 0;
}

編譯:

gcc write_shm.c -o write_shm
gcc read_shm.c -o read_shm

先運行寫入 SHM:

./write_shm

再運行讀取 SHM:

./read_shm
I'm share memory

成功讀取了寫進(jìn)程的寫入的數(shù)據(jù),雖然不是同步的,但是至少能夠獲取數(shù)據(jù)。最后再來分析分析內(nèi)核中的 SHM 調(diào)用過程吧。

共享內(nèi)存的內(nèi)核機(jī)制

由于 SHM 的操作函數(shù)比較多,我這里只分析 shmget 函數(shù),其他的函數(shù)都是類似的,先從 glibc 的庫函數(shù)開始:linux/shmget.c

int shmget (key, size, shmflg)
    key_t key;
    size_t size;
    int shmflg;
{
    return INLINE_SYSCALL (ipc, 5, IPCOP_shmget, key, size, shmflg, NULL);
}

這個函數(shù)直接進(jìn)行了系統(tǒng)調(diào)用,來繼續(xù)分析 Linux 3.4 內(nèi)核大體的執(zhí)行過程:陷入內(nèi)核后調(diào)用由 SYSCALL_DEFINE3 定義的系統(tǒng)調(diào)用 shmget,之后對 shm_ops 中的回調(diào)函數(shù)賦值,然后調(diào)用 ipcget,在這個函數(shù)中先進(jìn)行預(yù)處理,然后調(diào)用 shm_ops 中設(shè)置的 newseg 回調(diào)函數(shù),這個回調(diào)函數(shù)在內(nèi)核中開辟一段 SHM 內(nèi)存空間,至此就完成共享內(nèi)存的創(chuàng)建。

其中 shm_ops 結(jié)構(gòu)非常重要:

// ipc/shm.c
/*
 * Structure that holds some ipc operations. This structure is used to unify
 * the calls to sys_msgget(), sys_semget(), sys_shmget()
 *      . routine to call to create a new ipc object. Can be one of newque,
 *        newary, newseg
 *      . routine to call to check permissions for a new ipc object.
 *        Can be one of security_msg_associate, security_sem_associate,
 *        security_shm_associate
 *      . routine to call for an extra check if needed
 */
struct ipc_ops {
    int (*getnew) (struct ipc_namespace *, struct ipc_params *);
    int (*associate) (struct kern_ipc_perm *, int);
    int (*more_checks) (struct kern_ipc_perm *, struct ipc_params *);
};

從這個結(jié)構(gòu)的注釋可以看到這個結(jié)構(gòu)被「消息隊列」,「信號量」,「共享內(nèi)存」3 種 IPC 所使用,所以這個結(jié)構(gòu)非常的關(guān)鍵,其中的函數(shù)指針基本都指向某一個 IPC 的實現(xiàn)函數(shù),之后被底層回調(diào)使用。

整個過程如下:

shmget

因為 shmget 會創(chuàng)建或者獲取一個 SHM,所以最后會涉及內(nèi)核內(nèi)存的分配 vmallockmalloc,詳細(xì)的過程可以繼續(xù)跟蹤 ipc/shm.c

結(jié)語

這次我們學(xué)習(xí)了速度最快的 IPC 機(jī)制:共享內(nèi)存(Shared Memory),IPC 的 API 操作比較多這里不可能全部介紹,但是一定要善于使用 man 手冊。我們也在內(nèi)核中了解了 shmget 的基本調(diào)用過程,其他的函數(shù)也是類似的,都可以通過 SYSCALL_DEFINE 這個宏開始跟蹤,這個內(nèi)核系統(tǒng)調(diào)用的關(guān)鍵地方。希望你能多多實踐,也歡迎交流。

感謝你的閱讀,我們下次再見 :)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容