Nginx memory pool doesn't understand? This article takes you to see the memory pool of high-performance server

Posted by coelex on Tue, 04 Jan 2022 03:35:05 +0100

nginx memory pool_ pool_ t

Nginx implements its own memory pool, so in nginx_ pool_ T this structure can be seen everywhere. Here we mainly analyze the allocation logic of memory pool.

The memory pool implements the processing of several resources, including small memory, large memory and cleaning resources. It should cover the vast majority of usage scenarios.

Video Explanation related to the article:

C/C++Linux background server development senior architect free learning link: C/C++Linux Server Development Senior Architect / Linux background Architect - learning video

Why do high-performance servers need memory pools? How is memory allocated? How to design memory?

Memory pool and thread pool of Nginx source code analysis

Related structure definition

// Block memory
typedef struct ngx_pool_large_s  ngx_pool_large_t;
struct ngx_pool_large_s {
    ngx_pool_large_t     *next;         // Next large memory pool
    void                 *alloc;        // Actual allocated memory
};

// Small memory pool
typedef struct {
    u_char               *last;         // Allocable memory start address
    u_char               *end;          // End address of allocable memory
    ngx_pool_t           *next;         // Point to memory management structure
    ngx_uint_t            failed;       // Number of memory allocation failures
} ngx_pool_data_t;

// Memory pool management structure
typedef struct ngx_pool_s            ngx_pool_t;
struct ngx_pool_s {
    ngx_pool_data_t       d;            // Small memory pool
    size_t                max;          // Small memory is the largest allocated memory. Evaluate whether large memory or small memory
    ngx_pool_t           *current;      // Small memory pool currently starting allocation
    ngx_chain_t          *chain;        // chain
    ngx_pool_large_t     *large;        // Block memory
    ngx_pool_cleanup_t   *cleanup;      // Resources to be cleared
    ngx_log_t            *log;          // Log object
};

ngx_pool_t is the management structure of the entire memory pool. This structure may have multiple memory pool objects, but for users, the first access is always the one returned during creation. Multiple ngx_pool_t connect through d.next. current points to the small memory pool that is currently allocated. Note ngx_pool_data_t at the beginning of the memory pool structure, you can perform type conversion to access different members.

realization

memory alignment

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

Reference ngx_align value, aligned macro analysis, ngx_align_ptr homology

Create memory pool

The maximum value of Max is 4095. When the memory size applied from the memory pool is greater than max, it will not be allocated from the small memory.

ngx_uint_t  ngx_pagesize = getpagesize();  // 4096 on Linux
#define NGX_POOL_ALIGNMENT 16
#define NGX_MAX_ALLOC_FROM_POOL  (ngx_pagesize - 1)  // 4095

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);  // 16 byte alignment request size memory
    if (p == NULL) {
        return NULL;
    }

    p->d.last = (u_char *) p + sizeof(ngx_pool_t);  // Sets the beginning of allocable memory
    p->d.end = (u_char *) p + size;                 // Sets the end of allocable memory
    p->d.next = NULL;
    p->d.failed = 0;                                // Number of memory allocation failures

    size = size - sizeof(ngx_pool_t);               // Set the maximum amount of small memory that can be allocated (less than 4095)
    p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;

    p->current = p;                                 // Set initial allocation memory pool
    p->chain = NULL;
    p->large = NULL;
    p->cleanup = NULL;
    p->log = log;

    return p;
}

The structure logic of the memory pool after creation is shown in the figure below:

Memory request

The requested memory blocks are distinguished by max

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);
}

Small memory request

current refers to the small memory pool that starts to retrieve the allocated memory every time a memory request is made, and NGX_ palloc_ The parameter pool of small is fixed when the memory pool is not recycled.

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;  // Allocate appropriate memory from current

    do {
        m = p->d.last;

        if (align) {  // Memory alignment required
            m = ngx_align_ptr(m, NGX_ALIGNMENT);
        }

        // The remaining capacity of the current small memory pool meets the requested memory
        if ((size_t) (p->d.end - m) >= size) {
            p->d.last = m + size;

            return m;  // Once the assignment is met, exit directly
        }

        p = p->d.next;  // If not satisfied, find the next small memory pool

    } while (p);

    return ngx_palloc_block(pool, size); // If there is no memory pool that meets the allocation, apply for a small memory pool
}

