共享存儲
程序間通信方式之一,建立一個共享存儲區,各個程序通過在指定位址或者核心選用第一個可用位址來對共享存儲區進行資料的讀寫操作達到程序間資料互動。對同一個共享存儲區操作,會涉及到對同一塊資料進行修改操作,導緻資料混亂甚至段錯誤,所有應該給操作加上鎖,保證同一時刻隻有一個程序在操作共享存儲區。
1.建立共享存儲區
調用shmget建立或引用已存在的共享存儲區。每個共享存儲區都與一個key相對應,建立或引用共享存儲區時需指定共享存儲區的key,大小以及讀寫權限。大小一般為記憶體葉的整數倍,如果不是葉的整數倍,那麼最後一頁的剩餘空間是不可被其它程式所用。引用已經存在的共享存儲區,存儲區大小一般指定為0,如果指定的大小比存儲區還大出錯傳回。詳細使用參照以下shmget的描述。
2.設定擷取共享存儲區相關屬性值
調用shmctl可擷取或者設定共享存儲區相關資訊。比如共享存儲區的大小、權限、系統對共享存儲區的限制等等。也可以通過設定SHM_LOCK來把共享存儲區的内容鎖住,不讓系統把其内容交換到swap區,以提高對共享存儲區通路性能的提升。具體使用細節參照以下shmctl的使用
3.引用共享存儲區
利用shmat把共享存儲區映射到程序位址空間,供程序讀寫操作。映射共享記憶體區時可以指定位址,也可以不指定位址讓系統自動指定一個位址。建議讓系統來指定位址供共享記憶體區映射,因為不同
的系統,不同的硬體它們的位址值不一樣,所有程式指定了位址的話不便移植。較長的描述參照以下shmat的使用
4.解除共享存儲區映射
當不再需要對共享存儲區時,要調用shmdt進行解除映射,預設程序結束,自動解除映射。較長的描述參照以下shmdt的使用
5.删除共享存儲區
程式結束不會删除共享存儲,需通過調用shmct指定相應的删除指令來把共享存儲區删除,不會立即删除,隻有等到所有引用共享記憶體都解除映射後才會删除。較長的描述參照以下shmctl的使用
函數shmget
函數調用說明:
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
函數功能描述:該函數是用來建立一個共享存儲區,傳回指定key的共享存儲區的辨別。
建立一個新的共享存儲段有兩種情況:
1.key=IPC_PRIVATE
2.key!=IPC_PRIVATE,但是key也不等于已存在的任意一個共享存儲段的key,且shmflg = IPC_CREAT
如果指定的key對應的共享存儲區已經存在,且shmflg = IPC_CREAT|IPC_EXCL,那麼函數傳回失敗,errno= EEXIST
參數shmflg取值:
IPC_CREAT 建立一個新的共享存儲區, 如果沒有指定該标志,那麼函數會去查找key指定的共享存儲區是否存在,如果存在則檢查使用者是否對其有相應的操作權限
mode_flags 指定對新建立的存儲區擁有的權限,沒有可執行權限,與open裡的權限含義一樣
SHM_HUGETLB (since Linux 2.6) 指定共享存儲區使用大記憶體葉來讀寫資料,如果不指定則使用4k的葉
SHM_NORESERVE (since Linux 2.6.15) 不為共享記憶體保留交換區間,如果給共享存儲預留交換區,那麼使用者可以對共享存儲去進行修改,如果不給共享存儲區預留交換區,那麼當沒有更多實體記憶體來進行操作時會出現段錯誤。
當一個新的共享存儲區建立以後,共享存儲區會清0,初始化與共享存儲區相關的結構體shmid_ds:
shm_perm.cuid 和 shm_perm.uid初始化為調用程序的有效使用者id
shm_perm.cgid 和 shm_perm.gid初始化為調用程序的有效組id
shm_perm.mode初始化為參數shmflg設定的權限
shm_segsz共享存儲區的大小
shm_ctime目前時間
其他都置為0
傳回值
成功傳回共享存儲區id,失敗傳回-1,設定errno。
errno錯誤含義:
EACCES 調用程序沒有權限通路共享存儲區
EEXIST 指定了參數IPC_CREAT | IPC_EXCL,而且指定key的共享存儲區已經存在
EINVAL 建立了一個新的共享存儲區,但是大小小于SHMMIN或者大于SHMMAX。引用一個已經存在的共享存儲區,但是指定的大小大于該存儲區
ENFILE 建立的共享存儲區個數已經達到極限
ENOENT 引用一個存在的共享存儲區時,指定key的存儲區不存在
ENOMEM 沒有更多的記憶體來建立共享存儲區
ENOSPC 建立的共享存儲大小超過系統記憶體範圍
EPERM 指定了SHM_HUGETLB标志,但是調用程序沒有權限
EIDRM(linux 2.3.30)引用了一個已經删除的共享存儲區
注意:
IPC_PRIVAT是key_t類型不是标志。如果指定了該标志,系統調用會忽視其他東西,僅權限标志除外,并且建立一個新的共享存儲區
關于共享存儲區限制:
SHMALL 系統用于共享存儲的葉最大數量
SHMMAX 最大共享存儲區大小
SHMMIN 最小存儲區大小
SHMMNI 系統允許共享存儲區的最多個數
每個程序擁有共享存儲區個數限制沒有明确指定
BUGS
參數IPC_PRIVATE可能會導緻意想不到的結果,用IPC_NEW替代
函數shmctl
頭檔案
#include <sys/ipc.h>
#include <sys/shm.h>
函數調用格式
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
函數說明:shmctl()通過指定的指令cmd來操作指定的共享存儲區shmid。參數buf是指向結構體struct shmid_ds的指針類型,該結構體聲明于頭檔案<sys/shm.h>:
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;
pid_t shm_lpid;
shmatt_t shm_nattch;
...
};
結構體struct ipc_perm聲明于<sys/ipc.h>頭檔案中如下所示:
struct ipc_perm {
key_t __key;
uid_t uid;
gid_t gid;
uid_t cuid;
gid_t cgid;
unsigned short mode;
unsigned short __seq;
};
cmd取值:
IPC_STAT 從核心中把shmid指定的相關資訊拷貝到buf指定的結構體struct shmid_ds中,調用程序需對共享存儲區具有讀權限
IPC_SET 把buf指向的資料設定到核心關于shmid的相關資訊中,更新shm_ctime值
IPC_RMID 删除共享存儲區,隻有引用共享存儲區為0的時候才會被删除(shm_nattch為0)。使用者必須確定共享存儲區已被删除,否則頁面内容會一直存在于記憶體或者swap交換區中
IPC_INFO (Linux-specific) 傳回關于共享記憶體系統限制。儲存系統限制的結構體如下:
struct shminfo {
unsigned long shmmax;
unsigned long shmmin;
unsigned long shmmni;
unsigned long shmseg;
unsigned long shmall;
};
SHM_INFO (Linux-specific) 傳回系統關于提供的共享存儲區的資訊結構體shm_info 待考證
struct shm_info {
int used_ids;
unsigned long shm_tot;
unsigned long shm_rss;
unsigned long shm_swp;
unsigned long swap_attempts;
unsigned long swap_successe;
};
SHM_STAT (Linux-specific) 與IPC_STAT類似。但是參數shmid不是共享存儲區辨別,而是系統核心中關于所有共享存儲區數組辨別
SHM_LOCK (Linux-specific) 禁止共享存儲區内容交換到swap交換區。這樣目的在于提升性能。設定了該指令,同時會設定shm_perm.mode=SHM_LOCKED
SHM_UNLOCK (Linux-specific) 解鎖,允許其共享區内容被交換到swap交換區
在核心2.6.10之前隻有特權使用者才能使用SHM_LOCK和SHM_UNLOCK。2.6.10後隻要有效的組id比對了共享存儲區的所屬的組id或者建立者組id就可以使用SHM_LOCK和SHM_UNLOCK。允許程序被鎖上的
共享記憶體的大小是由RLIMIT_MEMLOCK限制的
傳回值
對于IPC_INFO或者SHM_INFO操作,成功傳回系統中關于所有共享存儲的數組辨別(該辨別和SHM_STAT一起使用來作業系統所有共享存儲區)。對于SHM_STAT操作,成功傳回共享存儲區的辨別符。其他操作成功傳回0.
失敗傳回-1,設定errno:
EACCES 使用者對共享存儲區進行IPC_STAT或者SHM_STAT操作時,沒有對該共享存儲區具有讀權限
EFAULT 使用者進行IPC_SET或者IPC_STAT操作時,參數buf指向的位址不可用
EIDRM 指定的共享存儲區已經被删除
EINVAL 共享存儲區辨別或者指令參數無效
ENOMEM (In kernels since 2.6.9) SHM_LOCK操作,但是鎖定在記憶體中的大小已經超過RLIMIT_MEMLOCK(程序可鎖定在記憶體位元組最大值)限定值
EOVERFLOW IPC_STAT操作,但是指定的gid和uid值太大無效
EPERM IPC_SET或者IPC_RMID 操作時,沒有相應的權限
注意:
IPC_INFO, SHM_STAT和SHM_INFO也被其他幾個ipc函數調用。在linux中運作一個程序引用一個已被标志要删除的共享存儲區,但是在其他系統中這種操作是被禁止的,避免使用的
函數shmat
頭檔案:#include <sys/types.h>
#include <sys/shm.h>
調用格式:void *shmat(int shmid, const void *shmaddr, int shmflg);
功能描述:把shmid指定的共享存儲區映射到調用程序指定位址空間shmaddr
shmaddr參數有兩種形式:1.為空,系統自動選擇一個可用的位址空間來映射共享存儲區.2不為空,且标志shmflg=SHM_RND(),那麼共享存儲區會映射到指定的shmaddr,shmaddr必須與頁位址對齊。因為考慮到平台之間的位址可能不一樣是以建議不指定位址。共享記憶體映射規則 如圖:

公式"shmaddr - (shmaddr % SHMLBA)"的含義是将位址shmaddr移動到低邊界位址的整數倍上。SHMLBA代表了低邊界位址的倍數
shmflg取值:
SHM_RDONLY 對指定的共享存儲區隻有讀權限
SHM_REMAP 替代已經在shmaddr指定的位址上已經存在的共享存儲區映射(如果不指定SHM_REMAP,映射共享存儲區到一段已經有映射的位址會傳回失敗EINVAL),shmaddr必須非空。程序結束 位址空間自動會解除與共享存儲區的映射。成功執行後會更新shmid_ds結構體:shm_atime更新為目前時間,shm_lpid更新為目前程序id,shm_nattch遞增1
傳回值:成功傳回程序位址空間的映射共享存儲區的位址,失敗傳回-1,并設定error
errno:
EACCES 調用程序沒有權限
EIDRM 指定的共享存儲區已被删除
EINVAL 無效的參數
ENOMEM 沒有更多的空餘空間
函數shmdt
頭檔案:#include <sys/types.h>
#include <sys/shm.h>
調用格式:int shmdt(const void *shmaddr);
功能說明:解除共享存儲的映射。操作成功更新結構體shmid_ds,shm_dtime更新為目前時間,shm_lpid更新為調用程序id,shm_nattch遞減1,如果此時shm_nattch為0,且該共享存儲區标志為删除,那麼共享存儲區被删除。
fork建立了子程序,子程序會從父程序那繼承共享存儲區
execve建立子程序,所有映射的共享存儲區會解除映射
傳回值:成功傳回0,失敗傳回-1,設定errno
errno:
EINVAL 指定的位址與頁位址不對齊,或者指定位址上并沒有映射共享存儲區
示例代碼:
#include <sys/ipc.h>
#include <sys/shm.h>
#define IPC_MEM_KEY 100001
#define IPC_MEM_BUF 4096
int ipc_sem_fun1(int a_ipc_id)
{
char * b_addr = NULL;
b_addr = (char *)shmat(a_ipc_id, NULL, 0);
if(NULL == b_addr)
{
printf("shmat fail line = %d, fun = %s, file = %s\n", __LINE__, __func__, __FILE__);
return -1;
}
sprintf(b_addr, "%s", "test");
printf("write into segment success!\n");
return 0;
}
int ipc_sem_fun2(int a_ipc_id)
{
char * b_addr = NULL;
b_addr = (char *)shmat(a_ipc_id, NULL, 0);
if(NULL == b_addr)
{
printf("shmat fail line = %d, fun = %s, file = %s\n", __LINE__, __func__, __FILE__);
return -1;
}
sleep(10);
printf("data = %s\n", b_addr);
return 0;
}
int main()
{
int b_mem_id;
int b_ret;
int b_pid[2];
struct shmid_ds b_mem_ds = {0};
struct ipc_perm * b_mem_perm = &(b_mem_ds.shm_perm);
struct shm_info b_mem_info = {0};
b_mem_id = shmget(IPC_PRIVATE, IPC_MEM_BUF, 666);
if(b_mem_id < 0)
{
printf("shmget fail:%s line = %d, fun = %s, file = %s\n", \
strerror(errno), __LINE__, __func__, __FILE__);
return -1;
}
b_pid[0] = fork();
if(b_pid[0] < 0)
{
printf("fork fail line = %d, fun = %s, file = %s\n", __LINE__, __func__, __FILE__);
return -1;
}
else if(0 == b_pid[0])
{
ipc_sem_fun1(b_mem_id);
exit(0);
}
b_pid[1] = fork();
if(b_pid[1] < 0)
{
printf("fork fail line = %d, fun = %s, file = %s\n", __LINE__, __func__, __FILE__);
return -1;
}
else if(0 == b_pid[1])
{
ipc_sem_fun2(b_mem_id);
exit(0);
}
while(1)
{
if(wait(NULL) < 0)
{
break;
}
}
b_ret = shmctl(b_mem_id, IPC_RMID, NULL);
if(b_ret < 0)
{
printf("shmctl fail:%s, line = %d, fun = %s, file = %s\n", \
strerror(errno), __LINE__, __func__, __FILE__);
return -1;
}
return 0;
}
運作結果如圖: