Autocultivo del programador, grupo de memoria de aprendizaje de código fuente nginx

Siempre he sentido que la parte más difícil de C / C ++ es la gestión de la memoria, que es mucho más que new / delete y malloc / free. A medida que aumenta la cantidad de código, aumenta la complejidad de la estructura del programa. Varios problemas de memoria se reproducen silenciosamente. Y como plataforma, las posteriores extensiones de complementos son inevitables. Las características de la plataforma de adquisición de larga duración plantean altos requisitos de estabilidad. No c #, java, no hay una máquina virtual para administrar la memoria por usted, todo depende de usted. Así que quiero ver cómo los clásicos de C como nginx, python y lua tratan la gestión de la memoria.
Echemos un vistazo a nginx primero, porque se dice en Internet que el grupo de memoria de nginx está muy bien diseñado:

1. Estructura básica

先来学习一下nginx内存池的几个主要数据结构:
 ngx_pool_data_t(内存池数据块结构)
typedef struct {
    
    
  u_char               *last;
  u_char               *end;
  ngx_pool_t           *next;
  ngx_uint_t            failed;
 } ngx_pool_data_t;

ngx_pool_s (estructura de encabezado de grupo de memoria)

 struct ngx_pool_s {
    
    
     ngx_pool_data_t       d;
     size_t                max;
     ngx_pool_t           *current;
     ngx_chain_t          *chain;
     ngx_pool_large_t     *large;
     ngx_pool_cleanup_t   *cleanup;
     ngx_log_t            *log;
 };

Se puede decir que ngx_pool_data_t y ngx_pool_s básicamente constituyen la estructura principal del grupo de memoria nginx. A continuación, se describe la estructura principal del grupo de memoria nginx en detalle:
Inserte la descripción de la imagen aquí
como se muestra arriba, el grupo de memoria nginx es en realidad una lista vinculada compuesta por ngx_pool_data_t y ngx_pool_s, donde:

ngx_pool_data_t : :

last: Es un puntero de tipo char sin firmar, que guarda la última dirección de la asignación actual del grupo de memoria, es decir, la siguiente asignación comienza desde aquí.

fin: la posición final del grupo de memoria;

Siguiente: Hay muchos bloques de memoria en el grupo de memoria Estos bloques de memoria están vinculados en una lista vinculada a través de este puntero, y el siguiente apunta al siguiente bloque de memoria.

fallado: el número de errores de asignación del grupo de memoria.

ngx_pool_s

d: el bloque de datos del grupo de memoria;

max: el valor máximo de bloques de datos en el grupo de memoria;

actual: apunta al grupo de memoria actual;

cadena: el puntero está vinculado a una estructura ngx_chain_t;

large: Lista de memoria grande vinculada, que se utiliza cuando el espacio asignado excede el máximo;

limpieza: libere la devolución de llamada del grupo de memoria

log: información de registro Lo
anterior es la estructura de datos principal involucrada en la reserva de memoria.Para simplificar tanto como sea posible, otras estructuras de datos involucradas se presentarán a continuación cuando se utilicen realmente.

Inserte la descripción de la imagen aquí
(Más C / C ++, Linux, Nginx, ZeroMQ, MySQL, Redis, fastdfs, MongoDB, ZK, medios de transmisión, CDN, P2P, K8S, Docker, TCP / IP, coroutine, DPDK, etc. Material didáctico más grupo 960994558)

2. Funcionamiento básico de la agrupación de memoria

Los principales métodos externos del grupo de memoria son:

Crear grupo de memoria ngx_pool_t * ngx_create_pool (tamaño_t tamaño, ngx_log_t * registro);
Destruye la reserva de memoria void ngx_destroy_pool (ngx_pool_t * grupo);
Restablecer grupo de memoria void ngx_reset_pool (ngx_pool_t * grupo);
Aplicación de memoria (alineación) void * ngx_palloc (ngx_pool_t * grupo, tamaño_t tamaño);
Aplicación de memoria (no alineada) void * ngx_pnalloc (ngx_pool_t * grupo, tamaño_t tamaño);
Memoria clara ngx_int_t ngx_pfree (ngx_pool_t * grupo, void * p);

Nota:
Antes de analizar el método del grupo de memoria, debe presentar varias funciones principales relacionadas con la memoria. A
continuación, se incluye solo una introducción a Win32:

