TI-RTOS Kernel(SYS/BIOS)---内存模块

本章描述了在SYS/BIOS中内存使用的相关问题

Background

本章讨论了静态内存(即内存映射和section放置)、缓存和堆栈的配置。还提供了关于动态内存分配(在运行时分配和释放内存)的信息。

静态内存配置与可执行文件可用的"内存映射"以及将代码和数据放置到内存映射中有关。内存映射由存在于CPU中的内部内存区域和位于硬件板上的外部内存区域组成。

代码和数据由链接器命令文件放置在内存区域。链接命令文件指定一个内存映射。对于每个内存区域,链接器命令文件指定源地址或基地地址、长度和属性(读、写和执行)。在链接器命令文件中指定的内存区域也称为"memory segment"。

下面是来自链接器命令文件的内存映射规范:

MEMORY {
    IRAM (RWX) : org = 0x800000, len = 0x200000
    DDR : org = 0x80000000, len = 0x10000000
}

链接器命令文件还包含关于"memory section"位置的信息,如下面的示例所示。段是编译器生成的可重定位的代码块。编译器生成一些段,用于放置各种类型的代码和数据。例如:.text,.switch,.bss,.far,.cinit,和.const。

SECTIONS {
    .text: load >> DDR
    .switch: load >> DDR
    .stack: load > DDR
    .vecs: load >> DDR
    .args: load > DDR
    .sysmem: load > DDR
    .far: load >> DDR
    .data: load >> DDR
    .cinit: load > DDR
    .bss: load > DDR
    .const: load > DDR
    .pinit: load > DDR
    .cio: load >> DDR
}

链接器将"memory sections"(如.text和.cinit)放入由链接器命令文件sections部分指定的"memory segments"(如 IRAM)。

Memory Map

可执行文件的内存映射由设备(有内部内存)和硬件板(有外部内存)决定。

当使用SYS/BIOS的应用程序创建CCS项目时,可以在RTSC Configuration Settings页面中选择一个"platform"。设备内和外部内存的内存映射由这个平台决定。平台还设置时钟速度和指定内存段的位置。

要在创建新项目或更改项目属性时选择平台,而不是在创建配置文件时。

需要不同内存映射的可执行文件必须使用不同的平台,即使它们运行在相同类型的板上。

平台被绑定到特定的设备(CPU),并从设备获取内存映射—例如,IRAM和FLASH。该平台还包含外部内存规格和缓存设置。内部和外部内存段一起形成内存映射。

Choosing an Available Platform

在构建SYS/BIOS 6.x 可执行文件之前,需要选择你使用的硬件板。可以在创建CCS项目时选择平台,或者在项目的CCS General属性RTSC选项卡中选择平台。Platform字段提供了与目标匹配的所有可用平台的下拉列表;这些项目代表你可以所选择设备(CPU)的各种评估板。

在这里插入图片描述

如果要查看平台的内存映射,可以通过选择Tools->RTSC Tools->Platform->Edit/View打开平台向导。在SYS/BIOS安装中选择包的存储库(即,你的<bios_install_dir>\packages目录)。选择正在使用的Package并单击Next。
在这里插入图片描述
在大多数情况下,可以使用评估板开始应用程序的开发,并可以从下拉列表中选择一个标准的平台。如果下列各项均为真,则应选择现有平台之一:

  • 正处于开发阶段并正在使用评估版
  • 不关心缓存大小,并且满足于现有平台设置的默认值
  • 不希望更改默认section的位置
  • 想要和评估版一样的时钟频率

Creating a Custom Platform

在应用程序开发的某个时刻,大多数客户构建自己的板子,选择TI设备并添加自定义的外部内存

如果下列任何一项是正确的,也可以创建自己的平台:

  • 想要自定义缓存大小
  • 想要手动覆盖默认section的位置

对于这样的自定义板子你需要使用平台向导来创建一个平台。平台向导是一个GUI工具,允许你轻松地创建自定义平台。在定义内存映射和选择用于section放置的默认内存段方面,创建自定义平台提供了很大的灵活性。

