内核栈和用户空间栈

136

内核栈和用户栈有什么区别?为什么要使用内核栈?如果在ISR中声明了一个本地变量,它将存储在哪里?每个进程都有自己的内核栈吗?那么进程如何在这两个栈之间协调?

3个回答

229
  1. 内核栈和用户栈有什么区别?

简而言之,除了使用不同的内存位置(因此堆栈指针寄存器的值不同)以及通常具有不同的内存访问保护之外,它们没有任何区别。也就是说,在用户模式下执行时,即使映射了内核内存(其中部分为内核栈),也将无法访问内核内存。反之亦然,没有通过内核代码明确请求(在Linux中,通过像copy_from_user()这样的函数),通常无法直接访问用户内存(包括用户堆栈)。

  1. 为什么要使用[单独的]内核栈?

特权和安全的分离。首先,用户空间程序可以使它们的堆栈(指针)成为任何它们想要的东西,通常没有必要甚至具有一个有效的堆栈指针的结构要求。因此,内核不能信任用户空间堆栈指针为有效或可用,因此需要一个由自己控制的堆栈。不同的CPU架构以不同的方式实现这一点。x86 CPU 在特权模式切换发生时自动切换堆栈指针,并且要使用不同的特权级别的值是可配置的,只有特权代码(即内核)才能够更改。

  1. 如果在ISR中声明了一个局部变量,它将存储在哪里?

在内核栈上。内核(即Linux内核)不会直接挂钩ISR到x86架构的中断门,而是将中断分派委托给通用内核中断入口/出口机制,在调用注册的处理程序之前保存预中断寄存器状态。当CPU分派中断时,可能会执行权限和/或堆栈切换,这由内核使用/设置,以便通用中断入口代码可以依赖于已经存在的内核栈。
话虽如此,当内核代码执行时发生中断时,将继续使用该点处的内核堆栈。如果中断处理程序具有深层嵌套的调用路径,则可能会导致堆栈溢出(如果中断了深度内核调用路径并且处理程序引起另一个深度路径;在Linux中,文件系统/软件RAID代码被具有iptables活动的网络代码中断已知会触发此类问题,对于这些工作负载的解决方案是增加内核堆栈大小)。

4. 每个进程都有自己的内核堆栈吗? 不仅每个进程 - 每个线程都有自己的内核堆栈(实际上还有自己的用户堆栈)。请记住,对于Linux来说,进程和线程之间唯一的区别是多个线程可以共享地址空间(形成一个进程)。
5. 进程如何在这两个堆栈之间进行协调? 根本不需要进行协调 - 这不是必须的。调度(不同线程何时运行,它们的状态如何保存和恢复)是操作系统的任务,进程无需关心这些。创建线程时(每个进程必须至少有一个线程),内核为它们创建内核堆栈,而用户空间堆栈则由用于创建线程的任何机制显式创建/提供(函数如makecontext()或pthread_create()允许调用者指定要用于“子”线程的堆栈的内存区域),或通过继承(通过访问时内存克隆,通常称为“写时复制”/COW,在创建新进程时)。话虽如此,进程可以影响其线程的调度和/或影响上下文(状态,其中包括线程的堆栈指针)。有多种方法:UNIX信号、setcontext()、pthread_yield()/pthread_cancel()等——但这有点偏离最初的问题。

1
@FrankH 很棒的回答.. 但我有一些与之相关的小问题,但是关于ARM架构.. 这个内核栈如何与不同的处理器模式相关? - Rahul
2
@Rahul:“SO评论的边距太小,无法容纳这样的答案”。不同ARM CPU模式下堆栈/堆栈指针寄存器的工作方式(哪些实现了分行SP)是一个很好的问题,但需要更多的空间来回答,而不仅仅是评论。对于诸如x86任务门或IST之类的事物也是如此-不存在“单个”内核堆栈指针(就像不存在“单个”用户堆栈指针一样),在不同操作模式下分别使用堆栈指针的硬件支持/强制执行取决于硬件。 - FrankH.
@FrankH。我为同样的问题创建了一个新的问题... http://stackoverflow.com/q/22601165/769260 我希望现在你可以帮助我而不用担心空格 :) - Rahul
1
@FrankH. 你能提供一个图示,展示内核栈在进程内存布局中的位置吗? - Jithin Pavithran
@JithinPavithran 使用源代码,Luke - 该图表可以在Linux内核代码中找到,对于ARM64,请参阅https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/arm64/memory.rst,对于x86_64,请参阅https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/x86/x86_64/mm.rst。 - FrankH.
1
x86 CPU在特权模式切换时不会自动切换堆栈指针,这种说法是错误的。syscall指令具有以下属性:SYSCALL指令不保存堆栈指针(RSP)。如果操作系统系统调用处理程序将更改堆栈指针,则由软件负责保存堆栈指针的先前值。 - Some Name

20

我的回答是从其他SO问题中收集的并加入了我的东西。

What's the difference between kernel stack and user stack?

作为内核程序员,您知道内核应该受到错误用户程序的限制。假设您为内核和用户空间保留相同的堆栈,那么用户应用程序中的简单段错误会导致内核崩溃并需要重新启动。

每个CPU都有一个“内核堆栈”,就像ISR堆栈一样,每个进程也有一个“内核堆栈”。虽然每个线程都有自己的堆栈,包括用户和内核线程,但每个进程只有一个“用户堆栈”。

http://linux.derkeiler.com/Mailing-Lists/Kernel/2004-10/3194.html

Why kernel stack is used?

所以当我们在内核模式下时,需要一种类似于用户空间的堆栈机制来处理函数调用和局部变量。

http://www.kernel.org/doc/Documentation/x86/kernel-stacks

If a local variable is declared in an ISR, where it will be stored?

它将存储在ISR堆栈(IRQSTACKSIZE)中。如果硬件支持,ISR将在单独的中断堆栈上运行。否则,ISR堆栈框架将被推入中断线程的堆栈上。

用户空间并不知道,并且实际上也不关心中断是在当前进程的内核堆栈还是单独的ISR堆栈中服务的。由于中断发生在每个CPU上,因此ISR堆栈必须为每个CPU设置。

 Does each process has its own kernel stack ?

是的,每个进程都有自己的内核栈。

 Then how the process coordinates between both these stacks?

我认为@FrankH的回答很棒。


1
在用户空间堆栈上处理中断也将成为特权升级安全漏洞,因为该内存仍具有用户空间的读写权限。同一进程的其他线程可以在sleep系统调用期间修改该内存,例如,将内核返回地址替换为跳转到某些内核代码的地址,该代码将为该进程设置UID=0。 - Peter Cordes

7
参考Robert Love的Linux Kernel Development,主要区别是大小:
用户空间可以在堆栈上静态分配许多变量,包括大型结构和千元素数组。这种行为是合法的,因为用户空间有一个可以动态增长的大型堆栈。内核堆栈既不大也不动态;它很小并且大小固定。内核堆栈的确切大小因架构而异。在x86上,堆栈大小在编译时是可配置的,可以是4KB或8KB。历史上,内核堆栈是两页,通常意味着32位架构上为8KB,64位架构上为16KB-此大小是固定且绝对的。每个进程都有自己的堆栈。
另外,内核堆栈包含指向thread_info结构的指针,该结构持有有关线程的信息。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接