Linux C語言 41-進程間通信IPC之共享內存
本節關鍵字:C語言 進程間通信 共享內存 shared memory
相關庫函數:shmget、shmat、shmdt、shmctl
什么是共享內存?
共享內存(Shared Memory)指兩個或多個進程共享一個給定的存儲區。
共享內存的特點
- 共享內存是最快的只用System V IPC,因為進程是直接對內存進行讀寫;
- 因為多個進程可以同時操作,所以需要進程同步;
- 信號量+共享內存通常結合在一起使用,信號量用來同步對共享內存的訪問;
- 共享內存被創建成功后需要手動釋放,否則將持續到內核重新引導。
共享內存相關庫函數
創建或鏈接一個共享內存
當創建新的共享內存段時,其內容被初始化為零,并且其相關的數據結構shmid_ds(參見shmctl(2))被初始化如下:
- shm_perm.uid 和shm_perm.uid 被設置為調用進程的有效用戶ID。
- shm_perm.cgid 和shm_perm_gid 被設置為調用進程的有效組ID。
- shm_perm.mode 的最低有效9位被設置為shmflg 的最低有效的9位。
- shm_setsz 設置為size 的值。
- shm_lpid、shm_natch、shm_time和shm_dtime 設置為0。
- shm_time 設置為當前時間。
如果共享內存段已經存在,則會驗證權限,并檢查它的銷毀標記是否有效。
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
key_t ftok(const char *pathname, int proj_id); // 前文消息隊列中已經介紹過,這里就不展開介紹了
int shmget(key_t key, size_t size, int shmflg);
/**
@brief 分配共享內存段。shmget()返回與參數key的值相關聯的共享內存段的標識符。如果key的值為IPC_PRIVATE或key不是IPC_PRIVATE,不存在與該密鑰對應的共享內存段,并且在shmflg中指定了IPC_CREAT,則會創建一個新的共享內存分段,其大小等于四舍五入到PAGE_SIZE的倍數。如果shmflg同時指定了IPC_CREAT和IPC_EXCL,并且key的共享內存段已經存在,那么shmget()將失敗,errno設置為EEXIST。(這類似于組合O_CREAT | O_EXCL對open(2)的效果。)
@param key 進程間通信的鍵值(密鑰),是ftok()的返回值
@param size 該共享內存的字節長度
@param shmflg 表示函數的行為及共享內存的權限,取值如下:IPC_CREAT 如果不存在就創建IPC_EXCL 如果存在則返回失敗mode_flags(最低有效9位),指定授予所有者、組和世界的權限。這些位具有與open(2)的模式自變量相同的格式和含義。目前,系統未使用執行權限。SHM_HUGETLB 自Linux 2.6起)使用“巨大的頁面”分配段。有關更多信息,請參閱內核源文件Documentation/vm/hugetlbpage.txt。SHM_NORESERVE (自Linux 2.6.15起)此標志的作用與mmap(2)MAP_NORESERVE標志相同。不要為該段保留交換空間。當保留交換空間時,可以保證可以修改段。當交換空間沒有保留時,如果沒有可用的物理內存,則可能會在寫入時獲得SIGSEGV。另請參閱proc(5)中文件/proc/sys/vm/overcommit_memory的討論。
@return 成功返回有效的貢獻內存標識符shmid,失敗返回-1,并設置錯誤碼errno錯誤碼errno的類型:
EACCES 用戶沒有訪問共享內存段的權限,也沒有CAP_IPC_OWNER功能。
EEXIST 指定了IPC_CREAT | IPC_EXCL,并且該段存在。
EINVAL 要創建一個新的段,其大小<SHMMIN或size>SHMMAX,或者不創建新的段、存在一個具有給定鍵的段,但大小大于該段的大小。
ENFILE 已達到打開文件總數的系統限制。
ENOENT 給定key不存在分段,并且未指定IPC_CREAT。
ENOMEM 無法為段開銷分配內存。
ENOSPC 已獲取所有可能的共享內存ID(SHMMNI),或者分配所請求大小的段將導致系統超過共享內存的系統范圍限制(SHMALL)。
EPERM 指定了SHM_HUGETLB標志,但調用方沒有特權(不具有CAP_IPC_LOCK功能)。NOTES
IPC_PRIVATE不是標志字段,而是key_t類型。如果這個特殊值用于key,則系統調用將忽略shmflg的除最低有效9位之外的所有內容,并創建一個新的共享內存段。
以下對共享內存段資源的限制會影響shmget()調用:
SHMALL 系統范圍內共享內存頁的最大值(在Linux上,可以通過/proc/sys/kernel/SHMALL讀取和修改此限制)。
SHMMAX 共享內存段的最大大小(以字節為單位):取決于策略(在Linux上,可以通過/proc/sys/kernel/SHMMAX讀取和修改此限制)。
SHMMIN 共享內存段的最小大小(以字節為單位):取決于實現(當前為1字節,但PAGE_size是有效的最小大小)。
SHMMNI 系統范圍內共享內存段的最大數量:取決于實現(目前為4096個,在Linux 2.3.99之前為128個;在Linux上,可以通過/proc/sys/kernel/SHMMNI讀取和修改此限制)。
該實現對每個進程的共享內存段的最大數量(SHMSEG)沒有特定限制。
*/
將共享內存映射到進程內存
一個成功的shmat()調用將更新與共享內存段相關聯的shmid_ds結構的成員(請參見shmctl(2)),如下所示:
- shm_time 設置為當前時間。
- shm_lpid 設置為調用進程的進程ID。
- shm_natch 增加1。
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
/**
@brief shmat()將shmid標識的共享內存段附加到調用進程的地址空間。附加地址由shmaddr根據以下條件之一指定:如果shaddr為NULL,系統會選擇一個合適的(未使用的)地址來附加段。如果shaddr不為NULL,并且在shmflg中指定了SHM_RND,則附加發生在等于shaddr的地址處,四舍五入到SHMLBA的最近倍數。否則,shaddr必須是發生附加的頁面對齊地址。如果在shmflg中指定了SHM_RDONLY,則附加該段進行讀取,并且進程必須具有該段的讀取權限。否則,將附加段進行讀寫操作,并且進程必須具有該段的讀寫權限。不存在只寫共享內存段的概念。
@param shmid 共享內存的標識符,是shmget()的成功返回值
@param shmaddr 共享內存映射地址(為NULL則系統自動指定),一般使用NULL
@param shmflg 共享內存端的訪問權限和映射條件(一般設置為0),具體取值如下:0 共享內存具有可讀可寫權限SHM_RDONLY 只讀SHM_RND shmaddr非空時才有效
@return 成功返回附加的共享內存段的地址;失敗返回-1,并設置錯誤碼errno錯誤碼errno的類型:
EACCES 調用進程不具有所請求的附加類型所需的權限,也不具有CAP_IPC_OWNER功能。
EIDRM shmid指向一個已刪除的標識符。
EINVAL 無效的shmid值、未對齊(即未對齊頁面且未指定SHM_RND)或無效的shaddr值,或者無法在shaddr處附加段,或者指定了SHM_REMAP且shaddr為NULL。
ENOMEM 無法為描述符或頁表分配內存。
*/#### 解除共享內存的映射
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
/**
@brief shmdt()從調用進程的地址空間中分離位于shaddr指定地址的共享內存段。
要分離的段當前必須附加等于附加shmat()調用返回值的shaddr。在成功調用shmdt()時,系統會更新與共享內存段相關聯的shmid_ds結構的成員,如下所示:shm_dtime 設置為當前時間。shm_lpid 設置為調用進程的進程ID。shm_natch 遞減一。如果它變為0并且該段被標記為刪除,則該段被刪除。
@param shmaddr 共享內存的參數地址
@return 成功返回0,失敗返回-1,并設置錯誤碼errno錯誤碼errno的類型:
EINVAL shaddr上沒有附加共享內存段;或者,shaddr沒有在頁面邊界上對齊。
*/
控制共享內存
#include <sys/ipc.h>
#include <sys/shm.h>struct ipc_perm
{key_t __key; // shmget(2)提供的密鑰uid_t uid; // 所有者的有效UIDgid_t gid; // 所有者的有效GIDuid_t cuid; // 創建者的有效UIDgid_t cgid; // 創建者的有效GIDunsigned short mode; // 權限+SHM_DEST和SHM_LOCKED標志unsigned short __seq; // 序列號碼
};struct shmid_ds
{struct ipc_perm shm_perm; // 所有權和權限size_t shm_segsz; // 段大小(字節)time_t shm_atime; // 上次連接時間time_t shm_dtime; // 上次分離時間time_t shm_ctime; // 上次修改時間pid_t shm_cpid; // 創建者進程PIDpid_t shm_lpid; // 上次執行shmat(2)或shmdt(2)的進程PIDshmatt_t shm_nattch; // 當前附件數量...
};struct shminfo
{unsigned long shmmax; // 共享內存最大分段長度unsigned long shmmin; // 共享內存最小分段長度,總為1unsigned long shmmni; // 共享內存最大分段數unsigned long shmseg; // 進程可以附加的最大段數;內核中未使用unsigned long shmall; // 系統范圍內共享內存的最大頁數// shmmni、shmmax和shmall設置可以通過相同名稱的/proc文件進行更改;有關詳細信息,請參見proc(5)。
};struct shm_info
{int used_ids; // # of currently existing segmentsunsigned long shm_tot; // Total number of shared memory pagesunsigned long shm_rss; // # of resident shared memory pagesunsigned long shm_swp; // # of swapped shared memory pagesunsigned long swap_attempts; // Unused since Linux 2.4unsigned long swap_successes; // Unused since Linux 2.4
};int shmctl(int shmid, int cmd, struct shmid_ds *buf);
/**
@brief 獲取和設置消息隊列的屬性
@param shmid 共享內存的標識符
@param cmd 函數功能的控制,取值如下:IPC_STAT 將信息從與shmid關聯的內核數據結構復制到buf指向的shmid_ds結構中。調用方必須具有對共享內存段的讀取權限。IPC_SET 將buf指向的shmid_ds結構的一些成員的值寫入與該共享內存段相關聯的內核數據結構,同時更新其shm_time成員。可以更改以下字段:shm_perm.uid、shm_perm_gid和(shm_per.mode的最低有效9位)。調用進程的有效uid必須與共享內存段的所有者(shm_erm.uid)或創建者(shm_term.cuid)匹配,或者調用方必須具有特權。IPC_RMID 標記要銷毀的段。只有在最后一個進程將該段分離后(即,當關聯結構shmid_ds的shm_natch成員為零時),該段才會被實際銷毀。調用方必須是所有者或創建者,或者具有特權。如果某個段已標記為要銷毀,則將設置IPC_STAT檢索到的相關數據結構中的SHM_perm.mode字段的(非標準)SHM_DEST標志。調用方必須確保一個段最終被銷毀;否則,其在中出現故障的頁面將保留在內存或交換中。IPC_INFO (特定于Linux)返回有關buf指向的結構中的系統范圍共享內存限制和參數的信息。如果定義了_GNU_SOURCE功能測試宏,則該結構的類型為shminfo(因此,需要強制轉換),在<sys/shm.h>中定義: struct shminfo;(結構內容及注釋在上方展示)SHM_INFO (特定于Linux)返回一個shm_info結構,其字段包含有關共享內存所消耗的系統資源的信息。如果定義了_GNU_SOURCE功能測試宏,則在<sys/shm.h>中定義此結構:struct shm_info;(結構內容及注釋在上方展示)SHM_STAT (特定于Linux)返回與IPC_STAT相同的shmid_ds結構。但是,shmid參數不是段標識符,而是內核內部數組的索引,該數組維護系統上所有共享內存段的信息。調用方可以阻止或允許使用以下cmd值交換共享內存段:SHM_LOCK (特定于Linux)防止交換共享內存段。調用方必須在啟用鎖定后所需的任何頁面中出錯。如果某個段已被鎖定,則將設置IPC_STAT檢索到的相關數據結構中的SHM_perm.mode字段的(非標準)SHM_locked標志。SHM_UNLOCK (特定于Linux)解鎖分段,允許將其交換出去。在2.6.10之前的內核中,只有特權進程可以使用SHM_LOCK和SHM_UNLOCK。由于內核2.6.10,如果非特權進程的有效UID與段的所有者或創建者UID匹配,并且(對于SHM_LOCK)要鎖定的內存量在RLIMIT_MEMLOCK資源限制范圍內,則該進程可以使用這些操作(請參見setrlimit(2))。@param buf shmid_ds 數據類型的地址,用來存放或修改共享內存的屬性。
@return 成功的IPC_INFO或SHM_INFO操作返回內核內部數組中使用次數最多的條目的索引,該數組記錄了有關所有共享內存段的信息。(此信息可與重復的SHM_STAT操作一起使用,以獲得有關系統上所有共享內存段的信息。)成功的SHM_TAT操作將返回共享內存段(其索引在shmid中給出)的標識符。其他操作成功后返回0。失敗返回-1,并設置錯誤碼errno錯誤碼errno的類型:
EACCES IPC_STAT或SHM_STAT被請求,并且SHM_perm.mode不允許對shmid進行讀取訪問,并且調用進程不具有CAP_IPC_OWNER功能。
EFAULT 參數cmd的值為IPC_SET或IPC_STAT,但buf指向的地址不可訪問。
EIDRM shmid指向一個已刪除的標識符。
EINVAL shmid不是有效的標識符,或者cmd不是有效命令。或者:對于SHM_STAT操作,shmid中指定的索引值引用了當前未使用的數組槽。
ENOMEM (在2.6.9之后的內核中),指定了SHM_LOCK,并且要鎖定的段的大小意味著鎖定的共享內存段中的總字節數將超過調用進程的真實用戶ID的限制。此限制由RLIMIT_MEMLOCK軟資源限制定義(請參閱setrlimit(2))。
EOVERFLOW 嘗試IPC_STAT,但GID或UID值太大,無法存儲在buf指向的結構中。
EPERM 嘗試IPC_SET或IPC_RMID,并且調用進程的有效用戶ID不是創建者(在shm_perm.cuid中找到)或所有者(在shr_perm.uid中找到)的ID,并且進程沒有特權(Linux:不具有CAP_SYS_ADMIN功能)。或者(在2.6.9之前的內核中),指定了SHM_LOCK或SHM_UNLOCK,但進程沒有特權(Linux:沒有CAP_IPC_LOCK功能)。(從Linux 2.6.9開始,如果RLIMIT_MEMLOCK為0并且調用方沒有特權,也可能發生此錯誤。)NOTES
ipcs(8)程序使用IPC_INFO、SHM_STAT和SHM_INFO操作來提供有關所分配資源的信息。將來,這些文件可能會被修改或移動到/proc文件系統接口。Linux允許進程使用shmctl(IPC_RMID)附加(shmat(2))已標記為刪除的共享內存段。此功能在其他Unix實現中不可用;可移植應用程序應避免依賴它。
結構shmid_ds中的各種字段在Linux 2.2下被鍵入為短字段,而在Linux 2.4下則變為長字段。為了利用這一點,在glibc-2.1.91或更高版本下重新編譯就足夠了。(內核通過cmd中的IPC_64標志來區分新舊調用。)
*/
共享內存例程
/*** 共享內存使用例程,創建、連接、寫、讀、斷開連接、刪除* 目前未增加線程安全措施,后期可以增加互斥鎖等*/#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>#define SHM_FULL 1
#define SHM_LINES_MAX 10
#define SHM_LINE_LEN_MAX 128typedef struct
{int w_idx, full;char msg[SHM_LINES_MAX][SHM_LINE_LEN_MAX];int len[SHM_LINES_MAX];
} SHM;void shmwrite(SHM *shm, const char *msg, size_t len);
char *shmread(SHM *shm);int main()
{key_t shmid;SHM *shmaddr = NULL;char message[SHM_LINE_LEN_MAX];// 創建一個新的共享內存段,也可自行指定key值(將IPC_PRIVARE換成其它值,例如:6565656)shmid = shmget(IPC_PRIVATE, sizeof(SHM), IPC_CREAT | 0666);if (shmid == -1) {perror("shmget");exit(EXIT_FAILURE);}// 連接到共享內存段shmaddr = shmat(shmid, NULL, 0);if (shmaddr == (void *)-1) {perror("shmat");exit(EXIT_FAILURE);}printf("shared memory: shmid[%d], address[%p]\n", shmid, shmaddr);// 對共享內存進行讀寫等操作...while (shmaddr->full != SHM_FULL){printf("please input your message: ");bzero(message, sizeof(message));scanf("\n%[^\n]", message); //注意嘗試思考和直接使用 %s的區別哦^!^shmwrite(shmaddr, message, strlen(message));printf("reading shared memory data...\n");shmread(shmaddr);// 此過程中可以在新開命令窗口中使用 ipcs -m 查看新建共享內存的相關信息}printf("shared memory will be deleted in 5 seconds\n");sleep(5);// 從共享內存分離if (shmdt(shmaddr) != 0) {perror("shmdt");exit(EXIT_FAILURE);}// 刪除共享內存段if (shmctl(shmid, IPC_RMID, NULL) != 0) {perror("shmctl");exit(EXIT_FAILURE);}// 此時再使用 ipcs -m 查看創建的共享內存已經不存在了printf("shared memory deleted: shmid[%d], address[%p]\n", shmid, shmaddr);return 0;
}void shmwrite(SHM *shm, const char *msg, size_t len)
{if (shm->full == SHM_FULL)return;strncpy(shm->msg[shm->w_idx], msg, len);shm->len[shm->w_idx] = len;shm->w_idx++;if (shm->w_idx == SHM_LINES_MAX)shm->full = SHM_FULL;
}char *shmread(SHM *shm)
{int i, count;if (shm->w_idx == 0){printf("share memory is empty\n");return;}count = shm->w_idx;for (i=0; i<count; i++){printf("[%d]: %s\n", i, shm->msg[i]);}return shm->msg[count];
}
提示:先做內容框架梳理,后期進行完善補充!