我看过几个地方,例如这里但没有人详细解释用于实现堆栈本身的结构(“任务”(进程/线程)存储其嵌套调用信息等的地方)。它是一个链表,还是一个数组,或者其他什么?我似乎找不到这些信息,但从图表上看,它们总是将其显示为一个大内存块(虚拟内存),其中开头是堆,结尾是堆栈。但这是我们正在处理的虚拟内存,它周围有各种数据结构,例如分页。那么问题是,在这一切之上堆栈的具体实现是什么?我忍不住想这一定是一个链表。
原因是,如果您有多个进程,每个进程都有自己的堆栈,那么这是如何实现的?
这里我们似乎取得了一些进展:
每个进程都有自己的堆栈供其在内核中运行时使用;在当前内核中,该堆栈的大小为 8KB 或(在 64 位系统上)16KB 内存。堆栈位于直接映射的内核内存中,因此它必须是物理连续的。
答案1
堆栈实际上是一个数组——它在连续的内存中包含一堆单词,有一个重要的限制——它只能在一端增长和收缩(因此是 FILO——先进后出),这也是 LIFO。
与数组的重要区别也是:处理器堆栈在逻辑上分为帧,并且(与数组不同)每个帧的大小可以与其他帧不同。
每个帧包含进行函数调用时需要存储的内容,包括:
- 被调用函数跳转的返回地址,以在调用函数中继续。
- 保存任何返回值的空间。
- 传递给被调用函数的每个参数的副本。
- CPU 寄存器的副本,以便单独函数中的寄存器优化不会受到干扰。
如果您曾经想知道一个函数如何递归,同时又对每个级别中的所有参数和局部变量使用相同的名称,答案是它们都具有相对于当前堆栈帧的地址。
每个处理器架构的堆栈帧结构都有不同的定义,以采用最自然的存储方式。不存在“Linux”堆栈——Intel、AMD 和 Sparc 都有自己的定义。请记住,您可以下载预编译的库,本地编译器必须知道如何从您自己的代码中调用这些库。
堆栈本身也是一种通用数据结构。例如,如果您正在解析 C、SQL 或 XML 等允许嵌套块构造的语言的源代码,那么很自然地会将您所在的块堆叠起来。您不想使用进程堆栈来执行此操作:您正在解析的内容具有块结构,而不是需要递归的您自己的代码。
每个进程的堆栈只是其用户进程内存的一部分。通常,用户地址空间的范围是 -8MB 到 0 到(比如说)60MB。堆栈从 -16 开始并向下增长(越来越负)。编译器分配的全局和静态内存从 0 开始向上,任何堆分配都会增长到固定内存之上。该代码位于单独的地方(出于保护原因)。将负地址范围映射到分页内存不会对虚拟存储系统造成任何损害。