定长内存管理

更新时间:
2024-05-13
下载文档

定长内存管理

所谓定长内存,指的是我们每次分配获得的内存大小是相同的,即使用的是有确定长度的内存块。同时,这些内存块总的个数也是确定的,即整个内存总的大小也是确定的。

这和我们通常理解的内存池的概念是一样的。使用这样的内存,有两大优点:一是由于事先已经分配好了足够的内存,可极大提高关键应用的稳定性;二是对于定长内存的管理通常有更为简单的算法,分配/释放的效率更高。

在 SylixOS 中,将管理的一个定长内存称作 PARTITION,即内存分区。

创建内存分区

#include <SylixOS.h>
LW_HANDLE Lw_Partition_Create(CPCHAR                pcName,
                              PVOID                 pvLowAddr,
                              ULONG                 ulBlockCounter,
                              size_t                stBlockByteSize,
                              ULONG                 ulOption,
                              LW_OBJECT_ID         *pulId)

函数 Lw_Partition_Create 原型分析:

  • 此函数成功时返回一个内存分区句柄,失败时返回 LW_HANDLE_INVALID 并设置错误号。
  • 参数 pcName 指定该内存分区的名称,可以为 LW_NULL。
  • 参数 pvLowAddr 为用户定义的一片内存的低地址,即起始地址。该地址必须满足一个 CPU 字长的对齐,如在 32 位系统中,该地址必须 4 字节对齐。
  • 参数 ulBlockCounter 为该内存分区的定长内存块数量。
  • 参数 stBlockByteSize 为内存块的大小,必须不小于一个指针的长度,在 32 位系统中为 4 字节。
  • 参数 ulOption 为创建内存分区的选项,如下表所示。
选项名称解释
LW_OPTION_OBJECT_GLOBAL表示该对象为一个内核全局对象
LW_OPTION_OBJECT_LOCAL表示该对象仅为一个进程拥有,即本地对象
LW_OPTION_DEFAULT默认选项
  • 输出参数 pulId 保存该内存分区的 ID,与返回值相同。可以为 LW_NULL。

注意:
驱动程序或内核模块才能使用 LW_OPTION_OBJECT_GLOBAL 选项,对应的 LW_OPTION_OBJECT_LOCAL 选项用于应用程序。为了使应用程序有更好的兼容性,建议使用 LW_OPTION_DEFAULT 选项,该选项包含了 LW_OPTION_OBJECT_LOCAL 的属性。

需要注意的是,SylixOS 对象名称的最大长度为 LW_CFG_OBJECT_NAME_SIZE,该宏定义位于 <SylixOS/config/kernel/kernel_cfg.h> 中,它的值为 32。如果参数 pcName 的长度超过此值,将导致内存分区创建失败。

删除内存分区

#include <SylixOS.h>
ULONG Lw_Partition_DeleteEx(LW_HANDLE      *pulId,  BOOL  bForce);
ULONG Lw_Partition_Delete(LW_HANDLE        *pulId);

以上函数原型分析:

  • 此函数成功返回 ERROR_NONE,失败返回错误码。
  • 参数 pulId 为内存分区句柄指针,操作成功将会置该句柄为无效。
  • 参数 bForce 表示是否强制删除该内存分区。

如果一个内存分区中有内存块还在被使用,则理论上不应该立刻被删除。如果 bForce 为 LW_TRUE,则 Lw_Partition_DeleteEx 忽略该条件直接删除该分区。通常情况下应用程序不应该使用该方式,这可能会导致内存错误。建议一般情况下使用 Lw_Partition_Delete 函数,它相当于下面调用。

Lw_Partition_DeleteEx(Id, LW_FALSE);

获取/返还内存块

#include <SylixOS.h>
PVOID Lw_Partition_Get(LW_HANDLE   ulId); 
PVOID Lw_Partition_Put(LW_HANDLE   ulId, PVOID pvBlock); 

函数 Lw_Partition_Get 原型分析:

  • 此函数成功时返回内存块指针,失败时返回 LW_NULL 并设置错误号。
  • 参数 ulId 为内存分区句柄。

函数 Lw_Partition_Put 原型分析:

  • 此函数成功时返回 LW_NULL,失败时返回当前返还的内存块指针。
  • 参数 ulId 为内存分区句柄。
  • 参数 pvBlock 为需要返还的内存块指针。

