引言

大家设计了NEMU, 如何让它们运行程序?

  • 程序和硬件有什么关系?
  • 为什么要有AM?
  • AM代码导读

从零开始设计计算机

现代处理器芯片

  • 你笔记本CPU的特性
cat /proc/cpuinfo | grep "^flag" | uniq

复杂系统是如何设计的?

A: 迭代改进

 

其他例子:

  • x86系列处理器的诞生和发展
    • 从1978年80条指令到2015年3600条
  • Windows操作系统
    • 窗口重叠, 双击运行, Ctrl+C/Ctrl+V…

 

不仅是这些具体的系统, 就连计算机技术本身, 也是迭代式发展的

图灵机(1936)

可以看成一个增强版本的状态机

  • 多了读写头和可移动的纸带
  • 状态转移表 = 指令
    • <input, output, move, next state>
  • 跳转到未定义状态时停机

通用图灵机 - 通用计算机的数学模型

图灵在1936年发表论文《论可计算数及其在可判定性问题上的应用》

  • 当时他才本科毕业两年
  • 据说图灵机的想法是他在一次长跑后坐在草坪上发呆时产生的

 

  • 论文开创了计算机领域
    • 提出通用图灵机UTM, 它输入任意一个图灵机T, 其执行结果与T的执行结果等价
      • UTM = 计算机, T = 程序: 类似在NEMU中执行程序
      • 今天计算机能做的事情, 图灵机也能做
  • 论文还证明了计算机领域的能力上界
    • 提出图灵停机问题, 推翻了希尔伯特计划的第三步(可判定性问题)
      • 无法正确判断 “任意程序是否会陷入死循环”

冯诺依曼计算机(1945)

  • 第一台电子计算机 - Atanasoff-Berry computer (ABC, 1942)
    • 不可编程, 只能解线性方程组
  • 第一台通用电子计算机 - ENIAC(1945)

冯诺依曼计算机的意义

  • 具备输入输出的真实机器
    • 不再是理论上的计算模型
    • 和物理世界产生联系, 用户可以操作计算机
    • ENIAC从读卡器中的程序卡片读入指令, 程序结果打印到其他卡片

 

  • 第一台存储程序的通用电子计算机 - 改进版的ENIAC(1948)
    • 存储程序(stored-program) - 将需要执行的程序存放在内存
      • 和读卡器不同, 内存是可写的, 一个程序可以读入另一个程序并执行
  • 大幅增强了计算机的实用性

 

  • 今天的通用计算机都是存储程序计算机
    • 例如在shell中输入ls

更多硬件机制

  • 自陷异常 -> 依次运行多个程序
    • 批处理系统
  • 中断 + 虚拟内存 -> 并发运行多个程序
    • 分时多任务
  • 多处理器 -> 并行运行多个程序
    • 多处理器系统

从Abstract Machine理解计算机

两个问题

  1. 计算机硬件几乎都支持这些机制, 但细节却不尽相同
    • 例如串口的地址不同, 甚至是型号不同
    • 直接在这些机制上编程, 就需要程序来处理这些细节

 

  1. 我们的自制计算机(处理器)也是迭代发展的
    • 例如目前大家的自制处理器应该跑不了操作系统
    • 程序的执行也需要运行时环境提供支持

 

如何应对?

计算机系统的两个诀窍

  1. 抽象 - 引入一个新的抽象层, 把这些机制的功能抽象成API
    • 程序只要调用这些API, 就能使用相应的机制, 而无需了解其细节
    • 我们给这套API起个名字, 叫Abstract Machine(AM)
      • 它对计算机的基础机制进行了抽象总结

 

  1. 模块化 - 把这些机制分模块, 并按需组合
    • TRM(Turing Machine) - 计算的支持
    • IOE(I/O Extension) - 输出输入扩展
    • CTE(ConText Extension) - 上下文扩展
    • VME(Virtual Memory Extension) - 虚拟存储扩展
    • MPE(Multi-Processor Extension) - 多处理器扩展

最简单的计算机

如果程序只想计算, 计算机需要提供什么?

  • 可以计算的部件 - 运算器
  • 可以驱动运算器的部件 - 控制器
  • 可以放置程序的部件 - 存储器(存储程序)
  • 指示当前程序执行进度的计数器 - PC
  • 自动执行程序的机制 - 指令周期

 

NEMU就可以满足程序的需求!

  • 运算器 - 执行相关的C代码
  • 控制器 - 译码相关的C代码
  • 存储器 - \(M\)

TRM - 最简单的裸机运行时环境

在程序看来, 它需要什么样的运行时环境?

  • 可以执行指令的环境 - 模型机
  • 可以用来自由计算的内存区间 - 堆区
  • 程序 “入口” - main(const char *args)
  • “退出”程序的方式 - halt()
  • 打印字符 - putch()
    • 输出是程序很自然的需求, 图灵机也要输出0/1

 

