最近の監視プラットフォームの設計では、C / C ++の最も難しい部分はメモリの管理であると常に感じていました。これは、単にnew / deleteやmalloc / freeをはるかに超えています。コードの量が増えると、プログラム構造の複雑さが増します。さまざまな記憶の問題が静かに繁殖します。そしてプラットフォームとして、後のプラグイン拡張は避けられません。長期にわたる買収プラットフォームの特性により、安定性に対する高い要件が提起されています。C#、Java、メモリを管理する仮想マシンではなく、すべてが自分次第です。したがって、nginx、python、luaなどのCの古典がメモリ管理をどのように処理するかを確認したいと思います。
nginxのメモリプールの設計は非常にデリケートであるとインターネット上で言われているので、最初にnginxを見てみましょう。
1.基本構造
まず、nginxメモリプールのいくつかの主要なデータ構造について学びましょう:[参照:./ src / core / ngx_palloc.h / .c]
ngx_pool_data_t(メモリプールデータブロック構造)
1: typedef struct {
2: u_char *last;
3: u_char *end;
4: ngx_pool_t *next;
5: ngx_uint_t failed;
6: } ngx_pool_data_t;
ngx_pool_s(内存池头部结构)
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: };
可以说,ngx_pool_data_t和ngx_pool_s基本构成了nginx内存池的主体结构,下面详细介绍一下nginx内存池的主体结构:
上に示したように、nginxメモリプールは実際にはngx_pool_data_tとngx_pool_sで構成されるリンクリストです。ここで、
ngx_pool_data_t ::
last:はunsigned char型のポインタであり、現在のメモリプール割り当ての最後のアドレスを保存します。つまり、次の割り当てはここから始まります。
end:メモリプールの終了位置。
次へ:メモリプールには多くのメモリブロックがあります。これらのメモリブロックは、このポインタを介してリンクリストにリンクされ、次は次のメモリブロックを指します。
failed:メモリプール割り当ての失敗の数。
ngx_pool_s
d:メモリプールのデータブロック。
max:メモリプール内のデータブロックの最大値。
current:現在のメモリプールを指します。
チェーン:ポインターはngx_chain_t構造体にリンクされています。
large:大容量メモリのリンクリスト。割り当てられたスペースが最大値を超えたときに使用されます。
クリーンアップ:メモリプールのコールバックを解放します
ログ:ログ情報
上記はメモリプールに含まれる主なデータ構造です。可能な限り単純化するために、実際に使用されるときに関連する他のデータ構造を以下に紹介します。
C / C ++ Linuxサーバー開発のエキサイティングなコンテンツには、C / C ++、Linux、Nginx、ZeroMQ、MySQL、Redis、MongoDB、ZK、ストリーミングメディア、P2P、Linuxカーネル、Docker、TCP / IP、coroutine、DPDK、より高度な知識の共有。リンクをクリックして購読し、直接視聴します。
C / C ++ Linuxサーバー開発/ Linuxバックグラウンドアーキテクト-学習ビデオ
ビデオ取得+1035101242(VX同番号)無料取得
2.メモリープールの基本操作
メモリプールの主な外部メソッドは次のとおりです。
メモリプールを作成する |
ngx_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); |
メモリアプリケーション(アライメント) |
void * ngx_palloc(ngx_pool_t * pool、size_t size); |
メモリアプリケーション(整列されていません) |
void * ngx_pnalloc(ngx_pool_t * pool、size_t size); |
メモリクリア |
ngx_int_t ngx_pfree(ngx_pool_t * pool、void * p); |
注意:
メモリプールメソッドを分析する前に、いくつかの主要なメモリ関連関数を紹介する必要があります(./src/Os/Unix(Win32)/ngx_alloc.h/.cを参照)。Win32の紹介のみです。ngx_alloc:( mallocの単純なカプセル化)
1: void *ngx_alloc(size_t size, ngx_log_t *log)
2: {
3: void *p;
4:
5: p = malloc(size);
6: if (p == NULL) {
7: ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
8: "malloc(%uz) failed", size);
9: }
10:
11: ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, log, 0, "malloc: %p:%uz", p, size);
12:
13: return p;
14: }
ngx_calloc:(调用malloc并初始化为0)
1: void *ngx_calloc(size_t size, ngx_log_t *log)
2: {
3: void *p;
4:
5: p = ngx_alloc(size, log);
6:
7: if (p) {
8: ngx_memzero(p, size);
9: }
10:
11: return p;
12: }
ngx_memzero:
1: #define ngx_memzero(buf, n) (void) memset(buf, 0, n)
ngx_free :
1: #define ngx_free free
ngx_memalign:
1: #define ngx_memalign(alignment, size, log) ngx_alloc(size, log)
ここでのアラインメントは、主に一部のUNIXプラットフォームの動的アラインメントの必要性を目的としており、POSIX 1003.1dによって提供されるposix_memalign()をカプセル化します。ほとんどの場合、コンパイラーとCライブラリーはアラインメントの問題に透過的に対処するのに役立ちます。マクロNGX_HAVE_POSIX_MEMALIGNを介してnginxで制御します。
2.1、メモリプールの作成(ngx_create_pool)
ngx_create_poolは、メモリプールを作成するために使用されます。作成するときは、初期サイズを渡します。
1: ngx_pool_t *
2: ngx_create_pool(size_t size, ngx_log_t *log)
3: {
4: ngx_pool_t *p;
5:
6: p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);
7: if (p == NULL) {
8: return NULL;
9: }
10:
11: p->d.last = (u_char *) p + sizeof(ngx_pool_t);//初始状态:last指向ngx_pool_t结构体之后数据取起始位置
12: p->d.end = (u_char *) p + size;//end指向分配的整个size大小的内存的末尾
13: p->d.next = NULL;
14: p->d.failed = 0;
15: //#define NGX_MAX_ALLOC_FROM_POOL (ngx_pagesize - 1)
16: //内存池最大不超过4095,x86中页的大小为4K
17: size = size - sizeof(ngx_pool_t);
18: p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;
19:
20: p->current = p;
21: p->chain = NULL;
22: p->large = NULL;
23: p->cleanup = NULL;
24: p->log = log;
25:
26: return p;
27: }
Nginxのメモリ管理は、大容量メモリと小容量メモリに分けられます。要求された特定のメモリが特定の値より大きい場合は、大容量メモリからスペースを割り当てる必要があります。それ以外の場合は、小容量メモリからスペースを割り当てます。nginxのメモリプールのサイズは、作成時に設定されます。後でメモリの小さなブロックが割り当てられたときに、メモリが十分でない場合は、元のメモリプールではなくメモリ文字列をメモリプールに再作成します。 。大きなメモリブロックを割り当てる場合は、大きなメモリプールと呼ばれるメモリプールの外部にスペースを再配分することで管理されます。
2.2、メモリアプリケーション
ngx_palloc
1: void *
2: ngx_palloc(ngx_pool_t *pool, size_t size)
3: {
4: u_char *m;
5: ngx_pool_t *p;
6:
7: if (size <= pool->max) {//如果申请的内存大小大于内存池的max值,则走另一条路,申请大内存
8:
9: p = pool->current;
10:
11: do {
12: m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT);//对内存地址进行对齐处理
13:
14: if ((size_t) (p->d.end - m) >= size) {//如果在当前内存块有效范围内,进行内存指针的移动
15: p->d.last = m + size;
16:
17: return m;
18: }
19:
20: p = p->d.next;//如果当前内存块有效容量不够分配,则移动到下一个内存块进行分配
21:
22: } while (p);
23:
24: return ngx_palloc_block(pool, size);
25: }
26:
27: return ngx_palloc_large(pool, size);
28: }
ここで注意すべき点:
1. ngx_align_ptr、これはメモリアドレスを丸めるために使用されるマクロです。これは非常にデリケートで、1文で実行できます。効果は自明です。丸めにより、CPUがメモリを読み取る回数を減らし、パフォーマンスを向上させることができます。ここでmallocやその他の関数を呼び出してメモリを適用することは実際には意味がないため、ポインタマークを移動するだけで、メモリの配置が役に立たないため、自分で行う必要があります。
1: #define ngx_align_ptr(p, a) \
2: (u_char *) (((uintptr_t) (p) + ((uintptr_t) a - 1)) & ~((uintptr_t) a - 1))
2、ngx_palloc_block(ngx_pool_t * pool、size_t size)
この関数は、新しいメモリブロックを割り当て、プールメモリプール用に新しいメモリブロックを開き、サイズメモリの使用を申請するために使用されます。
1: static void *
2: ngx_palloc_block(ngx_pool_t *pool, size_t size)
3: {
4: u_char *m;
5: size_t psize;
6: ngx_pool_t *p, *new, *current;
7:
8: psize = (size_t) (pool->d.end - (u_char *) pool);//计算内存池第一个内存块的大小
9:
10: m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);//分配和第一个内存块同样大小的内存块
11: if (m == NULL) {
12: return NULL;
13: }
14:
15: new = (ngx_pool_t *) m;
16:
17: new->d.end = m + psize;//设置新内存块的end
18: new->d.next = NULL;
19: new->d.failed = 0;
20:
21: m += sizeof(ngx_pool_data_t);//将指针m移动到d后面的一个位置,作为起始位置
22: m = ngx_align_ptr(m, NGX_ALIGNMENT);//对m指针按4字节对齐处理
23: new->d.last = m + size;//设置新内存块的last,即申请使用size大小的内存
24:
25: current = pool->current;
26: //这里的循环用来找最后一个链表节点,这里failed用来控制循环的长度,如果分配失败次数达到5次,
27: //就忽略,不需要每次都从头找起
28: for (p = current; p->d.next; p = p->d.next) {
29: if (p->d.failed++ > 4) {
30: current = p->d.next;
31: }
32: }
33:
34: p->d.next = new;
35:
36: pool->current = current ? current : new;
37:
38: return m;
39: }
3、ngx_palloc_large(ngx_pool_t * pool、size_t size)
ngx_pallocでは、最初に、要求されたメモリサイズがメモリブロックの最大制限を超えているかどうかを判断し、超えている場合は、ngx_palloc_largeを直接呼び出して、ラージメモリブロックの割り当てプロセスに入ります。
1: static void *
2: ngx_palloc_large(ngx_pool_t *pool, size_t size)
3: {
4: void *p;
5: ngx_uint_t n;
6: ngx_pool_large_t *large;
7: // 直接在系统堆中分配一块空间
8: p = ngx_alloc(size, pool->log);
9: if (p == NULL) {
10: return NULL;
11: }
12:
13: n = 0;
14: // 查找到一个空的large区,如果有,则将刚才分配的空间交由它管理
15: for (large = pool->large; large; large = large->next) {
16: if (large->alloc == NULL) {
17: large->alloc = p;
18: return p;
19: }
20:
21: if (n++ > 3) {
22: break;
23: }
24: }
25: //为了提高效率, 如果在三次内没有找到空的large结构体,则创建一个
26: large = ngx_palloc(pool, sizeof(ngx_pool_large_t));
27: if (large == NULL) {
28: ngx_free(p);
29: return NULL;
30: }
31:
32: large->alloc = p;
33: large->next = pool->large;
34: pool->large = large;
35:
36: return p;
37: }
2.3、メモリプールのリセット
ngx_reset_pool
1: void
2: ngx_reset_pool(ngx_pool_t *pool)
3: {
4: ngx_pool_t *p;
5: ngx_pool_large_t *l;
6: //释放所有大块内存
7: for (l = pool->large; l; l = l->next) {
8: if (l->alloc) {
9: ngx_free(l->alloc);
10: }
11: }
12:
13: pool->large = NULL;
14: // 重置所有小块内存区
15: for (p = pool; p; p = p->d.next) {
16: p->d.last = (u_char *) p + sizeof(ngx_pool_t);
17: }
18: }
2.4、メモリプールのクリーニング
ngx_pfree
1: ngx_int_t
2: ngx_pfree(ngx_pool_t *pool, void *p)
3: {
4: ngx_pool_large_t *l;
5: //只检查是否是大内存块,如果是大内存块则释放
6: for (l = pool->large; l; l = l->next) {
7: if (p == l->alloc) {
8: ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
9: "free: %p", l->alloc);
10: ngx_free(l->alloc);
11: l->alloc = NULL;
12:
13: return NGX_OK;
14: }
15: }
16:
17: return NGX_DECLINED;
18: }
したがって、Nginxメモリプール内の大きなメモリブロックと小さなメモリブロックの割り当てと解放は異なります。メモリプールを使用する場合、割り当てにngx_pallocを使用し、解放にngx_pfreeを使用できます。大容量メモリの場合、これは問題ありませんが、小容量メモリの場合、割り当てられた小容量メモリは解放されません。ラージメモリブロックの割り当てでは最初の3つのメモリブロックのみがチェックされるため、それ以外の場合はメモリが直接割り当てられるため、ラージメモリブロックの解放はタイムリーである必要があります。
ngx_pool_cleanup_s
Nginxメモリプールは、コールバック関数を介した外部リソースのクリーニングをサポートします。ngx_pool_cleanup_tは、リンクリストとしてメモリプールに格納されるコールバック関数構造体です。メモリプールが破棄されると、これらのコールバック関数が周期的に呼び出され、データがクリーンアップされます。
1: struct ngx_pool_cleanup_s {
2: ngx_pool_cleanup_pt handler;
3: void *data;
4: ngx_pool_cleanup_t *next;
5: };
その中で
ハンドラー:コールバック関数ポインターです。
data:コールバックするとき、このデータをコールバック関数に渡します。
next://次のコールバック関数構造を指す;
独自のコールバック関数を追加する必要がある場合は、ngx_pool_cleanup_addを呼び出してngx_pool_cleanup_tを取得し、ハンドラーをクリーンアップ関数として設定し、データをクリーンアップするデータとして設定する必要があります。このようにして、ハンドラーは周期的に呼び出され、ngx_destroy_pool内のデータをクリーンアップします。
例:開いているファイル記述子をリソースとしてメモリプールにマウントし、ファイル記述を閉じてハンドラーに登録する関数を提供すると、メモリプールが解放されると、提供されているファイルを閉じる関数が呼び出されます。ファイル記述子リソースを処理します。
2.5、メモリプールの破壊
ngx_destroy_pool
ngx_destroy_pool関数は、メモリプールを破棄するために使用されます。
1: void
2: ngx_destroy_pool(ngx_pool_t *pool)
3: {
4: ngx_pool_t *p, *n;
5: ngx_pool_large_t *l;
6: ngx_pool_cleanup_t *c;
7:
8: //首先调用所有的数据清理函数
9: for (c = pool->cleanup; c; c = c->next) {
10: if (c->handler) {
11: ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
12: "run cleanup: %p", c);
13: c->handler(c->data);
14: }
15: }
16:
17: //释放所有的大块内存
18: for (l = pool->large; l; l = l->next) {
19:
20: ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0, "free: %p", l->alloc);
21:
22: if (l->alloc) {
23: ngx_free(l->alloc);
24: }
25: }
26:
27: //最后释放所有内存池中的内存块
28: for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
29: ngx_free(p);
30:
31: if (n == NULL) {
32: break;
33: }
34: }
35: }