When a suitable memory is found in the small memory pool, the structure is as follows:

When no small memory pool meets the application, another small memory pool will be applied to meet the allocation. After setting the last and end memory indicators, the memory pool member failed from current will be automatically increased. When the number of failed allocations of the memory pool is greater than 4, it appears that the number of memory allocation failures is too many, According to experience, the next allocation may still fail, so skip this memory pool directly and move current.

C/C++Linux server development senior architect learning document e-book Click linux server learning materials Obtain content knowledge points, including Linux, Nginx, ZeroMQ, MySQL, Redis, thread pool, MongoDB, ZK, Linux kernel, CDN, P2P, epoll, Docker, TCP/IP, collaboration, DPDK, etc. Free learning link: C/C++Linux Server Development Senior Architect / Linux background Architect - learning video

The new memory block is inserted at the end of the memory pool chain table.

#define NGX_ALIGNMENT   sizeof(unsigned long)  // 8

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);  // Each memory pool is the same size

    m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);  // 16 byte alignment request
    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;  // Insert the tail to the end of the linked list

    return m;
}

After allocating a memory pool, the logical structure is as follows:

Block memory request

Large blocks of memory are connected through large and belong to NGX_ create_ NGX returned by pool_ pool_ T structure. malloc allocates memory by an ngx_pool_large_t node to mount, and this NGX_ pool_ large_ The T node is allocated from a small memory pool.

  • In order to avoid the excessive length of large linked list, it takes too long to find idle mounted nodes during traversal, which limits the traversal node to 3. If it does not meet the requirements, it will be allocated directly
  • The header insertion method is inserted into the large linked list, and the new node is also the first to be accessed
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);  // Call malloc
    if (p == NULL) {
        return NULL;
    }

    n = 0;

    for (large = pool->large; large; large = large->next) {  // Find the node with NULL alloc from the linked list in large, and hang the allocated memory on the node
        if (large->alloc == NULL) {
            large->alloc = p;
            return p;
        }

        if (n++ > 3) {  // In order to avoid excessive traversal, the number of times is limited to 0
            break;
        }
    }

    // When traversing NGX_ pool_ large_ When alloc in t node has memory pointed to, allocate an NGX from a small memory block_ pool_ large_ The T node is used to mount the newly allocated large memory
    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;  // Insert the header method into the linked list of large memory
    pool->large = large;

    return p;
}

The structure after the first block memory allocation is as follows:

Full memory pool structure logic

  • All memory pool structures are connected through d.next
  • The current of the first two memory pool structures points to the third memory pool structure
  • All ngx_pool_large_t nodes are allocated from a small memory pool
  • All ngx_pool_large_t nodes are connected to the first memory pool structure
  • ngx_ pool_ large_ alloc of T node is released, but ngx_pool_large_t node is not recycled

summary

ngx_ pool_ Tmemory allocation

  • Access other memory pool structures through current and d.next
  • Insertion mode
    • A small memory pool is inserted to the end of the memory pool chain table by tail interpolation
    • A large block of memory is inserted into the head of the large linked list through the head insertion method
  • Limit times
    • When the number of small memory allocation failures is greater than 4, it will no longer be used as a memory allocation pool
    • Large memory only looks for whether the first three nodes in the large linked list can mount the newly allocated memory
  • Memory alignment: multiple memory alignments reduce the number of memory across cache s

In fact, generally speaking, this is a relatively simple memory pool. There are still some places where memory is wasted. Limiting the number of times can illustrate this situation, but it is also a balance between simplicity, efficiency and memory allocation

Topics: Nginx