共享內存是Linux下進程之間進行數據通信的最有效方式之一,而nginx就為我們提供了統一的操作接口來使用共享內存。
在nginx里,一塊完整的內存以結構體ngx_shm_zone_s封裝.其中包括是共享內存的名字(shm_zone[i].shm.name),大小(shm_zone[i].shm.size),標簽(shm_zone[i].tag), ngx_shm_zone_init_pt init; (初始化共享內存時的回調函數)
一些共享內存的結構體:
struct ngx_shm_zone_s void *data; ngx_shm_t shm; ngx_shm_zone_init_pt init; void *tag; };
typedef struct { u_char *addr; size_t size; ngx_str_t name; //名字 ngx_log_t *log; ngx_uint_t exists; /* unsigned exists:1; */ } ngx_shm_t;
這些字段大都容易理解,只有tag字段需要解釋一下,因為看上去它和name字段有點重復,而事實上,name字段主要用作共享內存的唯一標識,它能讓nginx知道我想使用哪個共享內存,但它沒法讓nginx區分我到底是想新創建一個共享內存,還是使用那個已存在的舊的共享內存。舉個例子,模塊A創建了共享內存sa,模塊A或另外一個模塊B再以同樣的名稱sa去獲取共享內存,那么此時nginx是返回模塊A已創建的那個共享內存sa給模塊A/模塊B,還是直接以共享內存名重復提示模塊A/模塊B出錯呢?不管nginx采用哪種做法都有另外一種情況出錯,所以新增一個tag字段做沖突標識,該字段一般也就指向當前模塊的ngx_module_t變量即可。這樣在上面的例子中,通過tag字段的幫助,如果模塊A/模塊B再以同樣的名稱sa去獲取模塊A已創建的共享內存sa,模塊A將獲得它之前創建的共享內存的引用(因為模塊A前后兩次請求的tag相同),而模塊B則將獲得共享內存已做它用的錯誤提示(因為模塊B請求的tag與之前模塊A請求時的tag不同)。
當我們要使用一個共享內存時,總會在配置文件里加上該共享內存的相關配置信息,而nginx在進行配置解析的過程中,根據這些配置信息就會創建相應的共享內存,不過此時的創建只是代表共享內存的結構體變量的ngx_shm_zone_t的創建,具體實現在share_memory_add()中.nginx中所有共享內存都是以list鏈表的形式組織在全局變量cf->cycle->shared_memory下,在創建新的共享內存之前會先對該鏈表進行遍歷查找以及沖突檢測,對於已經存在且不存在沖突的共享內存可直接返回引用。
以nginx中ngx_http_limit_req_module模塊為例://nginx限制鏈接
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
rate=1r/s 的意思是每個地址每秒只能請求一次,原理 burst=120 一共有120塊令牌,並且每秒鍾只新增1塊令牌,120塊令牌發完后 多出來的那些請求就會返回503
nginx在進行配置解析的時,遇到limit_req_zone配置項時,調用ngx_http_limit_req_zone(),而在該函數中繼續調用share_memory_add()創建ngx_shm_zone_t結構體變量並加入到全局鏈表中:ngx_http_limit_req_zone() -> ngx_shared_memory_add() -> ngx_list_push()。
共享內存的真正創建是在配置文件全部解析完后,所有代表共享內存的結構體ngx_shm_zone_t變量以鏈表的形式掛接在全局變量cf->cycle->shared_memory下,nginx此時遍歷該鏈表並逐個進行實際創建,即分配內存、管理機制(比如鎖、slab)初始化等:
ngx_cycle_t * ngx_init_cycle(ngx_cycle_t *old_cycle) { part = &cycle->shared_memory.part; shm_zone = part->elts; ............... for (i = 0; /* void */ ; i++) { if (ngx_shm_alloc(&shm_zone[i].shm) != NGX_OK) { goto failed; } if (ngx_init_zone_pool(cycle, &shm_zone[i]) != NGX_OK) { goto failed; } if (shm_zone[i].init(&shm_zone[i], NULL) != NGX_OK) { goto failed; } }
其中函數ngx_shm_alloc()時共享內存的實際分配,針對當前系統可提供的接口,可以時mmap,shmget等,而ngx_init_zone_pool()函數是共享內存管理機制的初始化,共享內存涉及2個主題:(1)多進程共同使用共享內存塊,必須考慮互斥問題 (2)nginx以性能著稱,那么對於共享內存自然也有其獨特的使用方式,雖然我們可以不用,但在這里也默認都會以這種slab的高效訪問機制進行初始化.
回調函數shm_zone[i].init()是各個共享內存所特定的,根據使用方的自身需求不同而不同,這也是我們在使用共享內存時需特別注意的函數.
函數ngx_http_limit_req_init_zone()的第二個參數data表示‘舊’數據,在進行重新加載配置時(即nginx收到SIGHUP信號)該值將不為空,如果舊數據可繼續使用,那么可直接返回NGX_OK;否則,需根據自身模塊邏輯對共享內存的使用做相關初始化,比如ngx_http_limit_req_module模塊,在第634、642行直接使用默認已初始化好的slab機制,進行內存的分配等。當函數ngx_http_limit_req_init_zone()正確執行結束,一個完整的共享內存就已創建並初始完成,接着要做的就是共享內存的使用,這即回到前面提到的兩個主題:互斥與slab。
函數 |
含義 |
ngx_shmtx_create() |
創建 |
ngx_shmtx_destory() |
銷毀 |
ngx_shmtx_trylock() |
嘗試加鎖(加鎖失敗則直接返回,不等待) |
ngx_shmtx_lock() |
加鎖(持續等待,直到加鎖成功) |
ngx_shmtx_unlock() |
解鎖 |
ngx_shmtx_force_unlock() |
強制解鎖(可對其它進程進行解鎖) |
ngx_shmtx_wakeup() |
喚醒等待加鎖進程(系統支持信號量的情況下才可用) |