调用 Lw_Partition_Get 函数可以获得一个内存分区的内存块,其大小为创建内存分区时指定的大小,调用 Lw_Partition_Put 函数将获得的内存块(Lw_Partition_Get 函数获得)返回给内存分区,注意,如果 pvBlock 为 NULL,则设置错误号为 ERROR_PARTITION_NULL。

为了满足不同用户的使用需求,SylixOS 也提供了以下两组 API,其功能与上面的完全一致。

#include <SylixOS.h>
PVOID Lw_Partition_Take(LW_HANDLE       ulId); 
PVOID Lw_Partition_Give(LW_HANDLE       ulId, PVOID pvBlock); 
PVOID Lw_Partition_Allocate(LW_HANDLE   ulId); 
PVOID Lw_Partition_Free(LW_HANDLE       ulId, PVOID pvBlock);

获取内存分区的当前状态

#include <SylixOS.h>
ULONG Lw_Partition_Status(LW_HANDLE         ulId,
                          ULONG            *pulBlockCounter,
                          ULONG            *pulFreeBlockCounter,
                          size_t           *pstBlockByteSize); 

函数 Lw_Partition_Status 原型分析:

  • 此函数成功返回 ERROR_NONE,失败返回错误码。
  • 参数 ulId 为内存分区句柄。
  • 输出参数 pulBlockCounter 保存该内存分区总的内存块数量。
  • 输出参数 pulFreeBlockCounter 保存该内存分区未被使用的内存块数量。
  • 输出参数 pstBlockByteSize 保存该内存分区内存块大小。

调用 Lw_Partition_Status 函数可以获取内存分区的状态信息,如内存分区的总内存块数、内存块的大小、当前可以使用的内存块数量。特殊地,如果参数 pulBlockCounterpulFreeBlockCounterpstBlockByteSize 都为 NULL,此函数将平静地返回。

获取内存分区的名称

#include <SylixOS.h>
ULONG Lw_Partition_GetName(LW_HANDLE  ulId,  PCHAR  pcName);

函数 Lw_Partition_GetName 原型分析:

  • 此函数成功返回 ERROR_NONE,失败返回错误码。
  • 参数 ulId 为内存分区句柄。
  • 输出参数 pcName 保存该内存分区的名称。

如前面所讲,为保证操作缓冲区安全, pcName 缓冲区的长度应该不小于 LW_CFG_OBJECT_NAME_SIZE。

下面实例展示了如何使用内存分区。

#include <SylixOS.h>
typedef struct my_element {
    ULONG     ulValue;
} MY_ELEMENET;
#define ELEMENT_MAX     (8)
UINT8           _G_pucMyElementPool[sizeof(MY_ELEMENET) * ELEMENT_MAX];
LW_HANDLE       _G_hMyPartition;
int main (int argc, char *argv[])
{
    MY_ELEMENET     *peleTable[ELEMENT_MAX] = {LW_NULL};
    MY_ELEMENET     *peleTmp;
    ULONG               ulError;
    INT                 i = 0;
    _G_hMyPartition = Lw_Partition_Create("my_partition",
                                          _G_pucMyElementPool,
                                          ELEMENT_MAX,
                                          sizeof(MY_ELEMENET),
                                          LW_OPTION_DEFAULT,
                                          LW_NULL);
    if (_G_hMyPartition == LW_HANDLE_INVALID) {
        fprintf(stderr, "create partition failed.\n");
        return  (-1);
    }
    /*
     * 最多能够获得多少个元素内存
     */
    while (1) {
        peleTmp = (MY_ELEMENET *)Lw_Partition_Get(_G_hMyPartition);
        if (peleTmp != LW_NULL) {
            peleTable[i]         = peleTmp;
            peleTmp->ulValue  = i;
            fprintf(stdout, "get element successfully, count = %d.\n", i);
        } else {
            fprintf(stderr, "get element failed,         count = %d.\n", i);
            break;
        }
        i++;
    }
    /*
     * 在没有全部回收元素内存的情况下删除内存分区
     */
    ulError = Lw_Partition_Delete(&_G_hMyPartition);
    if (ulError != ERROR_NONE) {
        fprintf(stderr, "delete partition error.\n");
    } else {
        return  (0);
    }
    for (i = 0; i < ELEMENT_MAX; i++) {
        peleTmp = peleTable[i];
        if (peleTmp != LW_NULL) {
            fprintf(stdout, "element%d value = %d.\n", i, peleTmp->ulValue);
            peleTmp = Lw_Partition_Put(_G_hMyPartition, peleTmp);
            if (peleTmp != LW_NULL) {
                fprintf(stderr, "element%d put failed.\n", i);
            }
        } else {
            break;
        }
    }
    /*
     * 全部回收元素内存后删除内存分区
     */
    ulError = Lw_Partition_Delete(&_G_hMyPartition);
    if (ulError != ERROR_NONE) {
        fprintf(stderr, "delete partition error.\n");
        return  (-1);
    } else {
        fprintf(stderr, "delete partition successfully.\n");
    }
    return  (0);
}

注意
当在 64 位系统中,使用自己定义的结构体时,结构体大小大于或等于 8 字节,且 8 字节对齐。

内存分区不直接为我们分配内存,它只是提供了一个管理内存的方法。因此在创建内存分区时,需要我们指定需要管理的内存,该内存由使用的元素(即上面所述的内存块)大小以及元素的最大个数决定。

在以上程序中,创建了一个最大可以容纳 8 个类型为 MY_ELEMENT 对象的内存分区,通过获取元素对象、使用元素对象以及删除内存分区三方面展示 SylixOS 内存分区的使用。该程序运行后,结果如下所示:

get element successfully, count = 0.
get element successfully, count = 1.
get element successfully, count = 2.
get element successfully, count = 3.
get element successfully, count = 4.
get element successfully, count = 5.
get element successfully, count = 6.
get element successfully, count = 7.
get element failed,       count = 8.
delete partition error.
element0 value = 0.
element1 value = 1.
element2 value = 2.
element3 value = 3.
element4 value = 4.
element5 value = 5.
element6 value = 6.
element7 value = 7.
delete partition successfully.

从运行结果可以看出,最大元素个数为 8 个,因此第 9 次获取元素时会失败。随后使用 Lw_Partition_Delete 函数删除内存分区,由于此时元素还未被回收,因此删除失败,当回收完全部的元素后,才能成功删除。

我们定义的内存最大可容纳 8 个元素,通过内存分区获取的最大元素也是 8 个,因此可以推测,_G_pucMyElementPool 这片内存是完全被程序使用的,即元素 0 的地址等于 _G_pucMyElementPool 的地址,元素 1 的地址等于 _G_pucMyElementPool 加上 MY_ELEMENT 结构体大小。因此,上面的程序中,存在一个安全隐患,即当 _G_pucMyElementPool 的地址不满足结构体 MY_ELEMENT 的对齐需求时,在有些硬件上,访问成员变量 iValue 将产生多字节不对齐访问的错误(典型的硬件平台如 ARM)。虽然通常情况下,编译器都会为变量(不管是全局变量还是局部变量)分配对齐的地址,但不排除在不同的编译器下会有不同的处理方式,这时应该将 _G_pucMyElementPool 的类型定义为 UINT8,即单字节访问,逻辑上它的起始地址可以是任何对齐值。为了安全起见,建议使用以下方法定义内存缓冲区(如下程序清单的程序举例):

MY_ELEMENET    _G_pmyelementPool[ELEMENT_MAX];
LW_STACK       _G_pstackMyElementPool[sizeof(MY_ELEMENET) * ELEMENT_MAX / sizeof(LW_STACK)];

方法一中,将 _G_pucMyElementPool 的类型改为需要访问的元素的类型,编译器保证 _G_pucMyElementPool 的地址一定能满足 MY_ELEMENT 结构对齐访问的条件,这是一种最为普遍的方法。实际上,只要结构体的起始地址满足 CPU 字长对齐,对齐所有成员变量的访问就不会产生该问题。SylixOS 中定义了一个 LW_STACK 的数据类型,其含义为“字对齐的栈访问类型”,该类型的大小实际上就是 CPU 字长。方法二给出了使用 LW_STACK 定义内存缓冲区的方式。

文档内容是否对您有所帮助?
有帮助
没帮助