# 内存规划 ## 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`中要申请的内存大小。