日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

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

Nginx

Nginx内存管理详解

發布時間:2024/2/28 Nginx 71 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Nginx内存管理详解 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

目錄:

1.Nginx內存管理介紹

2.Nginx內存池的邏輯結構

3.Nginx內存池的基本數據結構

4.內存池基本操作介紹

5.內存池管理源碼詳解

6.內存池使用源碼詳解

7.小結

?

?

?

1.Nginx內存管理介紹

  在C/C++語言程序設計中,通常由程序員自己管理內存的分配和釋放,其方式通常是malloc(free)和new(delete)等API。這樣做的缺點在于:由于所申請內存塊的大小不定,當頻繁使用時會造成大量的內存碎片從降低性能。通常我們所使用的解決辦法就是內存池

  什么是內存池呢?內存池就是在真正使用內存之前,先申請分配一定數量的、大小相等(一般情況下)的內存塊留作備用。當有新的內存需求時,就從內存池中分出一部分內存塊,若內存塊不夠再繼續申請新的內存。而不是每次需要了就調用分配內存的系統API(如malloc)進行申請,每次不需要了就調用系統釋放內存的API(如free)進行釋放。這樣做的一個顯著優點是,使得內存分配效率得到提升。因此使用內存池的方式對程序所使用的內存進行統一的分配和回收,是當前最流行且高效的內存管理方法,能夠在很大程度上降低內存管理的難度,減少程序的缺陷,提高整個程序的穩定性。

  通過減少頻繁的內存申請和釋放可以提升效率很容易理解,那么內存池究竟是怎么提高程序的穩定性的呢?我們知道在C/C++語言中,并沒有提供直接可用的垃圾回收機制,因此在程序編寫中, 一個特別容易發生的錯誤就是內存泄露,對于運行時間短,內存需求小的程序來說,泄露一點內存除了影響程序運行效率之外可能并不會造成多大的問題。但是類似于Ngnix這樣需要長期運行的web服務器程序來說,內存泄露是一件非常嚴重的災難,這會使得程序由于內存耗盡而崩潰,重啟之前不再能夠提供相應的web服務。還有一種情況就是當內存分配與釋放的邏輯在程序中相隔較遠時,很容易發生內存被釋放兩次乃至多次的情況。使用內存池使得我們在開發程序時,只用關心內存的分配,而釋放就交給內存池來完成。

  那么內存池在Nginx中究竟是怎么使用的呢?通常我們對于每個請求或者連接都會建立相應的內存池,建立好內存池之后,我們可以直接從內存池中申請所需要的內存,而不用去管內存的釋放,唯一需要注意的就是當內存池使用完成之后需要記得銷毀內存池。此時,內存池會調用相應的數據清理函數(如果有的話),之后會釋放在內存池中管理的內存。

  大家可能會問,既然申請的內存在內存池銷毀的時候才會被釋放,這不會存在內存的浪費么?畢竟使用完了不再需要的內存為什么不立即釋放而非要等到銷毀內存池時才釋放呢?確實存在這個問題,不過大家不用擔心。在Nginx中,對于大塊內存可以使用ngx_pfree()函數提前釋放。并且由于Nginx是一個純粹的web服務器,而web服務器通常使用的協議是Http協議,并且在傳輸層使用的是Tcp協議,我們知道每一個tcp連接都是由生命周期的,因此基于tcp的http請求都會有一個很短暫的生命周期。對于這種擁有很短暫生命周期的請求,我們所建立的內存池的生命周期也相應會很短暫,因此其所占用的內存資源很快就可以得到釋放,不會出現太多的資源浪費的問題。畢竟工程就是一種折中嘛,我們需要在內存資源浪費和減低程序內存管理難度、提升效率之間選擇一個合適的權衡。

  說了這么多,現在就讓我們開始研究和學習Nginx內存管理的機制和源碼吧。注:本文的講解都是基于nginx-1.10.3版本。

?

?

?

2.Nginx內存池的邏輯結構

  前面提到Nginx內存管理機制其實就是內存池,其底層實現就是一個鏈表結構。我們需要對內存池進行管理和分配,依賴的就是ngx_pool_t結構體,可以認為該結構就是內存池的分配管理模塊。那么內存池的邏輯結構究竟是什么樣呢?其實就是一個ngx_pool_t結構體,在這個結構體中包含了三個部分小塊內存形成的單鏈表,大塊內存形成的單鏈表和數據清理函數形成的單鏈表。先給出一張整個內存池內部實現的結構圖,方便大家理解。具體如圖2.1所示:

?

?

圖2.1 Nginx內存池示意圖

?

?  圖2.1完整的展示了ngx_pool_t內存池中小塊內存、大塊內存和資源清理函數鏈表間的關系。圖中,內存池預先分配的剩余空閑內存不足以滿足用戶申請的內存需求,導致又分配了兩個小內存池。其中原內存池的failed成員已經大于4,所以current指向了第2塊小塊內存池,這樣當用戶再次從小塊內存池中請求分配內存空間時,將會直接忽略第1塊小內存池,從第2塊小塊內存池開始遍歷。從這里可以看到,我們使用的內存池確實存在當failed成員大于4之后不能利用其空閑內存的資源浪費現象(由于current指針后移)。值得注意的是:我們的第2、3塊小塊內存池中只包含了ngx_pool_t結構體和數據區,并不包含max、current、...、log。這是由于后續第1塊小內存池已經包含了這些信息,后續的小塊內存池不必在浪費空間存儲這些信息。我們在第6小節:內存池的使用中將會有所介紹。圖中共分配了3個大塊內存,其中第二塊的alloc為NULL(提前調用了ngx_pfree())。圖中還掛在了兩個資源清理方法。提醒一下的是:如果在這里沒有弄清楚,沒有關系,看完了后面的部分再回過頭來理解這個示意圖就能夠很好的理解了。這里只是先給出一個概括性的Nginx內存池邏輯結構的介紹,先給大家留下一個大概的印象。

?

?

?

3.Nginx內存池的基本數據結構

本部分主要介紹內存池中重要的數據結構,主要是ngx_pool_t,然后介紹ngx_pool_t中三個重要數據結構:ngx_pool_data_t,ngx_pool_large_t和ngx_pool_cleanup_t。

?

(1)ngx_pool_t

  我們可以在Nginx的源碼的src/core/目錄下的nax_palloc.h頭文件中看到:

?

1 struct ngx_pool_s { 2 ngx_pool_data_t d; 3 size_t max; 4 ngx_pool_t *current; 5 ngx_chain_t *chain; 6 ngx_pool_large_t *large; 7 ngx_pool_cleanup_t *cleanup; 8 ngx_log_t *log; 9 };

?

?  并且在src/core/ngx_core.h中:

1typedef?struct?ngx_pool_s??????? ngx_pool_t;

?

下面將具體講解ngx_pool_t結構體中每個成員的含義和用途:

?d:ngx_pool_data_t結構體,描述內存池中的小塊內存。當小塊內存不足時,會再分配一個ngx_pool_t(里面含有一個新分配且未使用的小塊內存空間和用于管理這塊內存空間的ngx_pool_data_t結構體)。這些小塊內存塊之間通過d中的next成員鏈接形成的單鏈表。掛在d成員上。

?

max:評估申請內存屬于小塊還是大塊的標準,在x86上默認是4095字節。

?

current:多個小塊內存構成單鏈表時,指向分配內存時遍歷的第一個小塊內存。

?

chain:與內存池關系不大,略過。

?

large:ngx_pool_large_t結構體當用戶申請的內存空間大于max時,就會分配大塊內存。而多個大塊內存之間是通過ngx_pool_large_t中的next成員鏈接形成的單鏈表。掛在large成員上。

?

cleanup:ngx_pool_cleanup_t結構體,所有待清理的資源(例如需要關閉或者刪除的文件)以ngx_pool_cleanup_t對象中的next成員鏈接形成單鏈表。掛在cleanup成員上。

?

log:內存池中執行時輸出日志的地方。

?

?

(a).ngx_pool_data_t

  我們可以在Nginx的源碼的src/core/目錄下的nax_palloc.h頭文件中看到:

?

123456typedef?struct?{????u_char?????????????? *last;????u_char?????????????? *end;????ngx_pool_t?????????? *next;????ngx_uint_t??????????? failed;} ngx_pool_data_t;

?

下面將具體講解ngx_pool_data_t結構體中每個成員的含義和用途: 

last:指向小塊內存中未分配的空閑內存的首地址。

?

end:指向當前小塊內存的尾部。

?

next:同屬于一個內存池的多個小塊內存之間,通過next成員鏈接形成單鏈表。

?

failed: 每當當前的小塊內存由于空閑部分較少而不能滿足用戶提出的內存申請請求時,failed成員就會加1。當failed成員大于4后,ngx_pool_t的current成員就會移向下一個小塊內存,在以后分配內存時,將從下一個小塊內存開始遍歷。

?

?

(b).ngx_pool_large_t

  我們可以在Nginx的源碼的src/core/nax_palloc.h頭文件中看到:

?

123456typedef?struct?ngx_pool_large_s? ngx_pool_large_t;struct?ngx_pool_large_s {????ngx_pool_large_t???? *next;????void?????????????????*alloc;};

?

下面將具體講解ngx_pool_large_t結構體中每個成員的含義和用途:

next:所有大塊內存通過next指針鏈接在一起形成單鏈表。

?

alloc:指向分配的大塊內存,后面我們將會看到大塊內存底層是通過ngx_alloc分配,ngx_free釋放。釋放完了之后賦值為NULL。

?

?

(c).ngx_pool_cleanup_t

