浅析trapframe与context的原理与区别

TRAPFRAME与CONTEXT的区别

在ucore操作系统中,trapframecontext是很容易混淆的两个结构,他们都会出现在线程的调度中。实际上,结构体trapframe用于切换优先级、页表目录等,而context则是用于轻量级的上下文切换。从技术上来看,两者的区别在于context仅仅能够切换普通寄存器,而trapframe可以切换包括普通寄存器、段寄存器以及少量的控制寄存器。

*在看后续内容之前,你需要提前了解汇编语言、栈、C函数调用的实现。

TRAPFRAME结构体

trapframe定义如下——

这个结构体中,依次储存了——

  • 目标寄存器
  • gs, fs, es, ds 段寄存器
  • trap_no, err 用于储存中断信息
  • eip, cs, eflags 用于储存陷阱(trap)返回后的目的地址
  • esp, ss 在权限发生变化时,用于指示新的栈的位置

有两个地方使用了trapframe,一个是中断调用,另一个是进程切换。两者对于trapframe的使用有相似之处,但也并不完全相同。

中断调用中使用TRAPFRAME

trapframe在中断中,在前期负责中断信息的储存,后期负责中断的恢复。同时,trapframe结构体是位于栈中的,其生成和使用都是通过栈的pushpop命令实现的,这将在后面详细解释。

中断发生时,下面代码,将一系列信息压到栈中。这些信息在后续的,trap(struct trapframe *tf)函数中,被对齐到了tf结构体中。

中断处理完成后,需要恢复原来的运行状态,此时,按顺序将之前push的所有信息pop出来即可。

当然,倘若读者认为trapframe仅仅像这样中规中矩的实现信息的保存,那就太小看他了。我们发现,在调用call trap之后,有一句popl %esp,而后续恢复的信息完全是基于该%esp进行定位的,那么在中断处理内存中,如果我们强行修改%esp成为我们希望接下来运行的代码段的trap描述,那么经过__trapret代码恢复trapframe后,你就可以让程序跳转到任何你希望的地方。

比如下面代码就实现了内核态到用户态的切换。

其中*((uint32_t *)tf - 1)这个位置的值就是之后popl %esp恢复的%esp的值。

进程切换中CONTEXT的作用

context的结构体定义如下,可以看到,其中储存了所有的用户寄存器的值。

context结构体干的事情也很简单,可以用switch_to函数囊括,即保存一系列寄存器,并恢复一系列寄存器。在C++中,switch_to是拥有两个context结构体为参数的函数switch_to(&(prev->context), &(next->context)); ,其实现如下——

进程切换中TRAPFRAME的作用

那是不是进程的切换就可以直接用switch_to函数呢?答案是否定的,因为switch_to仅仅保存、恢复了普通寄存器,无法实现优先级跳转、段寄存器修改等等。这时,就要借助trapframe了。

由于switch_to函数跳转后,将调到context.eip位置。而这个跳转我们没法完全实现进程切换,所以我们可以将其设置为一个触发二级跳转的函数,forkret

其中,forkret定义如下(current是当前进程,也就是进程切换的目标进程),forkret不同于switch_to,它尝试使用trapframe作为进程切换的手段,而相比于contexttrapframe的功能就强大多了。

而forkrets定义如下——

现在,又回到了中断恢复的那一段代码,而其中的逻辑也完全相同。最终,进程跳转目标进程的入口,而该入口的地址,被存放在proc->tf中。下面是kernel_threadtrapframe初始化代码,也能佐证最终调用函数入口fn被储存在了eip中。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据