lab5:深入理解进程切换

该函数在进程切换之前调用,内核会执行与体系结构相关的一些调测指令。上下文切换完成后,必须调用 finish_task_switch,即这两个函数一定是要成对出现的。

当A进程运行到一半触发系统调用或被中断时,进行中断上下文的切换,之后执行ISR中断服务例程,在中断处理结束后,返回前,是进程调度的时机,使用_schedule()函数进行进程切换。

_schedule()函数首先从CPU任务队列中取出当前进程的标识符记为prev进程。然后通过进程调度算法确定下一个要被换上的进程,记为next进程。之后,检查next如果和prev进程不一样,调用context_switch()函数进行上下文切换,next进程进入CPU运行。

在context_switch()中调用switch_to()进行寄存器和堆栈的切换,switch_to()会调用_switch_to_asm()函数,在_switch_to_asm()的中进行了从prev内核堆栈到next内核堆栈的切换,在最后不使用ret指令,而是通过jmp指令跳转到_switch_to()函数,在_switch_to()函数的结尾调用return返回,因为在_switch_to_asm()中进行了堆栈的切换,因此_switch_to()返回后,回到的是next进程的内核堆栈,而不是prev进程的内核堆栈。
 

/*
 * %eax: prev task
 * %edx: next task
 */
ENTRY(__switch_to_asm)//_switch_to_asm()函数
......
  /* switch stack */
  movl  %esp, TASK_threadsp(%eax)
  movl  TASK_threadsp(%edx), %esp
......
  jmp  __switch_to
END(__switch_to_asm)
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)

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

ENTRY(__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
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值