?  我們可以在Nginx的源碼的src/core/nax_palloc.h頭文件中看到:

?

1234567typedef?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;};

?

?下面將具體講解ngx_pool_cleanup_t結構體中每個成員的含義和用途:

handler:初始化為NULL,需要設置的清理函數。

?

1typedef?void?(*ngx_pool_cleanup_pt)(void?*data);

?

根據上面的聲明,可以看出,ngx_pool_clean_pt是一個函數指針,有一個通用型的參數data,返回類型為void。后面我們會看到當銷毀內存池的時候,底層會遍歷掛在cleanup成員上的單鏈表上的各個節點,調用各節點的數據清理函數完成相應的清理操作。這是通過回調函數實現的。

?

data:用于向數據清理函數傳遞的參數,指向待清理的數據的地址,若沒有則為NULL。我們可以通過ngx_pool_cleanup_add函數添加數據清理函數,當其中的參數size>0時,data不為NULL。

?

next:用于鏈接所有的數據清理函數形成單鏈表。由ngx_pool_cleanup_add函數設置next成員,用于將當前ngx_pool_cleanup_t(由ngx_pool_cleanup_add函數返回)添加到cleanup鏈表中。

?

?

?

4.內存池基本操作介紹

  這一部分主要簡單講解與內存池管理有關的基本操作(共15個)。主要包括四個部分:(a).內存池操作 (b).基于內存池的分配、釋放操作 (3).隨著內存池釋放同步釋放資源的操作 (4).與內存池無關的分配、釋放操作。在第5和第6節中,我們會對部分常用內存池的操作進行代碼上的詳細介紹。

?

(a).內存池操作:

?

123ngx_pool_t *ngx_create_pool(size_t?size, ngx_log_t *log);void?ngx_destroy_pool(ngx_pool_t *pool);void?ngx_reset_pool(ngx_pool_t *pool);

?

  ngx_create_pool

  創建內存池,其參數size為整個內存的大小,包括結構管理(ngx_pool_t)和后續可分配的空閑內存。這意味著,size必須大于等于sizeof(ngx_pool_t),通常在32位的系統是是40字節,后面我們介紹源碼時會詳細的介紹。通常size的默認大小為NGX_DEFAULT_POOL_SIZE(#define NGX_DEFAULT_POOL_SIZE??? (16 * 1024)),可以看到為16k。不用擔心其不夠用,因為當不夠用時,Nginx會對內存池進行內存空間的擴展,也就是申請一個新的內存池(鏈表)節點(程序中成為一個block),然后掛在內存池的最后面。

?

  ngx_destory_pool

  銷毀內存池,它會執行通過ngx_pool_cleanup_add函數添加的各種資源清理方法,然后釋放大塊內存,最后把整個pool分配的內存釋放掉。

?

  ngx_reset_pool

  重置內存池,即將在內存池中原有的內存釋放后繼續使用。后面我們會看到,這個方法是把大塊的內存釋放給操作系統,而小塊的內存則在不釋放的情況下復用。

?

?

(b).基于內存池的分配、釋放操作

?

12345void?*ngx_palloc(ngx_pool_t *pool,?size_t?size);void?*ngx_pnalloc(ngx_pool_t *pool,?size_t?size);void?*ngx_pcalloc(ngx_pool_t *pool,?size_t?size);void?*ngx_pmemalign(ngx_pool_t *pool,?size_t?size,?size_t?alignment);ngx_int_t ngx_pfree(ngx_pool_t *pool,?void?*p);

? 

???  ?ngx_palloc

  分配地址對齊的內存。內存對齊可以減少cpu讀取內存的次數,代價是存在一些內存浪費。

?

  ngx_pnalloc

  同ngx_palloc,區別是分配內存時不考慮對齊。

?

  ngx_pcalloc

  同ngx_palloc,區別是分配完對齊的內存后,再調用memset全部初始化為0。

?

  ngx_pmemalign

  按參數alignment進行地址對齊來分配內存。注意,這樣分配的內存不管申請的size有多小,都不會使用小塊內存,它們直接從進程的堆中分配,并掛在大塊內存組成的large單鏈表中。

?

  ngx_pfree

  提前釋放大塊內存。由于其實現是遍歷large單鏈表,尋找ngx_pool_large_t對應的alloc成員后調用ngx_free(alloc),實際上是直接調用free(alloc),釋放內存給操作系統,將ngx_pool_large_t移出鏈表并刪除。效率不高。

?

?

(c).隨著內存池釋放同步釋放資源的操作

?

1234ngx_pool_cleanup_t *ngx_pool_cleanup_add(ngx_pool_t *p,?size_t?size);void?ngx_pool_run_cleanup_file(ngx_pool_t *p, ngx_fd_t fd);void?ngx_pool_cleanup_file(void?*data);void?ngx_pool_delete_file(void?*data);

?   

   ?ngx_pool_cleanup_add

  添加一個需要在內存釋放時同步釋放的資源。該方法會返回一個ngx_pool_cleanup_t結構體,而我們得到該結構體后需要設置ngx_pool_cleanup_t的handler成員為釋放資源時執行的方法。ngx_pool_clean_add的參數size,當它不為0時,會分配size大小的內存,并將ngx_pool_cleanup_t的data成員指向該內存,這樣可以利用這段內存傳遞參數,供資源清理函數使用。當size為0時,data將為NULL。

?

  ngx_pool_run_cleanup_file

  在內存釋放前,如果需要提前關閉文件(調用ngx_pool_cleanup_add添加的文件,同時ngx_pool_cleanup_t的handler成員被設置為ngx_pool_cleanup_file),則調用該方法。

?

  ngx_pool_cleanup_file

  以關閉文件來釋放資源的方法,可以設置到ngx_pool_cleanup_t的handler成員。

?

  ngx_pool_delete_file 

  以刪除文件來釋放資源的方法,可以設置到ngx_pool_cleanup_t的handler成員。

?

?

(d).與內存池無關的分配、釋放操作

?

1void?*ngx_alloc(size_t?size, ngx_log_t *log);void?*ngx_calloc(size_t?size, ngx_log_t *log);#define ngx_free??????????free

  

  這部分的聲明和定義實際上并不在src/core/ngx_palloc.h中,而是在/src/os/unix/ngx_alloc.h中。

?

  ngx_alloc

  從操作系統中分配內存,通過調用malloc實現。

?

  ngx_calloc

  從操作系統中分配內存并全部初始化為0,通過調用malloc和memset實現。

?

  ngx_free

  從上面的宏定義可以看到,其就是free函數,釋放內存到操作系統。

?

?

?

5.內存池管理源碼詳解

?  本部分的源碼可以在src/core/ngx_palloc.h、src/core/ngx_palloc.c、src/os/unix/ngx_alloc.h和src/os/unix/ngx_alloc.c中找到。內存池的管理主要包括內存池的創建、銷毀以及重置操作。我們通過對源碼的分析來研究和學習Nginx的內存管理技術。

?

(a).內存池的創建

  創建內存池的操作主要由ngx_create_pool()函數完成,代碼如下:

?

1234567891011121314151617181920212223242526ngx_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;}

?

?  

  在這段代碼中,首先通過ngx_memalign()函數申請對齊的內存,其大小為size個字節。如果內存申請失敗,則返回NULL,否則對ngx_pool_t結構體中的成員進行初始化。在進行初始化之前,讓我們先討論以下什么是小塊內存?

?

?

12345/*?* 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)

?

?

?  這是ngx_palloc.h中的一個注釋及宏定義,從中我們可以看到在x86系統4095字節是一個標準。因為ngx_pagesize中存放的是當前Nginx服務器運行的系統中一頁內存頁的大小,而在x86的系統上就是4KB。由于存在減1的關系,這意味著在x86系統上,小于等于4095字節的內存被稱為小塊內存,而大于4095字節的內存被稱為大塊內存。當然這并不是絕對的,在上述源碼中,我們看到如果傳遞的參數size滿足:size - sizeof(ngx_pool_t) < NGX_MAX_ALLOC_FROM_POOL時,其max的值為size(小于NGX_MAX_ALLOC_FROM_POOL),而當size不滿足上述不等式時,其值為NGX_MAX_ALLOC_FROM_POOL。也就是說NGX_MAX_ALLOC_FROM_POOL是一個最大的門限,申請的小塊內存的大小應該不超過其大小。在初始化max之后,我們將last指向分配好的空閑內存空間的首地址,end指向內存池的尾部。并將next初始化為NULL,failed的值初始化為0。然后再將current指向這塊內存池的首地址,large和cleanup也被初始化為NULL,最后返回指向分配好的內存空間的首地址。為了更加清晰地展示內存池的創建過程,下面將會舉一個例子來說明。但是在這之前,我們先來分析以下ngx_memalign()函數的實現源碼。

?

  關于ngx_memalign()的細節我們可以在src/os/unix/ngx_alloc.c中看到其源碼,前面部分是聲明,后面是定義。如下所示:

?

12345678910111213141516/*?* Linux has memalign() or posix_memalign()?* Solaris has memalign()?* FreeBSD 7.0 has posix_memalign(), besides, early version's malloc()?* aligns allocations bigger than page size at the page boundary?*/#if (NGX_HAVE_POSIX_MEMALIGN || NGX_HAVE_MEMALIGN)void?*ngx_memalign(size_t?alignment,?size_t?size, ngx_log_t *log);#else#define ngx_memalign(alignment, size, log)? ngx_alloc(size, log)#endif

?

?

