LwIP protocol stack source code analysis (reading notes)--memory management

There are three dynamic memory management mechanisms of LWIP: the memory allocation strategy of the C runtime library, the dynamic memory heap (HEAP) allocation strategy and the dynamic memory pool (POOL) allocation strategy.
You can choose one of the first two, and support at the same time is not allowed. Generally, we use the latter by default, that is, the dynamic memory heap (HEAP) allocation strategy.

/*
   ------------------------------------
   ---------- Memory options ----------
   ------------------------------------
*/
/**
 * MEM_LIBC_MALLOC==1: Use malloc/free/realloc provided by your C-library
 * instead of the lwip internal allocator. Can save code size if you
 * already use it.
 */
#ifndef MEM_LIBC_MALLOC
#define MEM_LIBC_MALLOC                 0
#endif

As can be seen from the comments, using the dynamic memory heap (HEAP) allocation strategy can save code space.

Dynamic memory heap allocation strategy:

The dynamic memory heap allocation strategy can be implemented in two ways,
the first one: by opening up a memory heap, and then by simulating the memory allocation strategy of the C runtime library.
The second one is implemented by the dynamic memory pool, that is, the dynamic memory heap allocation function completes its function by simply calling the dynamic memory pool (POOL) allocation function.

1. By opening up a memory heap, and then by simulating the memory allocation strategy of the C runtime library:

In general, we choose this dynamic memory heap allocation strategy.
The principle of dynamic memory heap allocation strategy:
It is managed in a memory block with a predefined size, and its memory allocation strategy is to use the fastest fit (First Fit) method. Whenever a free block larger than the requested memory is found, a suitable block is cut out of it and the remainder is returned to the dynamic memory heap.
The allocated memory block has a minimum size limit, which requires that the requested allocation size cannot be smaller than MIN_SIZE, otherwise the request will be allocated to a memory space of MIN_SIZE size.
Generally, MIN_SIZE is 12 bytes. The first few bytes of these 12 bytes will store the private data for memory allocator management. This data area cannot be modified by the user program, otherwise it will cause fatal problems.
The process of memory release is the opposite process, but the allocator will check whether the adjacent memory blocks before and after the node are free, and if they are free, they will be merged into a large free memory block.
Advantages and disadvantages of allocation strategy:

  • Advantages: small memory waste, relatively simple, suitable for small memory management;
  • Disadvantages: If frequent dynamic allocation and release, it may cause serious memory fragmentation, if the fragmentation is serious, it may lead to unsuccessful memory allocation.

For the use of dynamic memory, the recommended method is to allocate->release->allocate->release, which can reduce memory fragmentation.
Let's take a look at how LWIP implements these functions in detail.
mem_init( ): The initialization function of the memory heap, mainly to inform the start and end addresses of the memory heap, and to initialize the free list. It is called by lwip itself when it is initialized. This interface is an internal private interface and is not open to the user layer.
mem_malloc( ) : Request to allocate memory. Pass the total number of bytes needed as a parameter to the function, the return value is a pointer to the newly allocated memory, and if the memory is not allocated, the return value is NULL, and the size of the allocated space will be affected by memory alignment. May be slightly larger than requested. The returned memory is "not" initialized. This memory may contain any random garbage, and you can immediately initialize this memory with valid data or at least zeros. The allocation and release of memory cannot be performed in the interrupt function. The memory heap is a global variable, so the memory application and release operations are thread-safe. If multiple threads are applying and releasing memory at the same time, the application may take a long time due to the waiting of the semaphore.
mem_calloc( ): It is a simple wrapper for the mem_malloc( ) function. It 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, which is the same as mem_malloc( ), the difference is that it will zero out the dynamically allocated memory. Experienced programmers prefer to use mem_calloc(), because then the contents of the newly allocated memory will be fine, calling mem_calloc() will definitely clear it, and you can avoid calling memset().

2. It is realized by the way of dynamic memory pool, that is, the dynamic memory heap allocation function completes its function by simply calling the dynamic memory pool (POOL) allocation function.

In this case, the user needs to define the macros MEM_USE_POOLS and MEM_USE_CUSTOM_POOLS to 1 in the header file lwippools.h, and also open up some additional buffer pool areas, as follows:

//摘自mem.c文件
* Define three pools with sizes 256, 512, and 1512 bytes
 * LWIP_MALLOC_MEMPOOL_START
 * LWIP_MALLOC_MEMPOOL(20, 256)
 * LWIP_MALLOC_MEMPOOL(10, 512)
 * LWIP_MALLOC_MEMPOOL(5, 1512)
 * LWIP_MALLOC_MEMPOOL_END

