Skip to main content
 首页 » 编程设计

linux-kernel之上下文切换内部结构

2024年02月27日34tintown

我想借助这个问题来学习并填补我的知识空白。

因此,用户正在运行一个线程(内核级),它现在调用 yield (我认为是一个系统调用)。 调度程序现在必须将当前线程的上下文保存在 TCB 中(存储在内核中的某个位置),并选择另一个线程来运行并加载其上下文并跳转到其 CS:EIP。 为了缩小范围,我正在研究运行在 x86 架构之上的 Linux。现在,我想了解详细信息:

所以,首先我们有一个系统调用:

1) yield 的包装函数会将系统调用参数压入堆栈。压入返回地址并引发中断,并将系统调用号压入某个寄存器(例如EAX)。

2)中断将CPU模式从用户模式更改为内核模式,并跳转到中断向量表,然后从那里跳转到内核中的实际系统调用。

3) 我猜调度程序现在被调用,现在它必须将当前状态保存在 TCB 中。这是我的困境。因为,调度程序将使用内核堆栈而不是用户堆栈来执行其操作(这意味着 SSSP 必须更改),它如何存储状态用户无需修改进程中的任何寄存器。我在论坛上读到,有用于保存状态的特殊硬件指令,但是调度程序如何访问它们以及谁运行这些指令以及何时运行?

4) 调度程序现在将状态存储到 TCB 中并加载另一个 TCB。

5) 当调度程序运行原始线程时,控制权返回到包装函数,该函数清除堆栈并恢复线程。

附带问题:调度程序是否作为仅内核线程运行(即只能运行内核代码的线程)?每个内核线程或每个进程是否都有单独的内核堆栈?

请您参考如下方法:

在较高的层面上,有两种独立的机制需要理解。第一个是内核进入/退出机制:它将单个正在运行的线程从运行用户模式代码切换到在该线程的上下文中运行内核代码,然后再返回。第二个是上下文切换机制本身,它在内核模式下从一个线程的上下文中运行切换到另一个线程的上下文中。

因此,当线程 A 调用 sched_yield() 并被线程 B 替换时,会发生什么:

  1. 线程A进入内核,从用户态转变为内核态;
  2. 内核上下文中的线程A-切换到内核中的线程B;
  3. 线程 B 退出内核,从内核模式更改回用户模式。

每个用户线程都有一个用户模式堆栈和一个内核模式堆栈。当线程进入内核时,用户模式堆栈(SS:ESP)和指令指针(CS:EIP)的当前值被保存到线程的内核中。模式堆栈,CPU 切换到内核模式堆栈 - 通过 int $80 系统调用机制,这是由 CPU 本身完成的。然后剩余的寄存器值和标志也被保存到内核堆栈中。

当线程从内核返回用户模式时,寄存器值和标志将从内核模式堆栈中弹出,然后从内核模式上保存的值恢复用户模式堆栈和指令指针值堆栈。

当线程上下文切换时,它会调用调度程序(调度程序不作为单独的线程运行 - 它始终在当前线程的上下文中运行)。调度程序代码选择接下来要运行的进程,并调用 switch_to() 函数。该函数本质上只是切换内核堆栈 - 它将堆栈指针的当前值保存到当前线程的 TCB 中(在 Linux 中称为 struct task_struct ),并从下一个线程的 TCB。此时,它还保存和恢复内核通常不使用的其他一些线程状态 - 例如浮点/SSE 寄存器。如果被切换的线程不共享相同的虚拟内存空间(即它们位于不同的进程中),则页表也会被切换。

因此您可以看到,线程的核心用户模式状态不会在上下文切换时保存和恢复 - 当您进入和离开内核时,它会保存并恢复到线程的内核堆栈中。上下文切换代码不必担心破坏用户模式寄存器值 - 此时这些值已经安全地保存在内核堆栈中。