Two Common Memory Management Methods: Heaps and Memory Pools

This article guides reading

During the running of the program, some data may be generated, for example, data received by the serial port and data acquired by the ADC. If data needs to be stored in the memory for further calculation and processing, a suitable memory space should be allocated to it, and after the data is processed, the corresponding memory space is released. In order to facilitate the allocation and release of memory, AWorks provides two kinds of memory management tools: heap and memory pool.

This article is "for the AWorks framework and interface programming (on)" Part III software - Chapter 9 memory management - Sections 1 to 2: heap manager and memory pool.

This chapter guides reading

In a computer system, data is generally stored in memory. Only when the data needs to participate in the operation is it removed from the memory and passed to the CPU for calculation. After the operation, the data is stored back in the memory. This requires the system to allocate appropriate memory space for various types of data.

The amount of memory required for some data can be determined before compiling. There are two main types: one is a global variable or a static variable. This part of the data is valid for the whole life cycle of the program. It allocates a fixed memory space for these data at compile time, and can be used directly afterwards without additional management. A class is a local variable, this part of the data is valid only in the current scope (such as in the function), they need memory allocated automatically from the stack, and no additional management, but it should be noted that due to this part of the data Memory is allocated from the stack. Therefore, you need to ensure that your application has enough stack space. Try to avoid defining local variables that occupy a large amount of memory (for example, an array that occupies several K of memory) to avoid stack overflow. Stack overflow may damage the system. The key data is very likely to cause the system to crash.

The memory size required by some data needs to be determined according to the actual situation during the running of the program, and cannot be determined before compiling. For example, a 1K memory space may be temporarily needed to store data sent from the remote end through the serial port. This requires that the system has the ability to dynamically manage the memory space. When the user needs a memory space, he or she will apply to the system. The system selects a suitable memory space for the user. After the user completes the use, it is released back to the system so that the system will This section of memory space is recycled and reused. In AWorks, there are two common memory management methods: heap and memory pools.

9.1 Heap Manager

The heap manager is used to manage a section of continuous memory space, and can allocate any size of memory block according to the user's needs in the case of satisfying system resources. It is completely "assigned on demand". When the user no longer uses the allocated memory block, Memory blocks can be released back to the heap for use by other applications. Similar to the function implemented by the malloc()/free() function in the C standard library.

9.1.1 Overview of the Heap Manager

Before using the heap manager, a brief introduction to its principle is provided through an example, so that users can use the heap manager more effectively. For example, use the heap manager to manage 1024 bytes of memory space. Initially, all memory is idle and the entire memory space can be considered as a large free memory block. The schematic diagram is shown in Figure 9.1.

Figure 9.1 Initial State - Individual Free Memory Blocks

The memory allocation strategy is: When a user requests a certain amount of memory space, the heap manager starts searching from the first free memory block until it finds a free memory block that meets the user's needs (that is, the capacity is not less than the memory space requested by the user. Size), then allocates a user-specified size of memory space from this free memory block.

For example, the user applies for 100 bytes of memory space to the heap manager. Since the capacity of the first free memory block is 1024, the demand can be satisfied. Therefore, 100 bytes of memory space can be allocated to the user from the allocated memory. The schematic diagram is shown in Figure 9.2.

Figure 9.2 allocate 100 bytes - split into two memory blocks

Note: The hatch is shaded to indicate that the block has been allocated. Otherwise, the block is not allocated and is in an idle state. The number indicates the capacity of the block.

Similarly, if the user requests three consecutive memory spaces from the heap manager, the memory space for each request is: 150, 250, and 200 bytes, respectively. For the details of the allocated memory, see Figure 9.3.

Figure 9.3 Reassign 100, 300, and 200-Byte Memory Spaces - Split into Five Memory Blocks

As the allocation continues, there may be more and more memory blocks. If the user's request cannot be satisfied, the allocation fails. If the user requests 400 bytes of memory on the basis of Figure 9.3, there is only one free block and the capacity is 324 bytes, which cannot satisfy the requirement. The allocation fails and the user cannot get a valid memory space.

When a certain segment of memory requested by the user is no longer used, the memory block should be released back to the heap for recycling.

The recovery strategy is: When a user releases a memory block back to the heap, first, the memory block becomes a free block, if the memory block adjacent to the memory block is also a free block, they are merged into a large Free memory block.

For example, on the basis of Figure 9.3, the previously allocated 250-byte memory space is freed. The memory block is first made free, as shown in Figure 9.4.

Figure 9.4 Freeing a Memory Block - Neighboring Blocks Are Not Free Blocks

