nginx中共享記憶體的使用

atskyline發表於2019-06-15

在nginx的程式模型下,類似流量統計、流量控制、資料共享、等需要多個工作程式共同配合完成任務,共享記憶體是一個重要的程式通訊的方案。本文介紹在nginx的程式碼中與共享記憶體相關的功能,包括ngx_shmem與ngx_slab的使用與注意事項,但不包括ngx_slab中實現的記憶體管理演算法。

ngx_shmem的使用

ngx_shmem.c/h檔案只是對mmap()/munmap()系統呼叫或者shmget()/shmdt()的一個很簡單的封裝。實現了ngx風格的基礎庫,可以申請和釋放一段連續的共享記憶體空間。一般用於固定長度的共享資料使用,使用過程中資料長度固定不會伸縮。

typedef struct {
    u_char      *addr;
    size_t       size;
    ...
} ngx_shm_t;
ngx_int_t ngx_shm_alloc(ngx_shm_t *shm);
void ngx_shm_free(ngx_shm_t *shm);

在ngxin中共享記憶體的使用流程,一般是由master程式建立,worker程式通過繼承的方式獲得記憶體指標。

關於ngx_shmem的使用,可以參考ngx_event_module_init()中部分片段,這部分程式碼在共享記憶體中建立了若干個變數,用於記錄各個狀態(accepted/reading/writing...)的請求數量,並在ngx_event_module中的幾個關鍵事件入口對這幾個變數進行加減統計操作。實現統計所有worker程式當前的請求狀態。

shm.size = size;
ngx_str_set(&shm.name, "nginx_shared_zone");
shm.log = cycle->log;

if (ngx_shm_alloc(&shm) != NGX_OK) {
    return NGX_ERROR;
}

shared = shm.addr;
...
ngx_stat_accepted = (ngx_atomic_t *) (shared + 3 * cl);
ngx_stat_handled = (ngx_atomic_t *) (shared + 4 * cl);
ngx_stat_requests = (ngx_atomic_t *) (shared + 5 * cl);
ngx_stat_active = (ngx_atomic_t *) (shared + 6 * cl);
ngx_stat_reading = (ngx_atomic_t *) (shared + 7 * cl);
ngx_stat_writing = (ngx_atomic_t *) (shared + 8 * cl);
ngx_stat_waiting = (ngx_atomic_t *) (shared + 9 * cl);

關於這個功能的更多細節,可以檢視程式碼中的NGX_STAT_STUB巨集定義相關程式碼與ngx_http_stub_status_module。

ngx_slab的使用

ngx_shmem是一層極簡的封裝,實現了共享記憶體的基本功能。但我們程式中大部分的場景共享資料並不會一個固定大小的結構,而更多是像ngx_array、ngx_list、ngx_queue、ngx_rbtree這類大小可以變化的資料結構。

我們期望能有像ngx_pool_t一樣可以動態申請釋放空間一個記憶體池。ngx_slab正是一個這樣的結構體,原理上與系統的malloc()有相識之處都是通過一系列演算法實現對一段段記憶體片段的申請與釋放。只不過ngx_slab操作的物件是基於ngx_shmem的共享記憶體。

先看一下ngx_slab的介面

typedef struct {
    ngx_shmtx_t       mutex;
    ...
    void             *data; /* 一般存放從pool中申請獲得的根資料地址(pool中第一個申請的資料介面) */
    void             *addr; /* 使用ngx_shmem申請獲得的共享記憶體基地址 */
} ngx_slab_pool_t;

void ngx_slab_init(ngx_slab_pool_t *pool);
void *ngx_slab_alloc(ngx_slab_pool_t *pool, size_t size);
void *ngx_slab_alloc_locked(ngx_slab_pool_t *pool, size_t size);
void *ngx_slab_calloc(ngx_slab_pool_t *pool, size_t size);
void *ngx_slab_calloc_locked(ngx_slab_pool_t *pool, size_t size);
void ngx_slab_free(ngx_slab_pool_t *pool, void *p);
void ngx_slab_free_locked(ngx_slab_pool_t *pool, void *p);

可以看到介面並不複雜,alloc與calloc的區別在於是否對申請獲得的記憶體段清零,_locked結尾的介面表示操作的pool已經是獲取到鎖的。在ngx_slab_pool_t的結構體有一個ngx_shmtx_t的互斥鎖用於同步多程式同時訪問pool的併發場景。注意ngx_slab_alloc()會先獲取鎖、然後申請空間、最後釋放鎖。而ngx_slab_alloc_locked()則直接申請空間,認為程式已經在其他邏輯中獲得鎖了。

在nginx的開發中使用ngx_shmem一般需要遵循以下初始化流程:

  • 模組在配置解析過程中呼叫ngx_shared_memory_add()介面,註冊一段共享記憶體。提供共享記憶體大小與記憶體初始化的回撥函式。
  • 框架在ngx_init_cycle()中使用ngx_shmem申請記憶體,並初始化ngx_slab,然後回撥模組註冊的初始化函式
  • 模組使用ngx_slab的申請/是否介面

在這個流程中,涉及到ngx_shared_memory_add()介面與對應的ngx_shm_zone_t結構體。

struct ngx_shm_zone_s {
    void                     *data;
    ngx_shm_t                 shm;
    ngx_shm_zone_init_pt      init;
    void                     *tag;
    void                     *sync;
    ngx_uint_t                noreuse;  /* unsigned  noreuse:1; */
};
ngx_shm_zone_t *ngx_shared_memory_add(ngx_conf_t *cf, ngx_str_t *name,
    size_t size, void *tag);

其中值得一提的是noreuse屬性,這個屬性控制了在nginx的reload過程中是否會重新申請共享記憶體。

由於關於ngx_init_cycle()函式較長,這個流程可以通過查詢/* create shared memory */這個註釋或者cycle->shared_memory這個物件檢視相關程式碼。

關於ngx_slab更多細節的使用,建議可以參考ngx_http_limit_conn_module,這是通過共享記憶體實現連線數限制的模組,模組複雜度底,是一個很好的參考範例。

參考資料

同時安利一波《深入理解Nginx》作者 陶輝 在極客時間出版的《Nginx核心知識100講》,近期618似乎有打折活動,通過我分享的連結進行購買,我也將獲得部分返現,感謝支援。

nginx中共享記憶體的使用

相關文章