123456789101112131415161718192021222324252627282930313233343536373839404142#if (NGX_HAVE_POSIX_MEMALIGN)void?*ngx_memalign(size_t?alignment,?size_t?size, ngx_log_t *log){????void??*p;????int????err;????err = posix_memalign(&p, alignment, size);????if?(err) {????????ngx_log_error(NGX_LOG_EMERG,?log, err,??????????????????????"posix_memalign(%uz, %uz) failed", alignment, size);????????p = NULL;????}????ngx_log_debug3(NGX_LOG_DEBUG_ALLOC,?log, 0,???????????????????"posix_memalign: %p:%uz @%uz", p, size, alignment);????return?p;}#elif (NGX_HAVE_MEMALIGN)void?*ngx_memalign(size_t?alignment,?size_t?size, ngx_log_t *log){????void??*p;????p = memalign(alignment, size);????if?(p == NULL) {????????ngx_log_error(NGX_LOG_EMERG,?log, ngx_errno,??????????????????????"memalign(%uz, %uz) failed", alignment, size);????}????ngx_log_debug3(NGX_LOG_DEBUG_ALLOC,?log, 0,???????????????????"memalign: %p:%uz @%uz", p, size, alignment);????return?p;}#endif

?

  我們還需要知道的就是在linux系統下,分配內存有三個系統調用,如果不考慮內存對齊,則有malloc();如果考慮內存對齊,則有:memalign()和posix_memalign();從ngx_memalign()的具體聲明和實現中,我們可以看出這其實一個條件編譯。如果系統定義了NGX_HAVE_POSIX_MEMALIGN,則調用posix_memalign()申請對齊的內存;如果系統定義了NGX_HAVE_MEMALIGN,則調用memalign()申請對齊的內存;并且這兩種內存對齊默認都是基于16字節的。否則直接調用ngx_alloc(),而ngx_alloc()直接調用malloc()申請不對齊的內存。講完了內存池中三種申請內存的方式之后,我們可以開始講解創建內存池的實例了。

  比如說我們需要創建一個大小為1024字節的內存池作為一個分配模塊:

?

1ngx_pool_t *pool = ngx_create_pool (1024,??log);

?

  為了方便,我們不妨假設申請的這塊內存的起始地址為10。執行完創建內存池的操作后,內存中的分布情況如圖5.1所示:

?

圖5.1 創建內存池內存片段圖

?

  從執行結果可以看出:創建的內存池總共占用了1024個字節,起始地址為10,結束地址為1034。指向內存池的指針為pool。last指針為50(10+40),因為起始地址是10,而ngx_pool_t結構體所占用的內存空間為40字節,怎么計算得到的呢?其實很簡單,只需要考慮結構體在內存中的對齊問題即可。在x86中(x64中指針在內存中占用8字節而不是4字節)如下所示:

?

?

1234567891011121314151617typedef?struct?{????u_char?????????????? *last;//4字節????u_char?????????????? *end;//4字節????ngx_pool_t?????????? *next;//4字節????ngx_uint_t??????????? failed;//4字節} ngx_pool_data_t;struct?ngx_pool_s {????ngx_pool_data_t?????? d;//16字節????size_t????????????????max;//4字節????ngx_pool_t?????????? *current;//4字節????ngx_chain_t????????? *chain;//4字節????ngx_pool_large_t???? *large;//4字節????ngx_pool_cleanup_t?? *cleanup;//4字節????ngx_log_t??????????? *log;//4字節};

?

?  

  我們可以計算得到,在x86的系統中ngx_pool_t結構體各個成員變量占用的空間為40字節。因此last的值為50。end的值為10+1024=1034。max的值為1024-40=984。current=10??梢钥吹?#xff1a;

在物理內存中,申請到的內存空間被分為了兩部分,前面一部分是ngx_pool_t內存管理結構各個成員變量所占用的空間,此處為40字節。后面部分的984字節的空閑空間才是我們可以在后續的程序中真正可以利用的,用來存放數據的。以上就是Nging內存池創建的主要原理和具體實現。

?

?

(b).內存池的銷毀

  銷毀內存池的工作主要由ngx_destroy_pool()函數完成。代碼如下:

?

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051voidngx_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);????????}????}#if (NGX_DEBUG)????/*?????* we could allocate the pool->log from this pool?????* so we cannot use this log while free()ing the pool?????*/????for?(l = pool->large; l; l = l->next) {????????ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,?"free: %p", l->alloc);????}????for?(p = pool, n = pool->d.next;?/* void */; p = n, n = n->d.next) {????????ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, pool->log, 0,???????????????????????"free: %p, unused: %uz", p, p->d.end - p->d.last);????????if?(n == NULL) {????????????break;????????}????}#endif????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;????????}????}}

?

  我們可以看到,銷毀內存池的主要步驟為:先通過遍歷掛在cleanup上數據清理函數鏈表,通過回調函數handler做相應的數據清理;中間輸出部分只與調試程序相關,可忽略。然后遍歷掛在large上的大塊內存鏈表,調用ngx_free()函數釋放節點所占的大塊內存空間;最后,遍歷掛在d->next上的小塊內存池鏈表,釋放小塊內存池(包括管理結構和數據區)占用的空間,在這一步中,我們首先清理了第一塊ngx_pool_t(包括了large、cleanup等成員)代表的小塊內存池,然后再清理剩下的其他小塊內存池。經過以上三個過程,就可以完成數據清理、釋放整個內存池占用的內存空間,并銷毀內存池。需要注意的是:由于內存池的結構,我們必須最后清理管理結構ngx_pool_t(第一塊小塊內存池),因為如果先清理第一塊ngx_pool_t代表的內存池的話,我們就找不到掛在large和cleanup上的單鏈表了,因為我們清理了其單鏈表的第一個節點。

?

?

(c).內存池的重置

  重置內存池,就是將內存池分配到初始分配的狀態。這是由ngx_reset_pool()函數完成的。代碼如下:

?

123456789101112131415161718192021voidngx_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;}

?  

  我們可以看到,重置內存池十分簡單。首先將掛在large上的大塊內存鏈表上的各個節點釋放掉,并將pool->large賦值為NULL。之后,將所有小塊內存池構成的單鏈表中的所有節點結尾的last指針重置到剛分配時的位置。小塊內存中存儲的數據并沒有被釋放,其在以后的內存池使用的過程中將會被覆蓋更新。這可以減少內存分配的次數,提升內存重用率。但會浪費一些內存空間。

?

?

?

6.內存池使用源碼詳解

?  內存池創建好之后,如何進行使用呢?這些內存使用完了之后是如何進行回收利用的呢?下面的部分將會詳細的介紹內存池的使用。

?

(a).從內存池中申請內存

  在Nginx中,基于內存池的申請方法主要有ngx_palloc、ngx_pnalloc、ngx_pcalloc和ngx_pmemalign共4種方法。而不基于內存池,直接從操作系統中申請內存的主要有ngx_alloc和ngx_calloc共兩種方法。在這一小節中,我們只講述從內存池中申請內存相關的4中方法。而其他的部分將會在后面的小節進行講解。

  基于內存池的4中內存申請方法的區別在第4章:內存池API介紹中已經詳細闡述了。此處不再贅述。

?

(1).ngx_palloc

  下面給出源碼:

 

1234567891011void?*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);}

?

  從其實現中,我們可以看出,ngx_palloc()總共有兩個參數,第一個是在那個內存池上申請內存(之前我們曾經提到過通常為每個Http請求或者連接創建一個內存池,此處需要傳遞的參數就是這些內存池對應的指針),另一個參數是size,表示申請內存的大小。進入函數后,首先是判斷申請的內存大小和max(小塊內存標準)的關系,如果size<max,就調用ngx_palloc_small()函數申請內存。否則調用ngx_palloc_large()函數申請內存。下面讓我們先來看ngx_palloc_small()函數的源碼,如下所示:

?

123456789101112131415161718192021222324252627static?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);}

?

  從上述源碼中,我們可以看到,該函數從current指向的內存池(小塊內存池鏈表)中開始循環遍歷。在每一次遍歷中,我們首先獲得目前內存池中未分配的空閑內存的首地址last,并賦值給m,然后由于從ngx_palloc()函數中傳遞過來的align=1,因此調用ngx_align_ptr(),這是個什么呢?僅從此我們不能判斷其是函數還是宏,下面我們給出其源碼,在src/core/ngx_config.h中,如下所示:

?

12#define ngx_align_ptr(p, a)?????????????????????????????????????????????????? \????(u_char *) (((uintptr_t) (p) + ((uintptr_t) a - 1)) & ~((uintptr_t) a - 1))

?

  可以看出,這是一個宏定義,該操作比較巧妙,用于計算以參數a對齊后的偏移指針p。實際上,我們最后分配的內存空間就是從對齊后的偏移指針開始的,這可能會浪費少數幾個字節,但卻能提高讀取效率。接著分析ngx_palloc-small()函數中的源碼,在調用完宏ngx_align_ptr(m,?NGX_ALIGNMENT)后我們得到了以默認參數16對齊的偏移指針m。此時,我們已經擁有了對齊后的空閑內存地址空間的首地址m和尾部地址end,我們就可以計算出該塊內存池(一個block)剩余的空閑內存空間大小:p->d.end - m。那么這個剩余的空閑內存空間是否一定能滿足用戶的內存申請請求(size個字節)呢?答案是否定的。因此我們需要將從current開始的每一個小塊內存池的剩余空閑內存空間和size進行比較,遍歷鏈表直到找到滿足申請大小(size個字節)的小塊內存池。如果小塊內存池鏈表上的某塊小塊內存能夠滿足需求,那么我們就將從Nginx的內存池中劃分出內存空間,并更新last的值(將last的值后移size個字節),然后返回m。

  如果遍歷完整個小塊內存池都沒有找到滿足申請大小的內存,則程序調用ngx_palloc_block()函數。其源碼如下所示:

