redis source code learning allocated memory

1. Overview of redis

In terms of memory allocation, Redis simply encapsulates the malloc/free of the system, and then adds the exception handling function and memory statistics function. Its implementation is mainly in zmalloc.c and zmalloc.h files

2. Function function

void *zmalloc(size_t size); // Call the zmalloc function to apply for space of size
void *zcalloc(size_t size); // Call the system function calloc to request memory space
void *zrealloc(void *ptr, size_t size); // Resize original memory to size space
void zfree(void *ptr);  // Call zfree to free up memory space
char *zstrdup(const char *s); // String copy method
size_t zmalloc_used_memory(void); // Gets the current and occupied memory space size
void zmalloc_enable_thread_safeness(void); // Set thread safe mode
void zmalloc_set_oom_handler(void (*oom_handler)(size_t)); // You can customize the processing method of memory overflow
float zmalloc_get_fragmentation_ratio(size_t rss); // Gets the ratio of the size of the given memory to the used memory
size_t zmalloc_get_rss(void); // Get RSS information (Resident Set Size)
size_t zmalloc_get_private_dirty(void); // Get actual memory size
size_t zmalloc_get_smap_bytes_by_field(char *field); // Gets the number of bytes in the / proc/self/smaps field
size_t zmalloc_get_memory_size(void); // Gets the physical memory size
void zlibc_free(void *ptr); // Original system free release method

Several variables
static size_t used_memory = 0;  // Size of memory used
static int zmalloc_thread_safe = 0; // Thread safe mode status
pthread_mutex_t used_memory_mutex = PTHREAD_MUTEX_INITIALIZER; // For this server

3. Step by step analysis

  1. Exception handling function
    If the allocation fails, the error is output to the standard error output byte stream, and the process terminates
    Assign it to the function pointer static void (*zmalloc_oom_handler)(size_t) = zmalloc_default_oom
static void zmalloc_default_oom(size_t size) {
    fprintf(stderr, "zmalloc: Out of memory trying to allocate %Iu bytes\n",    WIN_PORT_FIX size);
    fflush(stderr);  //fflush is used to empty the buffer stream
    abort(); //The abort() function must cause the process to terminate
}

2.zmalloc function
The essence of Redis's memory application function zmalloc is to call the malloc function of the system, and then properly encapsulate it, plus exception handling function and memory statistics

void *zmalloc(size_t size) {
    // Call malloc function to apply for memory
    // Multiple application PREFIX_SIZE memory is used to record the size of this segment of memory
    void *ptr = malloc(size+PREFIX_SIZE);

    // If ptr is NULL, the exception handler function is called
    if (!ptr) zmalloc_oom_handler(size);
    // The following is the memory statistics
    *((size_t*)ptr) = size;
    update_zmalloc_stat_alloc(size+PREFIX_SIZE); // Update used_ Value of memory
    return (char*)ptr+PREFIX_SIZE;
}

Think: why should there be PREFIX_SIZE?
Since the memory applied by malloc function does not identify the size of memory block, we need to count the memory size, so we need to apply prefix in multiple applications_ Size memory, used to store this size

Update used_ The function of memory value is given in macro definition

#define update_zmalloc_stat_alloc(__n) do { 
    size_t _n = (__n); 
    if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1));   // Will_ n is adjusted to an integer multiple of sizeof(long)
    if (zmalloc_thread_safe) { // If thread safe mode is enabled
        update_zmalloc_stat_add(_n);   // Call the atomic operation plus (+) to update the used memory
    } else { 
        used_memory += _n;   // If thread safety is not considered, the used memory is updated directly
    } 
} while(0)

Thinking: why will_ n is adjusted to an integer multiple of sizeof(long)?
Because it needs to be adjusted to a multiple of 8, it is adjusted according to the allocation principle of memory pool

In the above function, thread safe mode: for used_ The access of memory resource ensures that only one thread can access the share at the same time by adding a mutex lock
Improvement: c++11 provides some atomic operations, which will be used_memory is defined as atomic operation data, so there is no need to borrow the locking mechanism

#define update_zmalloc_stat_add(__n) do { \
    pthread_mutex_lock(&used_memory_mutex); \
    used_memory += (__n); \
    pthread_mutex_unlock(&used_memory_mutex); \
} while(0)
  1. zcalloc function
    Like malloc, zcalloc calls the calloc() given by the system to apply for memory.
void *zcalloc(size_t size) {
    void *ptr = calloc(1, size+PREFIX_SIZE);
    // Exception handling function
    if (!ptr) zmalloc_oom_handler(size);
    // Memory statistics function
    *((size_t*)ptr) = size;
    update_zmalloc_stat_alloc(size+PREFIX_SIZE);
    return (char*)ptr+PREFIX_SIZE;
}

