玩轉(zhuǎn)Linux共享內(nèi)存原理(含源代碼~)
在Linux系統(tǒng)中,每個(gè)進(jìn)程都有獨(dú)立的虛擬內(nèi)存空間,也就是說不同的進(jìn)程訪問同一段虛擬內(nèi)存地址所得到的數(shù)據(jù)是不一樣的,這是因?yàn)椴煌M(jìn)程相同的虛擬內(nèi)存地址會映射到不同的物理內(nèi)存地址上。
但有時(shí)候?yàn)榱俗尣煌M(jìn)程之間進(jìn)行通信,需要讓不同進(jìn)程共享相同的物理內(nèi)存,Linux通過 共享內(nèi)存 來實(shí)現(xiàn)這個(gè)功能。下面先來介紹一下Linux系統(tǒng)的共享內(nèi)存的使用。
共享內(nèi)存使用
1. 獲取共享內(nèi)存
要使用共享內(nèi)存,首先需要使用 shmget() 函數(shù)獲取共享內(nèi)存,shmget() 函數(shù)的原型如下:
參數(shù) key 一般由 ftok() 函數(shù)生成,用于標(biāo)識系統(tǒng)的唯一IPC資源。
參數(shù) size 指定創(chuàng)建的共享內(nèi)存大小。
參數(shù) shmflg 指定 shmget() 函數(shù)的動作,比如傳入 IPC_CREAT 表示要?jiǎng)?chuàng)建新的共享內(nèi)存。
函數(shù)調(diào)用成功時(shí)返回一個(gè)新建或已經(jīng)存在的的共享內(nèi)存標(biāo)識符,取決于shmflg的參數(shù)。失敗返回-1,并設(shè)置錯(cuò)誤碼。
2. 關(guān)聯(lián)共享內(nèi)存
shmget() 函數(shù)返回的是一個(gè)標(biāo)識符,而不是可用的內(nèi)存地址,所以還需要調(diào)用 shmat() 函數(shù)把共享內(nèi)存關(guān)聯(lián)到某個(gè)虛擬內(nèi)存地址上。shmat() 函數(shù)的原型如下:
參數(shù) shmid 是 shmget() 函數(shù)返回的標(biāo)識符。
參數(shù) shmaddr 是要關(guān)聯(lián)的虛擬內(nèi)存地址,如果傳入0,表示由系統(tǒng)自動選擇合適的虛擬內(nèi)存地址。
參數(shù) shmflg 若指定了 SHM_RDONLY 位,則以只讀方式連接此段,否則以讀寫方式連接此段。
函數(shù)調(diào)用成功返回一個(gè)可用的指針(虛擬內(nèi)存地址),出錯(cuò)返回-1。
3. 取消關(guān)聯(lián)共享內(nèi)存
當(dāng)一個(gè)進(jìn)程不需要共享內(nèi)存的時(shí)候,就需要取消共享內(nèi)存與虛擬內(nèi)存地址的關(guān)聯(lián)。取消關(guān)聯(lián)共享內(nèi)存通過 shmdt() 函數(shù)實(shí)現(xiàn),原型如下:
參數(shù) shmaddr 是要取消關(guān)聯(lián)的虛擬內(nèi)存地址,也就是 shmat() 函數(shù)返回的值。
函數(shù)調(diào)用成功返回0,出錯(cuò)返回-1。
【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【891587639】整理了一些個(gè)人覺得比較好的學(xué)習(xí)書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦!??!前100名進(jìn)群領(lǐng)取,額外贈送一份價(jià)值699的內(nèi)核資料包(含視頻教程、電子書、實(shí)戰(zhàn)項(xiàng)目及代碼)? ??


共享內(nèi)存使用例子
下面通過一個(gè)例子來介紹一下共享內(nèi)存的使用方法。在這個(gè)例子中,有兩個(gè)進(jìn)程,分別為 進(jìn)程A 和 進(jìn)程B,進(jìn)程A 創(chuàng)建一塊共享內(nèi)存,然后寫入數(shù)據(jù),進(jìn)程B 獲取這塊共享內(nèi)存并且讀取其內(nèi)容。
進(jìn)程A
進(jìn)程B
測試時(shí)先運(yùn)行進(jìn)程A,然后再運(yùn)行進(jìn)程B,可以看到進(jìn)程B會打印出 “Hello World”,說明共享內(nèi)存已經(jīng)創(chuàng)建成功并且讀取。
共享內(nèi)存實(shí)現(xiàn)原理
我們先通過一幅圖來了解一下共享內(nèi)存的大概原理,如下圖:

通過上圖可知,共享內(nèi)存是通過將不同進(jìn)程的虛擬內(nèi)存地址映射到相同的物理內(nèi)存地址來實(shí)現(xiàn)的,下面將會介紹Linux的實(shí)現(xiàn)方式。
在Linux內(nèi)核中,每個(gè)共享內(nèi)存都由一個(gè)名為 struct shmid_kernel 的結(jié)構(gòu)體來管理,而且Linux限制了系統(tǒng)最大能創(chuàng)建的共享內(nèi)存為128個(gè)。通過類型為 struct shmid_kernel 結(jié)構(gòu)的數(shù)組來管理,如下:
從注釋可以知道 struct shmid_kernel 結(jié)構(gòu)體各個(gè)字段的作用,比如 shm_npages 字段表示共享內(nèi)存使用了多少個(gè)內(nèi)存頁。而 shm_pages 字段指向了共享內(nèi)存映射的虛擬內(nèi)存頁表項(xiàng)數(shù)組等。
另外 struct shmid_ds 結(jié)構(gòu)體用于管理共享內(nèi)存的信息,而 shm_segs數(shù)組 用于管理系統(tǒng)中所有的共享內(nèi)存。
shmget() 函數(shù)實(shí)現(xiàn)
通過前面的例子可知,要使用共享內(nèi)存,首先需要調(diào)用 shmget() 函數(shù)來創(chuàng)建或者獲取一塊共享內(nèi)存。shmget() 函數(shù)的實(shí)現(xiàn)如下:
shmget() 函數(shù)的實(shí)現(xiàn)比較簡單,首先調(diào)用 findkey() 函數(shù)查找值為key的共享內(nèi)存是否已經(jīng)被創(chuàng)建,findkey() 函數(shù)返回共享內(nèi)存在 shm_segs數(shù)組 的索引。如果找到,那么直接返回共享內(nèi)存的標(biāo)識符即可。否則就調(diào)用 newseg() 函數(shù)創(chuàng)建新的共享內(nèi)存。newseg() 函數(shù)的實(shí)現(xiàn)也比較簡單,就是創(chuàng)建一個(gè)新的 struct shmid_kernel 結(jié)構(gòu)體,然后設(shè)置其各個(gè)字段的值,并且保存到 shm_segs數(shù)組 中。
shmat() 函數(shù)實(shí)現(xiàn)
shmat() 函數(shù)用于將共享內(nèi)存映射到本地虛擬內(nèi)存地址,由于 shmat() 函數(shù)的實(shí)現(xiàn)比較復(fù)雜,所以我們分段來分析這個(gè)函數(shù):
上面這段代碼主要通過 shmid 標(biāo)識符來找到共享內(nèi)存描述符,上面說過系統(tǒng)中所有的共享內(nèi)存到保存在 shm_segs 數(shù)組中。
上面的代碼主要找到一個(gè)可用的虛擬內(nèi)存地址,如果在調(diào)用 shmat() 函數(shù)時(shí)沒有指定了虛擬內(nèi)存地址,那么就通過 get_unmapped_area() 函數(shù)來獲取一個(gè)可用的虛擬內(nèi)存地址。
上面的代碼主要通過調(diào)用 kmem_cache_alloc() 函數(shù)創(chuàng)建一個(gè) vm_area_struct 結(jié)構(gòu),在內(nèi)存管理一章知道,vm_area_struct 結(jié)構(gòu)用于管理進(jìn)程的虛擬內(nèi)存空間。
上面的代碼主要是設(shè)置剛創(chuàng)建的 vm_area_struct 結(jié)構(gòu)的各個(gè)字段,比較重要的是設(shè)置其 vm_ops 字段為 shm_vm_ops,shm_vm_ops 定義如下:
shm_vm_ops 的 nopage 回調(diào)為 shm_nopage() 函數(shù),也就是說,當(dāng)發(fā)生頁缺失異常時(shí)將會調(diào)用此函數(shù)來恢復(fù)內(nèi)存的映射。
從上面的代碼可看出,shmat() 函數(shù)只是申請了進(jìn)程的虛擬內(nèi)存空間,而共享內(nèi)存的物理空間并沒有申請,那么在什么時(shí)候申請物理內(nèi)存呢?答案就是當(dāng)進(jìn)程發(fā)生缺頁異常的時(shí)候會調(diào)用 shm_nopage() 函數(shù)來恢復(fù)進(jìn)程的虛擬內(nèi)存地址到物理內(nèi)存地址的映射。
shm_nopage() 函數(shù)實(shí)現(xiàn)
shm_nopage() 函數(shù)是當(dāng)發(fā)生內(nèi)存缺頁異常時(shí)被調(diào)用的,代碼如下:
shm_nopage() 函數(shù)的主要功能是當(dāng)發(fā)生內(nèi)存缺頁時(shí),申請新的物理內(nèi)存頁,并映射到共享內(nèi)存中。由于使用共享內(nèi)存時(shí)會映射到相同的物理內(nèi)存頁上,從而不同進(jìn)程可以共用此塊內(nèi)存。