?

12345678910111213141516171819202122232425262728293031323334static?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;}

?

  既然當前整個內存池都不能滿足用戶內存的申請,而我們的操作系統明明還有內存可用(資源耗盡的情況除外),那我們總不能拒絕用戶的合理請求吧。ngx_palloc_block()函數就是應對這種情況而出現的。該函數實現了對內存池的擴容。

  需要注意的是,由于我們遍歷完了整個鏈表,因此此時的pool指針指向的是內存池鏈表的最后一個節點。所以說在ngx_palloc_block()中計算的是當前內存池最后一個節點的大小psize。該大小為需要擴展的空間大小。然后,我們調用前面提到過的ngx_memalgin()函數申請新的內存空間,大小為psize,作為新的小塊內存池節點。之后,我們將這個節點掛在內存池的最后面。具體怎么實現的呢?我們來詳細的看一看。

  首先將這個新節點進行初始化,包括d->end、d->next、d->failed。然后將指向這塊內存的首地址m后移sizeof(ngx_pool_data_t),大家可能還記得我們在創建內存池ngx_pool_create()時,內存池中空閑地址的首地址是在整個內存池的首地址的基礎上后移了sizeof(ngx_pool_t),那么為什么此處創建新的內存池節點只需要后移sizeof(ngx_pool_data_t)呢?在x86系統上,sizeof(ngx_pool_data_t)對應16個字節,而sizeof(ngx_pool_t)對應40個字節。其實大家仔細想一想,我們創建的內存池是小塊內存池鏈表的第一個節點,這個節點中除了包含ngx_pool_data_t結構體之外,還需要包含large指針、cleanup指針等。而小塊內存池后面的節點均沒有必要包含這些成員,因為我們的large鏈表和cleanup鏈表是直接且僅僅掛在小塊內存池鏈表的第一個節點上的。不需要再掛到后續的其他小塊內存池鏈表的結構上。這么想是不是覺得比較合理呢?答案就是這樣的。但是我們之前的重置內存池操作中,并沒有把后續的從第二個節點開始的小塊內存池鏈表上的空閑內存的起始地址初始化為(u_char *)p + sizeof (ngx_pool_data_t),而是將所有節點(包括第一個)的空閑內存地址初始化為(u_char *)p + sizeof (ngx_pool_t)。這樣做會浪費一些內存空間,但是整個重置內存池操作會簡單一點點。因為不用區分第一個節點和其他節點。如果區分的話,我們需要讓第一個節點的空閑內存的起始地址初始化為(u_char *)p + sizeof (ngx_pool_t),將其他節點的空閑內存的起始地址初始化為(u_char *)p + sizeof (ngx_pool_data_t)。我們的Nginx源碼就是這么實現的。大家知道就行了。因為這并不會影響內存池的使用。

  在完成對新的內存池節點的初始化之后。我們需要將這個節點加入到小塊內存池鏈表的尾部。具體怎么實現的呢?

  首先我們找到current指針,并根據這個指針遍歷小塊內存池鏈表,在每一個遍歷中,我們將每個節點的failed成員加1(這是因為你們這些節點不能給我分配內存啊,不然也不會調用我,因此對你們的failed成員統統加1)。并且加1之后,進行判斷,如果某個節點的failed成員的值大于4,那么就將current指向下一個節點(下次再分配內存時將會自動忽略這個節點)。

  在遍歷完小塊內存池的鏈表后,我們的pool指針已經指向了鏈表的最后一個節點,因此在鏈表的尾部插入一個節點非常簡單,p->d.next = new這個語句就能完成。之后返回這個指向這個新節點的空閑內存空間的首地址。

  上述就是ngx_palloc_small()函數完成的功能,內容比較多大家可能都忘了,我們還沒有講解ngx_palloc()函數的另外一個部分:ngx_palloc_large(),這個函數是用于當用戶申請的內存大小大于我們的小塊內存標準max的情況。下面我們將會看到,這種情況下,申請的內存將被當作是大數據塊,將會被掛在large鏈表上。先給出ngx_palloc_large()的源碼:

?

?

12345678910111213141516171819202122232425262728293031323334353637static?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;}

?

  從上面的代碼中我們可以看出我們首先調用ngx_alloc()函數申請一塊大小為size的內存空間,ngx_alloc()函數實際上就是簡單的封裝了以下malloc()函數,后面我們會詳細的講解。這里知道它是由malloc實現的就好了。申請完內存之后,開始遍歷large鏈表,找到鏈表中alloc為NULL的節點,用alloc指向剛申請到的內存空間并返回。注意這段循環代碼至多執行3次,如果在3次后都沒有找到alloc為NULL的節點,就會退出循環,繼續執行后面的代碼。限制代碼執行的次數是為了提升內存分配的效率,因為large鏈表可能會很大。

  之后,我們調用ngx_palloc_small()重新申請一塊大小為sizeof(ngx_pool_large_t)結構體大小的內存,建立一個新節點。最后我們把新建立的節點插入到large鏈表的頭部,返回申請的內存空間的起始地址。為什么是插入頭部而不是插入尾部呢?這里面其實是有依據的,因為我們之前為了防止large過大將遍歷large鏈表的次數設置為3,如果插在尾部,那么遍歷鏈表前面的三個節點就沒有意義了,因為每次都可能會遍歷不到后面的空閑節點,而導致每次都需要重新建立新節點。并且插入頭部,從頭部開始遍歷也會使得效率比較高。因為這樣遍歷到空閑的大塊內存節點的概率會高很多。

?

?

(2).ngx_pnalloc

  先給出其源碼:

?

1234567891011void?*ngx_pnalloc(ngx_pool_t *pool,?size_t?size){#if !(NGX_DEBUG_PALLOC)????if?(size <= pool->max) {????????return?ngx_palloc_small(pool, size, 0);????}#endif????return?ngx_palloc_large(pool, size);}

?

  我們可以看到,ngx_pnalloc()和ngx_palloc()非常相似,唯一的區別就是ngx_pnalloc()中調用的是ngx_palloc_small(pool, size, 0),而ngx_palloc()中調用的是ngx_palloc_small(pool, size, 1)。那么實際上的含義有什么區別呢?ngx_pnalloc()分配內存時不考慮內存數據對齊,而ngx_palloc()分配內存時考慮內存數據對齊。

?

?

(3).ngx_pcalloc

  我們先給出其源碼,如下所示:

?

123456789101112void?*ngx_pcalloc(ngx_pool_t *pool,?size_t?size){????void?*p;????p = ngx_palloc(pool, size);????if?(p) {????????ngx_memzero(p, size);????}????return?p;}

?

  從其實現可以看出,ngx_pcalloc()和ngx_palloc()非常的相似,唯一的區別就是ngx_pcalloc()函數將剛申請到的內存空間全部初始化為0。

?

?

(4).ngx_pmemalign

  我們給出其源碼,如下所示:

?

1234567891011121314151617181920212223void?*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;}

?

  從其源碼實現中,我們可以看出ngx_pmemalign()函數首先調用ngx_memalign()函數來申請對齊的內存地址空間。然后ngx_palloc_small()函數來建立一個新的大數據塊節點。并將ngx_pmemalign()函數申請的內存空間直接掛在新建的大塊數據節點的alloc成員上。最后再將新建的大數據塊節點掛在大塊內存組成的單鏈表中。

  上面就是整個基于內存池申請內存的4種方法的源碼實現及其分析。下面我們會繼續講解釋放內存和回收內存。

  ngx_pfree()函數用于提前釋放大塊內存。

?

?

(b).釋放內存

  此處我們將介紹基于內存池的內存釋放操作函數ngx_pfree(),與內存池無關的內存釋放操作ngx_free()將在后面被講解。

  在Nginx中,小塊內存并不存在提前釋放這么一說,因為其占用的內存較少,不太需要被提前釋放。但是對于非常大的內存,如果它的生命周期遠遠短于所屬的內存池,那么在內存池銷毀之前提前釋放它就變得有意義了。下面先給出其源碼:

?

123456789101112131415161718ngx_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;}

?

  從其實現中可以看出,ngx_pfree()函數的實現十分簡單。通過遍歷large單鏈表,找到待釋放的內存空間(alloc所指向的內存空間),然后調用ngx_free()函數釋放內存。后面我們會看到ngx_free()函數是free()函數的一個簡單封裝。釋放alloc所占用的空間后,將alloc設置為NULL。我們需要注意的是:ngx_pfree()函數僅僅釋放了large鏈表上每個節點的alloc成員所占用的空間,并沒有釋放ngx_pool_large_t結構所占用的內存空間。如此實現的意義在于:下次分配大塊內存時,會期望復用這個ngx_pool_large_t結構體。從這里可以想到,如果large鏈表中的元素很多,那么ngx_pfree()的遍歷耗損的性能是不小的,如果不能確定內存確實非常大,最好不要調用ngx_pfree。

?

?

(c).隨著內存池釋放同步釋放資源的操作

  在Nginx服務器程序中,有些數據類型在回收其所占的資源時不能直接通過釋放內存空間的方式進行,而需要在釋放之前對數據進行指定的數據清理操作。ngx_pool_cleanup_t結構體的函數指針handler就是這么一個數據清理函數,其data成員就指向要清理的數據的內存地址。我們將要清理的方法和數據存放到ngx_pool_cleanup_t結構體中,通過next成員組成內存回收鏈表,就可以實現在釋放內存前對數據進行指定的數據清理操作。而與這些操作相關的方法有:ngx_pool_cleanup_add()、ngx_pool_run_cleanup_file()、ngx_pool_cleanup_file()和ngx_pool_delete_file()共4種。下面我們將分別講解這些操作。

