抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

拉片分析xv6和rcore中的trap和上下文切换机制

学trap感觉非常绕, 浅记一下。个人认为rcore的实现会比较好理解一点, 不过xv6的也值得学习一下, 所有这里把两个的实现都整理了一下。(未开虚拟内存版)

拉片分析: 进程第一次启动 -> 进程第一次返回用户态 -> 进程陷入内核态 -> 进程再返回用户态

rcore对应代码ch3, 未开启分页版, 开启分页后大同小异

  • 名词解释
    • epc
      • 进入异常状态时刻的pc指针
    • mret/sret
      • 从异常状态返回原来状态(mode), 并根据对应epc设置pc指针
    • trapframe/TrapContext
      • 下陷时刻的寄存器现场 + 一些切换所需的记录
      • 比如下陷时刻sp应该是用户栈, 所以trapframe.sp保存的就是用户栈
  • 进程第一次启动(初始化)
    • 创建进程的上下文context
    • 然后调用swtch函数完成上下文切换
    • swtch是个函数调用, 函数返回时会根据ra寄存器返回对应地址
    • 为了实现一个完整的闭环, 第一次启动要和第n次下陷的操作是一模一样的, 所以我们要 “伪造”TrapFrame伪造Context(上下文)
      • 伪造trapframe:
        • trapframe可以理解成下陷时刻用户态的寄存器现场, 我们根据这个现场进入到用户态, 所以我们伪造的trapframe应该
          • trapframe.sp指向 用户栈
          • trapframe.epc指向 用户程序第一行
        • rcore的trapframe是保存在进程内核栈上, 而xv6的trapframe是保存在堆上通过proc->trapframe访问
      • 伪造context:
        • swtch就是对context的切换, 但是并没有做状态空间切换的操作
        • 事实上因为有内核态和用户态, context上下文是有着内核态上下文和用户态上下文两部分的, 这里我们的context结构记录的是内核态上下文, 会一一对应一个trapframe, 哪里保存了用户态上下文
        • 所以我们伪造的context应该
          • context.ra指向用户态返回函数: xv6中是usertrapret, rcore中是__restore
          • context.sp指向进程的内核栈
    • 总结
      • trapframe.sp指向用户栈
      • trapframe.epc指向用户主程序
      • context.sp指向内核栈
      • context可以理解为进程的内核态上下文
      • trapframe可以理解为进程的内核态上下文
  • 进程第一次返回用户态
    • swtch结束后, 函数返回到ra寄存器指示的地址, xv6中是usertrapret, rcore中是__restore
    • 此时sp指向内核栈, 我们要找到trapframe来还原用户态现场
      • rcore的实现:
        • 设置stvec为用户态下陷的入口地址__alltraps, trap::init()
        • 因为我们伪造trapframe是在内核栈栈顶, 可以使用使用sp + offset访问
        • 切换需要用到一个 “辅助寄存器” sscratch永久保存
        • 第一步先让sscratch指向trapframe.sp即用户栈, 而当前sp是内核栈, 最后一交换栈空间就进入了用户栈, 而sscratch将保存内核栈
        • 然后恢复各个寄存器的内容, 弹出内核栈trapframe: addi sp, sp, 34*8
        • 设置sepctrapframe.epc即用户主程序
        • 最后spsscratch交换完成栈空间切换: csrrw sp, sscratch, sp, sret后pc就成了sepc中的内容
      • xv6的实现:
        • 设置stvec为用户态下陷的入口地址uservec, w_stvec(uservec);
        • 设置sepc为用户态主程序
        • 设置trapframe用于还原用户态寄存器现场, 主要是设置trapframe.kernel_sp, 然后设置trapframe.kernel_trap为trap处理函数 为下次下陷做准备
        • 将trapframe通过a0寄存器传递给userret完成用户态切换
        • 与rcore类似, 需要使用sscratch寄存器做一个中介永久保留
        • userret第一步让sscratch保存trapframe.a0即用户态a0的值, 然后此时的a0是trapframe
        • 然后恢复各个寄存器
        • 最后a0sscratch交换把剩下的一个a0寄存器给恢复了, 之后a0为用户态a0值, sscratch为trapframe
    • 总结
      • rcore的切换是围绕sp(sp是内核栈顶trapframe的引用), sscratch完成的
      • xv6的切换是围绕a0(a0是堆中的trapframe的引用), sscratch完成的
      • 此时:
        • rcore的sscratch指向内核栈栈顶
        • xv6的sscratch指向堆中的trapframe
  • 进程陷入内核态
    • 进入用户态前都设置了stvec, rcore为__alltraps, xv6为uservec
    • 上一次返回用户态结束后
      • rcore的sscratch指向内核栈栈顶
      • xv6的sscratch指向堆中的trapframe
    • rcore的实现
      • 将下陷时刻的寄存器现场保存到内核栈顶的trapframe
      • 所以先从sscratch中恢复内核栈并开辟栈空间csrrw sp, sscratch, sp, addi sp, sp, -34*8
      • 然后保存各个寄存器现场
      • 最后将内核栈顶的trapframe传给trap_handler处理下陷
    • xv6的实现
      • csrrw a0, sscratch, a0获取到trapframe, 用户态a0保存到sscratch
      • 然后使用a0访问trapframe保存一系列寄存器现场
      • 因为我们在上一阶段中将trap处理函数保存到了trapframe.kernel_trap
      • 所以将sscratch记录的a0页保存到trapframe中, 最后读出trapframe.kernel_trap到t0, jr t0跳转到trap处理函数
    • 总结
      • 找到trapframe(在sscrach寄存器中), 然后存入下陷时刻的寄存器现场到其中
      • rcore和xv6最后都在trap处理函数中拿到了trapframe用于分析处理(读取参数等)
        • 只不过rcore通过sp定位trapframe, 然后用a0做参数传递
        • xv6则是通过结构体定位trapframe, 因为trapframe是在堆上
        • 最后都能根据trapframe保存了下陷时刻的用户态现场得到下陷信息
  • 进程再返回用户态
    • trap处理函数执行完成后: rcore的trap_handler, xv6的usertrap
    • xv6简单再次调用usertrapret就能返回了
    • rcore比较巧妙
      • rcore汇编中__alltrap之后紧接着就是__restore
      • 所以在__alltrap调用完trap_handler后就能进入__restore返回用户态
      • __restore对初始状态的断言是a0指向内核栈顶的trapframe
        • 所以在rcore中trap_handler结束要返回trapframe(rcore中叫TrapContext), 那么根据函数调用协议返回值就会自动保存到a0
        • 之后下一条指令就是__restore形成完美闭环
    • 总结
      • 要形成闭环就要满足trap return初始状态的断言
        • rcore中断言(__restore)a0指向内核栈中的trapframe, 所以trap_handler要返回trapframe(TrapContext), 函数调用协议会自动保存到a0中
        • xv6中断言(usertrapret)断言了a0指向trapframe, trapframe.kernel_trap指向trap处理函数
          • 不过xv6先用usertrapret过渡把初始状态处理好后再调用userret返回
  • 总结
    • context相当于进程的内核态上下文现场, trapframe相当于进程的 用户态上下文现场

评论