日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > Nginx >内容正文

Nginx

Nginx之共享内存与slab机制

發布時間:2024/1/23 Nginx 50 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Nginx之共享内存与slab机制 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

1. 共享內存

在 Nginx 里,一塊完整的共享內存以結構體 ngx_shm_zone_t來封裝,如下:

typedef struct ngx_shm_zone_s ngx_shm_zone_t;typedef ngx_int_t (*ngx_shm_zone_init_pt) (ngx_shm_zone_t *zone, void *data);typedef struct {/* 執行共享內存的起始地址 */u_char *addr;/* 共享內存的長度 */size_t size;/* 這塊共享內存的名稱 */ngx_str_t name;/*記錄日志的ngx_log_t 對象 */ngx_log_t *log;/*表示共享內存是否已經分配過的標志位,為1時表示已經存在 */ngx_uint_t exists; /* unsigned exists:1 */ } ngx_shm_t;struct ngx_shm_zone_t {// 通常指向創建該共享內存模塊的上下文結構體// 如對于ngx_http_limit_req_module模塊,則指向// ngx_http_limit_req_ctx_t 結構體void *data;// 描述了一塊共享內存ngx_shm_t shm;//初始回調函數ngx_shm_zone_init_pt init;void *tag;ngx_uint_t noreuse; /* unsigned noreuse: 1; */ };
  • tag 與 shm.name:name 字段主要用作共享內存的唯一標識,它能讓 Nginx 知道調用者想使用哪個共享內存,但它沒法讓 Nginx 區分user到底想創建一個共享內存,還是使用那個已經存在的舊的共享內存。如,模塊 A 創建了共享內存 sa,模塊 A 或另外一個模塊 B 再以同樣的名稱 sa 去獲取共享內存,那么此時 Nginx 是返回模塊 A 已創建的那個共享內存 sa 給模塊 A /模塊 B,還是直接以共享內存名重復提示模塊 A /模塊 B 出錯呢?因此新增一個 tag 字段做沖突標識,該字段一般指向當前模塊的 ngx_module_t 變量即可。通過 tag 字段,如果模塊A/模塊B再以同樣的名稱 sa 去獲取模塊A已創建的共享內存sa,模塊A將獲得它之前創建的共享內存的引用(因為模塊A前后兩次請求的tag相同),而模塊B則將獲得共享內存已做他用的錯誤提示(因為模塊B請求的tag與之前模塊A請求的tag不同)。

使用共享內存,需要在配置文件里加上該共享內存的相關配置信息,而Nginx在進行配置解析的過程中,根據這些配置信息就會創建對應的共享內存,不過此時的創建僅僅只是代表共享內存的結構體ngx_shm_zone_t變量的創建。具體實現在函數ngx_shared_memory_add內。

下面以ngx_http_limit_req_module模塊為例,講述共享內存的創建,配置如下:

limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;

當檢測到該配置項limit_req_zone時,ngx_http_limit_req_module模塊就會調用ngx_http_limit_req_zone函數進行解析,如下:

typedef struct {ngx_http_limit_req_shctx_t *sh;ngx_slab_pool_t *shpool;/* integer value, 1 corresponds to 0.001 r/s */ngx_uint_t ratengx_http_complex_value_t key;ngx_http_limit_req_node_t *node; } ngx_http_limit_req_ctx_t;static char * ngx_http_limit_req_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) {u_char *p;size_t len;ssize_t size;ngx_str_t *value, name, s;ngx_int_t rate, scale;ngx_uint_t i;ngx_shm_zone_t *shm_zone;ngx_http_limit_req_ctx_t *ctx;ngx_http_compile_complex_value_t ccv;// 獲取第一個參數,這里即為“limit_req_zone”value = cf->args->elts;// 為ngx_http_limit_req_ctx_t 結構體分配內存ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_limit_req_ctx_t);if (ctx == NULL) {return NGX_CONF_ERROR;}ngx_memzero(&ccf, sizeof(ngx_http_compile_complex_value_t));ccv.cf = cf;// 獲取第二個參數,即為"$binary_remote_addr"ccv.value = &value[1];ccv.complex_value = &ctx->key;if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {return NGX_CONF_ERROR;}size = 0;rate = 1;scale = 1;name.len = 0;for (i=2; i<cf->args->nelts; i++) {//value[2].data = zone=one:10mif (ngx_strncmp(value[i].data, "zone=", 5) == 0) {// name.data = one:10mname.data = value[i].data + 5;// p -> ":10m"p = (u_char *) ngx_strchr(name.data, ':');if (p == NULL) {return NGX_CONF_ERROR;}// name.len = 3name.len = p - name.data;// s.data = "10m"s.data = p + 1;// s.len = 3s.len = value[i].data + value[i].len - s.data;// size = 10 * 1024 * 1024 = 10485760size = ngx_parse_size(&s);if (size == NGX_ERROR) {return NGX_CONF_ERROR;}if (size < (ssize_t) (8 * ngx_pagesize)) {ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,"zone \"%V\" is too small", &value[i]);return NGX_CONF_ERROR;}continue;}// value[3].data = "rate=1r/s"if (ngx_strncmp(value[i].data, "rate=", 5) == 0) {//len = 9len = value[i].len;// p = "r/s"p = value[i].data + len - 3;if (ngx_strncmp(p, "r/s", 3) == 0) {scale = 1;// len = 6len -= 3;} else if (ngx_strncmp(p, "r/m", 3) == 0) {scale = 60;len -= 3;}// rate = 1rate = ngx_atoi(value[i].data + 5, len - 5);if (rate <= 0) {return NGX_CONF_ERROR;}continue;}return NGX_CONF_ERROR;}if (name.len == 0) {return NGX_CONF_ERROR;}// ctx->rate = 1000ctx->rate = rate * 1000 / scale;// 創建一個共享內存ngx_shm_zone_t, 并將該共享內存以list鏈表的形式// 添加到cf->cycle->shared_memory下shm_zone = ngx_shared_memory_add(cf, &name, size,&ngx_http_limit_req_module);if (shm_zone == NULL) {return NGX_CONF_ERROR;}if (shm_zone->data) {ctx = shm_zone->data;return NGX_CONF_ERROR;}//設置該共享內存的初始化函數shm_zone->init = ngx_http_limit_req_init_zone;shm_zone->data = ctx;return NGX_CONF_OK; }

該函數中會調用ngx_shared_memory_add為該ngx_http_limit_req_module模塊創建一個共享內存,并將其以list鏈表的形式組織到全局變量cf->cycle->shared_memory下,具體實現如下:

ngx_shm_zone_t * ngx_shared_memory_add(ngx_conf_t *cf, ngx_str_t *name, size_t size, void *tag) {ngx_uint_t i;// 代表一塊共享內存ngx_shm_zone_t *shm_zone;ngx_list_part_t *part;part = &cf->cycle->shared_memory.part;shm_zone = part->elts;// 先遍歷shared_memory鏈表,檢測是否有與name相沖突的共享內存,// 若name和size一樣的,則直接返回該共享內存for (i=0; /* void */; i++) {if (i >= part->nelts) {if (part->next == NULL) {break;}part = part->next;shm_zone = part->elts;i = 0;}if (name->len != shm_zone[i].shm.name.len) {continue; }if (ngx_strncmp(name->data, shm_zone[i].shm.name.data, name->len) != 0) {continue;}if (tag != shm_zone[i].tag) {return NULL;}if (shm_zone[i].shm.size == 0) {shm_zone[i].shm_size = size;}if (size && size != shm_zone[i].shm_size) {return NULL;}return &shm_zone[i];}// 從shared_memory鏈表中取出一個空閑項shm_zone = ngx_list_push(&cf->cycle->shared_memory);if (shm_zone == NULL) {return NULL;}//初始化該共享內存shm_zone->data = NULL;shm_zone->shm.log = cf->cycle->log;// 由上面知size為10mshm_zone->shm.size = size;// name = "one:10m"shm_zone->shm.name = *name;shm_zone->shm.exists = 0;shm_zone->init = NULL;// ngx_shm_zone_t 的tag字段指向ngx_http_limit_req_module變量shm_zone->tag = tag;shm_zone->noreuse = 0;//返回該表示共享內存的結構體return shm_zone; }

上面的執行僅是為ngx_http_limit_req_module模塊創建ngx_shm_zone_t結構體變量并將其加入到全局鏈表cf->cycle->shared_memory中。共享內存的真正創建是在配置文件全部解析完后,所有代表共享內存的結構體ngx_shm_zone_t變量以鏈表的形式掛接在全局變量cf->cycle->shared_memory下,Nginx此時遍歷該鏈表并逐個進行實際創建,即分配內存、管理機制(如鎖、slab)初始化等。具體代碼如下所示:

ngx_cycle_t * ngx_init_cycle(ngx_cycle_t *old_cycle) {.../* create shared memory */part = &cycle->shared_memory.part;shm_zone = part->elts;for (i=0; /* void */; i++) {if (i >= part->nelts) {if (part->next == NULL) {break;}part = part->next;shm_zone = part->elts;i = 0;}if (shm_zone[i].shm.size == 0) {ngx_log_error(NGX_LOG_EMERG, log, 0,"zero size shared memory zone \"%V\"",&shm_zone[i].shm.name);goto failed;}shm_zone[i].shm.log = cycle->log;opart = &old_cycle->shared_memory.part;oshm_zone = opart->elts;//檢測是否沖突for (n=0; /* void */; n++) {if (n >= opart->nelts) {if (opart->next == NULL) {break;}opart = opart->next;oshm_zone = opart->elts;n = 0;}if (shm_zone[i].shm.name.len != oshm_zone[n].shm.name.len) {continue;}if (ngx_strncmp(shm_zone[i].shm.name.data,oshm_zone[n].shm.name.data,shm_zone[i].shm.name.len)!= 0) {continue;}if (shm_zone[i].tag == oshm_zone[n].tag&& shm_zone[i].shm.size == oshm_zone[n].shm.size&& !shm_zone[i].noreuse) {shm_zone[i].shm.addr = oshm_zone[n].shm.addr; #if (NGX_WIN32)shm_zone[i].shm.handle = oshm_zone[n].shm.handle; #endifif (shm_zone[i].init(&shm_zone[i], oshm_zone[n].data) != NGX_OK) {goto failed;}goto shm_zone_found;}ngx_shm_free(&oshm_zone[n].shm);break;}// 分配新的共享內存,由前面分析知大小為10mif (ngx_shm_alloc(&shm_zone[i].shm) != NGX_OK) {goto failed;}// 共享內存分配成功后,調用該函數進行共享內存管理機制的初始化// 具體分析看下面的slab機制一節if (ngx_init_zone_pool(cycle, &shm_zone[i]) != NGX_OK) {goto failed;}// 該shm_zone[i].init是各個共享內存所特定的,根據使用方的自身需求不同而不同if (shm_zone[i].init(&shm_zone[i], NULL) != NGX_OK) {goto failed;}shm_zone_found:continue;}... }

2. slab 機制

Nginx 的 slab 機制主要是和共享內存一起使用,Nginx在解析完配置文件,把即將使用的共享內存全部以list鏈表的形式組織在全局變量cf->cycle->shared_memory下之后,就會統一進行實際的內存分配,而Nginx的slab機制要做的就是對這些共享內存進行進一步的內部劃分與管理。

先看ngx_init_zone_pool函數

static ngx_int_t ngx_init_zone_pool(ngx_cycle_t *cycle, ngx_shm_zone_t *zn) {u_char *file;ngx_slab_pool_t *sp;// 指向該共享內存的起始地址處sp = (ngx_slab_pool_t *) zn->shm.addr;// 判斷該共享內存是否已經存在了if (zn->shm.exists) {if (sp == sp->addr) {return NGX_OK;} #if (NGX_WIN32)... #endifreturn NGX_ERROR;}// 指向共享內存的末尾sp->end = zn->shm.addr + zn->shm.size;sp->min_shift = 3;// 指向共享內存的起始sp->addr = zn->shm.addr;//對于互斥鎖,優先使用支持原子操作的互斥鎖 #if (NGX_HAVE_ATOMIC_OPS)//對于原子操作,該gile是沒有意義的file = NULL; #else// 這里代表使用的是文件鎖file = ngx_pnalloc(cycle->pool, cycle->lock_file.len + zn->shm.name.len);if (file == NULL) {return NGX_ERROR;}(void) ngx_sprintf(file, "%V%V%Z", &cycle->lock_file, &zn->shm.name);#endif//初始化一個信號量: 對于nginx,若支持原子操作,則優先使用// 原子變量實現的信號量if (ngx_shmtx_create(&sp->mutex, &sp->lock, file) != NGX_OK) {return NGX_ERROR;}ngx_slab_init(sp);return NGX_OK; }

ngx_init_zone_pool() 函數是在共享內存分配好后的進行的初始化調用,而該函數先調用ngx_shmtx_create函數為該共享內存初始化好一個信號量,接著調用slab的初始化函數ngx_slab_init().此時,該新分配的共享內存初始布局圖如下:

由該圖可知,共享內存的開始部分內存已經被用作結構體ngx_slab_pool_t的存儲空間,這相當于是slab的額外開銷(overhead),后面還會有其他額外開銷,任何一種管理機制都有自己的一些控制信息需要存儲,所以這些內存使用是無法避免的。共享內存剩下的部分才是被管理的主體,slab機制對這部分內存進行兩級管理,下面以slot塊來指代這些slab塊),也就是說slot塊是在page頁內存的再一次管理。

如下變量是由系統環境(假設當前系統為 64 位)確定的,是常量值:

  • ngx_pagesize:4096
    • 描述:系統內存頁大小,Linux 下一般情況是 4KB。
  • ngx_pagesize_shift:12
    • 描述:對應 ngx_pagesize(4096),即是 4096 = 1 << 12。
  • ngx_slab_max_size:2048
    • 描述:slots 分配和 pages 分配的分割點,大于等于該值則需從 pages 里分配。
  • ngx_slab_exact_size:64
    • 描述:正好能用一個 uintptr_t 類型的位圖變量表示的頁劃分;比如在 4KB 內存頁、64 位系統環境下,一個 uintptr_t 類型的位圖變量最多可以對應表示 64 個劃分塊的狀態,所以要恰好完整地表示一個 4KB 內存頁的每一個劃分塊狀態,必須把這個 4KB 內存頁劃分為 64 塊,即每一塊大小為:ngx_slab_exact_size = 4096 / 64 = 64.
  • ngx_slab_exact_shift:4
    • 描述:對應 ngx_slab_exact_size(128),即是 128 = 1 << 4.
  • pool->min_shift:3
    • 描述:固定值為 3
  • pool->min_size:8
    • 描述:固定值為 8,最小劃分塊大小,即是 1 << pool->min_shift.

2.1 page 頁的靜態管理

page頁的靜態管理是通過ngx_slab_init函數完成的。

下面先分析ngx_slab_init,該函數主要對整個共享內存的使用進行劃分:

void ngx_slab_init(ngx_slab_pool_t *pool) {u_char *p;size_t size;ngx_int_t m;ngx_uint_t i, n, pages;ngx_slab_page_t *slots, *page;/* STUB */if (ngx_slab_max_size == 0) {//ngx_slab_max_size = 2048: 該變量slots分配和pages // 分配的分割點,大于等于該值則需要從pages里分配。ngx_slab_max_size = ngx_pagesize / 2;// ngx_slab_exact_size = 64: 正好能用一個uintptr_t類型的位圖變量表示的// 的頁劃分;比如在4KB內存頁、64位系統環境下,一個uintptr_t類型的位圖// 變量最多可以對應表示64個劃分塊的狀態,所以要恰好完整地表示一個4KB// 內存頁的每一個劃分塊狀態,必須把這個4KB內存頁劃分為64塊,即每一塊大小// 為ngx_slab_exact_size = 4096 / (8*8) = 64ngx_slab_exact_size = ngx_pagesize / (8*sizeof(uintptr_t));// ngx_slab_exact_shift = 6: 對應ngx_slab_exact_size(64), 即是64 = 1 << 6for (n=ngx_slab_exact_size; n>>=1; ngx_slab_exact_shift++) {/* void */}}// pool->min_shift = 3, pool->min_size = 8pool->min_size = (size_t) 1 << pool->min_shift;/* ngx_slab_slots宏實際是將(pool) + sizeof(ngx_slab_pool_t) 后的起始地址賦給slots*/slots = ngx_slab_slots(pool);p = (u_char *)slots;// 計算此塊共享內存中除去pool(即ngx_slab_pool_t 結構體)所占的內存后// 余下的內存帶下size = pool->end - p;ngx_slab_junk(p, size);// n = 9n = ngx_pagesize_shift - pool->min_shift;// 初始化slots數組,大小為9 * sizeof(ngx_slab_page_t)for (i=0 i<n; i++) {/* only "next" is used in list head */slots[i].slab = 0;slots[i].next = &slots[i];slots[i].prev = 0;}// p指針向前移動,指向slots數組的末尾p += n * sizeof(ngx_slab_page_t);// pool->start 指向slots數組的末尾pool->stats = (ngx_slab_stat_t *)p;// 將pool->stats[9]數組置零ngx_memzero(pool->size, n * sizeof(ngx_slab_stat_t));// p 再次向前移動,指向pool->stats[9]數組的尾部p += n * sizeof(ngx_slab_stat_t);//計算余下共享內存的大小,此時,共享內存已經用于三塊:// ngx_slab_pool_t + n*(sizeof(ngx_slab_page_t) + sizeof(ngx_slab_stat_t)size -= n * (sizeof(ngx_slab_page_t) + sizeof(ngx_slab_stat_t));// 計算余下的共享內存中可以分為多少個page(每個page為4096 +// 額外的sizeof(ngx_slab_page_t) 大小)// 由前面知,此時pages = 2544pages = (ngx_uint_t) (size / (ngx_pagesize + sizeof(ngx_slab_page_t)));// 將pool->pages指向pool->stats[9] 數組的尾部pool->pages = (ngx_slab_page_t *)p;ngx_memzero(pool->pages, pages * sizeof(ngx_slab_page_t));page = pool->pages;/* only "next" is used in list head */pool->free.slab = 0;pool->free.next = page;pool->free.prev = 0;// pages的大小為余下共享內存中可以劃分的頁數// 每頁(大小為 4096 + sizeof(ngx_slab_page_t))page->slab = pages;// pool->free代表當前空閑的頁page->next = &pool->free;page->prev = (uintptr_t) &pool->free;//指向經過對齊后的新地址起始處pool->start = ngx_align_ptr(p+pages * sizeof(ngx_slab_page_t),ngx_pagesize);m = pages - (pool->end - pool->start) / ngx_pagesize;if (m > 0) {pages -= m;page->slab = pages;}pool->last = pool->pages + pages;pool->pfree = pages;pool->log_nomem = 1;pool->log_ctx = &pool->zero;pool->zero = '\0'; }

經過調用該ngx_slab_init函數后,此時共享內存的初始布局大致如下:

由該圖知,slab機制對page頁的靜態管理主要體現在slab和page這兩個數組上,以下有幾點需要注意:

  • 雖然是一個頁管理結構(即ngx_slab_page_t元素)與一個page內存頁(大小4096)相對應,但因為有對齊消耗以及slot塊管理結構體的占用(圖中的slots數組),所以實際上頁管理結構體數目比page頁內存數目要多。
  • 如何根據頁管理結構page獲得對應內存頁的起始地址p? 計算方法如下:

p = (page - pool->pages) << ngx_pagesize_shift;

p += (uintptr_t) pool->start;

  • 對齊是值實際page內存頁按ngx_pagesize大小對齊,對齊能提高對內存頁的訪問速度,但有一些內存浪費,并且末尾可能因為不夠一個page內存頁而被浪費掉,所以在ngx_slab_init函數的最末尾有一次最終可用內存頁的準確調整。
m = pages - (pool->end - pool->start) / ngx_pagesize; // 若m大于0,說明對齊等操作導致實際可用內存頁數減少 // 所以if語句里進行調整 if (m > 0) {pages -= m;page->slab = pages; }

2.2 page 頁的動態管理

page 頁的動態管理即為page頁的申請與釋放。

先看空閑頁的管理,Nginx對空閑page頁進行鏈式管理,鏈表的頭結點pool->free,初始狀態下的空閑鏈表情況如下:

由該圖可知,該空閑鏈表的節點是一個數組,如上圖中的ngx_slab_page_t[N]數組就是一個鏈表節點,這個數組通過第0號數組元素,即ngx_slab_page_t[0],接入到這個空閑free頁鏈表內,并且整個數組的元素個數也記錄在這個第0號數組的slab字段內。

下面開始從該共享內存中申請size大小的內存,具體函數為ngx_slab_alloc:

void * ngx_slab_alloc(ngx_slab_pool_t *pool, size_t size) {void *p;//獲取互斥鎖ngx_shmtx_lock(&pool->mutex);p = ngx_slab_alloc_locked(pool, size);//釋放互斥鎖ngx_shmtx_unlock(&pool->mutex);return p; }

該函數主要調用ngx_slab_alloc_locked做進一步的處理。

void * ngx_slab_alloc_locked(ngx_slab_pool_t *pool, size_t size) {size_t s;uintptr_t p, n, m, mask, *bitmap;ngx_uint_t i, slot, shift, map;ngx_slab_page_t *page, *prev, *slots;// 由前面知ngx_slab_max_size為2048,該變量是為slots分配和pages//分配的分割點,大于該值則需要從pages里分配if (size > ngx_slab_max_size) {page = ngx_slab_alloc_pages(pool, (size >> ngx_pagesize_shift)+ ((size % ngx_pagesize) ? 1 : 0));if (page) {p = ngx_slab_page_addr(pool, page);} else {p = 0;}goto done;}// pool->min_size 為固定值8if (size > pool->min_size) {shift = 1;for (s = size-1; s >>= 1; shift++) { /* void */ }// 計算該頁劃分的slot塊的大小(該slot大小對應的移位值)slot = shift - pool->min_shift;} else {shift = pool->min_shift;slot = 0;}//設置該slot的引用計數加1pool->stats[slot].reqs++;// slots指向共享內存中slots數組的首地址slots = ngx_slab_slots(pool);// page指向slots[slot] 元素的首地址page = slots[slot].next;// 最開始的時候,即還沒有開始分配頁時,page->next等于pageif (page->next != page) {if (shift < ngx_slab_exact_shift) {bitmap = (uintptr_t *) ngx_slab_page_addr(pool, page);map = (ngx_pagesize >> shift) / (sizeof(uintptr_t) * 8);for (n=0; n<map; n++) {if (bitmap[n] != NGX_SLAB_BUSY) {for (m=1, i=0; m; m<<=1, i++) {if (bitmap[n] & m) {continue;}bitmap[n] |= m;i = (n * sizeof(uintptr_t) * 8 + i) << shift;p = (uintptr_t) bitmap + i;pool->stats[slot].used++;if (bitmap[n] == NGX_SLAB_BUSY) {for (n=n+1; n<map; n++) {if (bitmap[n] != NGX_SLAB_BUSY) {goto done;}}prev = ngx_slab_page_prev(page);prev->next = page->next;page->next->prev = page->prev;page->next = NULL;page->prev = NGX_SLAB_SMALL;}goto done;}}}} else if (shift == ngx_slab_exact_shift) {for (m=1, i=0; m; m<<=1, i++) {if (page->slab & m) {continue; }page->slab |= m;if (page->slab == NGX_SLAB_BUSY) {prev = ngx_slab_page_prev(page);prev->next = page->next;page->next->prev = page->prev;page->next = NULL;page->prev = NGX_SLAB_EXACT;}p = ngx_slab_page_addr(pool, page) + (i << shift);pool->stats[slot].used++;goto done;}} else { /* shift > ngx_slab_exact_shift*/mask = ((uintptr_t) 1 << (ngx_pagesize >> shift)) - 1;mask <<= NGX_SLAB_MAP_SHIFT;for (m = (uintptr_t) 1 << NGX_SLAB_MAP_SHIFT, i = 0;m & mask;m <<= 1, i++) {if (page->slab & m) {continue;}page->slab |= m;if ((page->slab & NGX_SLAB_MAP_MASK) == mask) {prev = ngx_slab_page_prev(page);prev->next = page->next;page->next->prev = page->prev;page->next = NULL;page->prev = NGX_SLAB_BIG;}p = ngx_slab_page_addr(pool, page) + (i << shift);pool->stats[slot].used++;goto done;}}ngx_slab_error(pool, NGX_LOG_ALERT, "ngx_slab_alloc(): page is busy");ngx_debug_point();}// 從空閑頁鏈表中申請1頁page = ngx_slab_alloc_pages(pool, 1);// 申請頁成功if (page) {//申請的內存小于ngx_slot_exact_size的情況//假設按8字節劃分,則1個4KB的page頁將被劃分為512塊,// 表示各個slot塊狀態的位圖也就需要512個bit位,一個slab字段// 明顯不足,所以需要為位圖另找存儲空間,而slab字段僅用于//存儲slot塊大小(僅存其對應的移位數)if (shift < ngx_slab_exact_shift) {bitmap = (uintptr_t *) ngx_slab_page_addr(pool, page);// n = (4096 >> 3) / ((1 << 3) * 8) = 8n = (ngx_pagesize >> shift) / ((1 << shift) * 8);if (n == 0) {n = 1;}/* "n" elements for bitmap, plus one requested */bitmap[0] = ((uintptr_t) 2 << n) - 1;// map = (4096 >> 3) / (8 * 8) = 8map = (ngx_pagesize>> shift) / (sizeof(uintptr_t) * 8);for (i=1; i<map; i++) {bitmap[i] = 0;}// slab僅用于存儲slot塊大小(僅對其對應的移位數)page->slab = shift;page->next = &slots[slot];page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_SMALL;slots[slot].next = page;pool->stats[slot].total += (ngx_pagesize >> shift) - n;p = ngx_slab_page_addr(pool, page) + (n << shift);pool->stats[slot].used++;goto done;} else if (shift == ngx_slab_exact_shift) {page->slab = 1;page->next = &slots[slot];page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_EXACT;slots[slot].next = page;pool->stats[slot].total += sizeof(uintptr_t) * 8;p = ngx_slab_page_addr(pool, page);pool->stats[slot].used++;goto done;} else { /* shift > ngx_slab_exact_shift */// 當劃分的每個slot塊比ngx_slab_exact_size還大,意味著一個page // 頁劃分的slot塊數更少,此時同樣適用ngx_slab_page_t// 結構體的slab字段作為位圖。由于此ngx_slab_exact_size大的//劃分可以有很多種,因此需要把其具體的大小記錄下來,這個值同樣//記錄在slab字段里。如下,slab字段的高端bit用作位圖,低端// bit用于存儲slot塊的大小(僅存其對應的移位數)page->slab = ((uintptr_t) 1 << NGX_SLAB_MAP_SHIFT) | shift;page->next = &slots[slot];page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_BIG;slots[slot].next = page;pool->stats[slot].total += ngx_pagesize >> shift;//計算出該頁實際提供給用戶使用的內存的起始地址// 將該起始地址返回給調用者p = ngx_slab_page_addr(pool, page);pool->stats[slot].used++;goto done;}}p = 0;pool->stats[slot].fails++;done:ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, ngx_cycle->log, 0,"slab alloc: %p", (void *)p);return (void *)p; }

當首次開始調用該函數進行分配的時候,即之前從沒有分配過頁,則會先調用ngx_slot_alloc_pages函數分配一頁。

/*** @pages: 請求分配的頁數*/ static ngx_slab_page_t * ngx_slab_alloc_pages(ngx_slab_pool_t *pool, ngx_uint_t pages) {ngx_slab_page_t *page, *p;//遍歷空閑頁free鏈表for (page = pool->free.next; page != &pool->free; page = page->next) {// 檢測當前共享內存總的頁數是否大于請求分配的頁數if (page->slab >= pages) {if (page->slab > pages) {page[page->slab - 1].prev = (uintptr_t) &page[pages];//計算當前共享內存中余下空間的頁數page[pages].slab = page->slab - pages;//使page[pages].next,指向pool->free的首地址page[pages].next = page->next;//使page[pages].prev指向pool->free的首地址page[pages].prev = page->prev;// p指向pool->free首地址p = (ngx_slab_page_t *) page->prev;p->next = &page[pages];// pool->free->prev指向當前空閑鏈表中的第一個空閑頁page->next->prev = (uintptr_t) &page[pages];//page->slab = pages} else {p = (ngx_slab_page_t *)page->prev;p->next = page->next;page->next->prev = page->prev;}//將該頁從空閑頁free鏈表中移除page->slab = pages | NGX_SLAB_PAGE_START;page->next = NULL;page->prev = NGX_SLAB_PAGE;//共享內存中空閑頁數減pagespool->pfree -= pages;// 上面從for循環開始到這里即完成一個頁的申請,// 即將原先空閑頁鏈表中的第一個空閑頁從空間頁鏈表中移除,// 并將pool->free指向原先空閑頁鏈表中的第一個空閑頁的下一個空閑頁// pages為0,表示分配頁已經完成了if (--pages == 0) {return page;}//若當前申請的頁數pages大于1,則設置當前已經申請出去的頁的接下來// 幾個頁(直到滿足所要申請的頁數為止)都將其從空閑鏈表中移除for (p = page+i; pages; pages--) {p->slab = NGX_SLAB_PAGE_BUSY;p->next = NULL;p->prev = NGX_SLAB_PAGE;p++;}// 申請多頁的情況下,這里返回所申請的多頁的第一頁,// 因為頁都是連續的,因此可根據該頁遍歷所有申請的頁。return page;}}if (pool->log_nomem) {ngx_slab_error(pool, NGX_LOG_CRIT,"ngx_slab_alloc() failed: no memory");}return NULL; }

假設進程A首次從共享內存中申請 1 頁,則經過 ngx_slab_alloc_pages 函數后,此刻共享內存中頁的布局情況如下圖所示.

假設進程B接著調用 ngx_slab_alloc_pages 函數申請了 2 頁,此時共享內存中頁的布局情況如下圖:

假設接著進程A調用 ngx_slab_free_pages 函數將剛才申請的 1 頁釋放了,具體代碼如下:

static void ngx_slab_free_pages(ngx_slab_pool_t *pool, ngx_slab_page_t *page,ngx_uint_t pages) {ngx_slab_page_t *prev, *join;// 更新共享內存中空閑頁數,即將其加上當前釋放的頁數 pool->pfree += pages;page->slab = pages--;if (pages) {ngx_memzero(&page[1], pages * sizeof(ngx_slab_page_t));}if (page->next) {prev = ngx_slab_page_prev(page);prev->next = page->next;page->next->prev = page->prev;}join = page + page->slab;if (join < pool->last) {if (ngx_slab_page_type(join) == NGX_SLAB_PAGE) {if (join->next != NULL) {pages += join->slab;page->slab += join->slab;prev = ngx_slab_page_prev(join);prev->next = join->next;join->next->prev = join->prev;join->slab = NGX_SLAB_PAGE_FREE;join->next = NULL;join->prev = NGX_SLAB_PAGE;}}}if (page > pool->pages) {join = page - 1;if (ngx_slab_page_type(join) == NGX_SLAB_PAGE) {if (join->slab == NGX_SLAB_PAGE_FREE) {join = ngx_slab_page_prev(join);}if (join->next != NULL) {pages += join->slab;join->slab += page->slab;prev = ngx_slab_page_prev(join);prev->next = join->next;join->next->prev = join->prev;page->slab = NGX_SLAB_PAGE_FREE;page->next = NULL; page->prev = NGX_SLAB_PAGE;page = join;}}}if (pages) {page[pages].prev = (uintptr_t) page;}page->prev = (uintptr_t) & pool->free;page->next = pool->free.next;page->next->prev = (uintptr_t) page;pool->free.next = page; }

此時共享內存中頁的布局如下圖所示:

由圖知,釋放的頁被插入到 free 空閑頁鏈表的頭部.

假設進程 B 將其申請的 2 頁也釋放了,則共享內存中空閑頁的布局如下圖:

由圖知,Nginx 對空閑 page 頁的鏈式管理不會進行節點合并。

下面分析 slab 機制的第二級管理機制,即 slot 塊。

slot 塊是對每一頁 page 內存的內存管理,它將 page 頁劃分為很多小塊,各個 page 頁的 slot 塊大小可以不相等,但同一個 page 頁的 slot 塊大小一定是相等。page 頁的狀態通過其所在的鏈表即可辨明,而 page 頁內各個 slot 塊的狀態卻需要一個額外的標記,在 Nginx 的具體實現里采用的是位圖方式,即一個 bit 位標記一個對應的 slot 塊的狀態, 1 為使用,0 為空閑。

根據 slot 塊的大小不同,一個 page 頁可劃分的 slot 塊數也不相同,從而需要的位圖大小也不一樣。每一個 page 頁對應一個名為 ngx_slab_page_t 的管理結構,該結構體有一個 uintptr_t 類型的 slab 字段。在 64 位平臺上,uintptr_t 類型占 8 個字節,即 slab 字段有 64 個bit位。如果 page 頁劃分的 slot 數小于等于 64,那么 Nginx 直接利用該字段充當位圖,這在 Nginx 內叫 exact 劃分,每個 slot 塊的大小保存在全局變量 ngx_slab_exact_size 以及 ngx_slab_exact_shift 內。比如,1 個 4KB 的 page 頁,如果每個 slot 塊大小為 64 字節,那么恰好可以劃分成 64 塊。

申請的內存大于 ngx_slab_exact_size 的情況

如果劃分的每個 slot 塊比 ngx_slab_exact_size 還大,那意味著一個 page 劃分的 slot 塊數更少,此時也是使用 ngx_slab_page_t 結構體的 slab 字段作為位圖。由于比 ngx_slab_exact_size 大的劃分可以有很多種,所以需要把其具體的大小也記錄下來,這個值同樣記錄在 slab 字段里。由于劃分總是按 2 次冪增長,所以比 ngx_slab_exact_size 還大的劃分至少要減少一半的 slot 塊數,因此利用 slab 字段的一半 bit 位即可完整表示所有 slot 塊的狀態。具體點說就是:slab 字段的高端 bit 用作位圖,低端 bit 用于存儲 slot 塊的大小(僅存其對應的移位數)。具體代碼如下:

page->slab = ((uintptr_t) 1 << NGX_SLAB_MAP_SHIFT) | shift;

如果要申請的內存大于等于 ngx_slab_max_size,Nginx 直接返回一個 page 整頁,此時不在 slot 塊管理里。

申請的內存小于 ngx_slab_exact_size 的情況

假若申請的內存小于 ngx_slab_exact_size 的情況,此時 slot 塊數目已經超過了 slab 字段可表示的容量。比如假設按 8 字節劃分,那個 1 個 4KB 的 page 頁將被劃分為 512 塊,表示各個 slot 塊的狀態的位圖也就需要 512 個 bit 位,一個 slab 字段明顯是不夠的,所以需要為位圖另找存儲空間,而 slab 字段僅用于存儲 slot 塊大小(僅存其對應的移位數)。

另找的位圖存儲空間就在 page 頁內,具體點說時其劃分的前面幾個 slot 塊內。512 個 bit 位的位圖,即 64 個字節,而一個 slot 塊有 8 個字節,所以就需要占用 page 頁的前 8 個 slot 塊用作位圖。一個按 8 字節劃分 slot 塊的 page 頁初始情況如下圖所示:

由于前幾個 slot 塊一開始就被用作位圖空間,所以必須把它們對應的 bit 位設置為 1,表示其狀態為使用。具體代碼片段如下:

if (shift < ngx_slab_exact_shift) {bitmap = (uintptr_t *) ngx_slab_page_addr(pool, page);n = (ngx_pagesize >> shift) / ((1 << shift) * 8);if (n == 0) {n = 1;}/* "n" elements for bitmap, plus one requested */bitmap[0] = ((uintptr_t) 2 << n) - 1;map = (ngx_pagesize >> shift) / (sizeof(uintptr_t) * 8);for (i = 1; i < map; i++) {bitmap[i] = 0;}page->slab = shift;page->next = &slots[slot];page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_SMALL;slots[slot].next = page;pool->stats[slot].total += (ngx_pagesize >> shift) - n;p = ngx_slab_page_addr(pool, page) + (n << shift);pool->stats[slot].used++;goto done;}

總結

以上是生活随笔為你收集整理的Nginx之共享内存与slab机制的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。