linux shared,从 0 开始学习 Linux 系列之「22.共享内存 Shared Memory」
共享內存
版權聲明:本文為 cdeveloper 原創文章,可以隨意轉載,但必須在明確位置注明出處!
共享內存 Shared Memory
這次我們來學習在 Linux 中最快的一種 IPC 方式:共享內存 Shared Memory,它的基本原理是:內核開辟一片內存區域,然后多個用戶進程可以將這片區域映射到它們自己的地址空間中進行讀寫。為什么這種方式最快?因為數據不需要在進程之間復制,只要一個進程寫入數據,另一個進程就能馬上讀取數據了,但是讀取和寫入必須同步。
共享內存屬性
我們可以在終端中使用 ipcs -m 查看系統當前開辟的共享內存:
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
...
可以看到列出了共享內存的一些屬性:地址,id,擁有者等等。
共享內存在哪里?
進程可以將共享內存映射到自己的地址空間中,如下:
進程地址空間
可以看到中間有一個內存映射區域,進程把內核的共享內存映射到這個地方,模型如下:
共享內存
進程可以通過共享內存的 API 獲得內核所管理的共享內存的一份映射,例如直接同步讀寫自己的映射文件就可以實現進程 A 和 B 之間的通信了,非常簡單。
共享內存的操作
共享內存使用起來比較簡單,但是需要注意必須保持不同進程的讀寫同步,可以使用信號量或者互斥鎖等,但是本篇文章主要介紹共享內存,不會涉及同步相關的操作,下面的例子主要介紹共享內存相關的 API 操作,當你熟悉了基本的用法,在以后學習同步時可以加上同步機制來保證讀寫正確。
共享內存(SHM)的操作主要分為下面 4 個步驟:
創建或獲取 SHM
映射 SHM 到進程地址空間
操作映射后的區域,即讀寫
關閉進程地址空間的映射區域
分別來了解這些操作的 API。
1. 創建:shmget
創建 SHM 調用 shmget:
#include
#include
/*
* key:SHM 標識
* size:SHM 大小
* shmflg:創建或得到的屬性,例如 IPC_CREAT
* return:成功返回 shmid,失敗返回 -1,并設置 erron
*/
int shmget(key_t key, size_t size, int shmflg);
2. 映射:shmat
shmat 將由 shmget 返回的 shmid 標識的 SHM 映射到進程的地址空間:
#include
#include
/*
* shmid:SHM ID
* shmaddr:SHM 內存地址
* shmflg:SHM 權限
* return:成功返回 SHM 的地址,失敗返回 (void *) -1,并設置 erron
*/
void *shmat(int shmid, const void *shmaddr, int shmflg);
其中 shmaddr 參數主要有 2 種情況:
shmaddr = NULL:系統選擇一塊合適的內存地址作為映射的起始地址
shmaddr != NULL:用戶自己地址,但是該地址需要符合一定的條件,詳情參考 man shmat
3. 關閉映射:shmdt
shmdt 解除當前進程映射的 SHM:
#include
#include
/*
* shmaddr:已經映射的 SHM 地址
* return:成功返回 0,失敗返回 -1,并設置 erron
*/
int shmdt(const void *shmaddr);
例子:write_shm.c,read_shm.c
我們先創建一個片 SHM,然后寫入內容:
#include
#include
#include
#include
#include
#include
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[] = "I'm share memory";
memcpy(shm, str, strlen(str) + 1);
// 4. 關閉 SHM
shmdt(shm);
} else {
perror("shmat:");
}
} else {
perror("shmget:");
}
return 0;
}
我們再寫一個程序來映射 SHM 并讀取其中的內容:
#include
#include
#include
#include
#include
#include
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. 關閉 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
成功讀取了寫進程的寫入的數據,雖然不是同步的,但是至少能夠獲取數據。最后再來分析分析內核中的 SHM 調用過程吧。
共享內存的內核機制
由于 SHM 的操作函數比較多,我這里只分析 shmget 函數,其他的函數都是類似的,先從 glibc 的庫函數開始: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);
}
這個函數直接進行了系統調用,來繼續分析 Linux 3.4 內核大體的執行過程:陷入內核后調用由 SYSCALL_DEFINE3 定義的系統調用 shmget,之后對 shm_ops 中的回調函數賦值,然后調用 ipcget,在這個函數中先進行預處理,然后調用 shm_ops 中設置的 newseg 回調函數,這個回調函數在內核中開辟一段 SHM 內存空間,至此就完成共享內存的創建。
其中 shm_ops 結構非常重要:
// 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 *);
};
從這個結構的注釋可以看到這個結構被「消息隊列」,「信號量」,「共享內存」3 種 IPC 所使用,所以這個結構非常的關鍵,其中的函數指針基本都指向某一個 IPC 的實現函數,之后被底層回調使用。
整個過程如下:
shmget
因為 shmget 會創建或者獲取一個 SHM,所以最后會涉及內核內存的分配 vmalloc 和 kmalloc,詳細的過程可以繼續跟蹤 ipc/shm.c
結語
這次我們學習了速度最快的 IPC 機制:共享內存(Shared Memory),IPC 的 API 操作比較多這里不可能全部介紹,但是一定要善于使用 man 手冊。我們也在內核中了解了 shmget 的基本調用過程,其他的函數也是類似的,都可以通過 SYSCALL_DEFINE 這個宏開始跟蹤,這個內核系統調用的關鍵地方。希望你能多多實踐,也歡迎交流。
感謝你的閱讀,我們下次再見 :)
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的linux shared,从 0 开始学习 Linux 系列之「22.共享内存 Shared Memory」的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux lz4 lzo,Linux六
- 下一篇: android内存优化方法,Androi