385 lines
12 KiB
Markdown
385 lines
12 KiB
Markdown
# 内存规划
|
||
|
||
## CCMRAM
|
||
|
||
- 用于系统默认的bss与data区
|
||
- 用于系统默认的栈区
|
||
- 用于FreeRTOS的动态内存分配与LVGL的动态内存分配
|
||
- 用于解码器等算法的缓存区
|
||
|
||
## AXIRAM
|
||
|
||
- 用做用户程序的堆区
|
||
- 用于newlib的堆区
|
||
|
||
## APBRAM
|
||
|
||
- 用于低速外设的DMA缓冲区,如USART、SAI等
|
||
|
||
|
||
# 内存管理函数移植
|
||
|
||
为了保证C语言运行库的线程安全性,标准的`libc`由MMU通过为每个进程分配独立的内存空间实现进程安全性,而线程安全性则通过在每一个线程上下文中分别保存该线程内libc所使用的上下文来实现,方法为在全局范围维护一个指向当前线程所使用的libc上下文的指针,并在调度器切换线程时将该指针指向正在运行任务的libc上下文。`arm-none-eabi-gcc`等目标为裸机的编译器,通常使用精简的`newlib`替代标准的`libc`作为其默认的C语言运行库,从而适应MCU等资源受限的应用。裸机环境中因不存在MMU而不存在进程的概念,而线程则退化为RTOS的任务,因此仍需保证C运行库在RTOS任务间的线程安全性。`arm-none-eabi-gcc`的编译选项中,默认启用了newlib 4引入的一种特殊的锁机制`_RETARGETABLE_LOCKING`,即[可重定向锁机制](https://sourceware.org/legacy-ml/newlib/2016/msg01165.html)。该机制避免了使用标准C语言库中基于保存的newlib上下文实现的锁`__malloc_lock(struct _reent *)`,取而代之的则是由用户提供如下的一系列的mutex与锁函数,分别为newlib中各个部分添加更轻量级的锁。
|
||
|
||
```c
|
||
struct __lock __lock___sfp_recursive_mutex;
|
||
struct __lock __lock___atexit_recursive_mutex;
|
||
struct __lock __lock___at_quick_exit_mutex;
|
||
struct __lock __lock___malloc_recursive_mutex;
|
||
struct __lock __lock___env_recursive_mutex;
|
||
struct __lock __lock___tz_mutex;
|
||
struct __lock __lock___dd_hash_mutex;
|
||
struct __lock __lock___arc4random_mutex;
|
||
|
||
void __retarget_lock_init (_LOCK_T * <[lock_ptr]>);
|
||
void __retarget_lock_init_recursive (_LOCK_T * <[lock_ptr]>);
|
||
void __retarget_lock_close (_LOCK_T <[lock]>);
|
||
void __retarget_lock_close_recursive (_LOCK_T <[lock]>);
|
||
void __retarget_lock_acquire (_LOCK_T <[lock]>);
|
||
void __retarget_lock_acquire_recursive (_LOCK_T <[lock]>);
|
||
int __retarget_lock_try_acquire (_LOCK_T <[lock]>);
|
||
int __retarget_lock_try_acquire_recursive (_LOCK_T <[lock]>);
|
||
void __retarget_lock_release (_LOCK_T <[lock]>);
|
||
void __retarget_lock_release_recursive (_LOCK_T <[lock]>);
|
||
```
|
||
|
||
[22](https://sourceware.org/newlib/libc.html#g_t_005f_005fretarget_005flock_005finit)
|
||
|
||
{{< admonition type=info title="其他newlib中的函数" open=true >}}
|
||
即使`_RETARGETABLE_LOCKING`为newlib实现了一种更加轻量的锁机制,但当使用到newlib中需要使用到除锁以外的上下文的函数,例如
|
||
{{< /admonition >}}
|
||
|
||
[11](https://sourceware.org/git/?p=newlib-cygwin.git;a=blob_plain;f=newlib/libc/stdlib/_mallocr.c)
|
||
|
||
|
||
在不使用`_RETARGETABLE_LOCKING`时,malloc的调用的流程如下:
|
||
|
||
- [`malloc`](https://sourceware.org/git/?p=newlib-cygwin.git;a=blob;f=newlib/libc/stdlib/malloc.c#l162)调用可重入版本的[`_malloc_r`](https://sourceware.org/git/?p=newlib-cygwin.git;a=blob;f=newlib/libc/stdlib/_mallocr.c#l162)函数;
|
||
- `_malloc_r`函数调用[`__malloc_lock`](https://sourceware.org/git/?p=newlib-cygwin.git;a=blob;f=newlib/libc/stdlib/mlock.c#l40)函数为内存分配器上锁;
|
||
-
|
||
newlib-cygwin\newlib\libc\stdlib\malloc.c
|
||
|
||
https://sourceware.org/git/?p=newlib-cygwin.git;a=blob;f=winsup/cygwin/passwd.cc#l190
|
||
|
||
|
||
|
||
|
||
|
||
使用newlib内置的内存分配函数编译工程,可以从连接器输出的map文件中看到
|
||
|
||
```
|
||
.text._malloc_r
|
||
0x000000000801c524 0x474 D:/Programs/STM32CubeCLT/GNU-tools-for-STM32/bin/../lib/gcc/arm-none-eabi/11.3.1/../../../../arm-none-eabi/lib/thumb/v7e-m+dp/hard\libg_nano.a(libc_a-mallocr.o)
|
||
0x000000000801c524 _malloc_r
|
||
```
|
||
|
||
`_malloc_r`函数是来自`newlib_nano`中的`mallocr.o`目标文件。
|
||
|
||
```cmake
|
||
set(TARGET_BUILD_NEWLIB_OPTIONS
|
||
-Wl,-wrap,_malloc_r -Wl,-wrap,_free_r -Wl,-wrap,_realloc_r # wrap newlib memory allocator functions
|
||
)
|
||
```
|
||
|
||
newlib源码中`newlib-cygwin\newlib\libc\stdlib\malloc.c`中有
|
||
|
||
```c
|
||
void *
|
||
malloc (size_t nbytes) /* get a block */
|
||
{
|
||
return _malloc_r (_REENT, nbytes);
|
||
}
|
||
|
||
void
|
||
free (void *aptr)
|
||
{
|
||
_free_r (_REENT, aptr);
|
||
}
|
||
```
|
||
|
||
因此实际是调用的`_malloc_r`与`_free_r`函数。这两个函数的原型在`newlib-cygwin\newlib\libc\stdlib\_mallocr.c`中。例如,`_malloc_r`的原型为
|
||
|
||
```c
|
||
#if __STD_C
|
||
Void_t* mALLOc(RARG size_t bytes)
|
||
#else
|
||
Void_t* mALLOc(RARG bytes) RDECL size_t bytes;
|
||
#endif
|
||
{
|
||
#ifdef MALLOC_PROVIDED
|
||
|
||
return malloc (bytes); // Make sure that the pointer returned by malloc is returned back.
|
||
|
||
#else
|
||
|
||
mchunkptr victim; /* inspected/selected chunk */
|
||
INTERNAL_SIZE_T victim_size; /* its size */
|
||
int idx; /* index for bin traversal */
|
||
mbinptr bin; /* associated bin */
|
||
mchunkptr remainder; /* remainder from a split */
|
||
long remainder_size; /* its size */
|
||
int remainder_index; /* its bin index */
|
||
unsigned long block; /* block traverser bit */
|
||
int startidx; /* first bin of a traversed block */
|
||
mchunkptr fwd; /* misc temp for linking */
|
||
mchunkptr bck; /* misc temp for linking */
|
||
mbinptr q; /* misc temp */
|
||
|
||
INTERNAL_SIZE_T nb = request2size(bytes); /* padded request size; */
|
||
|
||
/* Check for overflow and just fail, if so. */
|
||
if (nb > INT_MAX || nb < bytes)
|
||
{
|
||
RERRNO = ENOMEM;
|
||
return 0;
|
||
}
|
||
|
||
MALLOC_LOCK;
|
||
|
||
/* Check for exact match in a bin */
|
||
|
||
if (is_small_request(nb)) /* Faster version for small requests */
|
||
{
|
||
idx = smallbin_index(nb);
|
||
|
||
/* No traversal or size check necessary for small bins. */
|
||
|
||
q = bin_at(idx);
|
||
victim = last(q);
|
||
|
||
#if MALLOC_ALIGN != 16
|
||
/* Also scan the next one, since it would have a remainder < MINSIZE */
|
||
if (victim == q)
|
||
{
|
||
q = next_bin(q);
|
||
victim = last(q);
|
||
}
|
||
#endif
|
||
if (victim != q)
|
||
{
|
||
victim_size = chunksize(victim);
|
||
unlink(victim, bck, fwd);
|
||
set_inuse_bit_at_offset(victim, victim_size);
|
||
check_malloced_chunk(victim, nb);
|
||
MALLOC_UNLOCK;
|
||
return chunk2mem(victim);
|
||
}
|
||
|
||
idx += 2; /* Set for bin scan below. We've already scanned 2 bins. */
|
||
|
||
}
|
||
else
|
||
{
|
||
idx = bin_index(nb);
|
||
bin = bin_at(idx);
|
||
|
||
for (victim = last(bin); victim != bin; victim = victim->bk)
|
||
{
|
||
victim_size = chunksize(victim);
|
||
remainder_size = long_sub_size_t(victim_size, nb);
|
||
|
||
if (remainder_size >= (long)MINSIZE) /* too big */
|
||
{
|
||
--idx; /* adjust to rescan below after checking last remainder */
|
||
break;
|
||
}
|
||
|
||
else if (remainder_size >= 0) /* exact fit */
|
||
{
|
||
unlink(victim, bck, fwd);
|
||
set_inuse_bit_at_offset(victim, victim_size);
|
||
check_malloced_chunk(victim, nb);
|
||
MALLOC_UNLOCK;
|
||
return chunk2mem(victim);
|
||
}
|
||
}
|
||
|
||
++idx;
|
||
|
||
}
|
||
|
||
/* Try to use the last split-off remainder */
|
||
|
||
if ( (victim = last_remainder->fd) != last_remainder)
|
||
{
|
||
victim_size = chunksize(victim);
|
||
remainder_size = long_sub_size_t(victim_size, nb);
|
||
|
||
if (remainder_size >= (long)MINSIZE) /* re-split */
|
||
{
|
||
remainder = chunk_at_offset(victim, nb);
|
||
set_head(victim, nb | PREV_INUSE);
|
||
link_last_remainder(remainder);
|
||
set_head(remainder, remainder_size | PREV_INUSE);
|
||
set_foot(remainder, remainder_size);
|
||
check_malloced_chunk(victim, nb);
|
||
MALLOC_UNLOCK;
|
||
return chunk2mem(victim);
|
||
}
|
||
|
||
clear_last_remainder;
|
||
|
||
if (remainder_size >= 0) /* exhaust */
|
||
{
|
||
set_inuse_bit_at_offset(victim, victim_size);
|
||
check_malloced_chunk(victim, nb);
|
||
MALLOC_UNLOCK;
|
||
return chunk2mem(victim);
|
||
}
|
||
|
||
/* Else place in bin */
|
||
|
||
frontlink(victim, victim_size, remainder_index, bck, fwd);
|
||
}
|
||
|
||
/*
|
||
If there are any possibly nonempty big-enough blocks,
|
||
search for best fitting chunk by scanning bins in blockwidth units.
|
||
*/
|
||
|
||
if ( (block = idx2binblock(idx)) <= binblocks)
|
||
{
|
||
|
||
/* Get to the first marked block */
|
||
|
||
if ( (block & binblocks) == 0)
|
||
{
|
||
/* force to an even block boundary */
|
||
idx = (idx & ~(BINBLOCKWIDTH - 1)) + BINBLOCKWIDTH;
|
||
block <<= 1;
|
||
while ((block & binblocks) == 0)
|
||
{
|
||
idx += BINBLOCKWIDTH;
|
||
block <<= 1;
|
||
}
|
||
}
|
||
|
||
/* For each possibly nonempty block ... */
|
||
for (;;)
|
||
{
|
||
startidx = idx; /* (track incomplete blocks) */
|
||
q = bin = bin_at(idx);
|
||
|
||
/* For each bin in this block ... */
|
||
do
|
||
{
|
||
/* Find and use first big enough chunk ... */
|
||
|
||
for (victim = last(bin); victim != bin; victim = victim->bk)
|
||
{
|
||
victim_size = chunksize(victim);
|
||
remainder_size = long_sub_size_t(victim_size, nb);
|
||
|
||
if (remainder_size >= (long)MINSIZE) /* split */
|
||
{
|
||
remainder = chunk_at_offset(victim, nb);
|
||
set_head(victim, nb | PREV_INUSE);
|
||
unlink(victim, bck, fwd);
|
||
link_last_remainder(remainder);
|
||
set_head(remainder, remainder_size | PREV_INUSE);
|
||
set_foot(remainder, remainder_size);
|
||
check_malloced_chunk(victim, nb);
|
||
MALLOC_UNLOCK;
|
||
return chunk2mem(victim);
|
||
}
|
||
|
||
else if (remainder_size >= 0) /* take */
|
||
{
|
||
set_inuse_bit_at_offset(victim, victim_size);
|
||
unlink(victim, bck, fwd);
|
||
check_malloced_chunk(victim, nb);
|
||
MALLOC_UNLOCK;
|
||
return chunk2mem(victim);
|
||
}
|
||
|
||
}
|
||
|
||
bin = next_bin(bin);
|
||
|
||
#if MALLOC_ALIGN == 16
|
||
if (idx < MAX_SMALLBIN)
|
||
{
|
||
bin = next_bin(bin);
|
||
++idx;
|
||
}
|
||
#endif
|
||
} while ((++idx & (BINBLOCKWIDTH - 1)) != 0);
|
||
|
||
/* Clear out the block bit. */
|
||
|
||
do /* Possibly backtrack to try to clear a partial block */
|
||
{
|
||
if ((startidx & (BINBLOCKWIDTH - 1)) == 0)
|
||
{
|
||
binblocks &= ~block;
|
||
break;
|
||
}
|
||
--startidx;
|
||
q = prev_bin(q);
|
||
} while (first(q) == q);
|
||
|
||
/* Get to the next possibly nonempty block */
|
||
|
||
if ( (block <<= 1) <= binblocks && (block != 0) )
|
||
{
|
||
while ((block & binblocks) == 0)
|
||
{
|
||
idx += BINBLOCKWIDTH;
|
||
block <<= 1;
|
||
}
|
||
}
|
||
else
|
||
break;
|
||
}
|
||
}
|
||
|
||
|
||
/* Try to use top chunk */
|
||
|
||
/* Require that there be a remainder, ensuring top always exists */
|
||
remainder_size = long_sub_size_t(chunksize(top), nb);
|
||
if (chunksize(top) < nb || remainder_size < (long)MINSIZE)
|
||
{
|
||
|
||
#if HAVE_MMAP
|
||
/* If big and would otherwise need to extend, try to use mmap instead */
|
||
if ((unsigned long)nb >= (unsigned long)mmap_threshold &&
|
||
(victim = mmap_chunk(nb)) != 0)
|
||
{
|
||
MALLOC_UNLOCK;
|
||
return chunk2mem(victim);
|
||
}
|
||
#endif
|
||
|
||
/* Try to extend */
|
||
malloc_extend_top(RCALL nb);
|
||
remainder_size = long_sub_size_t(chunksize(top), nb);
|
||
if (chunksize(top) < nb || remainder_size < (long)MINSIZE)
|
||
{
|
||
MALLOC_UNLOCK;
|
||
return 0; /* propagate failure */
|
||
}
|
||
}
|
||
|
||
victim = top;
|
||
set_head(victim, nb | PREV_INUSE);
|
||
top = chunk_at_offset(victim, nb);
|
||
set_head(top, remainder_size | PREV_INUSE);
|
||
check_malloced_chunk(victim, nb);
|
||
MALLOC_UNLOCK;
|
||
return chunk2mem(victim);
|
||
|
||
#endif /* MALLOC_PROVIDED */
|
||
}
|
||
|
||
#endif /* DEFINE_MALLOC */
|
||
```
|
||
|
||
其中,在已定义`__STD_C`时,函数原型中的`RARG`展开为`struct _reent *reent_ptr,`,因此完整的函数原型为
|
||
|
||
```c
|
||
void* mALLOc(struct _reent *reent_ptr, size_t bytes);
|
||
```
|
||
|
||
其中,第一个参数为使函数可重入的锁,第二个参数则为常规`malloc`中要申请的内存大小。
|