为了运行平台向导,执行以下步骤:

  1. 在CCS中,从菜单中选择Tools > RTSC Tools > Platform > New。这将打开New Platform wizard。

  2. 输入包的名称。这将是为包含平台包而创建的目录的名称,并且将是在为项目选择要使用的平台时所选择的名称。可以使用简单名称或句点分隔的名称。在创建平台包时,"period"对应目录级别

  3. 在平台包存储库字段旁边,单击Browse。选择要保存平台包的存储库位置。默认是"C:\Users<username>\myRepository\packages"。
    4.
    如果你以前没有创建包存储库,并且不想使用默认值,那么创建一个新目录来包含存储库。在选择的目录中,创建一个名为"packages"的子目录。

  4. 另外,如果你已经创建了一个CCS项目,并且希望能够使用该平台,要选中Add Repository to Project Package Path框。然后选择应该有权访问此存储库的项目。现在不需要这么做,可以从项目的"生成属性"对话框中将存储库添加到项目中

  5. 从下拉列表中选择Device Family和Device Name。例如:
    在这里插入图片描述

  6. 点击Next。可以看到平台向导的Device Page

Getting and Setting the Clock Speed and Default Memory Settings

Device Page打开时没有时钟速度设置,没有外部内存段,也没有memory section分配。通常,要做的第一件事是从现有平台导入默认设置,以便可以使用这些设置作为进行所需修改的基础。

为了导入默认值,执行以下步骤:

  1. 单击"Clock Speed"字段旁边的"import"按钮
  2. 在选择平台对话框中,选择你想要导入的平台,并点击OK。
  3. 如果你确定想要改变设置,在确认对话框中点击Yes
  4. 可以看到默认的时钟速度和外部内存设置

在这里插入图片描述

Determining Cache Sizes for Custom Platforms

由于缓存大小会影响内存映射,如果使用C6000目标,则需要决定在创建平台时要使用的大小。例如,如果使用的是"ti.platforms.evmDA830"平台,L1P,L1D和L2缓存大小影响可用的L1PSRAM、L1DSRAM和IRAM的大小。

由于缓存大小是在平台中设置的,因此需要不同缓存配置的可执行文件也需要不同的平台。

下列得步骤使用平台向导的Device Page指定TMS320DA830平台的最大缓存大小:

  1. 设置L1D Cache为32K。设置L1P Cache为32K。将L2缓存设置为256K。
  2. 注意,L1PSRAM、L1DSRAM和IRAM的大小被调整为零。

在这里插入图片描述

Selecting Default Memory Segments for Data,Code and Stack

平台还确定用于放置代码、数据和堆栈的默认内存段。如果没有显示地放置section,则使用默认值。例如,如果没有再*.cfg文件中配置Task堆栈位置,那么task堆栈将被放置再平台指定的堆栈内存段中。

通过再平台定义中选择数据内存、代码内存和堆栈内存地值,您可以粗略地决定将代码、数据和堆栈放置在何处。

例如,在evmDA830上,可能希望代码在SDRAM中,数据在RAM中。可以通过在平台向导中选择代码内存为SDRAM,数据内存为IRAM来实现这一点。
在这里插入图片描述

Setting Custom Base Addresses and Lengths for Segments

可以自定义内部和外部内存段地名称、位置、大小、类型和权限。

如果需要自定义内存段,请在"设备内存"区域框中选择"自定义内存"。可以单击内存段列表中地字段并进行更改。在"Name"、“Base"和"Length"列中,键入要使用地值。在Space和Access列中,可以从选项列表中进行选择。
在这里插入图片描述
如果需要自定义外部内存段,可以在"外部内存"区域单击右键,选择"插入行"或"删除行”。

在这里插入图片描述
在新的行中,输入名字,基地址和内存段的长度。选择内存空间的类型和允许访问呢该内存的权限。

Placing Sections into Memory Segments

平台定义了应用程序的内存映射,以及这些段在内存中的默认区段位置。该平台提供了对"代码"、"数据"和"堆栈"部分位置的总体控制。对于更细粒度的部分控制,有几个选项:

  • 为了定义和放置不受SYS/BIOS管理的新sections,可以修改项目的配置(.cfg)文件
  • 要修改SYS/BIOS管理的section的位置,可以修改项目的配置(.cfg)文件

Configuring Simple Section Placement

在配置文件中,section的位置是通过Program.sectMap[]数据完成的。

配置segment的最简单方法如下:

Program.sectMap[".foo"] = "IRAM";

这个例子将导致IRAM segment被用于加载和运行.foo部分

Configuring Section Placement Using a SectionSpec

Program.sectMap[]数组将section的名字映射到类型为SectionSpec的结构体。如果使用上一节中所示的简单语句语法,则不需要创建SectionSpec结构。使用SectionSpec结构可以更精确地如何运行和加载指定section的内存段(或地址)。

