Source code analysis of nginx memory pool

Analysis and imitation of nginx memory pool source code

1, nginx introduction

Nginx is a high-performance HTTP and reverse proxy web server. It also provides IMAP/POP3/SMTP services. It is famous for its stability, rich function set, sample configuration files and consumption of low system resources. Its characteristics are less memory and concurrent capability. Chinese mainland users use nginx website: Baidu, Jingdong, Sina, NetEase, Tencent, Taobao, etc.

2, Important macros

//Maximum memory ngx_pagesize: one page 4k (4096)
#define NGX_MAX_ALLOC_FROM_POOL  (ngx_pagesize - 1)

//Default memory pool size, 16k
#define NGX_DEFAULT_POOL_SIZE    (16 * 1024)

//Memory byte aligned number
#define NGX_POOL_ALIGNMENT       16

//Minimum memory pool size
#define NGX_MIN_POOL_SIZE                                                     \
    ngx_align((sizeof(ngx_pool_t) + 2 * sizeof(ngx_pool_large_t)),            \
              NGX_POOL_ALIGNMENT) 

//Adjust the opening of memory to the multiple of adjacent a            
#define ngx_align(d, a)     (((d) + (a - 1)) & ~(a - 1))

3, Important type definition

//Header information of nginx memory pool
typedef struct {
    u_char               *last;
    u_char               *end;
    ngx_pool_t           *next;
    ngx_uint_t            failed;
} ngx_pool_data_t;

// Main structure of memory pool: nginx
struct ngx_pool_s {
ngx_pool_data_t d; // Header of memory pool
size_t max; // Maximum value of small memory allocation
ngx_pool_t *current; // Small memory pool entry pointer 
ngx_chain_t *chain;//Link all memory pools
ngx_pool_large_t *large; // Large memory allocation entry pointer
ngx_pool_cleanup_t *cleanup; // Clean up the entry pointer of the function handler
ngx_log_t *log;//journal
};

typedef struct ngx_pool_s ngx_pool_t;
// Small memory header information
typedef struct {
u_char *last; // Start location of allocable memory
u_char *end; // End of allocable memory
ngx_pool_t *next; // Save the address of the next memory pool
ngx_uint_t failed; // Records the number of times the current memory pool allocation failed
} ngx_pool_data_t;

typedef struct ngx_pool_large_s ngx_pool_large_t;
// Block memory type definition
struct ngx_pool_large_s {
ngx_pool_large_t *next; // Next chunk of memory
void *alloc; // Record the starting address of the allocated block of memory
};

typedef void (*ngx_pool_cleanup_pt)(void *data); // Clean up the type definition of callback function
typedef struct ngx_pool_cleanup_s ngx_pool_cleanup_t;
// The type definition of cleanup operation, including a cleanup callback function, the data passed to the callback function and the address of the next cleanup operation
struct ngx_pool_cleanup_s {
ngx_pool_cleanup_pt handler; // Clean up callback function
void *data; // Pointer passed to callback function
ngx_pool_cleanup_t *next; // Point to the next cleanup operation
};

4, Function interface analysis

ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log); // Create memory pool
void ngx_destroy_pool(ngx_pool_t *pool); // Destroy memory pool
void ngx_reset_pool(ngx_pool_t *pool); // Reset memory pool
void *ngx_palloc(ngx_pool_t *pool, size_t size); // Memory allocation function, supporting memory alignment
void *ngx_pnalloc(ngx_pool_t *pool, size_t size); // Memory allocation function, memory alignment is not supported
void *ngx_pcalloc(ngx_pool_t *pool, size_t size); // Memory allocation function, supporting memory initialization 0
ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p // Memory release (large memory)
ngx_pool_cleanup_t *ngx_pool_cleanup_add(ngx_pool_t *p, size_t size); // Add cleanup handler

1. Create memory pool

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);//Call malloc function
    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;//Use size for smaller pages and one page for larger pages

    p->current = p;
    p->chain = NULL;
    p->large = NULL;
    p->cleanup = NULL;
    p->log = log;

    return p;
}


