# 内存规划

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