In the opt.h file, the default configuration is:

/**
 * MEM_USE_POOLS==1: Use an alternative to malloc() by allocating from a set
 * of memory pools of various sizes. When mem_malloc is called, an element of
 * the smallest pool that can provide the length needed is returned.
 * To use this, MEMP_USE_CUSTOM_POOLS also has to be enabled.
 */
#ifndef MEM_USE_POOLS
#define MEM_USE_POOLS                   0
#endif

/**
 * MEM_USE_POOLS_TRY_BIGGER_POOL==1: if one malloc-pool is empty, try the next
 * bigger pool - WARNING: THIS MIGHT WASTE MEMORY but it can make a system more
 * reliable. */
#ifndef MEM_USE_POOLS_TRY_BIGGER_POOL
#define MEM_USE_POOLS_TRY_BIGGER_POOL   0
#endif

Generally not used.

Dynamic memory pool (POOL) allocation strategy:

The dynamic memory pool (POOL) allocation strategy can be said to be a relatively stupid allocation strategy,
but its allocation strategy is simple to implement, and the memory allocation and release efficiency is high, which can effectively prevent the generation of memory fragmentation. However, its disadvantage is that it will waste some memory.
Why is it called POOL? This is interesting, there are many POOLs and this depends on how the user configures LWIP.
For example, if the user defines LWIP_UDP as 1 in the header file opt.h, the memory pool with UDP type will be created during compilation; if LWIP_TCP is defined as 1, the memory pool with TCP type will be created during compilation.
In addition, there are many other types of memory pools, such as PBUF_POOL for storing network packet data information, and CUSTOM_POOLS mentioned above when explaining the dynamic memory heap allocation strategy, and so on.
The single size of a certain type of POOL is fixed, and the number of allocated POOLs of this type can be configured by the user, and the user should configure it according to the actual usage of the protocol stack. Put all the POOLs in the protocol stack together one by one, and put them in a contiguous memory area, what is presented to the user is a large buffer pool.
Therefore, the internal organization of the so-called buffer pool should be as follows: a POOL pool of type A is placed at the beginning, followed by a POOL pool of type B, and then a POOL pool of C type is placed... .Up to the last n POOL pools of N types. This is very similar to the process control block and event control block in UC/OSII. First open up a bunch of various types and put them there, you just need to fetch them directly. Note that the allocation here must be based on a single buffer pool, in such a case, it may lead to a waste of memory. It's so obvious, no explanation.
Let's take a look at how the large buffer pool discussed above is opened up in the LWIP implementation.

static u8_t memp_memory[MEM_ALIGNMENT - 1 
#define LWIP_MEMPOOL(name,num,size,desc) + ( (num) * (MEMP_SIZE + MEMP_ALIGN_SIZE(size) ) )
#include "lwip/memp_std.h"
];

The above code defines the memory buffer used by the buffer pool, and many people will doubt whether this is the definition of an array. Define an array, there are actually define and include keywords. The key to solving the problem lies in the header file memp_std.h, which can be simplified to many LWIP_MEMPOOL(name,num,size,desc).
And because the define keyword is used to define LWIP_MEMPOOL(name,num,size,desc) as +((num) * (MEMP_SIZE + MEMP_ALIGN_SIZE(size))), memp_std.h is compiled one by one + (), +(), +(), +()…. So the final array memp_memory is equivalently defined as:

static u8_t memp_memory [ MEM_ALIGNMENT – 1
+()
+()….];

MEM_ALIGNMENT - 1 for alignment consideration.
The protocol stack also establishes some global variables related to buffer pool management:
memp_num: this static array is used to save the number of members of various types of buffer pools
memp_sizes: this static array is used to save the structure size of various types of buffer pools
memp_tab: this pointer The array is used to point to the current free nodes of various types of buffer pools. Functions
implemented :
memp_init(): The initialization of the memory pool, mainly to establish a linked list memp_tab for each memory pool, and the linked list is in reverse order. In addition, if the statistical function is enabled If so, the number of various memory pools is also recorded.
memp_malloc(): If there are free nodes in the corresponding memp_tab linked list, cut out a node and return, otherwise return empty.
memp_free(): Add the freed node to the header of the corresponding linked list memp_tab.

Example:

/** This array holds the number of elements in each pool. */
static const u16_t memp_num[MEMP_MAX] = {
#define LWIP_MEMPOOL(name,num,size,desc)  (num),
#include "lwip/memp_std.h"
};

Enter lwip/memp_std.h to expand:

static const u16_t memp_num[MEMP_MAX] = {
(MEMP_NUM_RAW_PCB),//4
(MEMP_NUM_UDP_PCB),//6
(MEMP_NUM_TCP_PCB),//10
(MEMP_NUM_TCP_PCB_LISTEN),//6
(MEMP_NUM_TCP_SEG),//15
(MEMP_NUM_REASSDATA),//5
(MEMP_NUM_NETBUF),//2
(MEMP_NUM_NETCONN),
(MEMP_NUM_TCPIP_MSG_INPKT),//8
(MEMP_NUM_SYS_TIMEOUT),//8

The specific numbers are mainly based on the needs of the project. Different projects may have different values.

memp_std.h implementation skills:

Part of the code is as follows:

#ifndef LWIP_MALLOC_MEMPOOL
/* This treats "malloc pools" just like any other pool.
   The pools are a little bigger to provide 'size' as the amount of user data. */
#define LWIP_MALLOC_MEMPOOL(num, size) LWIP_MEMPOOL(POOL_##size, num, (size + sizeof(struct memp_malloc_helper)), "MALLOC_"#size)
#define LWIP_MALLOC_MEMPOOL_START
#define LWIP_MALLOC_MEMPOOL_END
#endif /* LWIP_MALLOC_MEMPOOL */ 

#ifndef LWIP_PBUF_MEMPOOL
/* This treats "pbuf pools" just like any other pool.
 * Allocates buffers for a pbuf struct AND a payload size */
#define LWIP_PBUF_MEMPOOL(name, num, payload, desc) LWIP_MEMPOOL(name, num, (MEMP_ALIGN_SIZE(sizeof(struct pbuf)) + MEMP_ALIGN_SIZE(payload)), desc)
#endif /* LWIP_PBUF_MEMPOOL */


/*
 * A list of internal pools used by LWIP.
 *
 * LWIP_MEMPOOL(pool_name, number_elements, element_size, pool_description)
 *     creates a pool name MEMP_pool_name. description is used in stats.c
 */
#if LWIP_RAW
LWIP_MEMPOOL(RAW_PCB,        MEMP_NUM_RAW_PCB,         sizeof(struct raw_pcb),        "RAW_PCB")
#endif /* LWIP_RAW */

#if LWIP_TCP
LWIP_MEMPOOL(TCP_PCB,        MEMP_NUM_TCP_PCB,         sizeof(struct tcp_pcb),        "TCP_PCB")
LWIP_MEMPOOL(TCP_PCB_LISTEN, MEMP_NUM_TCP_PCB_LISTEN,  sizeof(struct tcp_pcb_listen), "TCP_PCB_LISTEN")
LWIP_MEMPOOL(TCP_SEG,        MEMP_NUM_TCP_SEG,         sizeof(struct tcp_seg),        "TCP_SEG")
#endif /* LWIP_TCP */
......
/*
 * A list of pools of pbuf's used by LWIP.
 *
 * LWIP_PBUF_MEMPOOL(pool_name, number_elements, pbuf_payload_size, pool_description)
 *     creates a pool name MEMP_pool_name. description is used in stats.c
 *     This allocates enough space for the pbuf struct and a payload.
 *     (Example: pbuf_payload_size=0 allocates only size for the struct)
 */
LWIP_PBUF_MEMPOOL(PBUF,      MEMP_NUM_PBUF,            0,                             "PBUF_REF/ROM")
LWIP_PBUF_MEMPOOL(PBUF_POOL, PBUF_POOL_SIZE,           PBUF_POOL_BUFSIZE,             "PBUF_POOL")


/*
 * Allow for user-defined pools; this must be explicitly set in lwipopts.h
 * since the default is to NOT look for lwippools.h
 */
#if MEMP_USE_CUSTOM_POOLS
#include "lwippools.h"
#endif /* MEMP_USE_CUSTOM_POOLS */

/*
 * REQUIRED CLEANUP: Clear up so we don't get "multiply defined" error later
 * (#undef is ignored for something that is not defined)
 */
#undef LWIP_MEMPOOL
#undef LWIP_MALLOC_MEMPOOL
#undef LWIP_MALLOC_MEMPOOL_START
#undef LWIP_MALLOC_MEMPOOL_END
#undef LWIP_PBUF_MEMPOOL

Syntax #undef, that cancels the definition of the front-end related macros.
In the previous example, the static array defines

#define LWIP_MEMPOOL(name,num,size,desc)  (num),

Then enter the memp_std.h file of this chapter and cancel the definition at the end of the file

#undef LWIP_MALLOC_MEMPOOL

Enables reuse of this header file.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324777974&siteId=291194637