Since the memory block is not a free block before and after, there is no need for a merge operation. At this point, 250 bytes of free space are freed, and there is a total of 574 bytes of memory in the heap. However, if the user requests 400 bytes of memory space from the heap manager at this time, since the first free block and the second free block do not meet the requirements, the memory allocation will still fail.

This exposes the drawbacks of a heap manager. Frequent allocation and release of memory blocks of different sizes will generate many memory fragments, namely free memory blocks interrupted by allocated memory blocks in Figure 9.4. The figure shows free memory with a capacity of 250 bytes and a capacity of 324 bytes. The block is interrupted by a memory block with an allocated capacity of 200 bytes, so that each free block is physically discontinuous and cannot form a large free block. In this case, even if the size of the requested memory space is less than the total size of the free space of the current heap, the allocation may fail because there is not a large free block.

On the basis of Fig. 9.4, even if the first allocated 100-byte memory space is released, the total free space reaches 674 bytes, as shown in Fig. 9.5, and the allocation request for a 400-byte memory space cannot be satisfied.

Figure 9.5 Releases a 100-Byte Memory Block

With the allocation and recovery of a large number of memory blocks in the system, the memory fragmentation will be more and more, and some memory blocks that are not released for a long time will always be divided into some free memory blocks, resulting in memory fragmentation. This also informs the user that when a memory block is no longer used, the memory block should be released as soon as possible to avoid causing excessive memory fragmentation.

In Figure 9.5, there are 3 free blocks. If the user requests a 280-byte memory space on this basis, the heap manager first looks at the first free block according to the allocation strategy. Its capacity is 100 bytes. Satisfy the demand, and then look at the second free block, its capacity is 250 bytes, the same can not meet the demand, then look at the third free block, its capacity is 324 bytes, and finally allocate a space from the block to the user, allocate See Figure 9.6 for the memory diagram after.

Figure 9.6 Redistribute 280 bytes of memory space

On the basis of Fig. 9.6, if the user releases the 200-byte memory block first, it is first made free, as shown in Figure 9.7.

Figure 9.7 Release 200 bytes of memory space (1) - Marked as free

Since the memory block whose size is 250 on the left side is also an idle block, it is necessary to merge them into one large memory block, that is, to merge into a memory block with a size of 450, as shown in the figure in Figure 9.8.

Figure 9.8 Release of 200 bytes of memory space (2) - Merge with adjacent free blocks

At this point, since there is a free block of size 450, if the user requests 400 bytes of memory at this time, the application can be successfully applied. Compared with Figure 9.5, although Figure 9.5 has a total of 674 bytes of free space and Figure 9.8 has only 594 bytes of free space, Figure 9.8 can satisfy a 400-byte memory space request. It can be seen that, due to memory fragmentation, the total free space size does not determine the success or failure of a memory request.

The schematic diagram after successful application of 400 bytes is shown in Figure 9.9.

Figure 9.9 400-byte memory space

On the basis of Fig. 9.9, if the user releases another 280-byte memory block, it will be the same as the free block first. The figure is shown in Figure 9.10.

Figure 9.10 Release 280 bytes of memory space (1) - Marked as free

Since both left and right sides are free blocks, they need to be merged into one large memory block, that is, they are merged into a memory block of size 374, as shown in Figure 9.11.

Figure 9.11 Release 280 bytes of memory space (2) - Merge with adjacent free blocks

The reason for merging adjacent free memory blocks is to avoid memory blocks becoming more and more fragmented. If this continues, it will be difficult to have a large free block. Users will then apply for large memory free. Will not meet the demand.

Through a series of examples of memory allocation and release above, the heap manager's strategy of allocating and releasing memory is shown, enabling the user to have a certain understanding of the relevant principles.

In fact, in the software implementation of the heap manager, in order to facilitate management, each memory block is organized in the form of a linked list. The head of each memory block is fixed to store some related information for memory block management. Figure 9.12.

Figure 9.12 Memory block (with free memory blocks and allocated memory blocks) linked list

The figure shows four very important pieces of information: magic, used, p_next, and p_prev.

Magic is called Magic Number and is assigned a special fixed value. It indicates that the memory block is a memory block managed by the heap manager and can check the memory operation to a certain extent. For example, if this area is rewritten, magic's value is modified to other values, indicating that there is an illegal memory operation, may be the user's memory operation across the border, etc., should be handled in a timely manner; when a memory block is released, the heap manager will check the magic's The value, if its value is not a special fixed value, indicates that this is not a memory block allocated by the heap manager. This release operation is illegal.

Used is used to indicate whether the memory block has already been used. If the value is 0, it indicates that the memory block is a free block; if the value is 1, it means that the memory block has been allocated, not a free block.