Summary: open a new memory pool, let last point to the starting address of the newly applied available memory, end point to the end address, and current point to the starting address of the memory pool

2. Small memory allocation

Three main functions of memory application:

//Consider memory alignment
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);
}

//Memory alignment is not considered
void *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);
}

//After successful development, clear all memory
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;
}

Summary: if the current memory block is enough, directly offset the size of the last pointer to open up the memory; If all the current small pieces of memory are checked and are not enough, then open up a band NGX of the same size_ pool_ data_ T the memory block of header information, and then offset the memory. When traversing the previous memory block, fail records the number of memory allocation failures. If more than 4 times, point the current memory block to the starting address of the next memory block. The next allocation starts from this memory block to find out whether there is appropriate memory, and then connect the newly generated memory block to the back of the original memory block.

static ngx_inline void *ngx_palloc_small(ngx_pool_t *pool, size_t size, ngx_uint_t align)
{
    u_char      *m;//As the header pointer of the newly requested memory space
    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) {//The memory space of the memory pool is larger than the requested memory space, which can be allocated
            p->d.last = m + size;//Offset size bytes

            return m;
        }

        p = p->d.next;//If it is empty for the first time, directly jump out of the loop and execute ngx_palloc_block

    } while (p);

    return ngx_palloc_block(pool, size);
}


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);//Open up a new space the size of a pool

    m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);//m points to the starting address of the new space
    if (m == NULL) {
        return NULL;
    }

    new = (ngx_pool_t *) m;//New points to the starting address of the new space

    new->d.end = m + psize;//Point to end
    new->d.next = NULL;
    new->d.failed = 0;

    m += sizeof(ngx_pool_data_t);//m points to the newly opened available memory space. The second memory starts with NGX_ pool_ data_ Header information of T
    m = ngx_align_ptr(m, NGX_ALIGNMENT);//memory alignment 
    new->d.last = m + size;//It refers to the end address of allocated memory and the starting address of free memory
	
	//Apply for memory continuously. When the applied memory is relatively large, open up a new piece of memory. Check whether the first memory block is enough every time. If the four applications of this memory block are not enough, start from the first memory block next time, and so on
    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;
}

3. Block memory allocation

Memory header information

typedef struct ngx_pool_large_s  ngx_pool_large_t;

struct ngx_pool_large_s {//Record the information of large memory header
    ngx_pool_large_t     *next;//Address of the next block of memory
    void                 *alloc;//Memory starting address for storing large blocks of memory
};

Summary: first, open up a large block of memory through malloc, and find out whether there is a released large block of memory in the original small block of memory. If there is, record the header information of the large block of memory here (i.e. record the starting address of the available large block of memory). If it is not found three times, reallocate the large block of memory header directly in the small block of memory, Then point alloc to the newly opened memory space, and finally connect all large blocks of memory through header insertion and next pointer.

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);//The bottom layer calls malloc directly
    if (p == NULL) {
        return NULL;
    }

    n = 0;

    for (large = pool->large; large; large = large->next) {//Find empty alloc field of memory header
        if (large->alloc == NULL) {
            large->alloc = p;//Find it and point it directly to the newly opened memory
            return p;
        }

        if (n++ > 3) {//Can't find
            break;
        }
    }

    large = ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1);//Open up a large block of memory header information in a small block of memory
    if (large == NULL) {
        ngx_free(p);
        return NULL;
    }

    large->alloc = p;//Point to the newly opened available memory
    large->next = pool->large;//Connect large blocks of memory
    pool->large = large;

    return p;
}

4. Reset memory pool

There is no need to provide any memory release function for small memory allocation, because small memory is opened up by pointer offset, so it can't recycle small memory and can only be reset. Therefore, the http web server for short-term connection is tried in. nginx is essentially an http server. It sends a response response to the client after processing the client's request. After that, the server waits for the client to send a request. If the request is not received within a certain period of time, it will actively disconnect and call ngx_reset_pool resets the memory pool and waits for the next client's request

