newlib库中malloc函数依赖_sbrk函数,该函数使用链接脚本中的_end符号作为堆的初始地址.
问题:
具体是通过什么传递,从而使newlib得到用户设置的堆的起始地址的?在我的链接脚本文件中并没有看见__heap_start = .;和__heap_end = .;,那么newlib中的crt0.o是怎么知道用户设置的堆的起始地址的呢?
答:
在嵌入式系统中,newlib
是一个轻量级的 C 库,用于管理包括动态内存分配在内的标准 C 函数,如 malloc
和 free
。为了使 newlib
能够管理堆(heap),它需要知道堆的起始地址和大小。你提到在链接脚本中没有看到 __heap_start
和 __heap_end
这样的符号,并想知道 newlib
中的 crt0.o
是如何获取用户设置的堆的起始地址的。下面我将详细解答这个问题。
1. newlib
如何获取堆的起始地址?
newlib
获取堆的起始地址并不是通过硬性指定的符号(如 __heap_start
),而是通过链接脚本中定义的某个符号(通常是 end
或 _end
)来实现的。这个符号表示堆的起始位置,通常由链接脚本在内存布局中设置。
在你的链接脚本中,虽然没有显式定义 __heap_start = .;
和 __heap_end = .;
,但有以下部分:
._user_heap_stack :
{. = ALIGN(8);PROVIDE ( end = . );PROVIDE ( _end = . );. = . + _Min_Heap_Size;. = . + _Min_Stack_Size;. = ALIGN(8);
} >RAM
end
符号的作用:在这里,end
(和_end
)被定义为._user_heap_stack
节的起始地址。这个节通常紧接在数据段(如.bss
)之后,用于分配堆和栈的空间。- 堆的起始地址:
end
标记了堆的开始位置,而堆的结束位置是end + _Min_Heap_Size
。 newlib
的使用方式:newlib
的动态内存管理函数依赖于一个叫做_sbrk
的函数,而_sbrk
使用end
符号作为堆的初始地址。
因此,在你的配置中,newlib
通过链接脚本中的 end
符号获取了堆的起始地址。
2. _sbrk
函数如何工作?
_sbrk
是 newlib
中负责管理堆增长的关键函数。它的作用是根据请求的内存大小(incr
)扩展堆,并返回新分配的内存块地址。以下是 _sbrk
的典型实现(简化版):
caddr_t _sbrk(int incr)
{extern char end; /* 由链接脚本设置 */static char *heap_end;if (heap_end == 0)heap_end = &end; /* 初始化时将 heap_end 设置为 end */char *prev_heap_end = heap_end;heap_end += incr; /* 扩展堆 */return (caddr_t) prev_heap_end;
}
- 初始化:当
_sbrk
第一次被调用时,heap_end
被设置为end
,即链接脚本中定义的堆起始地址。 - 内存分配:之后,每次调用
_sbrk
时,heap_end
会根据请求的大小递增,从而分配新的内存块。 - 关键点:
end
是一个外部符号,直接由链接脚本提供,newlib
通过它知道堆的起始位置。
在你的链接脚本中,end
被定义为 ._user_heap_stack
的起始地址,因此 _sbrk
会从这个位置开始管理堆。
3. crt0.o
的角色是什么?
crt0.o
是 C 运行时库的一部分,负责在 main
函数执行之前进行初始化工作,比如设置堆和栈。然而,在 newlib
的标准实现中,crt0.o
并不需要显式地将堆的起始地址传递给 _sbrk
,原因如下:
end
符号的全局性:end
是链接脚本中定义的全局符号,newlib
的_sbrk
函数可以直接访问它。- 初始化过程:通常,
crt0.o
会设置一些基本的运行时环境(比如栈指针),但堆的初始化是由_sbrk
在第一次调用时完成的,而_sbrk
直接使用end
。 - 可能的汇编代码:在某些系统中,
crt0.o
可能包含类似以下的代码(以 ARM 为例):
但在ldr r0, =end bl _sbrk_init ; 调用初始化函数(视实现而定)
newlib
的典型实现中,_sbrk
不需要额外的初始化函数调用,因为它直接依赖end
符号。
在你的情况下,由于 end
已由链接脚本定义,crt0.o
不需要额外操作,newlib
的 _sbrk
就能通过 end
知道堆的起始地址。
4. 为什么没有 __heap_start
和 __heap_end
也能工作?
- 符号名称的灵活性:
newlib
并不强制要求使用__heap_start
和__heap_end
这样的符号。不同的系统和链接脚本可能使用不同的命名约定,而newlib
默认使用end
或_end
。 - 你的配置:在你的链接脚本中,
end
替代了__heap_start
的作用,提供了堆的起始地址。堆的大小由_Min_Heap_Size
定义,但_sbrk
通常只关心起始地址,结束地址的检查可能由额外的逻辑完成(比如确保heap_end
不超过 RAM 范围)。 - 结果:即使没有
__heap_start
和__heap_end
,newlib
仍然能通过end
正常工作。
5. 具体传递机制总结
- 通过什么传递:堆的起始地址通过链接脚本中的
end
符号传递给newlib
。 - 如何传递:
end
是在链接时确定的全局符号,newlib
的_sbrk
函数直接引用它,无需额外的运行时传递。 crt0.o
的作用:crt0.o
本身并不直接告诉newlib
堆的起始地址,而是依赖链接脚本的静态定义(end
)。_sbrk
在第一次调用时自动以end
为基础初始化堆。
因此,在你的系统中,newlib
通过以下方式得知用户设置的堆的起始地址:
- 链接脚本定义了
end = .
,标记._user_heap_stack
的起始地址。 newlib
的_sbrk
函数使用extern char end
访问这个地址。- 堆从
end
开始管理,crt0.o
不需要显式干预。
结论
在你的链接脚本中,虽然没有 __heap_start
和 __heap_end
,但 newlib
通过 end
符号获取了堆的起始地址。这个符号由链接脚本提供,crt0.o
不需要额外传递信息,_sbrk
函数直接使用 end
来初始化和管理堆。这种机制简单高效,非常适合嵌入式系统的内存管理需求。