你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

嵌入式系统裸机编程的内存管理的一些简介

[复制链接]
gaosmile 发布时间:2020-12-10 21:24
在嵌入式裸机编程中,作为一名初级的CODER。经常要与CPU、内存等打交道。CPU作为系统的动力源,其重要程度不言而喻。
8 V9 b3 C/ t+ E$ I8 H" \6 v
但是,在裸机编程中,对内存的管理也不容忽视。如果稍微不注意,轻则,可能造成内存泄漏,重则造成内存访问异常。导致系统死机。
# c5 w  J; Q( f  ^
嵌入式产品,对稳定性要求及其严格。动不动就死机,那可就麻烦大了。以下,是我本人对嵌入式系统裸机编程的内存管理的一些简介。
- n1 C1 u& A' K( n+ i  M
1、尽量不使用库自带的malloc和free。
5 w* b* ]% y' x
malloc和free在PC编程中是很好用的一种内存分配手段。但是,其在嵌入式中,就未必好用了。由于嵌入式裸机编程中,无MMU,即内存管理单元。无法实现对内存进行动态映射(不明白什么叫动态映射的同学,可以参考网上的资料)。也就是说,实际上,malloc和free并不能实现动态的内存的管理。这需要在启动阶段专门给其分配一段空闲的内存区域作为malloc的内存区。如STM32中的启动文件startup_stm32f10x_md.s中可见以下信息:

  1. # t8 B0 n4 S5 w3 {2 _
  2. Heap_Size       EQU     0x00000800& E0 g" \  b% s* X+ o9 f! p
  3. AREA    HEAP, NOINIT, READWRITE, ALIGN=3
    $ Y. a* c7 B. x) ?, B) [; r
  4. __heap_base* G* U; C( }9 U7 `) D1 r
  5. Heap_Mem        SPACE   Heap_Size
    1 T% d" d5 i# w+ Z5 c2 B; g
  6. __heap_limit
复制代码

" v1 N: ]- c% p% B0 d2 @3 P: W% \
其中,Heap_Size即定义一个宏定义。数值为 0x00000800。Heap_Mem则为申请一块连续的内存,大小为 Heap_Size。简化为C语言版本如下:6 z$ d. d, ^" x% h+ ]+ t
  1. $ ?9 {" Y+ Q. U* u/ u
  2. #define Heap_Size 0x00000800
    / Y: M) k7 U, V7 a7 L
  3. unsigned char Heap_Mem[Heap_Size] = {0};
复制代码
! k- u: [! C5 r# K5 z
在这里申请的这块内存,在接下来的代码中,被注册进系统中给malloc和free函数所使用:2 z% \/ ^0 {- T/ H% L9 ]% ~

  1. 0 P7 J* U4 Q( Z3 W/ F
  2. __user_initial_stackheap) ^8 D$ O3 J. N" D
  3. LDR     R0, =  Heap_Mem  ;  返回系统中堆内存起始地址
    ) r4 T6 E- H4 T0 O; o1 [; \: |' U' a$ y
  4. LDR     R1, =(Stack_Mem + Stack_Size)' y% f  k3 h! s6 m! y' d
  5. LDR     R2, = (Heap_Mem +  Heap_Size); 返回系统中堆内存的结束地址( m2 y7 b2 z4 \4 v: Y
  6. LDR     R3, = Stack_Mem4 J  A& R  K, c$ f# N: Q4 m# E; S. r
  7. BX      LR
复制代码
* e9 T  Y& v7 |: `* K
就如上面分析的那样,其实,在裸机编程的时候,对堆内存的管理。并非是智能化的,并非你想申请多少就多少。而是使用一块固定的内存用作堆内存的分配。这在设计的时候,往往不是最佳的方案。这块内存,如果被多次按照不同的大小进行申请,就会造成内存碎片。最终导致无法申请到足够的内存。导致系统运行出错。这在原本内存就已经很少的嵌入式系统中,更是不能接受的。所以,建议是把那个Heap_Size设置成 0 吧。放弃其使用吧。. a, [# @: [; h) x, t
而更为致命的是,有些malloc,free函数,由于工程人员的偷懒。实现甚至可能如下:; A. ]  K& I& h0 r: n8 C
  1. * M4 {6 m7 O" n( k! d' m; S
  2. unsigned char mem_buffer[512];
    * p- V) z; {/ C( _) _" g: c, c, x
  3. unsigned char *mem_offset = & mem_buffer;
    , Q% b9 k0 e0 V! h7 Y' J9 h5 F
  4. void *malloc(int size)
    2 d, \. T% ]) _) V/ ]0 r
  5. {4 d0 S7 d6 M6 v6 U, s
  6. unsigned char *tmp = mem_offset;! ^8 c, h$ @. q. I
  7.     mem_offset += size;  u0 n# ~4 ], G+ D0 M
  8. return (void *)tmp;$ @. e; `, R7 L3 T5 F
  9. }: [' V# |& d9 J( P* c0 A
  10. void free(void *mem)
    5 ]9 v" [$ `1 J/ ^$ J, i
  11. {. n# |- E6 g1 N, |! ?* y
  12. mem_offset = mem;
    - J) R8 m+ p# s% v5 _' {" Q' g
  13. }