P_next and p_prev are used to organize each memory block in the form of a doubly-linked list so that the next or previous memory block of the current memory block can be found conveniently. For example, when releasing a memory block, it is necessary to view adjacent memory blocks. (The previous memory block and the next memory block) are free blocks to decide whether to merge them into one free block.

In addition, in order to speed up the search for free blocks when allocating memory blocks, there are two additional pointers in the message for organizing all free blocks into a single linked list. In this way, when allocating a memory block, it is only necessary to find the free block satisfying the demand in the free linked list, and it is not necessary to traverse all the memory blocks in turn. The schematic diagram is shown in Figure 9.13.

Figure 9.13 Space block linked list

For the user, the memory space obtained by the user is the user data space (a pointer that directly points to the user data space). The information stored in the memory block header is invisible to the user, and the user should not attempt to access or modify the information. .

The need for users' attention is that since the relevant information of the memory block needs to occupy a certain amount of memory space (in a 32-bit system, usually 24 bytes), the actual size occupied by each memory block is larger than the memory space requested by the user. For example, the user requests a 100-byte memory space. In practice, a 124-byte memory block is allocated to store the memory block information. Based on this, the actual memory space available to the user is slightly less than the total space of the heap.

9.1.2 Heap Manager Interface

AWorks provides a heap manager, which can be used by users through related interfaces. The prototype of the correlation function is shown in Table 9.1.

Table 9.1 Heap Manager Interface (aw_memheap.h)

1. Defining a heap manager instance

Before using the heap manager, you must first use the aw_memheap_t type to define the heap manager instance. This type is defined in aw_memheap.h. Users of the specific type definition do not need to be concerned. You only need to use this type to define the heap manager instance. That is:

The address can be passed as an argument to the memheap parameter in the initialization interface.

In AWorks, a heap manager is used to manage a contiguous section of memory space. In a system, multiple heap managers can be used to manage multiple sections of contiguous memory space. This requires the definition of multiple heap manager instances, such as:

In this way, each application can define its own heap manager to manage memory allocation and release in the application, so that the memory usage between applications does not affect each other.

If all applications use a heap, the memory allocation among applications will affect each other. A memory leak occurs in an application. Over time, it is highly likely that a heap space will be completely leaked. This will affect all applications. . At the same time, all applications share a heap, which also results in more frequent allocation and release in one heap, more inconsistent allocated memory blocks, and more memory fragmentation.

2. Initialize the heap manager

After you define a heap manager instance, you must use this interface to initialize it before you can use it to specify the memory space it manages. Its function prototype is:

Among them, memheap is a pointer to the heap manager instance; name is the name of the heap manager, the name is a string, only used as an identifier; start_addr specifies the first address of the memory space it manages; size specifies the memory space it manages Size (bytes).

The return value of the function is the standard error number. When AW_OK is returned, the initialization is successful; otherwise, the initialization fails.

The core of the initialization function is to specify the memory space it manages. Usually, this continuous memory space can be obtained by defining a global array variable whose size is related to the actual application and should be determined according to the actual situation. For example, if you use the heap manager to manage 1 KB of memory, the sample program for initializing the heap manager is shown in Listing 9.1.

Listing 9.1 Initializing the Heap Manager Sample Program

In the program, an array of 1024 bytes is defined for allocating a 1 KB memory space for management using the heap manager.

3. Allocate memory

After the heap manager is initialized, the interface can be used to allocate a memory block of a specified size to the user. The size of the memory block can be any size that meets the resource requirements. The function prototype for allocating memory blocks is:

Among them, heap is a pointer to the heap manager; size is the memory block size. The return value is the first address of the allocated memory block. In particular, if the return value is NULL, the allocation fails.

When allocating memory blocks, because the heap manager does not know what type of data the allocated memory block uses to store, the return value is a generic typeless pointer, that is, the void * type, which indicates that it points to a certain type. The determined address. Obviously, what type of data is allocated by the allocated memory block is determined by the user. Therefore, when the user uses this memory space, it must convert it to a pointer of the actual data type.

For example, to allocate memory blocks for storing 100 unsigned 8-bit data, the sample program is detailed in Listing 9.2.

Listing 9.2 Sample Program for Allocating Memory Blocks

In the program, the return value of aw_memheap_alloc() is cast to a pointer to the uint8_t data type. Note that in memory allocated using aw_memheap_alloc(), the initial value of the data is random, not necessarily zero. Therefore, if the memory pointed to by ptr is not assigned, its value may be arbitrary.

4. Resizing memory