SectionSpec结构体包含以下的字段。

  • runSegment. 这个section段的segment是要运行的
  • loadSegment. 这个section段的segment是要加载的
  • runAddress. 这个section段要运行的起始地址
  • loadAddress. 这个section段要加载的地址
  • runAlign. 对齐由runSegment指定的section的Alignment。如果指定runSegment,还可以指定runAlign
  • loadAligh.由loadSegment来指定section的偏移。如果指定一个loadSegment,可以加载loadAlign
  • type. 可以使用此字段定义各种特定于目标的标志,以识别区段类型。例如,COPY、DSECT和NOLOAD
  • fill. 如果指定了该值,则该值用于初始化未初始化的section

下面的.cfg文件语句指定了加载和运行.foo部分的内存段。

Program.sectMap[".foo"] = new Program.SectionSpec();
Program.sectMap[".foo"].loadSegment = "FLASH";
Program.sectMap[".foo"].runSegment = "RAM";

如果对于一个section只指定了loadSegment或runSegment,默认的行为是同时使用指定的segment进行加载和运行。

下面的语句将Swi_post()函数加入到IRAM内存段中:

Program.sectMap[".text:_ti_sysbios_knl_Swi_post__F"] = new Program.SectionSpec();
Program.sectMap[".text:_ti_sysbios_knl_Swi_post__F"] = "IRAM";

下面的语句将Task模块的所有静态实例放入.taskStatic部分:

var Task = xdc.useModule('ti.sysbios.knl.Task');
Task.common$.instanceSection = ".taskStatic";
Program.sectMap[".taskStatic"] = new Program.SectionSpec();
Program.sectMap[".taskStatic"].loadSegment = "IRAM";

使用sectMap数组指定sections的配置语句会影响由配置生成的链接器命令文件中的section位置。

Providing a Supplemental Linker Command File

可以提供自己的链接器命令文件,以补充XDCtools生成的链接器命令文件。可以这样做来定义新的sections,并通过链接器命令语言利用所有可用的特性。

只需要添加一个链接器命令文件,你已经写入了CCS项目中。文件扩展名必须为*.cmd。CCS会自动识别这样的链接器命令文件,并在链接器步骤中调用它。

内存映射的定义(链接器命令文件的"MEMORY"特性)由平台处理,因此不能使用这种方法更改现有内存段的定义

Default Linker Command File and Customization Options

SYS/BIOS应用程序使用的linker命令文件是在处理配置时自动生成的。这个命令文件通常与配置包放在一起,如下所示:

在这里插入图片描述
自动生成的链接器命令文件使用与程序相关联的平台指定的模板。这个命令文件定义了在配置过程中确定的MEMORY和SECTIONS。配置文件中各节的位置反映在这个自动生成的命令文件中。

可以自定义自动生成的链接器命令文件使用下列任何一种技术:

  • 从自动生成的命令文件中排除sections
  • 替换生成的链接器命令文件的整个SECTIONS部分
  • 为程序链接器命令文件指定一个模板。为程序创建模板的最简单的方法是,首先自动生成链接器的命令文件,然后编辑它以满足应用程序的需要,然后设置程序。链接模板参数来引用编辑的文件。

Sections and Memory Mapping for MSP430, Stellaris M3, and C28x

在CCS中创建项目时,必须在CCS项目创建的向导中选择设备作为项目设置的一部分(例如,MSP430F5435A)。CCS会自动将指定设备的链接器命令文件添加到项目中。

在向导的RTSC配置设置页面中,一个Target和Platform将根据以前的选择自动选择。建议使用"release"作为Build-Profile,即使正处于代码的开发阶段。

MSP430、Stellaris Cortex-M3微控制器和C28x设备的平台与其他平台的不同之处是,它们没有为设备定义内存映射。相反,直接使用项目向导添加的链接器命令文件。通过直接编辑器命令文件,可以对内存映射和区段位置进行任何更改。

注意,一个额外的xdctools生成的链接器命令文件被添加到项目中,这个文件放置了一些特定于SYS/BIOS的部分。这个命令文件假设"FLASH"和"RAM"是内存映射的一部分。

Stacks

SYS/BIOS为硬件中断使用单个系统堆栈,为每个Task实例使用单个task堆栈。

System Stack

可以配置System堆栈的大小,它用作硬件中断和软件中断的栈(如果Task关闭,则通过Idle)。可以使用.stack部分来控制系统堆栈的位置。例如,下面的配置语句将大小为0x400的系统堆栈放置在IRAM段中。

Program.stack = 0x400;
Program.sectMap[".stack"] = "IRAM";

设置Program.stack在链接器命令文件中生成适当的链接器选项,以允许在链接时分配系统堆栈。例如,C6000应用程序的链接器命令文件可能包括命令选项 --stack 0x400.

Task Stacks