ngx_alloc: (solo una simple encapsulación de malloc)


```cpp

```cpp
void *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;
   }

ngx_calloc: (llamar a malloc e inicializar a 0)

void *ngx_calloc(size_t size, ngx_log_t *log)
    {
    
    
        void  *p;
     
        p = ngx_alloc(size, log);
     
        if (p) {
    
    
            ngx_memzero(p, size);
        }
    
       return p;
   }

ngx_memzero:

#define ngx_memzero(buf, n)       (void) memset(buf, 0, n)

ngx_free:

#define ngx_free          free

ngx_memalign:

 #define ngx_memalign(alignment, size, log)  ngx_alloc(size, log)

La alineación aquí está dirigida principalmente a la necesidad de alineación dinámica de algunas plataformas Unix y encapsula el posix_memalign () proporcionado por POSIX 1003.1d. En la mayoría de los casos, el compilador y la biblioteca C lo ayudan a lidiar con el problema de alineación de manera transparente. A través de la macro NGX_HAVE_POSIX_MEMALIGN para controlar en nginx;

2.1. Creación de un pool de memoria (ngx_create_pool)
ngx_create_pool se usa para crear un pool de memoria. Cuando lo creamos, pasamos nuestro tamaño inicial:

 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);//初始状态:last指向ngx_pool_t结构体之后数据取起始位置
       p->d.end = (u_char *) p + size;//end指向分配的整个size大小的内存的末尾
       p->d.next = NULL;
       p->d.failed = 0;
       //#define NGX_MAX_ALLOC_FROM_POOL  (ngx_pagesize - 1)
       //内存池最大不超过4095,x86中页的大小为4K
       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;
  }

La administración de memoria de Nginx se divide en memoria grande y memoria pequeña.Cuando una determinada memoria solicitada es mayor que un cierto valor, necesita asignar espacio de memoria grande, de lo contrario, asignar espacio de memoria pequeña.
El tamaño del grupo de memoria en nginx se establece cuando se crea. Cuando se asigna un pequeño bloque de memoria más adelante, si la memoria no es suficiente, es para volver a crear una cadena de memoria en el grupo de memoria en lugar del grupo de memoria original Expandir. Cuando se va a asignar un gran bloque de memoria, se administra mediante la redistribución del espacio fuera del grupo de memoria, que se denomina grupo de memoria grande.

2.2, aplicación de memoria
ngx_palloc

 void *
    ngx_palloc(ngx_pool_t *pool, size_t size)
    {
    
    
        u_char      *m;
        ngx_pool_t  *p;
        if (size <= pool->max) {
    
    //如果申请的内存大小大于内存池的max值,则走另一条路,申请大内存
           p = pool->current;  
           do {
    
    
               m = ngx_align_ptr(p->d.last, 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);
      }
     return ngx_palloc_large(pool, size);
 }

Puntos a tener en cuenta aquí:

1. ngx_align_ptr, esta es una macro utilizada para redondear direcciones de memoria, es muy delicada y se puede hacer en una sola frase. La función es evidente, el redondeo puede reducir la cantidad de veces que la CPU lee la memoria y mejorar el rendimiento. Debido a que realmente no tiene sentido llamar a malloc y otras funciones para solicitar memoria, simplemente mueva la marca del puntero, para que el compilador de C no pueda ayudarlo, debe hacerlo usted mismo.

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

2 、 ngx_palloc_block (ngx_pool_t * grupo, tamaño_t tamaño)

Esta función se utiliza para asignar un nuevo bloque de memoria, abrir un nuevo bloque de memoria para el grupo de memoria del grupo y solicitar el uso de la memoria de tamaño;

 static void *
 ngx_palloc_block(ngx_pool_t *pool, size_t size)
 {
    
    
     u_char      *m;
    size_t       psize;
    ngx_pool_t  *p, *new, *current; 
   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;//设置新内存块的end
   new->d.next = NULL;
    new->d.failed = 0;
    m += sizeof(ngx_pool_data_t);//将指针m移动到d后面的一个位置,作为起始位置
     m = ngx_align_ptr(m, NGX_ALIGNMENT);//对m指针按4字节对齐处理
    new->d.last = m + size;//设置新内存块的last,即申请使用size大小的内存
 
    current = pool->current;
    //这里的循环用来找最后一个链表节点,这里failed用来控制循环的长度,如果分配失败次数达到5次,
     //就忽略,不需要每次都从头找起
    for (p = current; p->d.next; p = p->d.next) {
    
    
         if (p->d.failed++ > 4) {
    
    
              current = p->d.next;
           }
       }
       p->d.next = new;
   pool->current = current ? current : new;
       return m;
  }

3. ngx_palloc_large (ngx_pool_t * pool, size_t size)
primero determinará si el tamaño de memoria solicitado excede el límite máximo del bloque de memoria en ngx_palloc. Si lo supera, llame directamente a ngx_palloc_large para ingresar al proceso de asignación de bloque de memoria grande;

 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;
    // 查找到一个空的large区,如果有,则将刚才分配的空间交由它管理  
      for (large = pool->large; large; large = large->next) {
    
    
         if (large->alloc == NULL) {
    
    
            large->alloc = p;
             return p;
         }
  
        if (n++ > 3) {
    
    
             break;
         }
     }
     //为了提高效率, 如果在三次内没有找到空的large结构体,则创建一个
     large = ngx_palloc(pool, sizeof(ngx_pool_large_t));
     if (large == NULL) {
    
    
         ngx_free(p);
        return NULL;
    }
     large->alloc = p;
     large->next = pool->large;
     pool->large = large;
     return p;
 }