?

(1).ngx_pool_cleanup_add()

  這個方法的目的是為了添加一個需要在內存池釋放時同步釋放的資源。我們依照慣例還是先給出其源碼,然后對源碼進行分析和學習。其源碼如下所示:

?

1234567891011121314151617181920212223242526272829ngx_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;}

?

  從其實現中我們可以看出,我們首先調用ngx_palloc()函數申請cleanup單鏈表中的一個新節點(指向ngx_pool_cleanup_t結構體的指針),然后根據參數size是否為0決定是否需要申請存放目標數據的內存空間。當size>0時,調用ngx_palloc()函數申請大小為size個字節的用于存放待清理的數據的內存空間。這些要清理的數據存儲在ngx_pool_cleanup_t結構體的data成員指向的內存空間中。這樣可以利用這段內存傳遞參數,供清理資源的方法使用。當size=0時,data為NULL。最后將新生成的ngx_pool_cleanup_t結構體掛在cleanup單鏈表的頭部。返回一個指向ngx_pool_cleanup_t結構體的指針。而我們得到后需要設置ngx_pool_cleanup_t的handler成員為釋放資源時執行的方法。

返回的指向ngx_pool_cleanup_t結構體的指針具體怎么使用呢?我們對ngx_pool_cleanup_t結構體的data成員指向的內存空間填充目標數據時,將會為handler成員指定相應的函數。

?

?

(2).ngx_pool_run_cleanup_file()

  在內存池釋放前,如果需要提前關閉文件,則調用該方法。下面給出其源碼,如下所示:

?

12345678910111213141516171819voidngx_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;????????????}????????}????}}

?

  再給出ngx_pool_cleanup_file結構體的聲明和定義(在src/core/ngx_palloc.h頭文件中),如下所示:

?

12345typedef?struct?{????ngx_fd_t????????????? fd;????u_char?????????????? *name;????ngx_log_t??????????? *log;} ngx_pool_cleanup_file_t;

?

  從上述源碼中,我們可以看出,ngx_pool_run_cleanup_file()通過遍歷cleanup單鏈表,尋找單鏈表上的一個節點,這個節點滿足handler(函數指針)等于ngx_pool_cleanup_file(在與函數名相關的表達式中,函數名會被編譯器隱式轉換成函數指針)。由于ngx_pool_cleanup_t結構體的data成員經常會指向ngx_pool_cleanup_file_t(在后面的ngx_pool_cleanup_file()函數中我們可以看到),我們將這個節點data指針賦值給cf(ngx_pool_cleanup_t結構指針)。之后如果傳遞過來的參數fd與cf->fd相同的話(代表我們找到了需要提前關閉的文件描述符fd),就提前執行ngx_pool_cleanup_file(fd),進行文件的關閉操作。

?

?

(3).ngx_pool_cleanup_file()

  該方法以關閉文件的方式來釋放資源,可以被設置為ngx_pool_cleanup_t的handler成員(函數指針)。我們給出其源碼實現,如下所示:

?

12345678910111213voidngx_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);????}}

?

  可以看出,ngx_pool_cleanup_t結構的data成員指向ngx_pool_cleanup_file_t結構體(前面講解ngx_pool_run_cleanup_file()提到過)。之后直接調用ngx_close_file()函數關閉對應的文件。而ngx_close_file()底層是是通過close()函數實現的。

?

?

(4).ngx_pool_delete_file()

  以刪除文件來釋放資源的方法,可以設置到ngx_pool_cleanup_t的handler成員。我們先給出其源碼,如下所示:

?

123456789101112131415161718192021222324voidngx_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);????}}

?

  可以看出,ngx_pool_cleanup_t結構的data成員指向ngx_pool_cleanup_file_t結構體,在程序中我們先將傳遞過來的參數data(待清理的目標數據)賦值給c,然后對c的成員name(文件名稱)調用ngx_delete_file()函數,完成對文件的刪除操作,之后調用ngx_close_file()函數關閉相應的文件流(關閉這個文件流可以阻止刪除的文件再次被訪問,并且釋放FILE結構使得它可以被做用于其他的文件),這就是我們為什么在刪除對應的文件后還需要關閉打開的文件流的原因。

  補充一下:ngx_close_file和ngx_delete_file其實是一個宏定義,我們可以在src/os/unix/ngx_files.h中看到其具體實現,如下所示:

?

123456#define ngx_close_file?????????? close#define ngx_close_file_n???????? "close()"#define ngx_delete_file(name)??? unlink((const char *) name)#define ngx_delete_file_n??????? "unlink()"

?

  可以看到,ngx_close_file其實就是close,在Nginx服務器程序編譯階段僅僅做一個簡單的替換。ngx_delete_file(name)也是一個宏定義,本質上為unlink((const char *) name),該函數會刪除參數name指定的文件。

  

?

(d).與內存池無關的資源分配、釋放操作

?  與內存池無關的內存分配和釋放操作主要有ngx_alloc()、ngx_calloc()和ngx_free()共3中操作方法。下面我們將繼續講解它們的具體實現。

?

(1).ngx_alloc()

  ngx_alloc()函數直接從操作系統中申請內存,其實現是對malloc()函數的一個簡單封裝。我們可以在src/os/unix/ngx_alloc.c中找到其源碼。如下所示:

?

123456789101112131415void?*ngx_alloc(size_t?size, ngx_log_t *log){????void??*p;????p =?malloc(size);????if?(p == NULL) {????????ngx_log_error(NGX_LOG_EMERG,?log, ngx_errno,??????????????????????"malloc(%uz) failed", size);????}????ngx_log_debug2(NGX_LOG_DEBUG_ALLOC,?log, 0,?"malloc: %p:%uz", p, size);????return?p;}

?

  可以看到,其實現非常簡單。僅僅是封裝了malloc()函數,并做了一些日志和調試方面的處理。

?

?

(2).ngx_calloc()

  ngx_calloc()和ngx_alloc()非常相似,唯一的區別是在調用malloc()函數申請完內存之后,會調用ngx_memzero()函數將內存全部初始化為0。ngx_memzero()就是memset()函數。

?

?

(3).ngx_free()

  我們可以在src/os/unix/ngx_alloc.h中看到其源碼,如下所示:

?

1#define ngx_free????????? free

?

  可以看到Nginx程序釋放內存的函數非常簡單,和銷毀內存池中用的是同一個(free)。這里需要再次說明的是:對于在不同場合下從內存池中申請的內存空間的釋放時機是不一樣的。一般只有大數據塊才直接調用ngx_free()函數進行釋放,其他數據空間的釋放都是在內存池銷毀的時機完成的,不需要提前完成。

  至此,Nginx與內存相關的操作的源碼實現已基本講完了。大家如果想進一步研究和學習Nginx內存管理機制,可以從官方下載Nginx源碼,從源碼中去發現Nginx降低系統內存開銷的方法。

?

?

?

7.小結

  所有的講解都講述完了,我們來進行總結一下。在第1節中,我們介紹了Nginx的內存管理機制-內存池的基本原理和使用內存池管理Nginx服務器程序帶來的好處。為了方便大家對內存池結構的理解,我們在第2節中特意給出了ngx_pool_t內存池的示意圖2.1,并簡單的闡述了這個圖的具體含義。在此基礎上,我們繼續在第3節中講述了與內存池相關的重要的數據結構,主要包括ngx_pool_t、ngx_pool_data_t、ngx_pool_large_t和ngx_pool_cleanup_t。然后為了給大家一個內存池操作方法的宏觀介紹,我們在第4節講述了內存的主要操作方法(共15個分成4類)。之后在第5節中我們詳細介紹了內存池的管理,主要包括內存池的創建、銷毀和重置。在第6節中我們詳細介紹了內存池的使用,主要包括從內存池中如何申請內存、釋放內存和回收內存。這兩個小結是整個Nginx內存管理的精華部分,我們在這部分中詳細的分析Nginx的源碼實現,從源碼的角度去講解Nginx內存管理用到的技術,方便我們在以后的程序設計中可以借鑒和學習。最后,希望這篇文章能真正幫助到大家學習Nginx。

總結

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

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