如果启用了Task模块,SYS/BIOS会为应用程序包含的每个Task实例创建一个额外的堆栈(加上一个空闲线程的task堆栈)。

你可以在配置文件中指定Task的堆栈大小。(可以使用XGCONF或者直接编辑.cfg文件)。例如:

var Task = xdc.useModule('ti.sysbios.knl.Task');
/* Set default stack size for tasks */
Task.defaultStackSize = 1024;
/* Set size of idle task stack */
Task.idleTaskStackSize = 1024;
/* Create a Task Instance and set stack size */
var tskParams = new Task.Params;
tskParams.stackSize = 1024;
var task0 = Task.create('&task0Fxn', tskParams);

可以使用Program.sectMap[]来控制静态创建的Task的任务栈的位置。例如:

/* Place idle task stack section */
Program.sectMap[".idleTaskStackSection"] = "IRAM";
/* Place other static task stacks */
Program.sectMap[".taskStackSection"] = "IRAM";

Cache Configuration

SYS/BIOS 6.x 中的C6000的cache大小由所选择的平台决定。

下面的小节描述了在SYS/BIOS中使用family指定的缓存模块来操作缓存行为的方法。

Configure Cache Size Register at Startup

对于C6000目标,ti.sysbios.hal.Cache模块从平台获取缓存大小,并在启动时设置缓存大小寄存器。ti.sysbios.hal.cache模块是一个泛型模块,它的实现是特定family家族的ti.sysbios.faimily.*.Cache模块。

例如,在DA830上,Cache模块在启动时设置L1PCFG、L1DCFG和L2CFG寄存器。

Configure Parameters to Set MAR Registers

对于C6000目标,ti.sysbios.family.c64p.Cache模块定义了Cache.MAR##-## 配置参数,允许控制哪些外部内存地址是可缓存的或不可缓存的配置参数。例如,Cache_MAR128_159就是这样的一个配置参数。这些配置参数直接映射到设备上的MAR寄存器。每个16MB的外部内存地址空间由一个MAR位(0:不可缓存,1:可缓存)

SYS/BIOS Cache模块具有映射到MAR寄存器的模块级配置参数。

默认情况下,C64P缓存模块通过将所有相应的MAR位设置为0x1,使平台定义的所有内存区域都可缓存。在DA830设备上禁用缓存,外部内存范围从8000 0000h到80FF FFFFh,设置Cache.MAR128_159=0x0如下。这将寄存器MAR128设置为0。

var Cache = xdc.useModule('ti.sysbios.family.c64p.Cache');
Cache.MAR_128_159 = 0x0;

在为外部内存空间设置MAR后,CPU访问的新地址将被缓存到L2缓存中,如果L2被禁用,则缓存到L1中。在系统启动时,Cache模块写入MAR寄存器并配置它们。

Cache Runtime APIs

对于任何具有缓存的目标,ti.sysbios.hal.Cache模块提供了在运行时操作缓存的API。这些包括Cache_enable()、Cache_disable()、Cache_wb()和Cache_inv()函数。

Dynamic Memory Allocation

“Heap”是一个实现IHeap接口的模块。堆是动态内存管理器:它们管理特定的内存块,并支持该内存块的分配和释放。

内存分配大小以内存的"最小可寻址单元"(MAUs)度量。MAU是CPU可读或可写的最小数据存储单位。对于C28x,这是一个16位的字。对于所有其他支持当前支持的目标系列—包括C6000、ARM和msp430—这是一个8位字节。

内存策略

通过在全局或每个模块的基础上设置memoryPolicy,可以减少应用程序使用的代码空间量。这对于代码内存严重受限的目标尤其有用。

选项有:

  • DELETE_POLICY. 这是默认值。应用程序在运行时创建和删除对象(或此模块的对象)。需要MODULE_create()函数和MODULE_delete()函数对应用程序可用。
  • CREATE_POLICY. 应用程序在运行时创建对象(或此模块的对象)。它不会在运行时删除对象。需要MODULE_create()函数对应用程序可用,但不需要MODULE_delete()函数。
  • STATIC_POLICY. 应用程序在配置文件中创建所有对象(或此模块的所有对象)。应用程序不需要使用MODULE_create()或MODULE_delete()函数。

例如,下面的配置语句将所有模块的默认内存策略设置为只创建静态实例:

var Defaults = xdc.useModule('xdc.runtime.Defaults');
var Types = xdc.useModule('xdc.runtime.Types');
Defaults.memoryPolicy = Types.STATIC_POLICY;

Specifying the Default System Heap

