在Linux系统上,一个进程有两种不同的栈,一种是用户栈,另一种是内核栈。
用户栈
用户栈就是应用程序直接使用的栈。如下图所示,它位于应用程序的用户进程空间的最顶端。
当用户程序逐级调用函数时,用户栈从高地址向低地址方向扩展,每次增加一个栈帧,一个栈帧中存放的是函数的参数、返回地址和局部变量等,所以栈帧的长度是不定的。
用户栈的栈底靠近进程空间的上边缘,但一般不会刚好对齐到边缘,出于安全考虑,会在栈底与进程上边缘之间插入一段随机大小的隔离区。这样,程序在每次运行时,栈的位置都不同,这样黑客就不大容易利用基于栈的安全漏洞来实施攻击。
用户栈的伸缩对于应用程序来说是透明的,应用程序不需要自己去管理栈,这是操作系统提供的功能。应用程序在刚刚启动的时候(由fork()系统调用复制出新的进程),新的进程其实并不占有任何栈的空间。当应用程序中调用了函数需要压栈时,会触发一个page fault,内核在处理这个异常里会发现进程需要新的栈空间,于是建立新的VMA并映射内存给用户栈。
内核栈
内核栈对于应用程序是不可见的,因为它位于内核空间中。在应用程序执行过程中,如果发生异常、中断或系统调用的话,应用程序会被暂停,系统进入内核态,转去执行异常响应等代码,这个时候所使用的栈就是内核栈。
与用户栈相比,内核栈的尺寸要小得多。在32位Linux系统上,用户栈最多可以扩展到64M,但内核栈最多也只有8K字节,而且有时为了提高内存利用率还常常把内核栈配置成4K。其实即使是只有4K,在绝大多数情况下也仍然是够用的,因为这里只是给内核代码使用的,栈不会很大。
每个进程在内核空间中都拥有一个对应的内核栈,而且这个栈是在进程fork的时候就预留出来的。以下是创建内核栈的代码(Kernel 2.6.35 版本):
[c]
static struct task_struct *dup_task_struct(struct task_struct *orig)
{