Sometimes, you need to dynamically adjust the previously allocated memory block size. If you initially allocate a smaller memory block, but the memory is insufficient with the increase of data, you can use this function to readjust the previously allocated memory block. size. Its function prototype is:

Among them, heap is a pointer to the heap manager; ptr is the first address of the memory block allocated using the aw_memheap_alloc() function, that is, the return value of the aw_memheap_alloc() function is called; new_size is the adjusted memory block size. The return value is the first address of the resized memory space. In particular, if the return value is NULL, the resize fails.

The new size specified by newsize can be larger (enlarged) than the original memory block, or it can be smaller (smaller) than the original memory block. If it is an enlarged memory block, the memory of the new extended part will not be initialized, the value is random, but the data in the original memory block remains unchanged. If it is to shrink the memory block, the memory that exceeds the new_size portion will be released, the data in it will be discarded, and the remaining data will remain unchanged. In particular, if the value of newsize is 0, it is equivalent to no longer using the memory block pointed to by ptr. In this case, the entire memory block will be directly released. In this case, the return value is also NULL.

The return value of the function may be different from the value of ptr, that is, the system may reselect a suitable memory block to meet the need for resizing. At this time, the first address of the memory will change. For example, when the new size is larger than the original space, the system first determines whether there is enough continuous space after the original memory block to meet the requirements for expanding the memory. If there is, the memory space is directly expanded and the memory address is unchanged. Return the first address of the original memory block, that is, the value of ptr; if not, allocate a new memory block according to newsize, copy the original data to the newly allocated memory block, and then automatically release the original memory. Block, the original memory block will no longer be available, at this time, the return value will be the first address of the memory block to be re-allocated, and there will be no direct relationship with ptr.

For example, first use aw_memheap_alloc() to allocate a 100-byte memory block and then resize it to a 200-byte memory block. The sample program is detailed in Listing 9.3.

Listing 9.3 Resize memory

It is worth noting that the original memory space is still valid after failing to resize the memory space. Therefore, if you use the call form of line 9 of Listing 9.3, if the memory expansion fails, the return value is NULL, which will cause the value of ptr to be set to NULL. At this point, although the original memory space is still valid, because the pointer information to the memory space is lost, the user can no longer access the corresponding memory space, nor can he release the corresponding memory space, causing a memory leak.

In actual use, in order to avoid the adjustment of the memory size, if the value of the original ptr is overwritten with NULL, a new pointer should be used to store the return value of the function. See Listing 9.4 for details.

Listing 9.4 Resize memory (use new pointer to save function return value)

At this point, even if the expansion of the memory space fails, the pointer ptr to the original memory space is still valid, and the originally allocated memory space can still be used to avoid the memory leak. When the expansion of the memory space is successful, the value of ptr is directly re-assigned to new_ptr to point to the memory space after the expansion is completed.

5. Release memory

When the user no longer uses the requested memory block, it must be released, otherwise it will cause a memory leak. The function prototype for releasing memory is:

Among them, ptr is the first address of the memory block allocated using the aw_memheap_alloc() or aw_memheap_realloc() function, which is the return value of calling these functions. Note that ptr can only be the return value of the above several functions, it can not be other address values, for example, can not use the first address of the array as a function parameter to release the memory space occupied by the static array. Passing in the wrong address value is very likely to cause the system to crash.

When aw_memheap_free() is used to free a block of memory, the corresponding block of memory becomes invalid and the user cannot continue to use it. The sample program for freeing memory blocks is shown in Listing 9.5.

Listing 9.5 Sample Program for Freeing Memory Blocks

In order to avoid releasing the memory block and then continue to use it, you can develop a good habit. When the memory is released, the corresponding ptr pointer is set to NULL.

9.1.3 System Heap Management

In AWorks, the entire memory space is first used to satisfy the storage of some data that is known to occupy memory space, such as: global variables, static variables, stack space, program code, or constants.

Global variables and static variables are better understood, and the amount of memory they occupy can be determined by the type of data. It should be noted that, when introducing the heap manager, the definition of a memory space to be managed also uses a static array. See line 6 of Listing 9.1 for details:

This is also a static variable whose memory size is known.

For stack space, in AWorks, the stack space is statically allocated, similar to a static array, and the amount of memory it occupies is determined by the user and is also known.

Under normal circumstances, program code and constants are stored in read-only memory such as ROM or FLASH and are not stored in memory. However, in some platforms, program code and constants may also be stored in memory for efficiency or chip architecture considerations. For example, in the i.MX28x platform, program code and constants are also stored in DDR memory. The size of memory space occupied by program code and constants can be determined after compilation, and the occupied memory space size is also known.

After the storage of these data is satisfied, all the remaining memory space is used as the system heap space, which is convenient for the user to use dynamically during the running of the program.