BIOS模块创建一个默认堆供SYS/BIOS使用。当使用NULL堆在运行时调用Memory_alloc()时,将使用这个系统堆。BIOS模块创建的默认系统堆是一个HeapMem实例。BIOS模块提供了与系统堆相关的配置参数:

  • BIOS.heapSize可用于设置系统堆大小
  • BIOS.heapSection可用于放置系统堆

例如,可以配置默认的系统堆如下:

var BIOS = xdc.useModule('ti.sysbios.BIOS');
BIOS.heapSize = 0x900;
BIOS.heapSection = "systemHeap";

如果想为系统堆使用不同的堆管理器,可以在配置文件中指定系统堆,SYS/BIOS将不会覆盖该设置。

下面的配置指定了使用HeapBuf而不是HeapMem系统堆:

/* Create a heap using HeapBuf */
var heapBufParams = new HeapBuf.Params;
heapBufParams.blockSize = 128;
heapBufParams.numBlocks = 2;
heapBufParams.align = 8;
heapBufParams.sectionName = "myHeap";
Program.global.myHeap = HeapBuf.create(heapBufParams);
Program.sectMap["myHeap"] = "DDR";
Memory.defaultHeapInstance = Program.global.myHeap;

如果不希望创建系统堆,可以设置BIOS.heapSize位零。BIOS模块将使用一个HeapNull实例来最小化代码/数据的使用。

Using the xdc.runtime.Memory Module

所有的动态分配都是通过xdc.runtime.Memory模块完成的。Memory模块提供了Memory_alloc()和Memory_free()等APIs。所有内存API都将IHeap_Handle作为它们的第一个参数。内存模块本身很少工作;它通过IHeap_Handle调用Heap模块。Heap模块负责管理内存。使用Memory APIs可以使应用程序和中间件具有可移植性,而不必绑定到特定的堆实现。

与Memory APIs一起使用的IHeap_Handles是通过静态或动态创建堆实例获得的。当传递给Memory APIs的IHeap_Handle为NULL时,将使用默认的系统堆。

Runtime example: 这个例子从两个不同的堆中分配和释放内存。它通过将NULL传递给Memory_alloc作为IHeap_Handle从系统堆进行分配。它通过显示传递"otherHeap"句柄从一个名为"otherHeap"的单独堆进行分配。

#include <xdc/std.h>
#include <xdc/runtime/IHeap.h>
#include <xdc/runtime/System.h>
#include <xdc/runtime/Memory.h>
#include <xdc/runtime/Error.h>
extern IHeap_Handle systemHeap, otherHeap;
Void main()
{
    Ptr buf1, buf2;
    Error_Block eb;
    Error_init(&eb);
    /* Alloc and free using systemHeap */
    buf1 = Memory_alloc(NULL, 128, 0, &eb);
    if (buf1 == NULL) {
        System_abort("Memory allocation for buf1 failed");
    }
    Memory_free(NULL, buf1, 128);
    /* Alloc and free using otherHeap */
    buf2 = Memory_alloc(otherHeap, 128, 0, &eb);
    if (buf2 == NULL) {
        System_abort("Memory allocation for buf2 failed");
    }
    Memory_free(otherHeap, buf2, 128);
}

Specifying a Heap for Module Dynamic Instances

可以指定在为动态创建的模块实例分配内存时使用默认堆。控制所有模块的默认堆的配置属性是Default.common$.instanceHeap。

例如,以下配置语句指定用于分配实例的堆:

var HeapMem = xdc.useModule('ti.sysbios.heaps.HeapMem');
var heapMemParams = new HeapMem.Params;
heapMemParams.size = 8192;
var heap1 = HeapMem.create(heapMemParams);
Default.common$.instanceHeap = heap1;

如果没有为实例指定一个单独的堆,则将使用为Memory.defaultHeapInstance指定的堆

如果要指定一个特定模块在动态创建的实例分配内存时使用的堆,请为该模块设置instanceHeap参数。例如,下面的配置语句为Semaphore模块指定了堆:

var Semaphore = xdc.useModule('ti.sysbios.knl.Semaphore');
var HeapMem = xdc.useModule('ti.sysbios.heaps.HeapMem');
var heapMemParams = new HeapMem.Params;
heapMemParams.size = 8192;
var heap1 = HeapMem.create(heapMemParams);
Semaphore.common$.instanceHeap = heap1;

Using malloc() and free()

应用程序可以调用malloc()和free()函数。通常这些函数由代码生成工具提供的RTS库提供的。但是,当您使用SYS/BIOS时,这些函数由SYS/BIOS提供,并将分配重定向到默认系统的堆

要更改malloc()使用的堆大小,请使用BIOS.heapSize配置参数。

