内存管理

与 C/C++ 之类的编程语言不同, MicroPython 通过支持自动内存管理的方式,向开发者隐藏了内存管理的细节。自动内存管理是操作系统或应用程序用于自动管理内存分配和释放的一种技术。它避免了诸如忘记释放分配给对象的内存等问题。自动内存管理还避免了使用已释放内存的关键问题。自动内存管理有多种形式,垃圾回收机制( GC )是其中一种。

垃圾回收器通常有两个职责:

  1. 分配新的对象到可用内存中。

  2. 释放未使用的内存。

业界存在许多种垃圾回收算法,而 MicroPython 使用 标记与扫描 算法来管理内存。该算法的标记阶段遍历堆栈,标记所有活动对象;扫描阶段遍历堆栈,释放所有未标记的对象。

MicroPython 中可通过 gc 内建模块来访问垃圾回收功能:

>>> x = 5
>>> x
5
>>> import gc
>>> gc.enable()
>>> gc.mem_alloc()
1312
>>> gc.mem_free()
2071392
>>> gc.collect()
19
>>> gc.disable()
>>>

即使 gc.disable() 已被调用,也可以使用 gc.collect() 触发垃圾回收。

对象模型

所有 MicroPython 对象在 C 中都是通过 mp_obj_t 数据类型引用。它通常是一个字长(即与架构相同大小的指针),可以是 32-bit (STM32, nRF, ESP32, Unix x86) 或 64-bit (Unix x64)。对于某些特定对象,它也可以比字长大,例如在 32-bit 架构中 OBJ_REPR_D 为 64-bit 大小的 mp_obj_t

一个 mp_obj_t 对象代表一个 MicroPython 对象,例如整数、浮点数、类型、字典或类实例。某些对象(例如布尔值和小整数)在 mp_obj_t 中直接存储其值,不需要额外的内存。其他对象的值存储在其他内存中(例如垃圾回收堆栈),其 mp_obj_t 包含一个指向该内存的指针。mp_obj_t 中的一部分信息作为标记,声明其对象类型。

参考 py/mpconfig.h 查看可用的对象表示的具体细节。

指针标记

因为指针是字长对齐的,当它们存储在 mp_obj_t 中时,这个对象的低位将会被设置为 0 。例如 32-bit 架构下,这个位将会被设置为 0 :

********|********|********|******00

这些位用于存储标记信息。该标记存储额外的信息,并非低效地在对象中新增一个字段来存储这些信息。在 MicroPython 中,这一标签告诉我们是在处理小整数、内部(小)字符串或具体对象,并且不同的语义都可一一对应。

对于小整数,这一对应关系如下:

********|********|********|*******1

其中星号表示具体的整数值。对于内部字符串或具体对象(例如 True ),mp_obj_t 的值的布局,分别如下:

********|********|********|*****010

********|********|********|*****110

当一个具体对象不是上述任何一个时,它的布局如下:

********|********|********|******00

这些星号表示具体对象在内存中的地址。

对象分配

小整数的值直接存储在 mp_obj_t 中,而不是堆或其他地方。因此,小整数的创建不会影响堆内存。同样地,内部字符串已经存储在其他地方,以及 NoneFalseTrue 等等的即时生效的值也不会影响堆内存。

所有其他的具体对象都会在堆内存中分配,并且其对象结构中会保留一个字段来存储对象的类型。

+++++++++++
+         +
+ type    + object header
+         +
+++++++++++
+         + object items
+         +
+         +
+++++++++++

堆内存中最小的分配单位是一个块,它是四个机器字( 32 位机器上是 16 字节,64 位机器上是 32 字节)的大小。堆内存中还有一个结构跟踪每个块中分配的对象。这个结构叫做 位图 ( bitmap )

../_images/bitmap.png

位图跟踪块是否是 “空闲” 或 “正在使用” 的状态,并且使用两个位来跟踪这个状态。

「标记-清扫垃圾收集器」会管理堆内存中分配的对象,并且使用位图来标记对象是否仍然在使用。请参见 py/gc.c 查看这些细节的完整实现。

分配:堆布局

堆内存中的块是池化的,每个块可以有不同的属性:

  • ATB (allocation table byte 分配表字节): 如果设置,则该块是一个普通块

  • FREE: 空闲块

  • HEAD: 块链的头部

  • TAIL: 块链的尾部

  • MARK : 标记的头部块

  • FTB (finaliser table byte 最终化表字节): 如果设置,则该块有一个最终化