User Interrupts[WIP]

少于 1 分钟阅读时长

发布时间:

​ 一直想找个时间好好整理下User Interrupts。看看能不能在自己懂了的基础上给大家讲明白,我想尝试一种新的叙述方式,模仿测试驱动开发TDD,来一次测试驱动叙事。

​ 我会通过划分功能模块的方式,来阐述UINTR中的组件和状态,并在最后和大家讨论UINTR可能的应用场景。

功能描述

既然是测试驱动,我们要先写好use case, 再逼近这个use case。在Intel的最初构思中,UINTR想达成以下三个功能:

  • Userspace-Userspace Communication w/o Kernel Trapping
  • Kernel-Userspace Communication w/o Kernel Signal
  • Hardware-Userspace Communication w/o Kernel Trapping

UINTR中繁杂的组件和状态在不同视角下有不同的分类方法,现在的这个Overview是存储位置视角下的。想构建这样一个系统,我首先把功能划分为三类,包括中断的发送、中断的识别和中断的触发。

我们先从最统一、最简单的中断触发开始。

如何触发一个用户态中断

这里说的触发,即在用户态直接调用中断处理例程的过程,此时所有触发中断的条件已经被识别到并确认,只差对处理例程的调用。这里似乎一切都很trivial,在触发一次传统的函数调用过程中,我们需要目标函数的地址和调用函数的参数,处理器会被动的帮我们维护调用者的栈帧。

作为一个中断,我们需要一个寄存器来保存目标函数地址,也需要一个调用参数作为中断来源的识别。因此,我们需要对这两个能力分别提供一个寄存器,这就是UIHANDLER和UIRR。

UIHANDLER作为一个64位寄存器,直接保存一个用户态地址,在用户态中断触发的时候,CPU会使得RIP := UIHANDLER 以完成控制流的跳转。

UIRR作为一个64位寄存器,其每一位代表一个可能的中断来源,每次中断触发会选择其中一个有效位来触发中断,并作为中断的参数。

当然,除了这些Developer可见的控制与参数流转,CPU还要在背后做更多的事情:维护栈帧。中断例程的调用和传统函数的调用中最显著的一个差别就是调用者不知道什么时候会触发一个中断例程。因此,Caller不会维护任何寄存器或者栈帧,作为被调用者的中断例程需要保证一切caller的临时状态不会被错误的改变。

Aeolia在做user interrupt的时候,遇到的第一个坑就是SYSV ABI 存在一个 Red Zone的概念。对于叶子函数调用,函数将不会再调整SP构建新的栈帧,而是直接使用栈顶外的最多128Bytes作为其临时栈帧。在这种情况下,中断例程调用中如果只是保存原来的RSP,会把Red Zone中的内容完全覆盖,因此,UISTKADJ作为调整栈偏移的参数单独作为一个寄存器。使得用户可以主动在指定位置或者指定偏移开始例程自己的栈帧。

有了以上的流程,我们已经完全可以在中断被识别的前提下完成一次中断例程的调用和返回了。

如何识别一个用户态中断

用户态中断的识别,是指在任何中断源发出一个中断信号之后,到下一指令用户态中断触发函数调用之前的整个过程。在这一部分,用户态终端实际上还可以被分为两个阶段,用Intel的原话来说,可以分为Notification Handling(包括Identification和Processing)和Recognition(),那依据我们的叙事主线,从最接近触发的、也是简单的Recognition开始说。Recognition非常简单,核心就一句话:UIRR的修改。当我们以以下五种方式对UIRR进行修改,且