In order to facilitate the use of the user, you need to use some appropriate method to manage the system heap space. In AWorks, it is managed by default using the previously described heap manager. For the scalability of the system, AWorks does not limit the heap space that must be managed based on the heap manager provided by AWorks. If users have more suitable management methods for special applications, they can also use their own methods to manage the system in specific environments. Heap space. In order to maintain the unity of the application, AWorks defined a set of dynamic memory management common interface, which facilitates the user to use the system heap space without having to care about the specific management method. The relevant function prototype is shown in Table 9.2.

Table 9.2 Dynamic Memory Management Interface (aw_mem.h)

According to the previous introduction, before using the heap manager, you need to define the heap manager instance, and then initialize the instance to specify the memory space it manages. After the initialization is complete, the user can request memory from it. If you use the heap manager to manage the system heap space (the default), then the interfaces in Table 9.2 can all be implemented based on the heap manager interface. At this point, a default heap manager instance will be defined in the system, and its initialization operation will be completed automatically when the system is started, and the memory space it manages will be designated as the system heap space. In this way, after the system is started, the user can apply for a memory block directly to the default heap manager in the system. For example, aw_mem_alloc() is used to allocate a block of memory that is directly based on the heap manager implementation. For an example program, see Listing 9.6.

Listing 9.6 Aw_mem_alloc() Function Example

Among them, __g_system_heap is a system-defined heap manager instance that has been initialized at system startup. Listing 9.6 is just a simple example of how the aw_mem_alloc() function is implemented. In practice, the user can use the most appropriate method to manage the system heap space according to the specific situation, and implement the universal memory management universal interface defined by AWorks.

The following describes in detail the meaning of each interface and how to use it.

Allocate memory

Aw_mem_alloc() is used to allocate a block of a specified size from the system heap. The usage is the same as malloc() in the C standard library. Its function prototype is:

The parameter size specifies the size of the memory space in bytes. The return value is a pointer of type void *, which points to the first address of the allocated memory block. In particular, if the return value is NULL, the allocation fails.

For example, to apply for a storage space to store an int type of data, the sample program is detailed in Listing 9.7.

Listing 9.7 Application Memory Sample Program

In the program, the return value of aw_mem_alloc() is cast to a pointer to an int data type. Note that in memory allocated using aw_mem_alloc(), the initial value of the data is random and not necessarily zero. Therefore, if the memory pointed to by ptr is not assigned, its value will be arbitrary.

2. Allocate multiple memory blocks of a specified size

In addition to using aw_mem_alloc() to directly allocate a memory block of a specified size, you can use aw_mem_calloc() to allocate multiple contiguous blocks of memory. The usage is the same as calloc() in the C standard library. Its function prototype is:

This function is used to allocate nelem memory size of the memory block, the total size of the memory space allocated: nelem × size, in fact, is equivalent to allocate a large memory block size nelem × size, the return value is also allocated memory block First address. Unlike aw_mem_alloc(), the memory block allocated by this function is initialized to 0. For example, to allocate memory for storing 10 int data, the sample program is detailed in Listing 9.8.

Listing 9.8 allocates 10 memory blocks for storing int data

Since the allocated memory space is initialized to 0, even if the memory pointed to by ptr is not assigned, its value is determined to be 0.

3. Allocate memory blocks with certain alignment requirements

Occasionally, a user-requested memory block may be used to store data with special alignment requirements. The first address required to allocate a block of memory must be aligned to the specified number of bytes. At this point, you can use aw_mem_align() to allocate a block of memory that satisfies the specified alignment requirement. Its function prototype is:

Among them, size is the allocated memory block size, align represents the alignment requirement, and its value is an integer power of 2, for example: 2, 4, 8, 16, 32, 64, etc. The return value is also the first address of the allocated memory block whose value satisfies the alignment requirement and is an integer multiple of align. If the value of align is 16, then in accordance with the alignment of 16 bytes, the first address of the allocated memory block will be an integer multiple of 16, and the lower 4 bits of the address are all 0. For a sample program, see Listing 9.9.

Listing 9.9 Sample Program for Allocating Blocks with Alignment Requirements

In the program, the assigned address is printed out through aw_kprintf() to see the specific value of the address. The actual operation can be found, and the address value is 16 bytes aligned. Note that this function is the same as the memory block allocated by aw_mem_alloc(), where the initial value of the data is random and not necessarily zero.

