我想借助这个问题来学习和填补我的知识空白。
因此,用户正在运行一个线程(内核级),它现在调用 yield
(我认为是系统调用)。
调度程序现在必须将当前线程的上下文保存在 TCB 中(它存储在内核中的某处)并选择另一个线程运行并加载其上下文并跳转到其 CS:EIP
。
为了缩小范围,我正在研究运行在 x86 架构之上的 Linux。现在,我想进入细节:
所以,首先我们有一个系统调用:
1) yield
的包装函数会将系统调用参数压入堆栈。推送返回地址并引发中断,并将系统调用号推送到某个寄存器(例如 EAX
)。
2) 中断将 CPU 模式从用户模式更改为内核模式,并跳转到中断向量表并从那里跳转到内核中的实际系统调用。
3) 我猜调度程序现在被调用,现在它必须将当前状态保存在 TCB 中。这是我的困境。因为,调度程序将使用内核堆栈而不是用户堆栈来执行其操作(这意味着 SS
和 SP
必须更改)它如何存储状态用户无需修改过程中的任何寄存器。我在论坛上读到有用于保存状态的特殊硬件指令,但调度程序如何访问它们以及谁在何时运行这些指令?
4) 调度程序现在将状态存储到 TCB 中并加载另一个 TCB。
5) 当调度程序运行原始线程时,控制权返回到包装函数,该函数清除堆栈并恢复线程。
附带问题:调度程序是否作为仅内核线程运行(即只能运行内核代码的线程)?每个内核线程或每个进程是否有单独的内核堆栈?
最佳答案
在高层次上,有两种不同的机制需要理解。第一个是内核进入/退出机制:这会将一个正在运行的线程从运行用户模式代码切换到在该线程的上下文中运行内核代码,然后再返回。第二个是上下文切换机制本身,它在内核模式下从一个线程的上下文中运行切换到另一个。
因此,当线程 A 调用 sched_yield()
并被线程 B 替换时,会发生以下情况:
- 线程A进入内核,从用户态切换到内核态;
- 内核中的线程 A 上下文切换到内核中的线程 B;
- 线程B退出内核,从内核态变回用户态。
每个用户线程都有一个用户模式堆栈和一个内核模式堆栈。当线程进入内核时,用户态栈(SS:ESP
)和指令指针(CS:EIP
)的当前值被保存到线程的内核中——模式堆栈,CPU 切换到内核模式堆栈 - 使用 int $80
系统调用机制,这是由 CPU 自己完成的。剩余的寄存器值和标志随后也保存到内核堆栈。
当线程从内核返回到用户模式时,寄存器值和标志从内核模式堆栈中弹出,然后用户模式堆栈和指令指针值从内核模式上保存的值中恢复堆栈。
当线程上下文切换时,它调用调度程序(调度程序不作为单独的线程运行 - 它始终在当前线程的上下文中运行)。调度程序代码选择下一个要运行的进程,并调用 switch_to()
函数。这个函数本质上只是切换内核堆栈——它将堆栈指针的当前值保存到当前线程的 TCB 中(在 Linux 中称为 struct task_struct
),并从下一个线程的 TCB。此时它还保存和恢复内核通常不使用的一些其他线程状态——比如浮点/SSE 寄存器。如果被切换的线程不共享相同的虚拟内存空间(即它们在不同的进程中),页表也会被切换。
因此您可以看到线程的核心用户模式状态不会在上下文切换时保存和恢复 - 它会在您进入和离开内核时保存并恢复到线程的内核堆栈中。上下文切换代码不必担心破坏用户模式寄存器值 - 到那时这些值已经安全地保存在内核堆栈中。
关于linux-kernel - 上下文切换内部,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13643110/