lab5 深入理解进程切换

找到linux内核中的content_switch函数

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);
}

context_switch函数用于进程/线程的切换,其中参数rq指向本次切换发生的CPU对应的run queue,prev指向将要被剥夺执行权利的那个进程,next指向即将执行的线程。

如果当前要切换的进程是内核线程,那么进入lazy tlb mode,不需要更新TLB,也不需要进行地址空间切换,因为内核线程不会访问用户空间,会借用当前正在使用的地址空间。

如果prev进程是用户进程,则将被借用的mm_struct的引用计数器加1。如果next进程是一个用户进程,则需要进行用户地址空间切换,包括页表切换和刷新TLB。

如果prev进程是内核线程,则停止借用地址空间并处理引用计数器。

当要切换的下一个进程是一个用户进程时,需要进行地址空间的切换。此时调用了switch_mm_irqs_off函数,它会切换页表并且刷新TLB。如果当前正在运行的进程是一个内核线程,则需要停止对地址空间的借用,并处理被借用的mm_struct的引用计数器。这样即使被借用的用户进程退出了,其地址空间也不会被马上销毁,需要等到引用计数器为0才能销毁

context_switch函数还调用了 switch_to,以此来实现切换寄存器状态和栈。switch_to函数会调用__switch_to_asm,而 __switch_to_asm 的实现和所处的体系结构有关。以x86_64为例,__switch_to_asm会更新寄存器状态和栈信息。

在更新寄存器状态时,将当前进程的栈指针rsp保存到prev->rsp,并将next的栈指针赋值给rsp,保存当前CPU的rsp0值到prev->thread.rsp0,并将next->thread.rsp0的值赋给rsp0,从而确保内核栈的正确切换。此外,需要更新其他寄存器状态,如rdi、rsi、rdx、rcx、r8和r9等,这些寄存器中保存的是系统调用需要的参数,因此要确保它们的值正确。在更新栈的过程中,会将prev->thread.sp值保存到prev->thread.bp处,同时将next->thread.sp的值赋给rsp,确保切换后能够正确访问next进程的用户栈。

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)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值