Heap Implementations

xdc.runtime.Memory模块是所有内存操作的公共接口。实际的内存管理是由Heap实例执行的,例如,HeapMem或HeapBuf实例。例如,Memory_alloc()在运行时被用来动态分配内存。所有的内存APIs都将Heap实例作为它们的参数之一。Memory_alloc()在运行时被用来动态分配内存。所有的内存api都将Heap实例作为它们的参数之一。Memory模块在内部调用堆的接口函数。

SYS/BIOS提供了以下堆实现:

  • HeapMin. 非常小的代码占用实现。支持内存分配,但不支持释放内存
  • HeapMem. 分配可变大小的块
  • HeapBuf. 分配固定大小的块
  • HeapTrack. 用于检测内存分配和重分配问题

该表比较SYS/BIOS堆实现:
在这里插入图片描述
不同的堆实现针对不同的内存管理特性进行优化。HeapMem模块接收所有可能大小的块的请求,因此它最小化内部碎片。另一方面,HeapMem模块只能分配固定大小的块,因此他最小化堆中的外部碎片,并且在分配和释放内存方面也更块。

HeapMin

HeapMin是一个占用内存最少的堆实现。这个模块是为那些通常在运行时分配内存和创建模块实例,但从不显示删除已创建实例或释放内存的应用程序设计的。

HeapMin不支持释放内存。默认情况下,如果应用程序调用HeapMin_free(),则会以错误状态终止应用程序。HeapMin.freeError配置参数可以设置为"false",以使HeapMin_free()简单地返回而不引发错误。

如果在运行时调用HeapMin_create(),应用程序将负责指定堆将管理缓冲区并对其进行对齐。

如果在静态配置中创建HeapMin实例,那么堆将按照支持静态对齐地目标所需地最大对齐方式进行对齐。对于不支持静态对齐的目标,缓冲区对齐未定义。

HeapMem

HeapMem可以被认为是堆中最"灵活"的,因为它允许你分配可变大小的块。当内存请求的大小直到运行时才知道时,理想的情况是能够准确地分配每次需要多少内存。例如,如果一个程序需要存储一个对象数组,而所需地对象数量直到程序实际执行时才知道,那么很可能需要从HeapMem中分配数组。

HeapMem提供地灵活性有许多性能权衡。

  • External Fragmentation. 分配大小可变的块可能导致碎片。当内存块被释放回HeapMem时,HeapMem中的可用内存就分散在整个堆中。HeapMem中的空闲空间总量可能很打,但因为它不是连续的,所以只能分配与堆中的"碎片"一样大的块

这种类型的碎片被称为"外部"碎片,因为块本身就是按照大小分配的,所以碎片位于整个堆中,是块本身的"外部"。

  • Non-Deterministic Performance. 当由HeapMem管理的内存变成碎片时,可用的内存块存储在一个链表中。为了分配另一块内存,必须遍历这个列表以找到一个合适的内存块。因为这个列表的长度可能不同,所以不知道分配请求将花费多长时间,因此性能变得“non-deterministic”

有许多建议可以帮助优化HeapMem的使用。

  • Larger Blocks First. 如果可能的话,首先分配较大的块。以前分配的小内存块可以减少用于较大内存分配的块的大小。
  • Overestimate Heap Size. 考虑到碎片的负面影响,请使用比程序可能需要的绝对内存量大得多的HeapMem。

当一个块被释放回HeapMem时,HeapMem将该块与相邻的空闲块合并,使可用的块大小尽可能大。

下面的示例创建一个大小为1024 MAUs的HeapMem实例。

Configuration example: 这个例子静态地配置heap:

var HeapMem = xdc.useModule('ti.sysbios.heaps.HeapMem');
/* Create heap as global variable so it can be used in C code */
var heapMemParams = new HeapMem.Params();
heapMemParams.size = 1024;
Program.global.myHeap = HeapMem.create(heapMemParams);

Runtime example: 第二个例子使用C代码动态创建一个HeapMem实例:

HeapMem_Params prms;
static char buf[1024];
HeapMem_Handle heap;
Error_Block eb;
Error_init(&eb);
HeapMem_Params_init(&prms);
prms.size = 1024;
prms.buf = (Ptr)buf;
heap = HeapMem_create(&prms, &eb);
if (heap == NULL) {
    System_abort("HeapMem create failed");
}

HeapMem使用一个Gate来阻止对操作在HeapMem的空闲块列表上的代码的并发访问。HeapMem使用的Gate类型是通过HeapMem的常见默认值静态配置的

Configuration example: 这个示例配置HeapMem来使用GateMutexPri保护代码的关键区域。

