Lab5深入理解进程切换

本文主要分析 Linux 5.4.34 版本内核中进程切换的基本操作与基本代码框架。

在 Linux 内核中,进程切换的核心函数是上下文切换函数 content_switch。该函数位于 Linux 内核源码目录下的 kernel/sched/core.c 中,代码如下:

static __always_inline struct rq *
context_switch(struct rq *rq, struct task_struct *prev,
         struct task_struct *next, struct rq_flags *rf)
{
  prepare_task_switch(rq, prev, next);
  /*
   * For paravirt, this is coupled with an exit in switch_to to
   * combine the page table reload and the switch backend into
   * one hypercall.
   */
  arch_start_context_switch(prev);
  /*
   * kernel -> kernel   lazy + transfer active
   *   user -> kernel   lazy + mmgrab() active
   *
   * kernel ->   user   switch + mmdrop() active
   *   user ->   user   switch
   */
  if (!next->mm) {                                // to kernel
    enter_lazy_tlb(prev->active_mm, next);
    next->active_mm = prev->active_mm;
    if (prev->mm)                           // from user
      mmgrab(prev->active_mm);
    else
      prev->active_mm = NULL;
  } else {                                        // to user
    membarrier_switch_mm(rq, prev->active_mm, next->mm);
    /*
     * sys_membarrier() requires an smp_mb() between setting
     * rq->curr / membarrier_switch_mm() and returning to userspace.
     *
     * The below provides this either through switch_mm(), or in
     * case 'prev->active_mm == next->mm' through
     * finish_task_switch()'s mmdrop().
     */
    switch_mm_irqs_off(prev->active_mm, next->mm, next);
    if (!prev->mm) {                        // from kernel
      /* will mmdrop() in finish_task_switch(). */
      rq->prev_mm = prev->active_mm;
      prev->active_mm = NULL;
    }
  }
  rq->clock_update_flags &= ~(RQCF_ACT_SKIP|RQCF_REQ_SKIP);
  prepare_lock_switch(rq, next, rf);
  /* Here we just switch the register state and the stack. */
  switch_to(prev, next, prev);
  barrier();
  return finish_task_switch(prev);
}

该函数实现了在一个 CPU 的运行队列上进行进程切换的操作。具体来说,该函数将当前正在运行的进程 prev 切换到下一个要运行的进程 next。
首先,函数调用 prepare_task_switch() 函数来准备进程切换。接着,调用 arch_start_context_switch() 函数来开始进程上下文的切换。
接下来是关于内存管理的操作,会根据进程的类型(内核空间进程或用户空间进程)进行不同的处理。如果切换到内核空间进程,则会进入“懒 TLB”(lazy tlb模式,并且直接使用前一个进程的地址空间;如果切换到用户空间进程,则需要切换地址空间,并调用 membarrier_switch_mm() 函数和 switch_mm_irqs_off() 函数进行一些额外的操作。
然后,函数调用 prepare_lock_switch() 函数准备锁的切换。最后,函数调用 switch_to() 函数来进行寄存器状态和栈的切换,并返回 finish_task_switch() 函数来完成进程切换。
context_switch中的一个重要函数是switch_to,switch_to调用了 __switch_to_asm

ENTRY(__switch_to_asm)
  UNWIND_HINT_FUNC
  /*
   * Save callee-saved registers
   * This must match the order in inactive_task_frame
   */
  pushq  %rbp
  pushq  %rbx
  pushq  %r12
  pushq  %r13
  pushq  %r14
  pushq  %r15

  /* switch stack */
  movq  %rsp, TASK_threadsp(%rdi) // 保存旧进程的栈顶
  movq  TASK_threadsp(%rsi), %rsp // 恢复新进程的栈顶

  /* restore callee-saved registers */
  popq  %r15
  popq  %r14
  popq  %r13
  popq  %r12
  popq  %rbx
  popq  %rbp

  jmp  __switch_to
END(__switch_to_asm)

这个函数是处理操作系统中进程切换的汇编代码,具体作用是将当前正在运行的进程切换为被调度的下一个进程。

定义一个名为__switch_to_asm的标号,作为进程切换函数的入口。内容如下:

pushq  %rbp
pushq  %rbx
pushq  %r12
pushq  %r13
pushq  %r14
pushq  %r15

将寄存器%rbp%rbx%r12%r13%r14%r15的值保存到当前进程的内核栈中,这些寄存器属于被调用者保护的寄存器,在调用函数时需要保存其值,以便在函数返回时进行恢复。

movq  %rsp, TASK_threadsp(%rdi)
movq  TASK_threadsp(%rsi), %rsp

将当前进程所使用的内核栈顶指针保存到旧进程的task_struct结构中的thread.sp字段中,并将新进程的task_struct结构中的thread.sp字段中保存的值作为新进程的内核栈顶指针。

popq  %r15
popq  %r14
popq  %r13
popq  %r12
popq  %rbx
popq  %rbp

从当前进程的内核栈中弹出之前保存的%rbp%rbx%r12%r13%r14%r15寄存器的值,并恢复其原本的值。

jmp  __switch_to
END(__switch_to_asm)

跳转到C语言实现的__switch_to函数,完成进程切换操作。END指令用于标识函数结尾。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值