Linux 程序設計1:深刻淺出 Linux 共享內存

筆者最近在閱讀Aerospike 論文時,發現了Aerospike是利用了Linux 共享內存機制來實現的存儲索引快速重建的。這種方式比傳統利用索引文件進行快速重啓的方式大大提升了效率。(減小了磁盤 i/o,可是缺點是耗費內存,而且服務器一旦重啓以後就只能冷重啓了~~)而目前筆者工做之中維護的 NoSQL 數據庫也是經過一樣的機制實現存儲索引的快速重建的,工欲善其事,必先利其器。因此筆者花時間調研了一下Linux共享內存的機制,但願對各位有所幫助~~node

1.共享內存簡介

說到共享內存,有過操做系統學習的童靴應該十分熟悉,每每聊到進程之間通訊的4種方式時就能脫口而出(面試最多見的問題之一啊,哈哈哈~~):面試

  • 共享內存
  • 消息隊列
  • 信號量
  • Socket

今天咱們的主角是共享內存。以下圖所示,所謂的共享內存,就是由多個進程的虛擬內存空間共同地映射到同一段物理內存空間,來實現內存的共享。
共享內存數據庫

共享內存一般是 ipc 之中效率最高的方式。Linux 之中實現共享內存的方式一般有以下幾類:服務器

  • mmap內存共享映射 (一般用於父子進程之間的內存共享,存在必定侷限性,後文不表
  • System V的共享內存
  • POSIX共享內存

咱們平時討論主要的共享內存就是後面二者,可是其實不管是 System V 仍是 POSIX 形式的共享內存,底層都是基於內存文件系統tmpfs實現的,兩者的主要區別是在接口設計上,POSIX旨在提供全部系統都一致的接口,遵循了 Linux 系統之中一切皆爲文件的理念。而System V只實現本身的一套內生的IPC邏輯,因此二者在使用上存在一些差別,因爲 Aerospike 之中沿用了 System V 的機制,因此筆者後續的介紹也以 System V 的共享內存來展開。app

共享內存雖然給了多進程通訊的效率帶來了質的飛躍,可是存在的問題也很明顯:每個參與使用共享內存的進程,均可以讀取寫入數據,這天然而然帶來了內存空間等競爭的問題。雖然這裏能夠經過相似於管道的機制來單向通訊來規避競爭的問題,可是額外引入的複雜度和內存佔用一樣也是問題)因此這裏咱們也能夠反思共享內存真的是用來進程間通訊的嗎?筆者這裏反而是這樣認爲的:經過通訊來共享內存,而不是經過共享內存來通訊函數

2.共享內存的設置與查看

使用共享內存,須要在系統層面進行一些設置。這章須要介紹一些共享內存相關的設置,在 Linux 系統之中和共享內存有關的文件有:學習

/proc/sys/kernel/shmmni:限制整個系統可建立共享內存段個數。

  /proc/sys/kernel/shmall: 限制系統用在共享內存上的內存的頁數。

  /proc/sys/kernel/shmmax:限制一個共享內存段的最大長度,字節爲單位。

在使用共享內存時,咱們能夠修改上述文件來知足咱們的設置需求。這裏要注意的是,上述的配置文件是臨時性的,重啓以後就失效了。若是須要永久性設置這些參數,能夠修改/etc/sysctl.conf來完成共享內存的設置。操作系統

共享內存本質上是對內存空間的使用,同時也是 ipc 的方式之一,因此咱們可使用對應的 Linux 命令來查看對應共享內存的使用:命令行

free 能夠顯示系統的內存佔用,共享內存的內存佔用會歸類在 shared,buffer/cache
free 命令查看共享內存
而更爲詳盡的共享內存的數據,能夠經過ipcs -m的命令來進行展現。
共享內存的使用情況
這裏簡單介紹一下,共享內存各個列所表明的含義:設計

  • key:共享內存的key,後文會經過程序來解釋 key 的含義。
  • shmil:共享內存的編號。
  • owner:建立的共享內存的用戶。
  • perms:共享內存的權限,基於用戶的。
  • bytes:共享內存的大小。
  • nattch:鏈接到共享內存的進程數。
  • status:共享內存的狀態,顯示「dest」表示共享內存段已經被刪除,可是還有別的引用,共享內存是經過引用計數的方式來決定生命週期,一旦程序應用內存地址的計數爲0,操做系統會回收對應的內存資源。

在這裏若是須要清理對應的共享內存,能夠藉助命令ipcrm -m [shmid]來回收對應的內存空間。

3.共享內存的使用

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命令刪除的話,那麼對應的共享內存將一直保留直到系統被關閉。

4.小結

到此爲止,筆者展開聊了聊 Linux 共享內存的做用,而且對如何操做共享內存進行了介紹,同時但願你們可以在實際開發工做以後可以很好的掌握共享內存這個「利器」,讓開發工做事倍功半~~

相關文章
相關標籤/搜索