var GateMutexPri = xdc.useModule('ti.sysbios.gates.GateMutexPri');
var HeapMem = xdc.useModule('ti.sysbios.heaps.HeapMem');
HeapMem.common$.gate = GateMutexPri.create();

所使用的Gate类型取决于应用程序所需的保护级别。如果没有并发访问堆的风险,那么可以分配"null"来放弃任何Gate的使用,这将提高性能。对于具有并发访问的应用程序,GateMutex是一个可能的选择。或者,如果关键线程可能需要与低优先级线程同时使用HeapMem,那么GateMutexPri将最适合确保关键线程尽可能快地接收到堆HeapMem的访问。

HeapBuf

HeapBuf用于分配固定大小的内存块,它被设计为快速和确定的。通常,程序需要创建和删除大小固定的对象的不同数量的实例。HeapBuf是为此类对象分配空间的理想选择,因为它可以快速处理请求,而不存在任何碎片。

当响应时间比有效内存使用更重要时,还可以使用HeapBuf分配大小不同的对象。在这种情况下,HeapBuf将受到"内部"碎片的影响。整个堆中永远不会有任何碎片空间,但已分配的块本身可能包含浪费的空间,因此碎片是已分配块的“内部”。

从HeapBuf分配和释放给HeapBuf总是花费相同的时间,因此HeapBuf是一个"确定的"内存管理器。

下面的示例创建一个具有10个大小为128的内存块的HeapBuf实例。

Configuration example: 第一个示例是静态配置堆。在本例中,不需要指定buffer或bufSize参数,因为配置可以计算这些值并在链接时分配正确的区段。

var HeapBuf = xdc.useModule('ti.sysbios.heaps.HeapBuf');
/* Create heap as global variable so it can be used in C code */
var heapBufParams = new HeapBuf.Params();
heapBufParams.blockSize = 128;
heapBufParams.numBlocks = 10;
Program.global.myHeap = HeapBuf.create(heapBufParams);

Runtime example: 第二个示例使用C代码动态创建一个HeapBuf实例。在本例中,必须传递bufSize和buf参数。在指定这些运行参数时要小心。块大小需要是最坏情况结构对齐大大小的倍数。而bufSize应该等于blockSize*numblocks。最坏情况下的结构与目标相关。在32位架构的设备上,使用8字节对齐。缓冲区的基址应该与相同的大小对齐。

HeapBuf_Params prms;
static char buf[1280];
HeapBuf_Handle heap;
Error_Block eb;
Error_init(&eb);
HeapBuf_Params_init(&prms);
prms.blockSize = 128;
prms.numBlocks = 10;
prms.buf = (Ptr)buf;
prms.bufSize = 1280;
heap = HeapBuf_create(&prms, &eb);
if (heap == NULL) {
    System_abort("HeapBuf create failed");
}

HeapMultiBuf

HeapMultiBuf旨在平衡HeapMem和HeapBuf的比重。在内部,HeapMultiBuf维护一个HeapBuf实例集合,每个实例具有不同的块大小、对齐方式和块数量。HeapMultiBuf实例可以接受任何大小的内存请求,并简单地决定从哪个heapbuf中分配内存。

与单个HeapBuf相比,HeapMultiBuf在块大小方面提供了更多的灵活性,但在很大程度上保留了HeapBuf的快速性能。HeapMultiBuf实例需要循环遍历heapbuf以确定从哪个heapbuf中分配资源。但是,在实践中,不同大小的块的数量通常很小,并且总是一个固定的数字,因此根据某些定义,HeapMultiBuf可以被认为是确定性的。

HeapMultiBuf为任何内存大小的请求提供服务,但总是返回一个固定大小的块。分配不会返回关于已分配块的实际大小的任何信息。当将块释放回HeapMultiBuf时,size参数被忽略。HeapMultiBuf通过比较地址来确定要释放的缓冲区。

当一个HeapMultiBuf用完它的一个缓冲区的块时,可以将它配置为从下一个最大的缓冲区分配块。这被称为"block borrowing"。

下面的示例创建一个管理1024个MAUs内存的HeapMultiBuf,这些内存被划分为3个缓冲区。他将管理8块16的MAUs,8块32的MAUs,5块128的MAUs,如下如所示
在这里插入图片描述

Configuration example: 第一个例子是静态配置HeapMultiBuf示例:

var HeapMultiBuf = xdc.useModule('ti.sysbios.heaps.HeapMultiBuf');
/* HeapMultiBuf without blockBorrowing. */
/* Create as a global variable to access it from C Code. */
var heapMultiBufParams = new HeapMultiBuf.Params();
heapMultiBufParams.numBufs = 3;
heapMultiBufParams.blockBorrow = false;
heapMultiBufParams.bufParams = 
     [{blockSize: 16, numBlocks:8, align: 0},
      {blockSize: 32, numBlocks:8, align: 0},
      {blockSize: 128, numBlocks:5, align: 0}];
Program.global.myHeap = 
   HeapMultiBuf.create(heapMultiBufParams);

Runtime example: 第二个示例使用C代码动态创建HeapMultiBuf示例:

HeapMultiBuf_Params prms;
HeapMultiBuf_Handle heap;
Error_Block eb;
Error_init(&eb);
/* Create the buffers to manage */
Char buf0[128];
Char buf1[256];
Char buf2[640];
/* Create the array of HeapBuf_Params */
HeapBuf_Params bufParams[3];
/* Load the default values */
HeapMultiBuf_Params_init(&prms);
prms.numBufs = 3;
prms.bufParams = bufParams;
HeapBuf_Params_init(&prms.bufParams[0]);
prms.bufParams[0].align = 0;
prms.bufParams[0].blockSize = 16;
prms.bufParams[0].numBlocks = 8;
prms.bufParams[0].buf = (Ptr) buf0;
prms.bufParams[0].bufSize = 128;
HeapBuf_Params_init(&prms.bufParams[1]);
prms.bufParams[1].align = 0;
prms.bufParams[1].blockSize = 32;
prms.bufParams[1].numBlocks = 8;
prms.bufParams[1].buf = (Ptr) buf1;
prms.bufParams[1].bufSize = 256;
HeapBuf_Params_init(&prms.bufParams[2]);
prms.bufParams[2].align = 0;
prms.bufParams[2].blockSize = 128;
prms.bufParams[2].numBlocks = 5;
prms.bufParams[2].buf = (Ptr) buf2;
prms.bufParams[2].bufSize = 640;
heap = HeapMultiBuf_create(&prms, &eb);
if (heap == NULL) {
    System_abort("HeapMultiBuf create failed");
}

HeapTrack

HeapTrack是一个缓冲区管理模块,用于跟踪任何堆实例当前分配的所有块。HeapTrack对于检测内存泄漏、缓冲区溢出和内存块的双重释放非常有用。任何XDCtools或SYS/BIOS堆实例都可以插入HeapTrack。对于每个内存分配,将添加一个额外的数据包。运行时对象视图(ROV)使用这些数据来显示关于堆实例的信息

HeapTrack实现了默认堆函数以及两个调式函数。第一个,HeapTrack_printHeap()打印给定HeapTrack实例的所有内存块。第二个,HeapTrack_printTask()打印给定Task句柄的所有内存块。

HeapTrack有几个检测关键内存错误的断言。其中包括两次释放同一个内存块,溢出已分配的内存块,删除非空堆实例,以及使用空堆对象调用HeapTrack_printHeap()。

在使用HeapTrack时,有性能和大小开销成本。在设置堆和缓冲区大小时,应该考虑到这些成本。

你可以通过在C代码中使用sizeof(HeapTrack_Tracker)来找到HeapTrack模块增加块大小的数量。这是当您的代码或其他代数从由HeapTrack管理的堆调用Memory_alloc()时大小增加的量。HeapTrack_Tracker结构被添加到已分配块的末尾;因此,HeapTrack不会影响已分配的对齐。

Configuration example: 此示例使用现有堆静态配置HeapTrack

var HeapTrack = xdc.useModule('ti.sysbios.heaps.HeapTrack');
var heapTrackParams = new HeapTrack.Params();
heapTrackParams.heap = heapHandle;
Program.global.myHeap = HeapTrack.create(heapTrackParams);

还可以通过设置heapTrackEnabled配置参数,将HeapTrack与默认的BIOS模块堆一起使用。

var BIOS = xdc.useModule('ti.sysbios.BIOS');
BIOS.heapTrackEnabled = true;

Runtime example: 这个例子使用C代码动态地创建一个带有现有堆地HeapTrack实例

HeapTrack_Params prms;
HeapTrack_Handle heap;
Error_Block eb;
Error_init(&eb);
HeapTrack_Params_init(&prms);
prms.heap = heapHandle;
heap = HeapTrack_create(&prms, &eb);
if (heap == NULL) {
    System_abort("HeapTrack create failed");
}

参考文献:

  1. 《TI-RTOS Kernel (SYS/BIOS) User’s Guide》

猜你喜欢

转载自blog.csdn.net/Xiao_Jie123/article/details/120833163