**Hyperkernel: Push-Button Verification of an OS Kernel**

####  资源

https://locore.cs.washington.edu/hyperkernel/

https://homes.cs.washington.edu/~xi/

https://github.com/locore/hv6

SMT(Satisfiability Modulo Theories)可满足性模理论

#### 简介

Hyperkernel是一个操作系统的内核，用于内核正确性的验证。自动化证明引入了三个关键思想：**限制了内核接口**避免无限的循环或递归，**内核和用户地址空间分离**以简化虚拟内存的推理，**在LLVM中间表示级别上验证**以避免在复杂的C语义上建模。

#### 概览

通过一个系统调用的设计、规范和验证，说明Hyperkernel的开发流程。

为了定义系统调用的行为，程序员需要写两份规范：一个是详细的状态机规范，用于功能验证；一个是更高层的描述性规范，方便人工审查。两个规范都是用python来表示的，用python的原因是它简单的语法和用户友好的z3求解器接口。验证者将规范和内核实现都合并到一个SMT查询中，并调用Z3来进行验证。验证的代码与未验证(可信)的代码链接在一块生成最终的内核镜像。

本节做了两个假设：内核运行在单处理器上且禁用中断，所以每个系统调用都是原子的。二，内核和用户的地址空间是分离的，虚拟内存采用的是identity映射。这两个假设在**验证**一节中会详细解释。

##### 有限的接口

我们让Hyperkernel的接口基于现有的规范，但是也进行了必要的调整。以POSIX规范的dup系统调用为例来说明什么是有限的接口。首先，系统调用`dup(oldfd)`不是有限的，因为它执行的时间依赖于文件描述符表的大小。然后，修改后的系统调用`dup(oldfd, newfd)`是有限的，因为内核只需常数次的操作就可实现。

##### 规范

状态机规约由两部分组成。一是对抽象内核状态的定义；二是对自陷处理程序的定义(比如系统调用)，实际上就是抽象状态的转换。描述性的规约是可选的，但它不能违反状态机规约。

- 抽象的内核状态。

  用定宽的整数和映射来定义。`hv6/spec/kernel/datatypes.py`

  ```python
  class KernelState(BaseStruct):
      current = Map(pid_t)	# current running process's ID
      proc_table_ptr_to_int = Map(uint64_t)
      file_table_ptr_to_int = Map(uint64_t)
      ...
  ```

  

- 状态转换规范。

  系统调用的规范有固定的模式：首先验证系统调用参数，如果通过则将内核转换到下一个状态并返回0，否则内核状态不变化并返回一个错误码。每个系统调用规约都提供一个验证条件和新状态。`hv6/spec/kernel/syscall_spec.py`

  ```python
   def sys_dup(self):
   	oldfd = util.FreshBitVec('oldfd', dt.fd_t)
   	pid = util.FreshBitVec('pid', dt.pid_t)
      newfd = util.FreshBitVec('newfd', dt.fd_t)
      return (oldfd, pid, newfd)
  ```

  

- 声明性规范。

##### 实现

Hyperkernel的dup实现在`hv6/fd.c`，使用了如下数据结构：

- 全局变量`current`，代表当前PID。
- 全局数组`procs`，代表所有进程。
- 结构体`proc`里的数组`ofile`，是文件描述符到文件的映射。
- 全局数组`files`，是文件到结构体`file`的映射。

##### 验证

Hyperkernel的验证证明了两个主要的定理：refinement和crosscutting。

##### 总结

#### 验证

##### 内核行为建模

##### LLVM IR推理

##### 横切特性

#### Hyperkernel

讨论Hyperkernel的设计。它基于xv6，使用z3进行自动化证明。其不足之处是目前只支持单核，且初始化部分和胶水代码未验证。

##### 设计概览

Hyperkernel设计的三个主要来源：Dune，外核，seL4。

- 进程基于硬件虚拟化

  Hyperkernel使用CPU的虚拟化技术来支持进程抽象。像**Dune**一样，内核作为宿主，用户进程作为guest。通过VM-exit处理程序来实现中断处理。有两点好处：一是内核与用户空间可以有不同的页表，二是虚拟化技术可以使IDT安全地暴露给用户进程。

- 显式地资源管理

  与**外核**一样，Hyperkernel要求用户空间显式地进程资源分配决策。有两点好处：一是避免了对内核的验证，二是可以通过基于数组的数据结构来实现。

- 可控制的页

  Hyperkernel的内存布局包含3种类型的内存区域：启动区域仅用于初始化，大部分区域用于保存内核资源(进程表，文件表，页的元数据等)和RAM页(保持内核和用户数据)，其它是不稳定的内存区域(DMA页和PCI页)。

  RAM页的控制方式和**seL4**是一样的：用户进程通过系统调用来控制页，内核通过页的元数据来跟踪页的类型和所有权并决定是否允许那样的系统调用。

##### 设计受限接口

- 通过引用计数器管理资源的生命周期

  如前所述，Hyperkernel为用户空间提供了系统调用来显式地回收资源(进程、文件描述符和页)。为避免资源泄露，内核需要仔细地管理它们的生命周期。这是通过在进程结构体上增加引用计数器来实现的。

- 执行细粒度的保护

  一些POSIX系统调用有复杂的语义，能把它们实现就已经相当不凡了，验证就更加复杂了。相反，Hyperkernel提供的是简单的系统调用。它创建的是只有三个页的最小进程结构，把大部分工作都交给了用户空间的库。

- 验证链接的数据结构

  内核维护了两个链表：一个页的空闲链表和一个进程的就绪链表。它们不是必须的，但可以简化用户空间的实现。

##### 用户空间的库

- 引导

  与XV6的不同之处有二：一是init进程可以访问IDT并设置用户级的异常调用，二是系统调用是在hypervisor特权级实现的。libc是一个与XV6兼容的库，它有助于XV6程序的移植。

- 文件系统

  xv6的日志文件系统已移植到Hyperkernel上，它是作为一个文件服务进程而出现的，该文件系统的驱动使用的是IOMMU系统调用。

- 网络

  已实现了用户空间上对E1000网卡的驱动，并运行了一个移植自lwIP的专有网络服务。已实现了一个简单的HTTP server and client。

- Linux用户模拟

  已实现了一个模拟器来执行未修改的、静态链接的Linux二进制文件。就像Dune一样，它会简单地截取系统调用并模仿Linux的系统调用行为。

##### 限制

#### 检查

对内核初始化过程和胶水代码的验证。

#### 经验

介绍开发Hyperkernel的经验。

##### Bug讨论

##### 开发工作

##### 验证表现

##### 运行时表现

##### 反射的硬件支持

#### 相关工作

列举了可验证的内核。

sel4首次表明了通用内核验证的可行性。

证明自动化的协同设计系统。

操作系统设计。

LLVM验证。

#### 总结

Hyperkernel是一个操作系统内核，具有较高的证明自动化和较低的证明负担。它通过限制内核接口，使用硬件虚拟化来简化虚拟内存的推理，工作在LLVM IR级别上以避免C语义建模等方式实现简易化证明。