In the heap manager, there is no similar allocation of memory block interface to meet certain alignment requirements, only ordinary memory allocation block interface: aw_memheap_alloc (). The allocated memory block may be aligned or misaligned. In order to make the memory block returned to the user to meet the alignment requirements, when using aw_memheap_alloc() to allocate memory blocks, the alignment-1 byte space can be allocated. At this time, even if the first address of the obtained memory block does not meet the alignment requirement, You can return the first aligned address from the beginning of the memory block to the user to meet the user's alignment requirements.

For example, to allocate 200 bytes of memory and require 8-byte alignment, first allocate a 207-byte (200 + 8 - 1) memory block using aw_memheap_alloc(), assuming a memory block address range of: 3 ~ 209, see Figure 9.14(a) for a diagram. Since the first address 3 is not an integer multiple of 8, it is not aligned by 8 bytes. In this case, the first aligned address is returned directly to the user, ie: 8. Since the user needs a 200-byte memory block, the address range of the memory block used by the user is: 8 ~ 207. Obviously, it is within the memory block address range actually obtained by using aw_memheap_alloc(). The memory blocks obtained are completely valid, as shown in Figure 9.14(b).

Figure 9.14 Memory Alignment Processing - Multi-allocation of align-1 bytes of space

Why allocate more space for align - 1 byte? When the actual memory block obtained does not meet the alignment requirement, it indicates that the first address of the memory block is not an integer multiple of alignment, that is, the result of the remainder of the alignment (the C language operator is not 0) is assumed to be a remainder of the alignment. The result is N (N ≥ 1). As long as the first address is aligned-N, the resulting address value is an integer multiple of align. The value is also the first aligned address starting from the first address. Since the first address is not aligned, there must be: N ≥ 1, so: align - N ≤ align - 1, that is, the offset of the first aligned address in the sequence relative to the starting address does not exceed align - 1. Based on this, as long as the allocation of memory block allocates 1 to 1 bytes of space, then you can return an aligned address to the user, while still meeting the user's requested memory block capacity requirements.

If there is no more allocation of align-1 bytes of memory, for example, only aw_memheap_alloc() is used to allocate 200 bytes of memory, the resulting memory block addresses range from 3 to 203, as shown in Figure 9.15(a). At this point, if you also return the first aligned address to the user in the same order, that is: 8. Since the user needs a 200-byte memory block, the address range of the memory block used by the user is: 8 to 208. Obviously, the space between 204 and 208 is not the allocated effective memory, making The user got an illegal memory space. Once accessed, it may cause application errors. The schematic diagram is shown in Figure 9.15 (b).

Figure 9.15 Memory Alignment Processing - No More Allocation of -1 Byte Space

In practice, since the value of align-1 is often a rather special odd value, such as: 3, 7, 15, 31, etc., it is often easy to allocate the first address of the memory block so that there are many unaligned addresses. Therefore, the memory space of align bytes is often directly allocated.

At the same time, for efficiency reasons, in AWorks, each allocated memory is always aligned according to the default number of CPU natural alignment bytes. For example, in a 32-bit system, all memory allocated by default is aligned by 4 bytes. Here, the implementation of the aw_mem_alloc() function can be updated as shown in Listing 9.10.

Listing 9.10 Memory allocated by the aw_mem_alloc() function is aligned to 4 bytes

4. Resizing memory

Sometimes, the size of the allocated memory block needs to be dynamically adjusted. If a small memory block is allocated at the beginning, but the memory is insufficient with the increase of data, this function can be used to readjust the previously allocated memory block size. . Its function prototype is:

Among them, ptr is the first address of the memory block allocated using the aw_mem_alloc(), aw_mem_calloc() or aw_mem_align() function, which is the return value of calling these functions. New_size is the adjusted size. The return value is the first address of the resized memory block. In particular, if the resize fails, the return value is NULL.

For example, first use aw_mem_alloc() to allocate a memory block that stores 1 int data, and then resize the memory block so that it can store 2 int data. The sample program is detailed in Listing 9.11.

Listing 9.11 Sample Memory Allocation Program (Resize Memory)

5. Release memory

Previously explained four methods of allocating memory blocks. No matter which way the dynamically allocated memory blocks are used, they must be released. Otherwise, memory leaks will occur. The function prototype for freeing memory blocks is:

Among them, ptr is the first address of the memory block allocated using the aw_mem_alloc(), aw_mem_calloc(), aw_mem_align(), or aw_mem_realloc() functions, that is, the return value of calling these functions.

When aw_mem_free() is used to free a memory block, the corresponding address space becomes invalid and the user cannot continue to use it. The sample program for freeing memory blocks is shown in Listing 9.12.

Listing 9.12 Sample program to free memory blocks

9.2 memory pool

