深入理解linux进程切换

实验要求:深入理解进程切换

  • 参考《庖丁解牛Linux操作系统分析》相关内容,跟踪分析进程切换的过程完成实验并写一篇实验报告,总结进程切换的工作机制以及sp和ip在不同体系结构下汇编代码的切换方法,深入理解进程切换。具体要求如下:

实验原理

进程切换:简述用户态进程X切换到用户态进程Y的过程

  1. 正在运行的用户态进程X。
  2. 发生中断(包括异常、系统调用等),CPU完成load cs:rip(entry of a specific ISR)即跳转到中断处理程序入口。
  3. 中断上下文切换,具体包括如下几点:
    swapgs指令保存现场,可以理解CPU通过swapgs指令给当前CPU寄存器状态做了一个快照。
    rsp point to kernel stack,加载当前进程内核堆栈栈顶地址到RSP寄存器。
    save cs:rip/ss:rsp/rflags:将当前CPU关键上下文压入进程X的内核堆栈,快速系统调用是由系统调用入口处的汇编代码实现的。
    此时完成了中断上下文切换,即从进程X的用户态到进程X的内核态。
  4. 中断处理过程中或中断返回前调用了schedule(),完成进程调度算法选择next进程、进程地址空间切换、switch_to关键的进程上下文切换等。
  5. switch_to调用了__switch_to_asm做关键的进程上下文切换。将当前进程X的内核堆栈切换到进程调度算法选出来的next进程(进程Y)的内核堆栈,并完成eip的状态切换,之后运行Y。
  6. 中断上下文恢复,进程Y进行中断的上下文恢复。
  7. 中断上下文恢复最后一步iret - pop cs:rip/ss:rsp/rflags,从Y的内核堆栈中弹出(3)中对应的压栈内容,完成中断上下文的切换,即从Y的内核态返回到Y的用户态。
  8. 继续运行用户态进程Y。

详细分析

在linux中的上下文切换以content_switch 为核心,位于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的控制权从一个进程转移到另一个进程。在多任务操作系统中,多个进程轮流执行,通过进程切换来实现任务的切换和调度。

1. prepare_task_switch(rq, prev, next)准备进程切换,传递了相关参数。rq 指向running queue(就绪队列);prevnext 分别指向切换前后进程的进程描述符

2. 根据下一个进程的mm字段是否为空来判断进程切换的类型。如果为空,表示将要切换到内核空间;否则,表示将要切换到用户空间。

 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;
    }
  }
  • 切换到内核空间的处理:
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;

从用户空间切换到内核空间时的处理。先调用enter_lazy_tlb()来处理TLB(转换后备缓冲)的延迟更新,再将下一个进程的active_mm设置为当前进程的active_mm。如果当前进程是从用户空间切换过来(即prev->mm不为空),则调用mmgrab()active_mm进行引用计数增加操作。最后,将当前进程的active_mm设置为NULL

  • 切换到用户空间的处理:
membarrier_switch_mm(rq, prev->active_mm, next->mm);
switch_mm_irqs_off(prev->active_mm, next->mm, next);
if (!prev->mm) {    // from kernel
  rq->prev_mm = prev->active_mm;
  prev->active_mm = NULL;
}

从内核空间切换到用户空间时,先调用membarrier_switch_mm()来处理内存屏障相关的操作,再调用switch_mm_irqs_off()来切换页表,并关闭中断。如果当前进程是从内核空间切换过来的(即prev->mm为空),则将当前进程的active_mm保存到rq->prev_mm中,并将prev->active_mm设置为NULL

3. prepare_lock_switch(rq, next, rf)准备锁的切换,传递了相关参数。

4. switch_to(prev, next, prev)实际的上下文切换函数,将当前进程切换到下一个进程。它保存当前进程的寄存器状态和堆栈,加载下一个进程的寄存器状态和堆栈。

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)

5.barrier()内存屏障,用于确保在继续执行之前,前面的所有内存操作都已完成。

6. return finish_task_switch(prev)用于完成进程切换,传递了当前进程的参数,会执行一些与进程切换相关的清理工作,并返回之前的进程。

实现进程切换的过程,根据下一个进程的mm字段是否为空,判断切换的类型(内核空间或用户空间)进行相应的处理,包括TLB更新、页表切换、中断关闭等。最后调用实际的上下文切换函数将控制权从当前进程转移到下一个进程,并完成一些清理工作。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Flyy.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值