【问题标题】:How does schedule()+switch_to() functions from linux kernel actually work?linux内核中的 schedule()+switch_to() 函数实际上是如何工作的?
【发布时间】:2011-09-25 10:28:43
【问题描述】:

我试图了解 linux 内核中的调度过程实际上是如何工作的。我的问题与调度算法无关。它是关于函数schedule()switch_to() 的工作原理。

我会尽力解释。我看到了:

当进程用完时间片时,need_resched 标志由scheduler_tick() 设置。内核检查该标志,发现它已设置,并调用schedule()(与问题1 相关)切换到新进程。这个标志是一个消息,应该尽快调用调度,因为另一个进程应该运行。 返回用户空间或从中断返回时,检查need_resched 标志。如果已设置,内核会在继续之前调用调度程序。

查看内核源码(linux-2.6.10-《Linux内核开发,第二版》一书所基于的版本),我也看到有些代码可以主动调用schedule()函数,给另一个进程运行的权利。 我看到函数switch_to() 是实际执行上下文切换的函数。我查看了一些依赖于架构的代码,试图了解 switch_to() 实际在做什么。

这种行为引发了一些我无法找到答案的问题:

  1. switch_to()结束时,当前运行的进程是什么?调用schedule()的进程?还是下一个流程,即被选中运行的流程?

  2. schedule() 被中断调用时,要运行的选定进程在中断处理完成时开始运行(在某种 RTE 之后)?还是在那之前?

  3. 如果不能从中断调用schedule()函数,标志-need_resched何时设置?

  4. 当定时器中断处理程序工作时,正在使用什么堆栈?

我不知道我是否可以说清楚。如果我不能,我希望我能在一些答案(或问题)之后做到这一点。 我已经查看了几个试图理解该过程的来源。我有《Linux Kernel Development, sec ed》一书,我也在使用它。 如果这有助于解释,我对 MIP 和 H8300 架构有所了解。

【问题讨论】:

    标签: linux-kernel context-switch


    【解决方案1】:
    1. 在调用switch_to() 之后,内核堆栈切换到next 中命名的任务的堆栈。更改地址空间等在例如context_switch(). 中处理
    2. schedule() 不能在原子上下文中调用,包括从中断中调用(请参阅schedule_debug() 中的检查)。如果需要重新安排,则设置 TIF_NEED_RESCHED 任务标志,在 interrupt return path 中进行检查。
    3. 见 2。
    4. 我相信,使用默认的 8K 堆栈,中断是由当前正在执行的任何内核堆栈处理的。如果使用 4K 堆栈,我相信会有一个单独的中断堆栈(由于某些 x86 魔法而自动加载),但我并不完全确定这一点。

    为了更详细一点,这里有一个实际的例子:

    1. 发生中断。 CPU 切换到一个中断蹦床例程,它将中断号压入堆栈,然后 jmps 到common_interrupt
    2. common_interrupt 调用do_IRQ,其中disables preemption 然后handles the IRQ
    3. 在某个时候,会做出切换任务的决定。这可能来自定时器中断或唤醒呼叫。无论哪种情况,都会调用 set_task_need_resched,设置 TIF_NEED_RESCHED 任务标志。
    4. 最终,CPU 从原始中断中的 do_IRQ 返回,并继续执行 IRQ exit path. 如果此 IRQ 是从内核中调用的,则它是 checks whether TIF_NEED_RESCHED is set,如果是,则调用 preempt_schedule_irq,它会短暂启用中断,同时执行schedule()
    5. 如果 IRQ 是从用户空间调用的,我们在返回之前首先 check whether there's anything that needs doing。如果是这样,我们转到retint_careful,它会检查挂起的重新调度(并在需要时直接调用schedule())以及检查挂起的信号,然后返回retint_check 进行另一轮直到没有更重要的标志设置。
    6. 最后,我们restore GS and return from the interrupt handler

    至于switch_to(); switch_to()(在 x86-32 上)的作用是:

    1. 保存 EIP(指令指针)和 ESP(堆栈指针)的当前值,以供我们稍后返回此任务时使用。
    2. 切换current_task的值。此时,current 现在指向新任务。
    3. 切换到新堆栈,然后将我们要切换到的任务保存的 EIP 推送到堆栈上。稍后会进行返回,使用这个EIP作为返回地址;这就是它如何跳回以前称为 switch_to() 的旧代码
    4. 致电__switch_to()。此时,current 指向新任务,我们在新任务的堆栈上,但其他各种 CPU 状态尚未更新。 __switch_to() 处理切换诸如 FPU、段描述符、调试寄存器等的状态。
    5. __switch_to() 返回时,将返回switch_to() 手动压入堆栈的返回地址,将执行置于新任务中switch_to() 之前的位置。切换到的任务现在已完全恢复执行。

    x86-64 非常相似,但由于 ABI 不同,必须做更多的状态保存/恢复。

    【讨论】:

    • 对不起,我还是不明白。示例:假设我们正在运行任务“A”。 1 - 发生定时器中断。 2 - 定时器中断处理程序启动。 3 - 是时候调用 schedule() 了,我们这样做了。 4 - schedule() 选择了任务'B',switch_to() 已经完成,并且任务'B' 是当前任务(现在我们正在使用任务'B' 的堆栈并且我们仍在运行中断代码)。 5 - 定时器中断结束,并继续执行任务“B”。这个例子正确吗?如果不是,这个过程是如何发生的?
    • 感谢您的时间和耐心,但我认为我遗漏了一些要点。您向我提供的信息超出了我目前所能处理的范围(当然,我对此非常感激)。你能用更一般的方式解释吗?我真正不明白的是: switch_to() 完成时会发生什么?在中断代码返回之前,所选任务(下一个)是否开始运行?此时当前是哪个堆栈?
    • 我想我明白了。坦率地说,我们没有中断,让我们忽略所有其他细节,只使用 switch_to()。假设我有两个任务,A 和 B,它们都有 10 条指令要做。任务 A 正在执行指令 3,任务 B 正在执行指令 6。任务 A 是当前任务。因此,任务 A 调用 switch_to(),在指令 3 处离开任务 A,并在指令 6 中恢复任务 B。任务 B 进入 7,并调用 switch_to(),在 7 处离开 B,并在 3 处恢复 A。任务 A 继续到4,再次调用switch_to(),进程继续。对吗?
    • 或多或少,只是周围多了几层函数调用,所以实际上switch_to返回的点只有几个。
    • 但是,当switch_to()从任务A转到任务B时,任务B实际上是在切换之后才开始运行的?原因,我知道它是否从一个任务直接跳转到另一个任务,但是如果 switch_to() 从中断(从 schedule())中调用,例如,在我看来,这将在之前留下中断代码它完成,并直接跳转到新任务。如果它是正确的,新任务将在内核模式下启动,无论如何,导致 IRET 不会被执行。我遗漏了一些东西,使这种情况变得毫无意义。
    猜你喜欢
    • 2014-01-07
    • 2013-09-19
    • 1970-01-01
    • 2014-09-24
    • 2014-03-11
    • 1970-01-01
    • 2017-09-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多