有了这些API, 程序就可以输出Hello World了!

  • 还记得之前提出的, 只支持两条RISC-V指令的自制运行时环境吗?
    • 其实它只是TRM的简化版
  • freestanding运行时环境是implementation-defined的, 不违反C标准

IOE - 输入输出

如果程序想输入输出, 运行时环境需要提供什么?

  • 输入函数ioe_read()和输出函数ioe_write()
  • 还有一些约定的抽象设备
    • 时钟, 键盘, 2D GPU[, 串口, 声卡, 磁盘, 网卡]
  • 我们得到了一个冯诺依曼计算机系统!

在x86-nemu上运行超级玛丽

第三期 “一生一芯”低配版超级玛丽

CTE - 上下文管理

想进行中断/异常处理, 运行时环境需要提供什么?

  • 上下文保存/恢复
  • 事件处理回调函数
  • kcontext()创建内核上下文
  • yield()自陷操作
  • ienabled()/iset()中断查询/设置

 

  • 我们得到了一个批处理系统!
    • 小霸王100 in 1
    • 还能跑仙剑!
  • 甚至是分时多线程操作系统
    • 类似RT-Thread

VME和MPE - 虚存管理和多处理器

迈向现代计算机系统

 

  • VME的运行时环境
    • protect()创建虚拟地址空间
    • map()添加va到pa的映射关系
    • ucontext()创建用户上下文

 

  • MPE的运行时环境
    • cpu_count()返回处理器数量
    • cpu_current()返回当前处理器编号
    • atomic_xchg()共享内存的原子交换

AM的设计原则和取舍

KISS原则: 提供最少的API来实现现代计算机系统软件

  • TRM - 最小的裸机运行时环境
  • IOE - 仅提供读写接口和少量抽象设备
  • CTE - 统一但相对低效的中断异常处理
  • VME - 页式虚存管理的基本机制
  • MPE - 简化的并发模型和原子操作

 

失去: 真实系统中某些特性的支持

  • 不连续/热插拔的内存, PCIe等现代设备, 快速中断, 页表的A/D位, 其他的原子操作

得到: 适合教学的简洁概念和实现

AM的意义

AM = 按照计算机发展史将计算机功能抽象模块化裸机运行时环境

 

  1. 按计算机发展史模块化 - 从需求的角度启发我们如何设计计算机
  • 模块化使得计算机可以按需组合
    • 程序要(高效地)计算 -> (支持指令集的)图灵机 [TRM] {必选}
    • 程序要输入输出 -> 冯诺依曼机 [IOE] {B阶段, 南大数电实验}
    • 想依次运行多个程序 -> 批处理系统 [CTE] {B阶段}
    • 想并发运行多个程序 -> 分时多任务 [VME] {A阶段, 南大PA}
    • 想并行运行多个程序 -> 多处理器系统 [MPE] {南大oslab}
  • 思想和RISC-V模块化的指令集扩展类似

 

  • 先完成, 后完美 - 先让计算机运行更多更复杂的程序, 再逐步优化
    • 有同学做了一个只支持几条指令的处理器, 就打算改成乱序多发射

AM的意义(2)

AM = 按照计算机发展史将计算机功能抽象模块化裸机运行时环境

 

  1. 裸机运行时环境 - 软硬协同地揭示程序与计算机的关系
  • 还记得只支持两条RISC-V指令的自制运行时环境吗?
    • AM就是它的完整版
  • 计算机的功能迭代过程
    • (在CPU中)实现功能 -> (在AM中)提供运行时环境 -> (在应用层)运行程序
    • (在CPU中)实现更强大的功能 -> (在AM中)提供更丰富的运行时环境 -> (在应用层)运行更复杂的程序

AM的意义(3)

AM = 按照计算机发展史将计算机功能抽象模块化裸机运行时环境

 

  1. 抽象 - 支持各种计算机和程序, 打通系统方向各课程实验的关键
  • 在AM上开发程序, 就可以在各种支持AM的计算机上运行
    • cputest, coremark, microbench, litenes, FCEUX, 甚至是OS及其软件栈
  • 为计算机实现AM, 就可以运行各种基于AM开发的程序
    • x86-nemu, mips32-qemu, riscv32-npc, riscv64-xiangshan, 甚至是Linux native

 

  • 真实案例: 国科大的系统结构实验用LoongArch, 有同学希望基于LoongArch接入 “一生一芯”, 怎么办?
    • 实现一个LoongArch的AM, 就能跑仙剑!

