nginx之內(nèi)存池的實(shí)現(xiàn)
目錄
- 一、簡(jiǎn)介
- 二、數(shù)據(jù)結(jié)構(gòu)
- 2.1 內(nèi)存池主要結(jié)構(gòu)
- 2.2 大內(nèi)存鏈
- 2.3 清理任務(wù)鏈
- 三、內(nèi)存結(jié)構(gòu)圖
- 3.1 邏輯
- 3.2 實(shí)際
- 四、實(shí)現(xiàn)
- 4.1 創(chuàng)建內(nèi)存池
- 4.2 從內(nèi)存池中分配空間
- 4.3 注冊(cè)清理任務(wù)
- 4.4 重置內(nèi)存池
- 4.5 銷毀內(nèi)存池
- 4.6 大內(nèi)存釋放
- 4.7 分配并清空數(shù)據(jù)
- 4.8 回調(diào)文件清理
一、簡(jiǎn)介
最新穩(wěn)定版本nginx1.20.2。
為了能高效、快速的分配內(nèi)存,以及減少內(nèi)存碎片等,nginx實(shí)現(xiàn)了自己的內(nèi)存池基礎(chǔ)組件。
主要實(shí)現(xiàn)文件ngx_palloc.h, ngx_palloc.c
二、數(shù)據(jù)結(jié)構(gòu)
2.1 內(nèi)存池主要結(jié)構(gòu)
typedef struct { u_char *last; u_char *end; ngx_pool_t *next; ngx_uint_t failed;} ngx_pool_data_t;struct ngx_pool_s { ngx_pool_data_t d; size_tmax; ngx_pool_t *current; ngx_chain_t *chain; ngx_pool_large_t *large; ngx_pool_cleanup_t *cleanup; ngx_log_t *log;};
內(nèi)存池中第一個(gè)成員是一個(gè)結(jié)構(gòu)體:
使用ngx_pool_data_t結(jié)構(gòu)體來(lái)表示當(dāng)前內(nèi)存池信息。
last :下次開始分配的地址
end: 內(nèi)存池的結(jié)束地址
next: 內(nèi)存池鏈表,將多個(gè)內(nèi)存池連接起來(lái)
max
整個(gè)內(nèi)存池的最大大小
current
指向從當(dāng)前內(nèi)存池開始查找可用內(nèi)存
chain
buffer使用的,這里不涉及
large
當(dāng)需要的內(nèi)存大于內(nèi)存池最大大小時(shí),需要通過(guò)malloc直接分配,然后形成鏈表進(jìn)行組織
cleanup
清理工作的回調(diào)鏈表
log
日志句柄
2.2 大內(nèi)存鏈
當(dāng)需要分配的內(nèi)存比內(nèi)存池的最大大小都大時(shí),內(nèi)存池?zé)o法滿足分配,所以直接從系統(tǒng)中分配,然后構(gòu)成一個(gè)鏈表進(jìn)行維護(hù)。
typedef struct ngx_pool_large_s ngx_pool_large_t;struct ngx_pool_large_s { ngx_pool_large_t *next; void *alloc;};
2.3 清理任務(wù)鏈
有一個(gè)回調(diào)任務(wù)的鏈表,當(dāng)內(nèi)存池銷毀時(shí),將依次遍歷此鏈表,逐一回調(diào)handler進(jìn)行清理工作。
typedef void (*ngx_pool_cleanup_pt)(void *data);typedef struct ngx_pool_cleanup_s ngx_pool_cleanup_t;struct ngx_pool_cleanup_s { ngx_pool_cleanup_pt handler; void *data; ngx_pool_cleanup_t *next;};
三、內(nèi)存結(jié)構(gòu)圖
3.1 邏輯
3.2 實(shí)際
可以看出,很多節(jié)點(diǎn)都是從內(nèi)存池中分配的,所以可以把精力都放在實(shí)際的數(shù)據(jù)上而不必在意其他細(xì)節(jié)上。
四、實(shí)現(xiàn)
4.1 創(chuàng)建內(nèi)存池
/* * NGX_MAX_ALLOC_FROM_POOL should be (ngx_pagesize - 1), i.e. 4095 on x86. * On Windows NT it decreases a number of locked pages in a kernel. */#define NGX_MAX_ALLOC_FROM_POOL (ngx_pagesize - 1)#define NGX_DEFAULT_POOL_SIZE (16 * 1024)
ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log){ ngx_pool_t *p; p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log); if (p == NULL) {return NULL; } p->d.last = (u_char *) p + sizeof(ngx_pool_t); p->d.end = (u_char *) p + size; p->d.next = NULL; p->d.failed = 0; size = size - sizeof(ngx_pool_t); p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL; p->current = p; p->chain = NULL; p->large = NULL; p->cleanup = NULL; p->log = log; return p;}
從代碼中可以看到,內(nèi)存池最大不超過(guò)pagesize的大小
4.2 從內(nèi)存池中分配空間
分配函數(shù)分了內(nèi)存對(duì)齊和內(nèi)存不對(duì)齊,但這只控制了內(nèi)存池中分配空間,不控制大內(nèi)存分配。
(1)分配小空間
- 內(nèi)存對(duì)齊
ngx_palloc
- 內(nèi)存不對(duì)齊
ngx_pnalloc
void *ngx_palloc(ngx_pool_t *pool, size_t size){#if !(NGX_DEBUG_PALLOC) if (size <= pool->max) {return ngx_palloc_small(pool, size, 1); }#endif return ngx_palloc_large(pool, size);}
當(dāng)需要分配的空間小于max時(shí),將使用小內(nèi)存分配方式(即從內(nèi)存池中分配空間),而ngx_pnalloc和ngx_palloc相比只是調(diào)用ngx_palloc_small時(shí)的最后一個(gè)參數(shù)為0。
從pool->current指向的內(nèi)存池開始遍歷,尋找滿足分配大小的空間,找到則返回首地址
static ngx_inline void *ngx_palloc_small(ngx_pool_t *pool, size_t size, ngx_uint_t align){ u_char *m; ngx_pool_t *p; p = pool->current; do {m = p->d.last;if (align) { m = ngx_align_ptr(m, NGX_ALIGNMENT);}if ((size_t) (p->d.end - m) >= size) { p->d.last = m + size; return m;}p = p->d.next; } while (p); return ngx_palloc_block(pool, size);}
當(dāng)現(xiàn)有內(nèi)存池中都無(wú)法滿足分配條件時(shí),創(chuàng)建新的內(nèi)存池
static void *ngx_palloc_block(ngx_pool_t *pool, size_t size){ u_char *m; size_t psize; ngx_pool_t *p, *new; psize = (size_t) (pool->d.end - (u_char *) pool); m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log); if (m == NULL) {return NULL; } new = (ngx_pool_t *) m; new->d.end = m + psize; new->d.next = NULL; new->d.failed = 0; m += sizeof(ngx_pool_data_t); m = ngx_align_ptr(m, NGX_ALIGNMENT); new->d.last = m + size; for (p = pool->current; p->d.next; p = p->d.next) {if (p->d.failed++ > 4) { pool->current = p->d.next;} } p->d.next = new; return m;}
其中,創(chuàng)建好新的內(nèi)存池后,又做了一次遍歷,將failed計(jì)數(shù)加一,當(dāng)大于4時(shí),將跳過(guò)此內(nèi)存池,下次就不從它開始查找。
即認(rèn)為超過(guò)4次你都不能滿足分配,以后都不能滿足分配,不再用你了,減少遍歷個(gè)數(shù),加快成功分配效率
(2)分配大空間
static void *ngx_palloc_large(ngx_pool_t *pool, size_t size){ void *p; ngx_uint_t n; ngx_pool_large_t *large; p = ngx_alloc(size, pool->log); if (p == NULL) {return NULL; } n = 0; for (large = pool->large; large; large = large->next) {if (large->alloc == NULL) { large->alloc = p; return p;}if (n++ > 3) { break;} } large = ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1); if (large == NULL) {ngx_free(p);return NULL; } large->alloc = p; large->next = pool->large; pool->large = large; return p;}
可以看出,為了避免分配空間,遍歷large鏈查找可重用的節(jié)點(diǎn),但是如果鏈表過(guò)大又可能太慢,所以只查找前三個(gè),如果三個(gè)都沒(méi)有找到,則直接分配(而且節(jié)點(diǎn)也是從內(nèi)存池中分配的,所以后續(xù)清理時(shí),不需要管節(jié)點(diǎn),只需要釋放申請(qǐng)的大內(nèi)存本身)
內(nèi)存對(duì)齊
void *ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment){ void *p; ngx_pool_large_t *large; p = ngx_memalign(alignment, size, pool->log); if (p == NULL) {return NULL; } large = ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1); if (large == NULL) {ngx_free(p);return NULL; } large->alloc = p; large->next = pool->large; pool->large = large; return p;}
4.3 注冊(cè)清理任務(wù)
ngx_pool_cleanup_t *ngx_pool_cleanup_add(ngx_pool_t *p, size_t size){ ngx_pool_cleanup_t *c; c = ngx_palloc(p, sizeof(ngx_pool_cleanup_t)); if (c == NULL) {return NULL; } if (size) {c->data = ngx_palloc(p, size);if (c->data == NULL) { return NULL;} } else {c->data = NULL; } c->handler = NULL; c->next = p->cleanup; p->cleanup = c; ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, p->log, 0, "add cleanup: %p", c); return c;}
可以看出,這里只是分配了一個(gè)節(jié)點(diǎn),并沒(méi)有設(shè)置handler以及data數(shù)據(jù),所以還得看具體的調(diào)用方進(jìn)行設(shè)置,因?yàn)檫@里返回了分配的節(jié)點(diǎn)。
比如在函數(shù)ngx_create_temp_file
中
ngx_int_tngx_create_temp_file(ngx_file_t *file, ngx_path_t *path, ngx_pool_t *pool, ngx_uint_t persistent, ngx_uint_t clean, ngx_uint_t access){ ... cln = ngx_pool_cleanup_add(pool, sizeof(ngx_pool_cleanup_file_t)); if (cln == NULL) {return NGX_ERROR; } ...file->fd = ngx_open_tempfile(file->name.data, persistent, access); ...if (file->fd != NGX_INVALID_FILE) { cln->handler = clean ? ngx_pool_delete_file : ngx_pool_cleanup_file; clnf = cln->data; clnf->fd = file->fd; clnf->name = file->name.data; clnf->log = pool->log; return NGX_OK;} ...}
生成臨時(shí)文件,將fd以及文件名注冊(cè)到清理任務(wù)中,后續(xù)文件不使用了則不需要特殊處理,內(nèi)存內(nèi)存池釋放時(shí)將統(tǒng)一清理。
4.4 重置內(nèi)存池
- 釋放大內(nèi)存
- 重置內(nèi)存中l(wèi)ast
- 重置failed計(jì)數(shù)
voidngx_reset_pool(ngx_pool_t *pool){ ngx_pool_t*p; ngx_pool_large_t *l; for (l = pool->large; l; l = l->next) {if (l->alloc) { ngx_free(l->alloc);} } for (p = pool; p; p = p->d.next) {p->d.last = (u_char *) p + sizeof(ngx_pool_t);p->d.failed = 0; } pool->current = pool; pool->chain = NULL; pool->large = NULL;}
這里有個(gè)現(xiàn)象:
在內(nèi)存池中空間不足時(shí),將調(diào)用ngx_palloc_block
創(chuàng)建一個(gè)新的內(nèi)存池,而last指向的是m += sizeof(ngx_pool_data_t);
, 因此當(dāng)前新分配的內(nèi)存池將比第一個(gè)內(nèi)存池可用大小多了(max,current,chain,large,cleanup,log)這幾個(gè)字段大小(可能沒(méi)有那么多,因?yàn)橐獙?duì)齊,可能對(duì)齊后就完全一樣了),而現(xiàn)在重置時(shí),p->d.last = (u_char *) p + sizeof(ngx_pool_t);
每個(gè)內(nèi)存池可用大小又變成一樣的。
4.5 銷毀內(nèi)存池
- 回調(diào)清理任務(wù)
- 釋放大內(nèi)存
- 釋放內(nèi)存池本身
voidngx_destroy_pool(ngx_pool_t *pool){ ngx_pool_t *p, *n; ngx_pool_large_t *l; ngx_pool_cleanup_t *c; for (c = pool->cleanup; c; c = c->next) {if (c->handler) { ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0, "run cleanup: %p", c); c->handler(c->data);} } for (l = pool->large; l; l = l->next) {if (l->alloc) { ngx_free(l->alloc);} } for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {ngx_free(p);if (n == NULL) { break;} }}
4.6 大內(nèi)存釋放
通過(guò)遍歷找到要釋放的節(jié)點(diǎn),將內(nèi)存釋放,并且將alloc設(shè)置成NULL,則有了節(jié)點(diǎn)重用的情況。
ngx_int_tngx_pfree(ngx_pool_t *pool, void *p){ ngx_pool_large_t *l; for (l = pool->large; l; l = l->next) {if (p == l->alloc) { ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0, "free: %p", l->alloc); ngx_free(l->alloc); l->alloc = NULL; return NGX_OK;} } return NGX_DECLINED;}
4.7 分配并清空數(shù)據(jù)
void *ngx_pcalloc(ngx_pool_t *pool, size_t size){ void *p; p = ngx_palloc(pool, size); if (p) {ngx_memzero(p, size); } return p;}
正常分配的空間中都是垃圾數(shù)據(jù),所以當(dāng)前函數(shù)在分配空間后,將分配的空間清零。
4.8 回調(diào)文件清理
(1) 手動(dòng)關(guān)閉指定fd
遍歷清理任務(wù),找到ngx_pool_cleanup_file的handler,如果是要關(guān)閉的fd,則回調(diào)
voidngx_pool_run_cleanup_file(ngx_pool_t *p, ngx_fd_t fd){ ngx_pool_cleanup_t *c; ngx_pool_cleanup_file_t *cf; for (c = p->cleanup; c; c = c->next) {if (c->handler == ngx_pool_cleanup_file) { cf = c->data; if (cf->fd == fd) {c->handler(cf);c->handler = NULL;return; }} }}
(2) 關(guān)閉fd
voidngx_pool_cleanup_file(void *data){ ngx_pool_cleanup_file_t *c = data; ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, c->log, 0, "file cleanup: fd:%d", c->fd); if (ngx_close_file(c->fd) == NGX_FILE_ERROR) {ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno, ngx_close_file_n " \"%s\" failed", c->name); }}
(3) 刪除文件并關(guān)閉fd
voidngx_pool_delete_file(void *data){ ngx_pool_cleanup_file_t *c = data; ngx_err_t err; ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, c->log, 0, "file cleanup: fd:%d %s", c->fd, c->name); if (ngx_delete_file(c->name) == NGX_FILE_ERROR) {err = ngx_errno;if (err != NGX_ENOENT) { ngx_log_error(NGX_LOG_CRIT, c->log, err, ngx_delete_file_n " \"%s\" failed", c->name);} } if (ngx_close_file(c->fd) == NGX_FILE_ERROR) {ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno, ngx_close_file_n " \"%s\" failed", c->name); }}
到此這篇關(guān)于nginx之內(nèi)存池的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)nginx 內(nèi)存池內(nèi)容請(qǐng)搜索以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持!
相關(guān)文章:
1. Linux如何配置本地yum源(光盤鏡像掛載)2. tomcat正常啟動(dòng)但網(wǎng)頁(yè)卻無(wú)法訪問(wèn)的幾種解決方法3. linux下如何將無(wú)線網(wǎng)卡工作模式切換為監(jiān)聽模式4. 詳解Tomcat中Filter的執(zhí)行流程5. 內(nèi)網(wǎng)環(huán)境nginx配置https訪問(wèn)的過(guò)程詳解6. Linux命令之mv和cp的用法示例7. linux中數(shù)據(jù)庫(kù)的定時(shí)備份8. Windows10安裝Apache2.4的方法步驟9. 阿里云云服務(wù)器Linux系統(tǒng)FTP服務(wù)器搭建設(shè)置教程10. windows服務(wù)器之WSB(windows server backup)的備份和還原圖文方法
