编辑
2025-04-03
工作知识
0
请注意,本文编写于 64 天前,最后修改于 64 天前,其中某些信息可能已经过时。

目录

一、堆控制结构体
二、内存块
三、示例程序
四、演示

rtems的内存初始化在bootcard解析的时候已经提到了,这里为了了解系统的内存申请情况,以c库的申请函数为例,探索内存的申请效果

一、堆控制结构体

回顾RTEMS初始化-bootcard调用流程可以知道,_Malloc_Initialize会初始化内存,其调用如下:

void _Malloc_Initialize( void ) { RTEMS_Malloc_Heap = ( *_Workspace_Malloc_initializer )(); }

我们知道,这里赋值了一个函数指针_Workspace_Malloc_initializer的返回值,跟踪函数可以查找到默认赋值为了_Workspace_Malloc_initialize_separate,此函数的实现如下:

Heap_Control *_Workspace_Malloc_initialize_separate( void ) { return _Malloc_Initialize_for_one_area( &_Malloc_Heap ); }

我们留意static Heap_Control _Malloc_Heap的全局变量,其在_Malloc_Initialize_for_one_area中给了RTEMS_Malloc_Heap,并调用了_Heap_Initialize_Heap_Initialize已经分析过了,这里重点记住Heap_Control结构体如下

struct Heap_Control { Heap_Block free_list; uintptr_t page_size; uintptr_t min_block_size; uintptr_t area_begin; uintptr_t area_end; Heap_Block *first_block; Heap_Block *last_block; Heap_Statistics stats; };

二、内存块

对于每个malloc申请的内存,都对应一个Heap_Block,这里结构体如下:

struct Heap_Block { uintptr_t prev_size; uintptr_t size_and_flag; Heap_Block *next; Heap_Block *prev; };

可以发现,这个和glibc有点相似。但是是简化版本的。简单解释一下:

prev_size是前一个block的大小,如果size_and_flag的bit0为1(HEAP_PREV_BLOCK_USED),也就是正在使用则无效

size_and_flag是整个block大小和是否HEAP_PREV_BLOCK_USED的flag的判断

next是下一个block的地址,双向链表

prev是上一个block的地址,双向链表

三、示例程序

为了测试malloc,需要如下测试程序

static void test_early_malloc( void ) { void *p; char *q; void *r; void *s; void *t; p = malloc( 1 ); rtems_test_assert( p != NULL ); free( p ); q = calloc( 1, 1 ); rtems_test_assert( q != NULL ); rtems_test_assert( p != q ); rtems_test_assert( q[0] == 0 ); free( q ); /* * This was added to address the following warning. * warning: pointer 'q' used after 'free' */ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wuse-after-free" r = realloc( q, 128 ); rtems_test_assert( r == q ); #pragma GCC diagnostic pop s = malloc( 1 ); rtems_test_assert( s != NULL ); free( s ); t = realloc( r, 256 ); rtems_test_assert( t != NULL ); rtems_test_assert( t != r ); free( t ); }

四、演示

演示通过gdb,可以断点在函数test_early_malloc,此时我们先查看_Malloc_Heap 地址,如下

(gdb) p _Malloc_Heap $28 = {free_list = {prev_size = 0, size_and_flag = 0, next = 0x1342a0, prev = 0x1342a0}, page_size = 16, min_block_size = 32, area_begin = 1262239, area_end = 1072431104, first_block = 0x1342a0, last_block = 0x3febfff0, stats = {lifetime_allocated = 0, lifetime_freed = 0, size = 1071168848, free_size = 1071168848, min_free_size = 1071168848, free_blocks = 1, max_free_blocks = 1, used_blocks = 0, max_search = 0, searches = 0, allocs = 0, failed_allocs = 0, frees = 0, resizes = 0}} (gdb) p &_Malloc_Heap $30 = (Heap_Control *) 0x102c90 <_Malloc_Heap>

我们记住这个0x102c90和首个malloc地址0x1342b0。打印如下

(gdb) x/2g 0x1342a0 0x1342a0: 0x000000003fec0000 0x000000003fd8bd51

此时代码malloc(1)后,得到虚拟地址0x1342b0,对应p地址,我们查看p的block如下:

(gdb) x/4g p-0x10 0x1342a0: 0x000000003fec0000 0x0000000000000021 0x1342b0: 0x0000000000102c90 0x0000000000102c90

这里可以知道prev_size默认是area_end,size_and_flag是0x21,最低位1是HEAP_PREV_BLOCK_USED,代表正在使用,大小是32字节,next和prev相等则这是第一个内存。而0x102c90正好是默认heap的地址_Malloc_Heap

这里我们可以知道一个block的大小就是32字节。故malloc(1)默认是一个block的size。

接下来calloc(1,1)。获得了q的地址是0x1342d0,可以发现刚刚是0x1342b0 + 0x20。一切正常,此时打印q如下

(gdb) x/4g q-0x10 0x1342c0: 0x0000000000000000 0x0000000000000021 0x1342d0: 0x0000000000102c00 0x0000000000102c90

然后realloc( q, 128 ) 扩大q的大小,扩大之前我们知道size是32,扩大后如下

(gdb)x/4g r-0x10 0x1342c0: 0x0000000000000000 0x0000000000000091 0x1342d0: 0x0000000000100140 0x00000000001342b0

这里看到0x91,其中0x90是144,刚好是128+16。这里16正好是uintptr_t prev_size; + uintptr_t size_and_flag;的大小。一切正常

然后s = malloc( 1 );,这里理所应当是从0x1342d0+0x90,也就是0x134360。查看内存如下:

(gdb) x/4g s-0x10 0x134350: 0x0000000000000000 0x0000000000000021 0x134360: 0x0000000000102c90 0x0000000000102c90

完全没问题,最后t = realloc( r, 256 );将r的地址扩大256。因为r中间有一个s,所以这里t的地址发生改变,如下

(gdb) x/4g t-0x10 0x134370: 0x0000000000000000 0x0000000000000111 0x134380: 0x0000000000134360 0x00000000001342b0

因为是调用的realloc,所以链表的next指向了0x0000000000134360,prev是0x00000000001342b0,size是0x110也就是272,也就是256 + 16.一切正常。

至此,我们演示了rtmes通过malloc调用的内存行为,其行为是简化的glibc的分配行为。简单易懂。