The heap manager is extremely flexible and can allocate any size memory block, which is very convenient. However, it also has obvious disadvantages: First, the distribution efficiency is not high. In each allocation, all free memory blocks must be sequentially searched until they find a free memory block that meets the demand. Second, memory fragments of different sizes are easily generated. .

In order to improve the efficiency of memory allocation and to avoid memory fragmentation, AWorks provides another memory management method: Memory Pool. It dispenses with the advantage that a chunk of memory of any size can be allocated in the heap manager, setting the size of the allocated memory block to a fixed value.

Since the size of the allocated memory block is a fixed value each time, there is no limitation of the space size. Therefore, each time the user applies for a memory block, the first free memory block can be allocated without any searching process. Similarly, the memory is released. When a memory block, it only needs to be marked as free, without any additional merge operation, which greatly improves the efficiency of memory allocation and release.

At the same time, since each memory block requested and released is the same size, as long as there are free blocks, it can be allocated successfully. It is impossible for some free memory blocks to be unusable due to being divided by an allocated memory block. In this case, any free block can be used without limitation, no memory fragmentation exists, and memory fragmentation is completely avoided.

However, fixing the memory block size limits its flexibility of use and may result in unnecessary memory space waste. For example, the user only needs a small memory space, but if each memory in the memory pool is large, This will cause a large amount of memory to be allocated during memory allocation, resulting in wasted memory.这就要求在定义内存池时,应尽可能将内存池中内存块的大小定义为一个合理的值,避免过多的内存浪费。

系统中可以存在多个内存池,每个内存池包含固定个数和大小的内存块。基于此,在实际应用中,为了满足不同大小的内存块需求,可以定义多种尺寸(内存池中内存块的大小)的内存池(比如:小、中、大三种),然后在实际应用中根据实际用量选择从合适的内存池中分配内存块,这样可以在一定程度上减少内存的浪费。

9.2.1 内存池原理概述

内存池用于管理一段连续的内存空间,由于各个内存块的大小固定,因此,首先将内存空间分为若干个大小相同的内存块,例如,管理1024字节的内存空间,每块大小为128字节。则共计可以分为8个内存块。初始时,所有内存块均为空闲块,示意图详见图9.16。

图9.16 初始状态——8个空闲块

在AWorks中,为便于管理,将各个空闲内存块使用单向链表的形式组织起来,示意图详见图9.17。

图9.17 以单向链表的形式组织各个空闲块

这就要求一个空闲块能够存放一个p_next指针,以便组织链表。在32位系统中,指针的大小为4个字节,因此,要求各个空闲块的大小不能低于4个字节。此外,出于对齐考虑,各个空闲块的大小必须为自然对齐字节数的正整数倍。例如,在32位系统中,块大小应该为4字节的整数倍,比如:4、8、12、6……而不能为5、7、9、13等。

基于此,当需要分配一个内存块时,只需从链表中的取出第一个空闲块即可。例如,需要在图9.17的基础上分配一个内存块,可以直接从链表中取出第一个空闲块,示意图详见图9.18。

图9.18 从链表中取出一个内存块

此时,空闲块链表中,将只剩下7个空闲块,示意图详见图9.19。

图9.19 剩余7个空闲块

值得注意的是,虽然在空闲块链表中,各个内存块中存放了一个p_next指针,占用了一定的内存空间,但是,当该内存块从空闲链表中取出,分配给用户使用时,已分配的内存块并不需要组织为一个链表,p_next的值也就没有任何意义了,因此,用户可以使用内存块中所有的内存,不存在用户不可访问的区域,不会造成额外的空间浪费。

而在堆管理器中,无论是空闲块还是已分配的内存块,头部存储的相关信息都必须保持有效,其占用的内存空间用户是不能使用的,对于用户来讲,这相当于造成了一定的内存空间浪费。

当用户不再使用一个内存块时,需要释放相应的内存块,释放时,直接将内存块重新加入空闲块链表即可。示意图详见图9.20。

图9.20 释放一个内存块

释放后,空闲链表中将新增一个内存块,示意图详见图9.21。

图9.21 释放后,新增一个内存块

由此可见,整个内存池的分配和释放操作都非常简单。分配时,从空闲链表中取出一个内存块,释放时,将内存块重新加入空闲链表中。

9.2.2 内存池接口

AWorks提供了内存池软件库,用户通过相关接口使用即可。相关函数的原型详见表9.3。

表9.3 内存池接口(aw_pool.h)

1. 定义内存池实例

在使用内存池前,必须先使用aw_pool_t类型定义内存池实例,该类型在aw_pool.h中定义,具体类型的定义用户无需关心,仅需使用该类型定义内存池实例即可,即:

其地址即可作为初始化接口中p_pool参数的实参传递。