Inserte la descripción de la imagen aquí
2.3, reinicio del grupo de memoria

ngx_reset_pool
void
ngx_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);
         }
     }
   pool->large = NULL;
     // 重置所有小块内存区  
     for (p = pool; p; p = p->d.next) {
    
    
        p->d.last = (u_char *) p + sizeof(ngx_pool_t);
    }
 }

2.4, limpieza del grupo de memoria

ngx_pfree
 ngx_int_t
ngx_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;
 }

Por lo tanto, la asignación y liberación de grandes bloques de memoria y pequeños bloques de memoria en el grupo de memoria de Nginx son diferentes. Cuando usamos el grupo de memoria, podemos usar ngx_palloc para la asignación y ngx_pfree para liberar. Para memoria grande, esto no es un problema, pero para memoria pequeña, la memoria pequeña asignada no se liberará. Debido a que la asignación de un bloque de memoria grande solo verifica los primeros 3 bloques de memoria, de lo contrario, la memoria se asigna directamente, por lo que la liberación del bloque de memoria grande debe ser oportuna.

ngx_pool_cleanup_s

El grupo de memoria de Nginx admite la limpieza de recursos externos mediante funciones de devolución de llamada. ngx_pool_cleanup_t es una estructura de función de devolución de llamada, que se almacena en el grupo de memoria como una lista vinculada. Cuando se destruye el grupo de memoria, estas funciones de devolución de llamada se invocan cíclicamente para limpiar los datos.

struct ngx_pool_cleanup_s {
    
    
  ngx_pool_cleanup_pt   handler;
  void                 *data;
    ngx_pool_cleanup_t   *next;
 };

entre ellos

manejador: es el puntero de la función de devolución de llamada;

datos: al devolver la llamada, pase estos datos a la función de devolución de llamada;

siguiente: // Apunta a la siguiente estructura de función de devolución de llamada;

Si necesitamos agregar nuestra propia función de devolución de llamada, debemos llamar a ngx_pool_cleanup_add para obtener un ngx_pool_cleanup_t, luego configurar el controlador como nuestra función de limpieza y configurar los datos como los datos que queremos limpiar. De esta forma, se llamará cíclicamente al controlador para limpiar los datos en ngx_destroy_pool;

Por ejemplo: podemos montar un descriptor de archivo abierto como un recurso para el grupo de memoria y proporcionar una función para cerrar la descripción del archivo para registrarlo en el controlador, luego, cuando se libere el grupo de memoria, llamará a la función de cierre de archivo que proporcionamos a Procesar los recursos del descriptor de archivos. Inserte la descripción de la imagen aquí
2.5, destrucción del grupo de memoria

ngx_destroy_pool

La función ngx_destroy_pool se usa para destruir un grupo de memoria:

void
ngx_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) {
    
    
 
        ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0, "free: %p", l->alloc);
  
        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;
         }
     }
 }

Supongo que te gusta

Origin blog.csdn.net/weixin_52622200/article/details/110492971
Recomendado
Clasificación