久久香蕉国产 | 国产一区二区三区黄 | 国产色小视频 | 久久91网 | 久久视频这里只有精品 | 在线香蕉视频 | 国产永久网站 | 色悠悠久久综合 | 米奇影视7777| 色综合亚洲精品激情狠狠 | 丝袜美腿在线 | 久久经典视频 | 色橹橹欧美在线观看视频高清 | 久久精品国产精品亚洲精品 | 国产麻豆精品免费视频 | 99久久精品一区二区成人 | 久草视频在线观 | 国产91九色蝌蚪 | 91在线免费播放 | 国产视频97 | 欧美在线不卡一区 | 国产在线精品一区 | 亚洲狠狠干| 精品在线观看一区二区三区 | 国产一区二区久久久久 | 成人高清在线观看 | 人成在线免费视频 | 亚洲精品视频免费 | 日韩成人免费电影 | 黄污视频网站 | 久久久久女教师免费一区 | 91麻豆精品国产91久久久更新时间 | 成人av影视观看 | 黄a在线看| 香蕉精品视频在线观看 | 免费在线精品视频 | 中文国产在线观看 | 亚洲一区免费在线 | 久久久久久高潮国产精品视 | 亚洲va在线va天堂va偷拍 | 高清免费在线视频 | 欧美在线1区 | 亚洲欧美成人网 | 免费在线观看一级片 | 久久久久久久久国产 | av看片网 | 激情视频91 | 欧美色精品天天在线观看视频 | 奇米影视8888 | 美女国产免费 | 99精品国产亚洲 | 久久激情网站 | 菠萝菠萝蜜在线播放 | 国产小视频在线免费观看 | 国产不卡在线观看 | 国产天天爽 | 国产精品毛片一区二区三区 | 97网站| 国产又黄又爽又猛视频日本 | 国产视频一区在线 | 一级片黄色片网站 | 免费福利影院 | 午夜精品久久 | 日韩最新av | 人人看看人人 | 国产盗摄精品一区二区 | 免费99视频| 国内精品久久天天躁人人爽 | 久久亚洲私人国产精品va | 成人久久18免费网站 | 欧美精品一区二区免费 | 久久精品视频国产 | 91av在线播放视频 | 天天干天天干天天射 | 天天做天天爱天天爽综合网 | 午夜精品一区二区三区视频免费看 | 国产涩涩网站 | 久久男人视频 | 亚洲天堂毛片 | 天天激情天天干 | 日日躁夜夜躁xxxxaaaa | 日本精品久久久久中文字幕5 | 免费观看一级视频 | 亚洲精品综合在线观看 | 日韩久久久久久久 | 欧美国产精品一区二区 | 96看片| 亚洲国产午夜精品 | 久久国产视屏 | 狠狠伊人| 午夜视频在线观看一区二区三区 | 亚洲高清91 | 久久精品国产亚洲精品2020 | 久久黄色影院 | 四虎影视成人精品国库在线观看 | 国产成人精品综合久久久久99 | 片黄色毛片黄色毛片 | 91干干干 | 欧美精品一区二区免费 | 日韩欧美一区二区在线 | 国产精品入口麻豆www | 色综合亚洲精品激情狠狠 | 成人亚洲欧美 | 麻豆国产在线播放 | 99re热精品视频 | 91视频在线观看大全 | 99福利片| 91在线视频免费91 | 久久er99热精品一区二区 | 丁香色综合 | 色综合久久久久综合体桃花网 | 天天夜夜亚洲 | 国产视频亚洲 | 一级片视频免费观看 | 欧美做受xxx | 高清中文字幕 | 亚洲最大av网 | 中文字幕在线看视频国产 | 久久精品亚洲综合专区 | 久久久久久久免费看 | 视频国产| 国产无套精品久久久久久 | 久久狠狠亚洲综合 | 色吊丝在线永久观看最新版本 | 国产黄色在线网站 | 亚洲专区免费观看 | 最近中文字幕免费 | 最近中文字幕第一页 | 精品国产aⅴ麻豆 | 最新国产一区二区三区 | 久久九九久久 | 久久99久久99精品免观看软件 | 国产录像在线观看 | 91女神的呻吟细腰翘臀美女 | 亚洲欧洲久久久 | 欧洲一区精品 | 91久草视频 | 91福利专区| 午夜精品一区二区三区免费 | 91丨九色丨国产在线 | 亚洲精品88欧美一区二区 | 成人国产精品免费观看 | 国产一级精品视频 | 日韩在线视频网址 | 日韩精品欧美精品 | 欧美在线视频一区二区三区 | 国产精品毛片一区二区三区 | 久久综合免费视频 | 久久久久国产精品一区二区 | 一区二区精品在线观看 | 久久亚洲影视 | 亚洲综合色视频 | 国产自偷自拍 | 日日干日日色 | 免费在线色视频 | 国产高清在线观看av | 色午夜 | 国产麻豆精品在线观看 | 欧美国产亚洲精品久久久8v | 丁香五月亚洲综合在线 | 日韩黄色一级电影 | 日韩综合一区二区三区 | 99爱精品在线 | 国偷自产中文字幕亚洲手机在线 | 日本一区二区三区免费观看 | 久草爱视频 | 国产69精品久久99不卡的观看体验 | 欧美日韩国产在线精品 | 97人人艹 | 日韩免费一二三区 | 国产99久久九九精品 | 97成人免费视频 | 日韩毛片精品 | 久久黄色小说 | 最近中文国产在线视频 | 国产精品久久片 | 91久久国产自产拍夜夜嗨 | 最近中文字幕高清字幕在线视频 | 九九热精| 2024av | 国产精品99视频 | 最近2019中文免费高清视频观看www99 | 99久久精 | 国产在线观看你懂的 | 九色精品免费永久在线 | 国产精品24小时在线观看 | 天堂网av 在线 | 91中文字幕网 | 国产日产精品一区二区三区四区 | av爱干 | 91免费在线看片 | 免费网站色 | 亚洲不卡123 | 在线看片一区 | 久久久国产日韩 | 免费在线91 | 亚洲丁香日韩 | 久久国产91| 中文字幕人成乱码在线观看 | 人人草在线视频 | 伊人永久 | 精品福利国产 | 欧美另类巨大 | 国产亚洲婷婷免费 | 亚洲最新av网址 | 亚洲欧洲av | 天天操天天操天天干 | www.av中文字幕.com | 国产精品美女 | 中国一级片免费看 | 国产精品一区二区三区久久久 | 亚洲成人高清在线 | 国产四虎影院 | 波多野结衣电影一区二区 | 超碰97人人爱 | 正在播放五月婷婷狠狠干 | 国产成人精品999在线观看 | av网站免费线看精品 | 青青草国产在线 | 国产黄色电影 | 992tv成人免费看片 | 中文字幕免费不卡视频 | 99在线视频免费观看 | 久久久国产电影 | 中文字幕在线免费观看 | 一级黄色片网站 | 国产在线资源 | 欧美激情视频在线免费观看 | 一本一道久久a久久精品 | 日韩网站免费观看 | 亚洲视屏在线播放 | 视频国产在线观看18 | 午夜精品一区二区三区在线 | 亚洲人精品午夜 | 国产精品一区欧美 | 天堂网一区 | 丁香六月婷婷开心 | 免费av在线播放 | 亚洲国产片色 | 免费的黄色的网站 | 精品国产99国产精品 | 丝袜少妇在线 | 午夜av影院 | 日女人电影 | 亚洲国产午夜 | 国内视频 | 在线免费观看黄色小说 | 91av小视频| 精品国产欧美一区二区三区不卡 | 天天干夜夜爽 | 亚洲免费成人 | 在线观看国产日韩欧美 | 日韩欧美高清在线观看 | 国产成人久久精品77777 | 日韩激情小视频 | 久久天堂精品视频 | 国产精品av免费在线观看 | 婷婷丁香花五月天 | 久久这里只有精品久久 | 亚洲传媒在线 | 久久久精品一区二区 | 特级黄色视频毛片 | 人人爽人人爽av | 天天操狠狠操网站 | 91在线中字 | 99精品热 | av综合网址| 99久久久免费视频 | 久久精品91视频 | 中文字幕有码在线观看 | 黄污视频网站 | 九九精品视频在线观看 | 久久亚洲成人网 | 涩涩网站免费 | 九九有精品 | 免费成人在线观看 | 四虎永久免费网站 | 99久久精品国产一区 | 人人插人人插 | a视频在线看 | 九九交易行官网 | 国产成人精品一区在线 | 免费成人短视频 | 日韩精品高清不卡 | 97自拍超碰 | 黄色a视频 | 夜夜视频资源 | 91精品视频免费看 | 视频二区在线 | 精品久久1 | 精品久久久久久久久久久久久 | 成人一区二区在线 | 精品国产黄色片 | 久久免费av电影 | 视频在线一区 | 国产一区二区三精品久久久无广告 | 色在线免费视频 | 国产中文字幕在线看 | 99中文字幕在线观看 | av中文字幕剧情 | 国产成人a亚洲精品 | 国产69精品久久久久久久久久 | 亚洲另类视频在线 | 久久午夜色播影院免费高清 | 人人操日日干 | 国产一区二区高清不卡 | av三区在线 | 最新国产精品拍自在线播放 | 五月婷婷丁香综合 | 日日日干| 国产精品一区二区免费在线观看 | 久久国产精品一二三区 | 97精品国产aⅴ | 日韩丝袜 | 精品免费久久久久 | 欧美精品一区二区三区四区在线 | 婷婷免费在线视频 | 久久久久久久久久久免费 | av网在线观看 | 国产精品中文字幕在线 | 免费精品视频在线观看 | 久久视频国产精品免费视频在线 | 天天av天天 | 精品一区二区在线看 | 国产精品日韩在线播放 | 久久国产精品一区二区 | 精品视频在线观看 | 国产vs久久 | 在线亚洲天堂网 | 久久久久久久久久久高潮一区二区 | 色综合中文综合网 | 在线观看免费国产小视频 | 国产一级三级 | 99在线高清视频在线播放 | 亚州国产视频 | 麻豆一区二区 | 亚洲综合激情 | 91精品办公室少妇高潮对白 | 免费在线播放黄色 | 国产福利小视频在线 | 精品福利在线视频 | 国产高清视频在线观看 | 亚洲成 人精品 | av福利网址导航大全 | 色搞搞 | 91av观看 | 人人添人人澡人人澡人人人爽 | 97精品超碰一区二区三区 | 在线视频a| 国产成人一区二区在线观看 | 美女视频久久黄 | 国产一区二区影院 | 久久久精品日本 | 这里只有精品视频在线观看 | 国产一区二区在线免费视频 | 中文字幕资源网在线观看 | 91看片黄色 | 欧美精品久久久久性色 | 中文字字幕在线 | 九九在线播放 | 免费成人在线电影 | 成人av免费在线播放 | 91麻豆精品国产自产在线游戏 | 99在线精品视频观看 | 麻豆91视频 | 国产三级香港三韩国三级 | 最新午夜电影 | 久久a免费视频 | 黄色片网站免费 | 人人狠| 欧美激情xxxx| 日韩视频中文 | 精品国产一区二区三区久久影院 | 人人爽人人干 | av片子在线观看 | 欧美 日韩 久久 | 日韩在线中文字幕 | 久久精品久久精品久久39 | 欧美日韩国产在线一区 | a天堂一码二码专区 | 丁香婷婷射| 久久久久久久久久久久久国产精品 | 亚洲精品美女免费 | 欧美日韩中文在线观看 | 国产精品一区二区久久精品爱微奶 | 国产一区国产精品 | 日韩黄色影院 | 中文字幕国产在线 | 欧美久久久久久久 | 草久电影 | 久久只有精品 | 国产91电影在线观看 | 热久久在线视频 | 国产视频一区二区在线播放 | 男女激情免费网站 | 日韩免费| 国产91成人在在线播放 | 最新国产一区二区三区 | 亚洲理论在线 | 日本99久久| 九九九九精品 | www.天堂av | 人人插人人爱 | 人人精品 | 在线观看www视频 | 亚洲天堂视频在线 | 99精品视频免费在线观看 | 伊人国产女 | 国产手机av | 午夜精品视频一区二区三区在线看 | 日韩有码专区 | 五月婷婷久久综合 | 国产黄视频在线观看 | 亚洲午夜精品电影 | 日韩电影中文字幕在线观看 | 中文字幕免费国产精品 | 国产一区二区三区免费观看视频 | 99国内精品 | 在线看的av网站 | 久草在线最新视频 | 91成熟丰满女人少妇 | 在线观看黄色 | 97超碰人人 | 婷婷在线免费 | 美女网站久久 | 久av电影| 99这里只有精品视频 | 美女久久一区 | 99久久er热在这里只有精品15 | 国产成人av | www.色国产 | 国产成人精品一区二区三区网站观看 | 免费在线一区二区三区 | 草久久久 | 日韩欧美xxx | 中文久草 | 欧美孕交vivoestv另类 | a天堂最新版中文在线地址 久久99久久精品国产 | 欧美一级看片 | 国产精品99视频 | 天天插天天狠 | 在线免费观看国产黄色 | 免费色视频网站 | www天天干com | 韩国一区在线 | 亚洲欧洲一区二区在线观看 | 黄色一级免费电影 | 日韩av在线小说 | 久久综合国产伦精品免费 | 色视频成人在线观看免 | 蜜臀av性久久久久av蜜臀妖精 | 成人理论在线观看 | 二区三区在线观看 | 成人精品视频久久久久 | 国产精品成人一区二区 | 日韩视频欧美视频 | 欧美激精品 | 亚洲欧美国产精品 | 在线视频国产区 | 日韩亚洲欧美中文字幕 | 狠狠躁夜夜躁人人爽超碰91 | 国产精品嫩草69影院 | 天天操天天射天天添 | 91成人小视频 | 久久福利 | 亚洲精品久久久蜜臀下载官网 | 四虎成人精品在永久免费 | 国内视频在线观看 | 涩涩爱夜夜爱 | 国产偷国产偷亚洲清高 | www.com久久久 | 久久久久久久久久久国产精品 | 久在线观看| 免费三级a| 色综合天天色综合 | 激情av在线播放 | 国产亚洲精品美女 | 天天夜夜操 | 国产一区二区在线观看免费 | 99re在线视频观看 | 精品久久久久久国产 | 中文字幕日本在线观看 | 最近中文字幕mv免费高清在线 | 久久久av电影 | 成人av免费看 | 亚洲伦理一区 | 97国产小视频 | 97在线超碰 | 中文字幕有码在线 | 人人爽网站 | 欧美一级视频在线观看 | 97色婷婷人人爽人人 | 日韩xxxx视频 | 人人澡人人澡人人 | 国产精品 日本 | 久久国产精品系列 | 五月婷婷丁香综合 | 黄色av电影免费观看 | 国产色视频 | 免费在线色视频 | 欧美成人精品在线 | 久久久久久久久久影视 | av东方在线 | 91毛片在线 | 深爱五月网 | 亚洲国产69| 国产一级91 | 亚洲成人影音 | 免费网站在线观看人 | 婷婷av色综合 | 国产不卡一二三区 | 久久精彩免费视频 | 日韩va欧美va亚洲va久久 | 久久精品中文字幕一区二区三区 | 欧美色888 | 人人看黄色 | 四虎成人精品永久免费av | 欧美激情视频一区二区三区免费 | 国产精品高潮久久av | 国产黄色av | 日本性视频 | 午夜久久影院 | 密桃av在线| 亚洲精品成人在线 | 人人狠狠 | 国产一区二区视频在线 | 玖玖视频| 国产精品美女毛片真酒店 | 国产精品ssss在线亚洲 | 国产精品原创视频 | caobi视频 | 成人av在线资源 | 久久久96 | 亚洲国产精品va在线看黑人动漫 | 久久成 | 久久久麻豆精品一区二区 | 日韩精品久久久免费观看夜色 | 国产免费久久 | 中文字幕在线播放日韩 | 欧美少妇xx | 天天操天天干天天干 | 婷婷精品国产一区二区三区日韩 | 国产精品白浆视频 | 中文字幕 在线 一 二 | 丝袜av一区 | 在线播放精品一区二区三区 | 911av视频| 91精品网站在线观看 | 91黄色小视频 | 激情久久一区二区三区 | 99久久久国产精品免费观看 | 久草干| 国产精品精品久久久久久 | 久久香蕉电影网 | 性色av一区二区三区在线观看 | 波多野结衣最新 | 在线观看视频一区二区三区 | 天天做天天爽 | 国产三级香港三韩国三级 | www日 | 中文字幕视频免费观看 | 久久免费视频3 | 国产永久免费观看 | 91精品人成在线观看 | 国产精品一区免费看8c0m | 免费试看一区 | 99久久久免费视频 | 日韩三级不卡 | 91久久久国产精品 | 黄色免费在线看 | 97av影院 | 91人人爽久久涩噜噜噜 | 免费国产在线视频 | 亚洲日本va午夜在线电影 | 四虎影视成人永久免费观看亚洲欧美 | 欧美精品久久久久 | 2022久久国产露脸精品国产 | 91在线精品秘密一区二区 | 黄色av在| 色香蕉网| 国产精彩视频一区 | 久久九九精品久久 | 日韩精品视频免费在线观看 | av中文字幕日韩 | 黄色毛片大全 | 国产女人40精品一区毛片视频 | 天天搞天天 | 免费亚洲成人 | 免费开视频 | 国产一区二区电影在线观看 | 久草视频免费在线观看 | 天天干.com | 国产区在线视频 | 日韩欧美视频一区二区三区 | 日韩,中文字幕 | 国产精品日韩精品 | 久久天天拍 | 91视频专区| 亚洲欧美日韩精品久久久 | 最近最新最好看中文视频 | 色婷婷天天干 | 射综合网 | 中文字幕在线人 | 日韩精品中文字幕久久臀 | 亚洲最新合集 | 免费在线色 | 伊人五月天.com | 亚洲欧美一区二区三区孕妇写真 | 中文字幕亚洲欧美日韩2019 | 五月天激情开心 | 国产高清视频在线播放一区 | av中文字幕在线播放 | 欧美国产大片 | 97精品国产手机 | 精品亚洲欧美一区 | 九九九九九九精品任你躁 | av在线电影网站 | 国产黄| 一区二区三区久久精品 | 欧美日韩一区久久 | 天天综合天天做天天综合 | 欧美与欧洲交xxxx免费观看 | 天天爽夜夜爽人人爽曰av | 久久字幕 | 91香蕉视频在线下载 | 久久www免费视频 | 黄色国产在线 | 亚洲精品久久久久www | 97在线视频免费观看 | 国产h在线观看 | 久久精品中文字幕 | 久久精品—区二区三区 | 中文在线免费一区三区 | 91九色porny蝌蚪视频 | 国产一区高清在线 | 特级毛片爽www免费版 | 俺要去色综合狠狠 | 中文字幕 国产视频 | 日韩精品在线视频免费观看 | 国产精品人成电影在线观看 | 国产精品久久久久久久久久久免费 | 国产精品永久免费在线 | 欧美福利精品 | 久久精品国产一区 | 国产黄色片免费在线观看 | 日韩城人在线 | 一级黄色电影网站 | 国产精品日韩 | 91视频电影 | 亚洲欧洲av在线 | 国产爽妇网 | 欧美视频在线观看免费网址 | 黄色片网站av | 丁香花在线视频观看免费 | 在线中文字幕网站 | www.激情五月.com | 91人人人 | 色吧久久| 视频在线亚洲 | 精品久久影院 | 日日躁夜夜躁aaaaxxxx | 成人免费xxxxxx视频 | 高清av中文在线字幕观看1 | 亚洲理论影院 | 国产精品免费视频久久久 | 91精品在线免费观看视频 | 久久综合久久综合这里只有精品 | 免费黄色在线网站 | 欧美国产日韩激情 | 97超碰网| 91精品啪在线观看国产线免费 | 日日干 天天干 | 永久中文字幕 | 免费欧美精品 | 久久久久亚洲精品成人网小说 | 午夜在线免费观看 | 国产无套一区二区三区久久 | 韩国av在线播放 | 欧美一级视频免费看 | 精品国产亚洲一区二区麻豆 | 欧美91av| 久久精品官网 | 中文在线8资源库 | 国产精品久久久久久久久久了 | 天天操天天操一操 | 少妇高潮流白浆在线观看 | 国产小视频国产精品 | 中文字幕在线观看你懂的 | 成人影片在线免费观看 | 亚洲午夜久久久久久久久 | 99久久精品免费看国产麻豆 | 深夜免费小视频 | 欧美色综合天天久久综合精品 | 色综合久久综合网 | 日韩特黄av | 天天操天天操天天操天天操天天操 | av天天澡天天爽天天av | 欧美最新大片在线看 | 欧美一区二区精美视频 | 国产精品2018| 中文字幕一区二区三区精华液 | 国产精品福利av | 国产高清视频在线免费观看 | www.五月天婷婷 | 久久久久综合视频 | 国产视频精品免费 | 欧美日韩精品在线免费观看 | 日韩高清毛片 | 成人一级片在线观看 | 狠狠干夜夜爱 | 黄色三级免费观看 | 91在线精品一区二区 | 日韩欧美成 | 日韩精品一区二区久久 | 久草免费新视频 | 狠狠做六月爱婷婷综合aⅴ 日本高清免费中文字幕 | 成人久久久久久久久久 | 在线黄色国产 | 国产色一区 | 精品国产乱码久久久久久久 | 国产精品va | 亚洲免费av电影 | 午夜美女av | 国产精品成人一区二区三区吃奶 | 四虎影视成人永久免费观看亚洲欧美 | 国产精在线 | 99热在线观看 | 国产精品自在线 | 国精产品999国精产 久久久久 | 欧美性色19p | 99久久这里有精品 | 欧美性色综合网站 | 91视频麻豆 | 免费人成网ww44kk44 | 偷拍精偷拍精品欧洲亚洲网站 | 久久1区 | 国产精品网红直播 | 久久久久 | 福利一区二区 | 五月婷婷黄色网 | 伊人黄色网 | 又黄又爽的免费高潮视频 | 99精品视频免费看 | 91av视频播放 | 黄色大片日本 | japanesexxxhd奶水 91在线精品一区二区 | 99精品热视频| 欧美精品免费一区二区 | 精品一区二区在线观看 | 美女露久久| 999视频在线观看 | 精品久久久久亚洲 | 久久激五月天综合精品 | 尤物一区二区三区 | 亚洲天天综合 | 五月天激情综合 | 996久久国产精品线观看 | 亚洲激情中文 | 一区二区三区免费在线播放 | 97**国产露脸精品国产 | 99视频这里有精品 | 日本午夜在线观看 | 人人爽久久久噜噜噜电影 | 91色亚洲 | 精品一区中文字幕 | 2024av| 亚洲精品视频免费看 | 91精品黄色 | 中文字幕视频免费观看 | 日韩av专区 | 最新av在线播放 | 国产乱码精品一区二区三区介绍 | 高清av中文在线字幕观看1 | 欧美亚洲国产日韩 | 国产精品美女久久久久久 | 首页国产精品 | 日韩高清在线不卡 | 国产精品99久久久久久宅男 | 五月天久久婷 | 91污视频在线 | 成人av片免费观看app下载 | aaaaaa毛片| av手机在线播放 | 国内免费的中文字幕 | 在线观看视频国产一区 | 亚洲午夜精品一区二区三区电影院 | 亚洲影视资源 | 久久不射影院 | 亚洲另类交 | 免费在线观看av网站 | 婷婷五天天在线视频 | 亚洲国产精品人久久电影 | 免费在线观看成人小视频 | 午夜精品电影一区二区在线 | 日本中文字幕影院 | 中文字幕美女免费在线 | 亚洲视频久久久 | 国产手机在线观看 | 波多野结衣在线视频免费观看 | 久久刺激视频 | 国产精品第52页 | av免费线看 | 五月婷婷黄色网 | 日韩三级视频在线观看 | 国产日韩欧美在线播放 | 天堂素人在线 | 五月天色站| 久草在线免费色站 | 正在播放国产精品 | 成人性生爱a∨ | 中文字幕视频网 | av中文字幕在线电影 | 免费在线观看国产黄 | 最近中文字幕在线 | 久久一区二区三区四区 | 国产精品欧美久久久久三级 | 日韩免费高清 | 国产二区av | 成人av电影在线播放 | 日韩免费av网址 | 日韩中文字幕a | 黄在线 | av丁香| 久久黄色精品视频 | 在线精品亚洲一区二区 | 91成人免费在线 | 狠狠操综合网 | 免费观看一区二区 | 99久久日韩精品免费热麻豆美女 | 91视频a| 日韩伦理片hd | 日一日操一操 | 91精品久久久久久久91蜜桃 | 这里只有精品视频在线观看 | 亚洲高清在线视频 | 国内揄拍国内精品 | 欧美日韩中文国产一区发布 | 亚洲在线精品 | 国产精品成人自拍 | 一区二区亚洲精品 | 欧美激情综合五月色丁香 | 中文字字幕在线 | 丁香花在线视频观看免费 | 国产小视频在线 | 麻豆视频免费在线 | 久久精品毛片基地 | 精品人人爽| 亚州精品成人 | 人人看人人爱 | 超碰在线最新地址 | 色网站在线免费 | 爱爱av在线| 欧美日韩一区三区 | 久热只有精品 | 九九亚洲精品 | 国精产品满18岁在线 | 午夜91在线 | 亚洲精品乱码久久久久v最新版 | 久久激情精品 | 五月综合色 | av一级久久 | 成人av网站在线观看 | 久久久久久久久久久久久久免费看 | 国产精品久久久久久电影 | 色综合久久久久综合99 | 九九99 | 91中文字幕永久在线 | 久久精品福利视频 | 亚州av免费 | 日韩电影久久 | 亚洲精品资源在线观看 | 婷婷av电影 | 人人讲下载 | 久久久高清一区二区三区 | 久久综合亚洲鲁鲁五月久久 | 99精品国产福利在线观看免费 | 最新日韩视频在线观看 | 天天色天天射天天操 | 国产96在线视频 | 天干啦夜天干天干在线线 | 四虎成人精品永久免费av | 手机av在线免费观看 | 成av人电影 | 2017狠狠干| av黄色亚洲 | 精品国产乱码久久久久久天美 | 欧美日韩国产一区二区在线观看 | 成年人国产在线观看 | 精品99久久 | 91日韩在线视频 | 国产色视频网站 | 天天干,天天操,天天射 | 日韩一区在线播放 | 亚洲精品国产精品99久久 | 草久久久久 | 特黄一级毛片 | 超碰免费在线公开 | 欧美日韩精品二区第二页 | 最近日本韩国中文字幕 | 在线日韩一区 | 中文字幕高清在线播放 | 在线观看成人国产 | 天天弄天天干 | 久久国产影院 | www.夜夜爱 | 一区二区中文字幕在线观看 | 久久97超碰| 日韩在线视频网站 | 日本精品午夜 | 久久免费国产精品1 | 久久与婷婷 | 在线导航福利 | 九九精品视频在线观看 | 四虎国产精品成人免费影视 | 婷婷播播网| 久久99国产精品免费网站 | 国产精品专区h在线观看 | 国产中文字幕在线免费观看 | 午夜天使| 国产精品 日韩 欧美 | 日韩欧美在线观看 | 99久久成人 | 亚洲综合色激情五月 | 人人爽人人澡人人添人人人人 | 成人一区二区在线观看 | 国产综合激情 | 免费看在线看www777 | 五月婷婷在线播放 | 国产亚洲高清视频 | 五月婷婷视频在线 | 国产专区视频在线观看 | 日韩精品aaa | 中文字幕在线不卡国产视频 | 日韩专区在线 | 日本丰满少妇免费一区 | 麻豆视频国产 | 五月激情天 | 国产精品免费在线 | 欧美国产不卡 | 国产成人精品一区二 | 久久黄色成人 | 黄色小说免费在线观看 | 天天干夜夜夜操天 | 欧美日韩二三区 | 99视频精品免费视频 | av成人在线观看 | 国内精品久久久久久久久久久 | 九九热只有精品 | 久久免费公开视频 | 日本精品久久久久久 | 精品中文字幕在线观看 | 国产视频在线观看免费 | 国产成人精品一区二区三区福利 | 久久精品国产一区二区三区 | 在线观看v片 | 91成人天堂久久成人 | 日韩三级视频在线观看 | 狠狠狠色丁香综合久久天下网 | 欧美大香线蕉线伊人久久 | 中文字幕有码在线 | 99久视频 | 人人插人人费 | 国产麻豆视频在线观看 | 国内小视频在线观看 | 免费观看91| 午夜av剧场 | 天堂av影院 | 国产拍揄自揄精品视频麻豆 | www.99av | 国产大尺度视频 | 欧美怡红院 | 亚洲少妇天堂 | 欧美在线一二区 | 日韩网站在线看片你懂的 | 日本夜夜草视频网站 | 国产麻豆精品传媒av国产下载 | 狠狠操91 | 在线视频一二三 | 99se视频在线观看 | 天天射天 | 久久有精品 | 亚洲精品在线资源 | 911久久 | 欧美精品少妇xxxxx喷水 | 99久久99久久 | av成人免费在线观看 | 国产一区二区网址 | 国产女人免费看a级丨片 | 中文字幕在线免费看线人 | 国产精品激情在线观看 | 中文字幕av网站 | 日本特黄特色aaa大片免费 | 久久九九免费 | 久久99久久99精品免视看婷婷 | 欧美亚洲免费在线一区 | 手机成人免费视频 | 日本公妇在线观看 | 久久久久久久久电影 |