POSIX 标准内存管理
POSIX 标准内存管理相关的函数在功能和内部行为上与 上文“变成内存管理” 中讲的变长内存管理完全相同。POSIX 规定,通过 malloc、calloc 和 realloc 分配的内存地址必须对齐。规定地址对齐的目的是为了在任何硬件平台上高效地访问任意类型的数据结构,同时还可以避免在某些硬件平台上,因为在不对齐的地址上进行多字节访问造成的硬件异常错误。这一点也与 SylixOS 内部的默认处理一致。换句话说,SylixOS 内存管理本身就符合 POSIX 标准。每创建一个新的进程,系统内部会自动分配内存为其创建内存堆,而不是像内存区域那样需要用户指定内存空间。
分配内存
#include <malloc.h>
void *malloc(size_t stNBytes);
void *calloc(size_t stNNum, size_t stSize);
void *realloc(void *pvPtr, size_t stNewSize);
函数 malloc 原型分析:
- 此函数成功返回分配的内存指针,失败返回 LW_NULL 并设置错误号。
- 参数 stNBytes 表示分配内存的字节数。
函数 calloc 原型分析:
- 此函数成功返回分配的内存指针,失败返回 LW_NULL 并设置错误号。
- 参数 stNNum 表示数据块的数量。
- 参数 stSize 表示一个数据块的大小,以字节为单位。
注意:
calloc 的参数似乎表明它分配的是 stNNum 个大小为 stSize 的内存,但实际上它分配的是一个大小为 stNNum * stSize 的连续地址空间的内存,这点与 malloc 并没有差异。不同于 malloc 的是,calloc 会将分配的的内存进行清零处理。
函数 realloc 原型分析:
- 此函数成功返回分配的内存指针,失败返回 LW_NULL 并设置错误号。
- 参数 stNewSize 表示新分配内存的字节数。
注意:
realloc 的行为与“变长内存管理”中的 Lw_Region_Realloc 完全一致,这里不再赘述。
调用 malloc 函数可以为应用程序分配内存,在 SylixOS 中支持 3 中内存分配方法:dlmalloc 方法、orig 方法(这个方法由 SylixOS 内核实现,因此通常用在内核的内存分配中)、tlsf 方法。
dlmalloc 是一种内存分配器,由 Doug Lea 在 1987 年开发完成,被广泛应用在多种操作系统中。dlmalloc 采用两种方式申请内存,如果应用程序单次申请的内存量小于 256kb,dlmalloc 调用 brk 函数扩展进程堆空间,但是 dlmalloc 向内核申请的内存量大于应用程序申请的内存量,申请到内存后 dlmalloc 将内存分成两块,一块返回给应用程序,另一块作为空闲内存先保留起来,下次应用程序申请内存时 dlmalloc 就不需要再次向内核申请内存,从而加快了内存分配效率。当应用程序调用 free 函数释放内存时,如果内存块小于 256kb,dlmalloc 并不马上将内存块释放,而是将内存块标记为空闲状态。这么做的原因有两个:一是内存块不一定能马上释放回内核(比如内存块不是位于堆顶端),二是供应用程序下次申请内存使用(这是主要原因)。当 dlmalloc 函数中空闲内存量达到一定值时才将空闲内存释放回内核。如果应用程序申请的内存大于 256kb,dlmalloc 函数调用 mmap 函数向内核申请一块内存,返回给应用程序使用。如果应用程序释放的内存大于 256kb,dlmalloc 函数马上调用 munmap 函数释放内存。dlmalloc 不会缓存大于 256kb 的内存块。
tlsf 主要用于支持嵌入式实时系统的动态内存管理,它结合了分类搜索算法和位图搜索算法的优点,速度快、内存浪费少,tlsf 的 malloc、free 的时间复杂度并不随空闲内存块的数量而变化,总是 O(1)。
注意:
tlsf 虽然拥有 O(1)时间复杂度的内存管理算法,适用于实时操作系统,但是在 32 位系统上仅能保持 4 字节对齐特性,在 64 位系统上仅能保持 8 字节对齐特性,不满足 POSIX 对 malloc 具有 2 * sizeof(size_t)对齐的要求,所以有些软件可能会出现严重错误,例如 Qt/JavaScript 引擎,所以使用时需慎重!只有确认应用没有 2 * sizeof(size_t)对齐要求时,方可使用。
SylixOS 中可以通过配置宏 LW_CFG_VP_HEAP_ALGORITHM 来选择使用哪种内存分配方法,该宏可在头文件 <SylixOS/config/kernel/memory_cfg.h> 发现。
分配指定对齐值的内存
#include <malloc.h>
void *memalign(size_t stAlign, size_t stNbytes);
int posix_memalign(void **memptr, size_t alignment, size_t size);
函数 memalign 原型分析:
- 此函数成功时返回分配的内存指针,失败时返回 LW_NULL 并设置错误号。
- 参数 stAlign 为对齐值,必须为 2 的幂。
- 参数 stNbytes 为需要分配的内存大小。
函数 posix_memalign 原型分析:
- 此函数成功返回 ERROR_NONE,失败返回错误码。
- 输出参数 memptr 保存分配的对齐内存的指针。
- 参数 alignment 为对齐值,必须为 2 的幂,同时该值必须不小于 CPU 字长。
- 参数 size 为需要分配的内存大小,以字节为单位。
posix_memalign 为 POSIX1003.1d 中定义的函数,该函数不同于 memalign,它需要对齐值不小于 CPU 字长,这和 SylixOS 中的 Lw_Region_AllocateAlign 函数的要求是一样的。注意当该函数分配内存失败时, memptr 的值是未定义的,因此应用程序不应该以 memptr 的值是否为 NULL 来判断内存是否分配成功。
释放内存
#include <malloc.h>
void free(void *pvPtr);
函数 free 原型分析:
- 参数 pvPtr 为需要释放的内存指针。
如果输入参数 pvPtr 等于 NULL,则 free 函数不会做任何工作。free 函数可以释放以上所有内存分配函数所分配的内存。
带有安全检测的内存分配函数
#include <malloc.h>
void *xmalloc(size_t stNBytes);
void *xcalloc(size_t stNNum, size_t stSize);
void *xrealloc(void *pvPtr, size_t stNewSize);
void *xmemalign(size_t stAlign, size_t stNbytes);
上面的函数与对应名称(没有 x 前缀)的内存分配函数的功能相同,只是在行为上存在不同。当分配内存失败时,这些函数内部会将错误信息输出到标准错误输出设备,即 stderr,同时还会调用 exit 函数结束当前进程,因此判断这些函数的返回值将没有任何意义。使用这些函数可以在某些时候给应用程序带来方便,例如当应用程序中存在未检测内存是否分配成功的错误代码而访问空指针导致程序崩溃,此种错误常常难以检测。使用这类函数可以为我们提供有用的信息。但这类函数由于已经固定了内存分配失败时的行为(结束当前进程),很多时候并不能满足需求。
注意,这一组函数在某些系统中可能存在一个 xfree 函数来释放对应的内存,SylixOS 使用 free 函数释放。
内存泄漏追踪
mtracer 简介
mtracer,是 SylixOS 系统提供的内存问题检测工具,它可以用来协助定位内存泄露问题。它的实现源码在 SylixOS 源码的 mtracer 目录下。
SylixOS 内核编译完成会生成 libmtracer.so 动态库,应用程序按照下面方式链接该库,便可使用 mtracer 工具。
#*********************************************************************************************************
# Depend library (eg. LOCAL_DEPEND_LIB := -la LOCAL_DEPEND_LIB_PATH := -L"Your library search path")
#*********************************************************************************************************
LOCAL_DEPEND_LIB := -lmtracer
LOCAL_DEPEND_LIB_PATH :=
mtracer 初始化和反初始化
mtracer 工具提供了两个接口函数,用于初始化和反初始化。
这两个接口函数无需用户调用,libmtracer.so 动态库链接成功后,SylixOS 系统会自动调用。
int mtracer_init (void);
int mtracer_exit (void);
- mtracer_init 函数用于 mtracer 工具的初始化,前面加了LW_CONSTRUCTOR_BEGIN 属性,表示该函数将在用户 main进入之前被自动调用。
- mtracer_exit函数用于 mtracer 工具的反初始化,前面加了LW_DESTRUCTOR_BEGIN属性,表示当执行的进程退出的时候,会自动输出内存泄漏详细信息。
mtracer 接口函数
mtracer 给用户提供了四个 API 接口函数,用于灵活追踪内存泄漏情况。
int mtracer_show(void);
该函数会显示内存追踪的具体信息,其原型分析如下:
- 此函数成功返回 0。
void mtracer_reset(void);
该函数会清空内存追踪信息。
void mtracer_suppress_free_null(BOOL suppress);
该函数会指定是否输出空指针警告提示,其原型分析如下:
- 参数suppress:为 TRUE 表示禁止输出空指针警告提示,为 FALSE表示允许输出空指针警告提示,默认值是 FALSE。
void mtracer_config(int backtrace_malloc_cnt, unsigned long backtrace_mem_addr, int log_fd);
该函数用于配置 mtracer 工具参数,其原型分析如下:
- backtrace_malloc_cnt:表示 malloc 次数的阈值设置,当该值设置为 N ,且内存分配次数等于 N 时,mtracer 工具会自动打印输出当前任务调用栈信息,该值默认是 -1。
- backtrace_mem_addr:表示用户想追溯的内存地址,当该值等于内存分配的地址时,mtracer 工具会自动打印输出当前任务调用栈信息,该值默认是 0。
- log_fd:表示 mtracer 存放内存追踪信息的文件描述符,该值默认是 STD_OUT(标准输出)。
调用 mtracer_config 接口函数设置 malloc 次数的阈值为 2,当第二次进行内存分配的时候,系统自动会输出调用栈信息。
#include <stdio.h>
#include "mtracer/mtracer.h"
int main (int argc, char **argv)
{
char *p = (char *)malloc(100);
char *p1 = NULL;
mtracer_config(2, p1, STD_OUT);
free(p);
p = (char *)malloc(100);
free(p);
return (0);
}
运行结果如下所示,并会提示内存已经分配了两次:
[root@sylixos:/apps/mtracer_app]# ./mtracer_app
memory malloc reach to 2 times, mem = 0x187a45f8, backtrace show >>
[13] 0x16751044 (<unknown>)
[12] 0x167518e4 (/lib/libmtracer.so@0x16750000+0x18e4 mtracer_lib_malloc+468)
[11] 0x16751958 (/lib/libmtracer.so@0x16750000+0x1958 malloc+44)
[10] 0x1673022c (/apps/mtracer_app/mtracer_app@0x16730000+0x22c main+80)
[09] 0x1678be54 (/lib/libvpmpdm.so@0x16770000+0x1be54 _start+920)
[08] 0x10225d40 (kernel@0x16770000+0xf9ab5d40 vprocRun+820)
[07] 0x10226928 (kernel@0x16770000+0xf9ab6928 API_ModuleRunEx+180)
[06] 0x10220384 (kernel@0x16770000+0xf9ab0384 __ldGetFilePath+1916)
[05] 0x1038ff34 (kernel@0x16770000+0xf9c1ff34 __tshellUndef+1316)
[04] 0x103906f8 (kernel@0x16770000+0xf9c206f8 __tshellExec+1388)
[03] 0x10390fc8 (kernel@0x16770000+0xf9c20fc8 __tshellRestartEx+992)
[02] 0x101aee60 (kernel@0x16770000+0xf9a3ee60 _ThreadShell+144)
[01] 0x101aedd0 (kernel@0x16770000+0xf9a3edd0 _ThreadShell+0)
[root@sylixos:/apps/mtracer_app]#
调用 mtracer_config 接口函数设置 malloc 次数的阈值为 10,backtrace_mem_addr设置为内存分配的地址,第二次内存分配的时候,系统自动会输出调用栈信息。
#include <stdio.h>
#include "mtracer/mtracer.h"
int main (int argc, char **argv)
{
char *p = (char *)malloc(100);
char *p1 = NULL;
mtracer_config(2, p1, STD_OUT);
free(p);
p = (char *)malloc(100);
free(p);
return (0);
}
运行结果如下所示:
[root@sylixos:/apps/mtracer_app]# ./mtracer_app
memory malloc reach to 2 times, mem = 0x187a45f8, backtrace show >>
[13] 0x16751044 (<unknown>)
[12] 0x167518e4 (/lib/libmtracer.so@0x16750000+0x18e4 mtracer_lib_malloc+468)
[11] 0x16751958 (/lib/libmtracer.so@0x16750000+0x1958 malloc+44)
[10] 0x16730224 (/apps/mtracer_app/mtracer_app@0x16730000+0x224 main+72)
[09] 0x1678be54 (/lib/libvpmpdm.so@0x16770000+0x1be54 _start+920)
[08] 0x10225d40 (kernel@0x16770000+0xf9ab5d40 vprocRun+820)
[07] 0x10226928 (kernel@0x16770000+0xf9ab6928 API_ModuleRunEx+180)
[06] 0x10220384 (kernel@0x16770000+0xf9ab0384 __ldGetFilePath+1916)
[05] 0x1038ff34 (kernel@0x16770000+0xf9c1ff34 __tshellUndef+1316)
[04] 0x103906f8 (kernel@0x16770000+0xf9c206f8 __tshellExec+1388)
[03] 0x10390fc8 (kernel@0x16770000+0xf9c20fc8 __tshellRestartEx+992)
[02] 0x101aee60 (kernel@0x16770000+0xf9a3ee60 _ThreadShell+144)
[01] 0x101aedd0 (kernel@0x16770000+0xf9a3edd0 _ThreadShell+0)
[root@sylixos:/apps/mtracer_app]#
当调用 malloc 接口进行内存分配的时候,忘记了内存释放,进程退出的时候,会自动输出忘记释放内存的地址信息。
#include <stdio.h>
#include "mtracer/mtracer.h"
int main (int argc, char **argv)
{
char *p = (char *)malloc(100);
return (0);
}
运行结果如下所示:
[root@sylixos:/apps/mtracer_app]# ./mtracer_app
process exit, unfreed memory list >>
/apps/mtracer_app/mtracer_app(0x16730000), caller: main(0x16730194), callee: malloc, memory addr: 0x187a45f8, malloc count: 1 [memory OK]
[root@sylixos:/apps/mtracer_app]#
当调用 malloc 接口函数进行内存分配的时候,忘记了内存释放,且进程死循环不会退出的时候,可以调用mtracer_show 接口进行内存泄漏追踪,当调用 free 接口释放空指针时,由于 mtracer_suppress_free_null 接口参数默认是 FALSE ,所以会输出警告提示信息。
#include <stdio.h>
#include "mtracer/mtracer.h"
int main (int argc, char **argv)
{
char *p = (char *)malloc(100);
char *p_null = NULL;
mtracer_show();
free(p_null);
while(1);
return (0);
}
运行结果如下所示,当调用 mtracer_show 时,会输出未释放的内存地址信息;当释放一个空指针时候,会打印输出警告信息 "ERROR: user free NULL memory",并输出当前调用栈信息。
[root@sylixos:/apps/mtracer_app]# ./mtracer_app
unfreed memory list >>
/apps/mtracer_app/mtracer_app(0x16730000), caller: main(0x167301f4), callee: malloc, memory addr: 0x187a45f8, malloc count: 1 [memory OK]
ERROR: user free NULL memory
[12] 0x16751044 (<unknown>)
[11] 0x167519c4 (/lib/libmtracer.so@0x16750000+0x19c4 lib_free+88)
[10] 0x16730210 (/apps/mtracer_app/mtracer_app@0x16730000+0x210 main+56)
[09] 0x1678be54 (/lib/libvpmpdm.so@0x16770000+0x1be54 _start+920)
[08] 0x10225d40 (kernel@0x16770000+0xf9ab5d40 vprocRun+820)
[07] 0x10226928 (kernel@0x16770000+0xf9ab6928 API_ModuleRunEx+180)
[06] 0x10220384 (kernel@0x16770000+0xf9ab0384 __ldGetFilePath+1916)
[05] 0x1038ff34 (kernel@0x16770000+0xf9c1ff34 __tshellUndef+1316)
[04] 0x103906f8 (kernel@0x16770000+0xf9c206f8 __tshellExec+1388)
[03] 0x10390fc8 (kernel@0x16770000+0xf9c20fc8 __tshellRestartEx+992)
[02] 0x101aee60 (kernel@0x16770000+0xf9a3ee60 _ThreadShell+144)
[01] 0x101aedd0 (kernel@0x16770000+0xf9a3edd0 _ThreadShell+0)