linux 内核中的 schedule()+switch_to() 函数实际上是如何工作的? [英] How does schedule()+switch_to() functions from linux kernel actually work?

查看:21
本文介绍了linux 内核中的 schedule()+switch_to() 函数实际上是如何工作的?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm trying to understand how the schedule process in linux kernel actually works. My question is not about the scheduling algorithm. Its about how the functions schedule() and switch_to() work.

我会试着解释一下.我看到了:

I'll try to explain. I saw that:

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

When a process runs out of time-slice, the flag need_resched is set by scheduler_tick(). The kernel checks the flag, sees that it is set, and calls schedule() (pertinent to question 1) to switch to a new process. This flag is a message that schedule should be invoked as soon as possible because another process deserves to run. Upon returning to user-space or returning from an interrupt, the need_resched flag is checked. If it is set, the kernel invokes the scheduler before continuing.

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

Looking into the kernel source (linux-2.6.10 - version that the book "Linux Kernel Development, second edition" is based on), I also saw that some codes can call the schedule() function voluntarily, giving another process the right to run. I saw that the function switch_to() is the one that actually does the context switch. I looked into some architecture dependent codes, trying to understand what switch_to() was actually doing.

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

That behavior raised some questions that I could not find the answers for :

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

  1. When switch_to() finishes, what is the current running process? The process that called schedule()? Or the next process, the one that was picked to run?

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

When schedule() gets called by an interrupt, the selected process to run starts to run when the interrupt handling finishes (after some kind of RTE) ? Or before that?

如果schedule()函数不能从中断中调用,标志-need_resched什么时候设置?

If the schedule() function can not be called from an interrupt, when is the flag- need_resched set?

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

When the timer interrupt handler is working, what stack is being used?

不知道能不能说清楚.如果我不能,我希望我能在得到一些答案(或问题)后做到这一点.我已经查看了几个试图理解这个过程的来源.我有一本书Linux 内核开发,第二章",我也在使用它.我对 MIP 和 H8300 架构略知一二,如果这有助于解释的话.

I don't know if I could make myself clear. If I couldn't, I hope I can do this after some answers (or questions). I already looked at several sources trying to understand that process. I have the book "Linux Kernel Development, sec ed", and I'm using it too. I know a bit about MIPs and H8300 architecture, if that help to explain.

推荐答案

  1. 调用后switch_to(),内核堆栈切换到next中命名的任务的堆栈.更改地址空间等在例如 context_switch 中处理().
  2. schedule() 不能在原子上下文中调用,包括从中断中调用(请参阅 schedule_debug()).如果需要重新调度,则设置 TIF_NEED_RESCHED 任务标志,该标志在 中断返回路径.
  3. 见 2.
  4. 我相信,对于默认的 8K 堆栈,中断由当前正在执行的任何内核堆栈处理.如果使用 4K 堆栈,我相信会有一个单独的中断堆栈(由于一些 x86 魔法而自动加载),但我并不完全确定这一点.
  1. After calling switch_to(), the kernel stack is switched to that of the task named in next. Changing the address space, etc, is handled in eg context_switch().
  2. schedule() cannot be called in atomic context, including from an interrupt (see the check in schedule_debug()). If a reschedule is needed, the TIF_NEED_RESCHED task flag is set, which is checked in the interrupt return path.
  3. See 2.
  4. I believe that, with the default 8K stacks, Interrupts are handled with whatever kernel stack is currently executing. If 4K stacks are used, I believe there's a separate interrupt stack (automatically loaded thanks to some x86 magic), but I'm not completely certain on that point.

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

To be a bit more detailed, here's a practical example:

  1. 发生中断.CPU切换到一个中断trampoline例程,将中断号压入堆栈,然后jmps到common_interrupt
  2. common_interrupt 调用 do_IRQ禁用抢占然后处理IRQ
  3. 在某些时候,会做出切换任务的决定.这可能来自定时器中断,或来自唤醒呼叫.无论哪种情况,都会调用 set_task_need_resched,设置TIF_NEED_RESCHED 任务标志.
  4. 最终,CPU从原始中断中的do_IRQ返回,继续执行IRQ 退出路径. 如果这个 IRQ 是从内核中调用的,它 检查是否设置了 TIF_NEED_RESCHED,如果设置了则调用 preempt_schedule_irq,它在执行 schedule() 时短暂启用中断.
  5. 如果 IRQ 是从用户空间调用的,我们首先 在返回之前检查是否有任何需要做的事情.如果是这样,我们去retint_careful,它检查挂起的重新调度(并在需要时直接调用 schedule())以及检查挂起的信号,然后在 retint_check 直到没有设置更重要的标志.
  6. 最后,我们恢复GS并从中断处理程序.
  1. An interrupt occurs. The CPU switches to an interrupt trampoline routine, which pushes the interrupt number onto the stack, then jmps to common_interrupt
  2. common_interrupt calls do_IRQ, which disables preemption then handles the IRQ
  3. At some point, a decision is made to switch tasks. This may be from the timer interrupt, or from a wakeup call. In either case, set_task_need_resched is invoked, setting the TIF_NEED_RESCHED task flag.
  4. Eventually, the CPU returns from do_IRQ in the original interrupt, and proceeds to the IRQ exit path. If this IRQ was invoked from within the kernel, it checks whether TIF_NEED_RESCHED is set, and if so calls preempt_schedule_irq, which briefly enables interrupts while performing a schedule().
  5. If the IRQ was invoked from userspace, we first check whether there's anything that needs doing prior to returning. If so, we go to retint_careful, which checks both for a pending reschedule (and directly invokes schedule() if needed) as well as checking for pending signals, then goes back for another round at retint_check until there's no more important flags set.
  6. Finally, we restore GS and return from the interrupt handler.

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

As for switch_to(); what switch_to() (on x86-32) does is:

  1. 保存 EIP(指令指针)和 ESP(堆栈指针)的当前值,以备稍后返回此任务时使用.
  2. 切换current_task的值.此时,current 现在指向新任务.
  3. 切换到新的堆栈,然后将我们要切换到的任务保存的 EIP 推送到堆栈上.之后会进行返回,使用这个EIP作为返回地址;这就是它跳回到以前调用 switch_to()
  4. 的旧代码的方式
  5. 调用 __switch_to().此时,current 指向新任务,我们在新任务的堆栈上,但其他各种 CPU 状态尚未更新.__switch_to() 处理诸如 FPU、段描述符、调试寄存器等的状态切换.
  6. __switch_to() 返回后,switch_to() 手动压入堆栈的返回地址被返回,将执行放回到 switch_to() 之前的位置code>switch_to() 在新任务中.现在已完全恢复切换到的任务的执行.
  1. Save the current values of EIP (instruction pointer) and ESP (stack pointer) for when we return to this task at some point later.
  2. Switch the value of current_task. At this point, current now points to the new task.
  3. Switch to the new stack, then push the EIP saved by the task we're switching to onto the stack. Later, a return will be performed, using this EIP as the return address; this is how it jumps back to the old code that previously called switch_to()
  4. Call __switch_to(). At this point, current points to the new task, and we're on the new task's stack, but various other CPU state hasn't been updated. __switch_to() handles switching the state of things like the FPU, segment descriptors, debug registers, etc.
  5. Upon return from __switch_to(), the return address that switch_to() manually pushed onto the stack is returned to, placing execution back where it was prior to the switch_to() in the new task. Execution has now fully resumed on the switched-to task.

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

x86-64 is very similar, but has to do slightly more saving/restoration of state due to the different ABI.

这篇关于linux 内核中的 schedule()+switch_to() 函数实际上是如何工作的?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