Summary: traverse the large memory and release all the large memory under alloc; Traverse the small memory and assign the small memory header information to the initial state. For the small memory reset, there is a small error, because from the second memory block, the small memory has only data header information and no data operation header information, so the reset operation is divided into two categories, otherwise there will be memory waste.

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

/*  Except for the first memory pool, other memory pools only have ngx_pool_data_t's header information, this operation wastes space
    for (p = pool; p; p = p->d.next) {
        p->d.last = (u_char *) p + sizeof(ngx_pool_t);
        p->d.failed = 0;
    }
*/
	//correct
	//Processing the first memory pool
	p = pool;
	p->d.last = (u_char *) p + sizeof(ngx_pool_t);
    p->d.failed = 0;
	
	//The second memory pool begins to cycle to the last memory pool
	for (p = p->d.next; p; p = p->d.next) {
        p->d.last = (u_char *) p + sizeof(ngx_pool_data_t);
        p->d.failed = 0;
    }

    pool->current = pool;
    pool->chain = NULL;
    pool->large = NULL;
}

5. Memory release (large memory)

Summary: find the alloc field of large memory, release it through free and set it empty

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);//Release the memory through the starting address of the large memory
            l->alloc = NULL;

            return NGX_OK;
        }
    }

    return NGX_DECLINED;
}

6. Release and destroy external resources of memory pool

Problem introduction: for the Nginx memory pool, the release of large memory is through the call to NGX_ reset_ NGX in pool function_ Free, the bottom layer is to call free. However, this can only release large blocks of memory, which is equivalent to releasing the resources occupied by the object itself, while the external resources occupied by the member variables are not released, so it will lead to memory leakage.

Solution: refer to the idea of destructor. Before releasing this large block of memory, call the destructor in the object and release the external resources to solve the problem. This function requires the user to set it as a callback function in advance (ngx_pool_cleanup_add, i.e. through the function pointer). First release the external resources occupied by the member variables in the block memory, and then release the memory of the block memory itself.

ngx_pool_cleanup_t types are as follows:

typedef void (*ngx_pool_cleanup_pt)(void *data);//Callback function type

typedef struct ngx_pool_cleanup_s  ngx_pool_cleanup_t;

struct ngx_pool_cleanup_s {
    ngx_pool_cleanup_pt   handler;//Save callback function
    void                 *data;//The address of the external resource passed into the callback function
    ngx_pool_cleanup_t   *next;//Next field of the linked list
};

Summary: NGX_ pool_ cleanup_ The add function first creates ngx_pool_cleanup_t clean up the memory header of the function, that is, you need to clean up external resources before releasing large blocks of memory. Then to NGX_ pool_ cleanup_ Initialize the type variable of the T structure, and finally return the starting address of the created memory header information.

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));//Allocate and clear the memory header information of external resources
    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;//Open up and return the memory header information of the function. The Handler has not been assigned yet
    c->next = p->cleanup;//Connect the memory header of the preset callback function allocated in advance to the cleanup linked list

    p->cleanup = c;

    ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, p->log, 0, "add cleanup: %p", c);

    return c;
}

Problem: if you clean up a small piece of memory first, then NGX_ pool_ cleanup_ The type variables of the T structure, like the memory header information of the large memory, are also stored in the memory pool on the small memory block (through ngx_palloc small memory allocation function), and all of them are invalid.

ngx_ destroy_ Release order of pool function: first release the external resources occupied by the member variables on the large memory, that is, execute the callback resource cleaning function provided by the user, pass the data to the handler, then traverse the large pointer to release the large memory, and finally traverse the pool to release the small memory pools one by one.

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) {//Preset callback function
            ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
                           "run cleanup: %p", c);
            c->handler(c->data);//Call the callback function and pass in the data in the current header as a parameter
        }
    }

#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);//Free large blocks of memory
        }
    }

    for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
        ngx_free(p);//Free small memory

        if (n == NULL) {
            break;
        }
    }
}

5, Postscript

Keywords: Nginx

Added by play_ on Mon, 07 Mar 2022 23:23:33 +0200