Thinking: why do calloc allocate memory with malloc?
Both malloc() and calloc() functions can be used to dynamically allocate memory space, but they are slightly different.
malloc() function has one parameter, that is, the size of memory space to be allocated:
void *malloc(size_t size);
The calloc() function has two parameters: the number of elements and the size of each element. The product of these two parameters is the size of the memory space to be allocated.
void *calloc(size_t numElements,size_t sizeOfElement);
If the call is successful, both malloc() and calloc() will return the first address of the allocated memory space.
However, the main difference between malloc () and calloc() is that the former cannot initialize the allocated memory space, while the latter can. If the memory space allocated by the malloc() function has not been used, each bit may be 0; On the contrary, if this part of memory has been allocated, there may be a variety of data left in it, and errors may occur in later execution
The function calloc() initializes every bit in the allocated memory space to zero, that is, if you allocate memory for elements of character type or integer type, these elements will be guaranteed to be initialized to 0; If you allocate memory for pointer type elements, these elements will usually be initialized as null pointers; If you allocate memory for real data, these elements will be initialized to floating-point zeros

  1. zrecalloc function
    Function: adjust memory
    The defined zrecalloc is used to adjust the size of the requested memory. Its essence is to directly call the system function recalloc()
void *zrealloc(void *ptr, size_t size) {
    size_t oldsize;
    void *newptr;
    // If it is empty, exit directly
    if (ptr == NULL) return zmalloc(size);
    // Find the real starting position of memory
    realptr = (char*)ptr-PREFIX_SIZE;
    oldsize = *((size_t*)realptr);
    // Call the recalloc function
    newptr = realloc(realptr,size+PREFIX_SIZE);
    if (!newptr) zmalloc_oom_handler(size);
    // Memory statistics
    *((size_t*)newptr) = size;
    update_zmalloc_stat_free(oldsize); // Subtract the original used memory size first
    update_zmalloc_stat_alloc(size); // After adding the adjusted size
    return (char*)newptr+PREFIX_SIZE;
}

Realloc (void * _ptr, size_t _size): change the configured memory space, that is, change the size of the memory space allocated by malloc() function
If the allocated memory is expanded, the following conditions exist:
1) If there is a required memory space behind the current memory segment, expand the memory space directly, and realloc() will return the original pointer.
2) If there are not enough free bytes behind the current memory segment, the first memory block in the heap that can meet this requirement is used to copy the current data to a new location, release the original data block and return to the new memory block location.
3) If the application fails, NULL will be returned. At this time, the original pointer is still valid

  1. zfree function
    Memory release is also achieved by calling the free() function of the system
void zfree(void *ptr) {
    if (ptr == NULL) return;  // Return directly if it is empty
    realptr = (char*)ptr-PREFIX_SIZE; // Find the real starting position of this segment of memory
    oldsize = *((size_t*)realptr);
    update_zmalloc_stat_free(oldsize+PREFIX_SIZE);// Update use_memory function
    free(realptr); // Call the memory release function of the system
}

Update memory status statistics function

#define update_zmalloc_stat_free(__n) do {
    size_t _n = (__n); 
    if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1));  // Adjust the memory size to an integer multiple of sizeof(long)
    if (zmalloc_thread_safe) {  // If thread safe mode is enabled
        update_zmalloc_stat_sub(_n); // Update use_memory value (similar to the above update_zmalloc_stat_add, which will not be described here)
    } else {
        used_memory -= _n; // If there is no thread safety, it will be reduced directly
    }
} while(0)
  1. zsize function
    Returns the size of a memory block
size_t zmalloc_size(void *ptr) {
    void *realptr = (char*)ptr-PREFIX_SIZE; //Memory start address
    size_t size = *((size_t*)realptr) ;
    if (size&(sizeof(PORT_LONG)-1)) size += sizeof(PORT_LONG)-(size&(sizeof(PORT_LONG)-1));//Adjust to an integer multiple of long
    return size+PREFIX_SIZE;
}
  1. String copy
char *zstrdup(const char *s) {
    size_t l = strlen(s)+1; 
    char *p = zmalloc(l); // Open up a new memory
    memcpy(p,s,l); // Call string copy function
    return p;
}
  1. Set exception handling function
    Self defined exception function, parameter: function pointer
void zmalloc_set_oom_handler(void (*oom_handler)(size_t)) {
    zmalloc_oom_handler = oom_handler; // Binding custom exception handling functions
}
  1. Turn on thread safety
void zmalloc_enable_thread_safeness(void) {
    zmalloc_thread_safe = 1;  // This parameter controls whether thread safety is turned on
}
  1. Get used memory
size_t zmalloc_used_memory(void) {
    size_t um;
    // If thread safe mode is enabled
    if (zmalloc_thread_safe) {
#if defined(__ATOMIC_RELAXED) || defined(HAVE_ATOMIC)
        um = update_zmalloc_stat_add(0);
#else
        pthread_mutex_lock(&used_memory_mutex);
        um = used_memory;
        pthread_mutex_unlock(&used_memory_mutex);
#endif
    }
    else {
        um = used_memory; // If it is not turned on, use used directly_ memory
    }

    return um;
}

Keywords: Database Redis

Added by glassroof on Wed, 24 Nov 2021 06:44:23 +0200