目錄
1. System V共享內存簡介
System V IPC通常指的是以下三種:
- System V 消息隊列(message queues)
- System V 信號量(semaphores)
- 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參數,這是一個整型變量,用於對應一個標識符。該標識符通常有三種方法生成:
- 使用隨機固定值
- 使用IPC_PRIVATE,這表示每次生成IPC對象的時候都會創建一個新的標識符,因此不相干的進程之間無法知道標識符ID,因此通常只用於父子進程,子進程繼承父進程的IPC標識符,因而可以通信。
- 使用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. 注意事項
- 當多個進程同時讀寫共享內存存在競態衝突,需要用戶自己做好互斥,可以使用信號量等機制。
- 共享內存具有內核持久性,除非手動調用shmctl刪除,否則會一直存在,直到系統重啓。
- fork後執行exec,則共享內存會自動解除映射,detach
- 創建共享內存有一些限制。
- 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),感興趣的同學可以加羣討論、交流、資料查找等,前進的道路上,你不是一個人奧^_^。...