复制代码

/ c2 {4 b  j  d6 Q% e6 c  h+ W8 n
2、不用malloc、free的原因
4 ]2 p2 L/ K& G, s3 R; ~
一般单片机的内存都比较小,而且没有MMU,malloc 与free的使用容易造成内存碎片。而且可能因为空间不足而分配失败,从而导致系统崩溃,因此应该慎用,或者自己实现内存管理。如:《一个简单而强大的单片机内存管理器》
% l( o% n8 a9 P' o
在函数中使用malloc,如果是大的内存分配,而且malloc与free的次数也不是特别频繁,使用malloc与free是比较合适的,但是如果内存分配比较小,而且次数特别频繁,那么使用malloc与free就有些不太合适了。  A- |, O+ c" P2 m( O
因为过多的malloc与free容易造成内存碎片,致使可使用的堆内存变小。尤其是在对单片机等没有MMU的芯片编程时,慎用malloc与free。如果需要对内存的频繁操作,可以自己实现一个内存管理。& A  @1 s$ Z4 {4 d- @/ f
使用动态内存分配,应分不同的应用场合。1 T4 g) T" a# ?0 U* M0 k7 H) c
对于在操作系统上运行的程序,实际的物理内存分配与释放使用操作系统来实现的,即使程序调用了 malloc和free物理内存并不会马上变化。物理内存的变化,直到系统的内存管理操作时才发生。
% @8 A: _4 w6 H! `( t8 v0 n& K
对于裸机跑在MCU上的程序,分配与释放内存都会造成实际物理内存的变化。因为此时物理内存的分配是由自己实现的,而内存管理我们自己并没有去做。这样,盲目的使用malloc与free恰恰并不好,反而会造成内存的不恰当使用。甚至于内存溢出。
" \; k, I" N6 X5 p. X
所以,动态内存的使用前提是有一套好的内存管理方法,这样动态内存的使用才会合理使用内存。如果没有合适的内存管理代码,还是用静态内存好一些。: X# s3 a" M! P! D- E% _
3、 更好的替代方案:内存池。
7 O1 ^0 s6 v' k' @  X# E$ {7 S( m2 j
可能有些同学,觉得:内存池,这是什么东西?9 y2 K4 d1 _  y- B5 ^7 Z& W7 Y
内存池,简洁地来说,就是预先分配一块固定大小的内存。以后,要申请固定大小的内存的时候,即可从该内存池中申请。用完了,自然要放回去。注意,内存池,每次申请都只能申请固定大小的内存。这样子做,有很多好处:
  R3 J4 d1 x* R8 n
(1)每次动态内存申请的大小都是固定的,可以有效防止内存碎片化。(至于为什么,可以想想,每次申请的都是固定的大小,回收也是固定的大小)
% j) [9 z# @: P0 z" o
(2)效率高,不需要复杂的内存分配算法来实现。申请,释放的时间复杂度,可以做到O(1)。. p; N0 K% G2 ^* S! Y
(3)实现简单,易用。
1 r# S$ O1 N8 G6 n: x% k! ?3 K
(4)内存的申请,释放都在可控的范围之内。不会出现以后运行着,运行着,就再也申请不到内存的情况。
2 s% S9 |+ S, {! _
内存池,并非什么很厉害的技术。实现起来,其实可以做到很简单。只需要一个链表即可。在初始化的时候,把全局变量申请来的内存,一个个放入该链表中。在申请的时候,只需要取出头部并返回即可。在释放的时候,只需要把该内存插入链表。以下是一种简单的例子(使用移植来的linux内核链表,对该链表的移植,以后有时间再去分析):- J- V& `& X! J0 p. s
  1. 8 C; V" i9 B3 a5 W# a" C+ L' d
  2. #define MEM_BUFFER_LEN  5    //内存块的数量
    & ~) o: n: K7 O; g+ W" {
  3. #define MEM_BUFFER_SIZE 256 //每块内存的大小
    : U1 d+ U1 @: G1 ]
  4. , [$ _" N; V5 `) X+ ?) S
  5. //内存池的描述,使用联合体,体现穷人的智慧。就如,我一同学说的:一个字节,恨不得掰成8个字节来用。
    + W3 e' e! Q7 j. d- b
  6. typedef union mem {# o, G& \) v5 T! Y7 {$ ^/ ?' H3 A, e
  7. struct list_head list;
    3 ~/ U7 a/ N. x2 A! m  W" F& T
  8. unsigned char buffer[MEM_BUFFER_SIZE];) h/ J5 E# M* i& k
  9. }mem_t;
    ( ^- S2 T8 Y( ^+ W
  10. ' H6 N0 T7 Z  x2 H  \7 h% A9 I7 Q
  11. static union mem gmem[MEM_BUFFER_LEN];6 `0 j3 `6 H1 y4 k
  12. * x! R/ ?8 V0 s# h% ^+ y9 c* h
  13. LIST_HEAD(mem_pool);
    5 w0 G& W9 \& y$ [2 D/ ?
  14. 1 g6 t- r6 _9 \$ T  ~
  15. //分配内存
    5 K. F- m8 y7 R
  16. void *mem_pop()
    : M* z1 `& C$ f: x, Z# [
  17. {- ]% a9 _  E$ a
  18.     union mem *ret = NULL;
    4 ]- m( o. }7 `6 Q6 z8 \( Q! I
  19.     psr_t psr;
    , B; b+ D# m) S
  20. 5 L$ V" p8 \7 h' l
  21.     psr = ENTER_CRITICAL();
    & D) F" ]) C, K$ @* U- S' w
  22.     if(!list_empty(&mem_pool)) { //有可用的内存池
    : ?# W1 E6 U$ g9 }& `5 F
  23.         ret = list_first_entry(&mem_pool, union mem, list);4 t6 ]4 Y/ U- Z, `; N, M
  24.         //printf("mem_pool = 0x%p  ret = 0x%p\n", &mem_pool, &ret->list);& W$ ^/ }+ l' H6 e+ l- _
  25.         list_del(&ret->list);
    2 J. o: h, Y8 x) d- {0 }: ]
  26. }
    5 m8 u& o- B0 K# _! L# B
  27. EXIT_CRITICAL(psr);
    1 |" q  N/ S# @; Z& c3 h
  28. return ret;//->buffer;
    1 K' o+ V# i8 b- b1 O9 Z
  29. }; [4 Q1 r4 h7 {

  30. / `9 |0 s4 j7 E- |
  31. 7 U2 `9 l) ?, C  P
  32. //回收内存8 P  i- U6 a  _1 W4 H
  33. void mem_push(void *mem)' T( v! y; F, Y3 l
  34. {
    ' @; z7 a  |7 a0 O$ \7 c3 ^
  35.     union mem *tmp = NULL;
    ' i9 B4 ]4 R+ f0 M1 x. h# P
  36.     psr_t psr;
    ' T, G  W6 u5 A6 g5 y0 q! I
  37. ! U, T8 C9 ^! E, K
  38.     tmp = (void *)mem;//container_of(mem, struct mem, buffer);
    / a: L8 H( |! ~" {' W
  39.     psr = ENTER_CRITICAL();; B. S2 ~; f9 N9 f3 J* j
  40.     list_add(&tmp->list, &mem_pool);
    4 H8 Q6 V, W2 y5 W/ o
  41.     //printf("free = 0x%p\n", &tmp->list);
    0 o. A# ]7 ^& u! e/ P6 e
  42. , L  e/ p$ m; G- p
  43.     EXIT_CRITICAL(psr);
    * I; ]" d4 P/ l) b5 ?( X6 Z
  44. }
    " ^# z1 W- [- u
  45. ! `) B- j% y' r& Y* b) W
  46. //初始化内存池1 J# j) x/ b0 f) A
  47. void mem_pool_init()" J% b8 L8 a; ^3 \$ H
  48. {* Y0 B! ]1 Z% J) H* Y
  49.     int i;
    4 P2 k3 v& H0 l- ?
  50.     psr_t psr;0 ]! n0 t3 s4 W2 c' L7 o
  51.     psr = ENTER_CRITICAL();
    " y- G- A# ]) ~
  52.     for(i=0; i<MEM_BUFFER_LEN; i++) {
      ?9 O' `2 B' M+ W2 j0 `
  53.         list_add(&(gmem[i].list), &mem_pool);0 Y. T3 H# k9 Y* i$ i9 U
  54.         //printf("add mem 0x%p\n", &(gmem[i].list));$ U0 k9 a% p3 C0 X& \+ S
  55. }, F& A% R: ^' a3 H
  56. EXIT_CRITICAL(psr);
    ( i" O6 A! R2 k8 S: B, e0 o" ~- @
  57. }
复制代码
1 M3 M/ I, X: l4 d
收藏 评论0 发布时间:2020-12-10 21:24

举报

0个回答

所属标签

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版