抽象带来的其他好处

  1. 裸机程序更容易开发
    • 无需关心硬件细节, 几乎和hosted运行时环境一样
  2. 操作系统更容易理解
    • 传统的操作系统课程为了讲系统启动, 需要讲一大堆复杂概念
      • x86: 保护模式, CR0, 段描述符, 段选择符, GDT, IDT, 门描述符, TR, TSS, CR3, 页表…
      • RISC-V: 看看BBL的代码, 就知道也不简单
    • 有了AM: 操作系统就是一个从main()开始执行的C程序
      • 直接从操作系统的核心概念开始讲(jyy的OS课)
  3. 全系统更容易调试
    • Linux native的实现是正确的, 调好软件再到硬件上运行
    • 更多可能性: 基于AM的trace/model checking/formal verification

AM代码导读

代码导读

  1. 编译链接 - Makefile
    • 老规矩, 先看trace

 

  1. 启动和结束 - TRM
    • 链接脚本(linker script)
      • 指示程序入口和程序的内存布局
    • 程序入口设置栈指针后, 跳转到_trm_init()函数
    • _trm_init()调用main()
    • main()返回后调用halt()

 

  1. 机器无关的运行时环境 - klib
    • 为方便程序开发, 提供glibc的常用功能, 如printf(), strcpy()

AM的八卦

课程实验集成是各大高校计算机系统教学的梦想

南京大学也经历了这个过程

  • 2008年: 06级学长设计了第一版组成原理实验
  • 2010年: 07级学长设计了第一版操作系统实验
  • 2012年: 08级学长设计了第一版编译原理实验
  • 2014年: 10级学长设计了第一版系统基础实验

 

2017年1月和jyy讨论: 该有的都有了, 要不搞个大的?

  • 自己写个CPU, 上面跑自制OS, 上面跑自制编译器编译出的程序
  • 这个程序可以是NEMU模拟器, 上面又可以跑自制OS… (递归了)

听上去很酷, 但最大的两个挑战是

  1. 每个实验的ISA不同, 怎么解决?
    • 组成原理/编译原理用MIPS, 操作系统/系统基础用x86
  2. 全系统有bug, 怎么调试?

现在有很多大佬成功单挑这一目标

例如北科大某巨佬的本科毕设成果(点此膜拜)

 

教学上的目标: 提出可传承的实验方案, 帮助更多同学实现这一目标

jyy首次提出AM, 同时解决两大挑战

  • 2017年3月10日的计算机系统综合实验课上, jyy给大家介绍AM的初步想法
    • 通过抽象层把ISA和OS解耦
  • 3月17日, jyy给大家介绍AM的第一版API

2017年南京大学提出若干有影响力的教学成果

  1. 提出AM
  2. NEMU加入DiffTest(后面课程进一步介绍)
  3. 第一版Project-N教学生态系统
Project-N组件 说明
Navy-apps 应用程序集
NCC NJU C Compiler, C编译器
Newlib 嵌入式C库(从官方版本移植)
Nanos/Nanos-lite NanJU OS, 操作系统/简化版
Nexus-AM(旧名称) 抽象计算机, 裸机运行时环境
NEMU NJU EMUlator, 全系统模拟器
NPC NJU Personal Computer, SoC
NOOP NJU Out-of-Order Processor, 处理器

影响了 “一生一芯”(1/2/3), 香山(1/2), 龙芯杯(2)的流程

2018年龙芯杯南京大学作品

  • 通过NEMU与NOOP进行DiffTest
  • 成功在乱序发射乱序执行处理器上运行自制OS
  • 并通过自制GUI界面分时运行仙剑/马里奥/x86-NEMU
  • 展示全自制计算机系统生态(C库和应用程序除外)

第三期 “一生一芯”陈同学也部分实现了这个目标

不过和北科大巨佬相比, 还有不少差距 😂

一些很酷的想法

  • 给RT-Thread加一个用AM实现的BSP(Board Support Package)
    • 我们已经实现了, 已加入B阶段的学习流程
  • 将xv6移植到AM
    • jyy课题组正在开展
    • 我们争取加入A阶段的学习流程
  • 给Linux加一个用AM实现的arch
    • 也许不完整的CPU也能partially运行Linux
    • 需要舍弃一些现代功能, 还要考虑如何整合Linux驱动和AM的IOE
    • 拍脑袋想的, 但很酷!

 

  • 把verilator编译的香山移植到AM, 可以在NPC上仿真香山!
    • 编译的C++很复杂, 用来测试大家的指令实现是一个不错的选择
    • 不过这些C++依赖的运行时环境比较复杂, 我们找了替代方案

总结

AM = 按照计算机发展史将计算机功能抽象模块化裸机运行时环境

  • 按计算机发展史模块化 - 从需求的角度启发我们如何设计计算机
  • 裸机运行时环境 - 软硬协同地揭示程序与计算机的关系
  • 抽象 - 支持各种计算机和程序, 打通系统方向各课程实验的关键