呼叫shmget函式後,系統會在核心空間中分配一塊共享物理記憶體,程序想要訪問該共享記憶體的話,必須呼叫shmat函式把程序空間的虛擬位址對映到該共享記憶體的實體地址。
當然這個位址對映過程是由系統來完成的,也就是說,系統核心會在當前的程序空間選擇合適的,且空閒未使用的虛擬位址對映到共享記憶體的物理頁面。
#include
#inlude
#include
void
*shmat
(int shmid ,
const
void
*shmaddr,
int shm***)
;
引數shmid:ipc物件的識別符號(由shmget函式返回建立的ipc物件識別符號)。
shm***引數:是乙個許可權標誌位,如果shm*** = 0表示預設許可權可讀寫,如果指定shm*** = hm_rdonly表示唯讀模式,其他為讀寫模式。
一般引數shmaddr一般有以下幾種方式:
shmaddr不為null,但未指定了shm*** = shm_rnd,那麼將shmaddr位址掛接到共享記憶體的實體地址,如果掛接成功返回shmaddr位址。如果指定了shm*** = shm_rnd,那麼將shmaddr位址掛接到共享記憶體的(shmaddr - (shmaddr % shmlba))所表示的位址,shm_rnd表示取整的意思。如果掛接成功返回shmaddr位址。
為什麼推薦方式一?
當shmat函式對映成功後,就會返回乙個虛擬位址,然後程式就可以通過這個虛擬位址訪問共享記憶體了。有時候我們並不知道程序的虛擬空間中哪些虛擬位址沒有使用,因此shmaddr引數指定的虛擬位址有可能是已經使用的虛擬位址,為了防止這種情況,一般都會把shmaddr引數指定為null,讓系統自動對映乙個位址。
還有乙個原因在於,如果shmaddr指定為非null的話,這降低了程式的可移植性,可能會出現在乙個unix系統上有效的位址,在另乙個unix系統上卻無效。
關於shmat函式多次呼叫的問題。
shmget函式第一次建立的共享記憶體還不能被程序訪問,為了能夠訪問共享記憶體必須呼叫shmat函式把程序的虛擬位址掛接(attach)到共享記憶體。
void
*ptr =
shmat
(shmid ,
null,0
);
當乙個程序呼叫shmat函式對同一塊共享記憶體(物理記憶體頁)進行多次掛接,且shmaddr引數為null的話,那麼shmat函式每次呼叫返回的都是不同的虛擬位址,且對該共享記憶體的掛接次數(attach)加1,也就是程序的多個虛擬位址指向同一塊物理記憶體,那麼在刪除共享記憶體時也應該呼叫shmdt函式取消掛接,直到attach為0 。
因此考慮這麼一種情況:乙個程序的多個虛擬位址指向同一塊物理記憶體,但是程式在後期沒有多次呼叫shmdt函式取消對該共享記憶體的掛接(虛擬位址沒有全部釋放),由於程序並沒有取消關聯共享記憶體,那麼會一直占用程序空間資源,這可能會導致程序空間虛擬位址使用完下次呼叫shmat函式或其他操作出錯。
為了避免乙個程序對同一共享記憶體多次掛接這種情況發生,我們需要對shmat函式再進行封裝,並判斷需要申請共享記憶體的指標ptr 是否不為null,如果ptr = null則允許掛接共享記憶體,如果ptr != null則不允許掛接。
**如下:
if
(shmaddr !=
null
)else
shmat函式的作用我們知道了,那麼shmdt函式的作用正好相反,shmdt是用來取消
程序空間的位址空間和共享記憶體的實體地址的掛接,不讓程序訪問共享記憶體了。
#include
#inlude
#include
intshmdt
(const
void
*shmaddr)
;
返回值說明:成功返回0,失敗返回-1並設定errno
shmdt函式的引數就是shmat掛接成功後返回的虛擬位址。
shmdt函式注意事項:
1 . shmdt函式只是將當前程序和共享記憶體分離,並不會從系統中刪除其ipc物件id以及其相關資料結構,只是將該記憶體對於當前程序來說不再可用,並不代表其他程序不可以對共享記憶體的訪問。
2. 一般來說程序終止的話,那麼該程序對共享的記憶體的掛接也會分離,但是此時共享記憶體並不會立即從系統中消失,因為此時可能還有別的程序在連線著這塊共享記憶體。
3. 一旦使用shmdt函式將程序的虛擬位址和共享記憶體實體地址分離後,共享記憶體會對該程序標記為不可訪問,該程序不能再使用shmat函式建立對映,雖然可以建立連線,但是該程序在訪問共享記憶體時會發生segment fault錯誤。
shmctl函式是用來操作共享記憶體的函式,比如:獲取共享記憶體資訊,設定共享記憶體資訊,刪除共享記憶體等操作,都可以用shmctl來完成。
#include
#include
#include
intshmctl
(int shmid,
int cmd,
struct shmid_ds *buf)
;
引數shmid: shmid是共享記憶體ipc核心物件的識別符號shmid
引數cmd: 表示要執行的命令操作,總共有5種,這裡只說三種ipc_stat,ipc_set,ipc_rmid三種命令
ipc_stat: ipc_stat: 獲取共享記憶體屬性,並把獲取到的共享記憶體的shmid_ds結構體資訊拷貝到buf中。
ipc_set: 設定共享記憶體屬性,把buf指向的shmid_ds結構體中的uid,gid,mode等資訊複製到共享記憶體的shmid_ds結構體中。
ipc_rmid: 刪除這片共享記憶體
引數buf: 共享記憶體管理結構體(struct shmid_ds)
關於ipc_rmid選項:
在呼叫shmctl函式指定ipc_rmid刪除共享記憶體時,如果該共享記憶體的掛接次數(attach)為0,那麼將會執行刪除操作,如果當前還有程序在使用該共享記憶體(attach不為0),那麼將會在所有程序呼叫shmdt與共享記憶體分離後再執行刪除操作。
shmctl函式的引數buf的資料型別是乙個struct shmid_ds結構體,該結構體具體定義資訊參考:57-system v 共享記憶體-shmctl
父程序往共享記憶體中寫資料,子程序從共享記憶體中讀取資料。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
intmain
(void
)//對映虛擬位址
shmaddr =
shmat
(shmid ,
null,0
);//出錯
if(shmaddr ==
(void*)
-1)printf
("shmaddr = %p\n",(
char
*)shmaddr)
;//清空記憶體
memset
(shmaddr ,0,
4096);
char str[20]
="hello world"
;//往共享記憶體寫入hello world
strncpy
(shmaddr , str ,
strlen
(str));
pid_t pid;
pid =
fork()
;//子程序讀取資料
if(pid ==0)
}//父程序等待
sleep(5
);//刪除共享記憶體
ret =
shmctl
(shmid , ipc_rmid ,
null);
if(ret <0)
//**子程序
if(pid >0)
return0;
}
程式執行結果:
在呼叫fork後,子程序會繼承父程序的shmid,掛接的虛擬位址等資訊,因此子程序可以直接通過shmid訪問共享記憶體並列印hello world
,然後子程序取消對共享記憶體的掛接後,nattch減為1,當父程序呼叫shmtcl刪除共享記憶體ipc物件退出時,nattch就會減為0,並把ipc物件從系統核心中刪除掉。
本篇著重介紹了在使用共享記憶體操作函式應該注意的事項以及可能會出現的錯誤和處理方法,掌握這些注意事項和錯誤處理,在使用共享記憶體函式時能避免一些坑了。
System V 共享記憶體
一.共享記憶體資料結構 對於每個共享記憶體區,核心維護如下資訊結構,定義在,在ubuntu中路徑 usr include linux shm.h 二.system v共享記憶體函式 include include int shmget key t key,size t size,int shm vo...
共享記憶體System V
system v 共享記憶體區 對於每個共享記憶體區,核心維護如下的資訊結構 obsolete,used only for backwards compatibility and libc5 compiles struct shmid ds struct shmid ds 共享記憶體操作api 0....
System V共享記憶體
標頭檔案 include include intshmget key t key,size t size,int shm 功能 用來建立共享記憶體 引數 a key 這個共享記憶體段名字 b size 共享記憶體大小 c shm 由幾個許可權標誌構成,他們的用法和建立檔案時使用的model模式標誌是...