堆栈是一个连续的块 包含数据的内存。一个寄存器 称为堆栈指针(SP)点 到堆栈的顶部。底部 堆栈的位置是固定的。
内核的堆栈如何固定?
答案 0 :(得分:6)
我输出了一个更长的答案,但是它很粗糙,所以这里的答案较短......
当进程启动时,它需要一个堆栈,以便在调用函数时存储临时值(即自动分配)或堆栈帧。这个堆栈的内存必须来自某个地方。
操作系统所做的是在堆栈的虚拟内存中创建映射,并将堆栈指针分配给该块的高地址。在预定义堆栈中,堆栈指针在解除引用之前递减,初始堆栈指针实际上是映射空间中最后一个地址之后的地址。
当进程尝试在堆栈上放置某些东西时,它会导致访问此内存区域,该区域没有映射到它的任何物理RAM。这会导致页面错误,从而导致操作系统将最少使用(或接近它)的RAM页面踢入交换驱动器或页面文件,并将物理RAM页面重新分配给正在访问的堆栈页面。现在有物理RAM,操作系统返回并继续进程,将推送的数据放入堆栈内存。
那么如果你把所有东西从堆栈中弹出然后尝试再次弹出会发生什么?最后一个pop,其中堆栈指针处于其初始值,导致访问不映射到堆栈的虚拟内存地址。这会导致分段错误,这意味着该进程尝试访问它从未分配的内存。操作系统通过终止流程并尽可能清理来做出响应。
为什么不将页面映射到堆栈的末尾?因为这会导致读取未初始化的RAM,其中包含过去使用该物理RAM页面的内容。只有无法这可以产生一个功能正常的程序(更不用说这是一个巨大的安全风险),所以最好还是杀死程序。
答案 1 :(得分:1)
这将是堆栈本身实现的一部分。如果您在(例如)C中实现堆栈,则可以存储堆栈指针和当前元素数。或者堆栈指针以及堆栈的基础,如:
typedef struct {
int *sp_empty;
int *sp;
int *sp_full;
} tIntStack;
tIntStack stk;
// Initialise 20-element stack.
stk.sp = stk.sp_empty = malloc (sizeof(int) * 20);
stk.sp_full = &(stack[20]);
// Push a value x, detecting overflow.
if (stk.sp == stk.sp_full) { error here}
*(stk.sp) = x;
stk.sp++;
// Pop a value x, detecting underflow.
if (stk.sp == stk.sp_empty) { error here}
stk.sp--;
x = *(stk.sp);
如果您正在谈论CPU堆栈(返回地址等),您可能会因崩溃而检测到堆栈下溢。不好。
例如,在处理器限制为64K地址空间的旧时代,CPU通常会测试内存,直到找到第一个与刚刚写入的内容不相同的字节(在启动过程中)。这是超出实际物理内存的第一个字节,因此它们将SP设置为低于该值。当然,一些(少量)机器具有 64K的物理RAM,所以只需将SP设置为地址空间的顶部。
现在,在大地址空间,虚拟内存,多任务操作系统中,这个过程稍微复杂一点,但它仍然归结为(在大多数情况下):
那时,就内核而言,你可能就自己而言。除了任务切换和按要求提供服务之外,它的责任在代码开始运行时停止,但这与您的堆栈无关。如果你的错误代码溢出或下溢了堆栈,那就是你的问题。
内核可以检查你的任务开关上的SP,看看你是否做错了什么,但这绝不是保证。它还可以使用硬件内存保护来检测下溢(如果堆栈位于分配的地址空间的顶部)。但同样,这完全取决于您正在使用的内核。
答案 2 :(得分:0)
我认为你的意思是“固定在进程的内存空间中”,而不是“固定在整个内存中”。您可以通过搜索类似
的行来查看堆栈底部在任何最新Linux系统中的位置bfbce000-bfbe3000 rw-p bffeb000 00:00 0 [stack]
在cat /proc/<pid-here>/maps
的输出中。在这种情况下,堆栈的底部是0xbffeb000
。在我的系统中,所有堆叠底部似乎都落在bffca000 bffcb000 bffdd000 bffe0000 bffe4000 bffe6000 bffeb000之一(循环完成~200个过程之后)。
我猜这些值是在内核深处分配的,无论在哪里创建进程。
答案 3 :(得分:0)
实际上,堆栈底部有一些东西。在堆栈之后,有argv数组的元素,然后是env数组的元素。接下来是障碍,然后是libc代码。