一个内存池可以管理一段连续的内存空间,在AWorks中,可以使用多个内存池,以分别管理多段连续的内存空间。此时,就需要定义多个内存池实例,例如:

为了满足各种大小的内存块需求,可以定义多个具有不同内存块大小的内存池。例如:定义小、中、大三种尺寸的内存池,它们对应的内存块大小分别为8、64、128。用户根据实际用量选择从合适的内存池中分配内存块,以在一定程度上减少内存的浪费。

2. 初始化内存池

定义内存池实例后,必须使用该接口初始化后才能使用,以指定内存池管理的内存空间,以及内存池中各个内存块的大小。其函数原型为:

其中,p_pool指向待初始化的内存池,即使用aw_pool_t类型定义的内存池实例;p_pool_mem为该内存池管理的实际内存空间首地址;pool_size指定整个内存空间的大小;item_size指定内存池中每个内存块的大小。

函数的返回值为内存池ID,其类型aw_pool_id_t,该类型的具体定义用户无需关心,该ID可作为其它功能接口的参数,用以表示需要操作的内存池。特别地,若返回ID的值为NULL,表明初始化失败。

初始化时,系统会将pool_size大小的内存空间,分为多个大小为item_size的内存块进行管理。例如,使用内存池管理1KB的内存空间,每个内存块的大小为16字节,初始化范例程序详见程序清单9.13。

程序清单9.13 初始化内存池

程序中,将1024字节的空间分成了大小为16字节的内存块进行管理。注意,出于效率考虑,块大小并不能是任意值,只能为自然对齐字节数的正整数倍。例如,在32位系统中,块大小应该为4字节的整数倍,若不满足该条件,初始化时,将会自动向上修正为4字节的整数倍,例如,块大小的值设置为5,将被自动修正为8。用户可以通过aw_pool_item_size ()函数获得实际的内存块大小。

3. 获取内存池中实际的块大小

前面提到,初始化时,为了保证内存池的管理效率,可能会对用户传入的块大小进行适当的修正,用户可以通过该函数获取当前内存池中实际的块大小。其函数原型为:

其中,pool_id为初始化函数返回的内存池ID,其用于指定要获取信息的内存池。返回值即为内存池中实际的块大小。

例如,初始化时,将内存池的块大小设定为5,然后通过该函数获取内存池中实际的块大小。范例程序详见程序清单9.14。

程序清单9.14 获取内存池中实际的块大小

运行程序可以发现,实际内存块的大小为8。

实际应用中,为了满足不同容量内存申请的需求,可以定义多个内存池,每个内存池定义不同的块大小。如定义3种块大小尺寸的内存池,分别为8字节(小)、64字节(中)、128字节(大)。范例程序详见程序清单9.15。

程序清单9.15 定义多种不同块大小的内存池

程序中,将三种类型内存池的总容量分别定义为了512、1024、2048。实际中,应根据情况定义,例如,小型内存块需求量很大,则应该增大对应内存池的总容量。

4. 获取内存块

内存池初始化完毕后,用户可以从内存池中获取固定大小内存块,其函数原型为:

其中,pool_id为初始化函数返回的内存池ID,其用于指定内存池,表示从该内存池中获取内存块。返回值为void *类型的指针,其指向获取内存块的首地址,特别地,若返回值为NULL,则表明获取失败。从内存池中获取一个内存块的范例程序详见程序清单9.16。

程序清单9.16 获取内存块范例程序

5. 释放内存块

当获取的内存块使用完毕后,应该释放该内存块,将其返还到内存池中。其函数原型为:

其中,pool_id为初始化函数返回的内存池ID,其用于指定内存池,表示将内存块释放到该内存池中。p_item为使用aw_pool_item_get()函数获取内存块的首地址,即调用aw_pool_item_get()函数的返回值,表示要释放的内存块。

返回值为aw_err_t类型的标准错误号,若值为AW_OK,表示释放成功,否则,表示释放失败,释放失败往往是由于参数错误造成的,例如,释放一个不是由aw_pool_item_get()函数获取的内存块。注意,内存块从哪个内存池中获取,释放时,就必须释放到相应的内存池中,不可将内存块释放到其它不对应的内存池中。

当使用aw_pool_item_return()将内存块释放后,相应的内存空间将变为无效,用户不能再继续使用。释放内存块的范例程序详见程序清单9.17。

程序清单9.17 释放内存块范例程序

Cable Assemblies /Wiring Harness

Ribbon Flat Cable,Ffc Cable,Rf Coaxial Cable,Electronics Wire

Shenzhen Hongyian Electronics Co., Ltd. , https://www.hongyiancon.com