筆者最近在閱讀Aerospike 論文時,發現了Aerospike是利用了Linux 共享內存機制來實現的存儲索引快速重建的。這種方式比傳統利用索引文件進行快速重啓的方式大大提升了效率。(減小了磁盤 i/o,可是缺點是耗費內存,而且服務器一旦重啓以後就只能冷重啓了~~)而目前筆者工做之中維護的 NoSQL 數據庫也是經過一樣的機制實現存儲索引的快速重建的,工欲善其事,必先利其器。因此筆者花時間調研了一下Linux共享內存的機制,但願對各位有所幫助~~node
說到共享內存,有過操做系統學習的童靴應該十分熟悉,每每聊到進程之間通訊的4種方式時就能脫口而出(面試最多見的問題之一啊,哈哈哈~~):面試
今天咱們的主角是共享內存。以下圖所示,所謂的共享內存,就是由多個進程的虛擬內存空間共同地映射到同一段物理內存空間,來實現內存的共享。
數據庫
共享內存一般是 ipc 之中效率最高的方式。Linux 之中實現共享內存的方式一般有以下幾類:服務器
咱們平時討論主要的共享內存就是後面二者,可是其實不管是 System V 仍是 POSIX 形式的共享內存,底層都是基於內存文件系統tmpfs實現的,兩者的主要區別是在接口設計上,POSIX旨在提供全部系統都一致的接口,遵循了 Linux 系統之中一切皆爲文件的理念。而System V只實現本身的一套內生的IPC邏輯,因此二者在使用上存在一些差別,因爲 Aerospike 之中沿用了 System V 的機制,因此筆者後續的介紹也以 System V 的共享內存來展開。app
共享內存雖然給了多進程通訊的效率帶來了質的飛躍,可是存在的問題也很明顯:每個參與使用共享內存的進程,均可以讀取寫入數據,這天然而然帶來了內存空間等競爭的問題。 (雖然這裏能夠經過相似於管道的機制來單向通訊來規避競爭的問題,可是額外引入的複雜度和內存佔用一樣也是問題)因此這裏咱們也能夠反思共享內存真的是用來進程間通訊的嗎?筆者這裏反而是這樣認爲的:經過通訊來共享內存,而不是經過共享內存來通訊。函數
使用共享內存,須要在系統層面進行一些設置。這章須要介紹一些共享內存相關的設置,在 Linux 系統之中和共享內存有關的文件有:學習
/proc/sys/kernel/shmmni:限制整個系統可建立共享內存段個數。 /proc/sys/kernel/shmall: 限制系統用在共享內存上的內存的頁數。 /proc/sys/kernel/shmmax:限制一個共享內存段的最大長度,字節爲單位。
在使用共享內存時,咱們能夠修改上述文件來知足咱們的設置需求。這裏要注意的是,上述的配置文件是臨時性的,重啓以後就失效了。若是須要永久性設置這些參數,能夠修改/etc/sysctl.conf來完成共享內存的設置。操作系統
共享內存本質上是對內存空間的使用,同時也是 ipc 的方式之一,因此咱們可使用對應的 Linux 命令來查看對應共享內存的使用:命令行
free 能夠顯示系統的內存佔用,共享內存的內存佔用會歸類在 shared,buffer/cache列
而更爲詳盡的共享內存的數據,能夠經過ipcs -m的命令來進行展現。
這裏簡單介紹一下,共享內存各個列所表明的含義:設計
在這裏若是須要清理對應的共享內存,能夠藉助命令ipcrm -m [shmid]來回收對應的內存空間。
int shmget(key_t key, size_t size, int shmflg); int shmctl(int shmid, int cmd, struct shmid_ds *buf); void *shmat(int shmid, const void *shmaddr, int shmflg); int shmdt(const void *shmaddr); extern key_t ftok (const char *__pathname, int __proj_id)
萬事俱備,如今咱們要來介紹一下如何在對應的代碼之中使用共享內存,主要涉及上述五個函數,咱們經過一個簡單的 demo 來介紹這些函數:
int shmget(key_t key, size_t size, int shmflg)
是申請共享內存的函數,這裏須要理解的是key這個參數,它自己是一個 int 類型,這個 key_t
參數是經過key_t ftok (const char *__pathname, int __proj_id)
產生的,這裏的pathname
指的是一個固定的路徑,proj_id
則表示對應項目的 id。因此在一個操做系統內,如何讓兩個不相關(沒有父子關係)的進程能夠共享一個內存段呢?Bingo!就是經過這個 key_t
類型讓全部的進程都惟一映射到對應內存空間,這裏就是經過對應的文件路徑和項目 id來產生對應的key。
因此說,在一個使用到共享內存的程序之中,須要程序設定一個文件路徑和一個項目的proj_id
,來獲取系統之中肯定一段共享內存的key。這裏須要注意的是ftok
須要指定一個存在而且進程能夠訪問的pathname路徑。由於 ftok
使用的是指定文件的inode編號。因此,用了不一樣的文件名一樣可能獲得相同的key,由於能夠經過硬連接的方式讓不一樣的文件名指向相同 inode 編號文件。
key_t shm_key; proj_id = 111; if ((shm_key = ftok("/home/happen", proj_id)) == -1) { exit(1); } shm_id = shmget(shm_key, sizeof(int), IPC_CREAT|IPC_EXCL|0600); if (shm_id < 0) { exit(1); }
ok,獲取了共享內存以後,咱們須要將這部分共享內存的地址映射到當前進程的內存空間之上,須要藉助這個函數void *shmat(int shmid, const void *shmaddr, int shmflg)
返回對應進程內存空間的指針,來對這部份內存進行操做。
shm_p = (int *)shmat(shm_id, NULL, 0); if ((void *)shm_p == (void *)-1) { exit(1); }
這裏能夠用過shmflg
來設定對應內存空間的讀寫權限,這裏咱們取的是0,表明對應的空間有讀寫權限。SHM_RDONLY
能夠設置爲只讀權限。以後咱們就能夠對對應的內存空間進行操做了:
*shm_p = 100; if (shmdt(shm_p) < 0) { perror("shmdt()"); exit(1); } if (shmctl(shm_id, IPC_RMID, NULL) < 0) { perror("shmctl()"); exit(1); } return 0;
在使用完共享內存以後,須要使用int shmdt(const void *shmaddr)
解除內存空間的映射,不然虛擬內存地址的泄漏,致使沒有可用地址可用。shmdt僅僅只是解除共享內存空間和進程地址的映射,而想要刪除一個共享內存須要使用int shmctl(int shmid, int cmd, struct shmid_ds *buf)
函數進行處理同時也能夠在命令行中使用第二小節的ipcrm命令來刪除指定的共享內存。在這裏必須強調的是,若是沒有顯式用shmctl或ipcrm命令刪除的話,那麼對應的共享內存將一直保留直到系統被關閉。
到此爲止,筆者展開聊了聊 Linux 共享內存的做用,而且對如何操做共享內存進行了介紹,同時但願你們可以在實際開發工做以後可以很好的掌握共享內存這個「利器」,讓開發工做事倍功半~~