提议:为 Go 增加裸机支持

我提议增加一个新的 GOOS 目标,例如 GOOS=none,以允许 Go 运行时在特定的应用程序定义的退出函数下执行,而不是任意的 OS 系统调用,从而实现无需直接 OS 支持的独立执行。

目前这已经在 GOOS=tamago 项目中实现,但出于 Proposal Background 部分中阐述的原因,建议将其纳入上游。

使用 GOOS=none 构建的 Go 应用程序将在裸机上运行,没有任何底层 OS。所有需要的支持都由 Go 运行时和外部驱动程序包提供,这些驱动程序包也用 Go 编写。

Go 运行时变更

所有提议变更的工作示例可以在 GOOS=tamago实现 中找到。

板级支持包或应用程序需要(仅在 GOOS=none 下)定义以下函数来支持运行时。

如果使用 go:linkname 是不可取的,那么可以使用不同的策略,现在 linkname 被用作一种方便的方式,以便在运行时早期直接调用外部定义的函数。

// cpuinit handles pre-runtime CPU initialization
TEXT cpuinit(SB),NOSPLIT|NOFRAME,$0

该函数需要在汇编中定义,因为在运行时前缺乏对原生 Go 语句的支持。

// Init takes care of the lower level initialization triggered early in runtime
// setup.
//
//go:linkname Init runtime.hwinit
func Init()
// printk emits a single 8-bit character to standard output
//
//go:linkname printk runtime.printk
func printk(c byte)
// initRNG initializes random number generation
//
//go:linkname initRNG runtime.initRNG
func initRNG()
// getRandomData generates len(b) random bytes and writes them into b
//
//go:linkname getRandomData runtime.getRandomData
func getRandomData(b []byte)
// nanotime1 returns the system time in nanoseconds
//
//go:linkname nanotime1 runtime.nanotime1
func nanotime1() int64
//go:linkname ramStart runtime.ramStart
var ramStart uint
//go:linkname ramSize runtime.ramSize
var ramSize uint
//go:linkname ramStackOffset runtime.ramStackOffset
var ramStackOffset uint

板级支持包或应用程序可以选择性地定义以下内容:

// SocketFunc must be set externally by the application on GOOS=tamago to
// provide the network socket implementation. The returned interface must match
// the requested socket and be either net.Conn, net.PacketConn or net.Listen.
var SocketFunc func(ctx context.Context, net string, family, sotype int, laddr, raddr Addr) (interface{}, error)

Go 运行时将实现以下或类似函数来辅助 中断处理:

// GetG returns the pointer to the current G and its P.
func GetG() (gp uint64, pp uint64)
// WakeG modifies a goroutine cached timer for time.Sleep (g.timer) to fire as
// soon as possible.
//
// The function is meant to be invoked within Go assembly and its arguments
// must be passed through registers rather than on the frame pointer, see
// definition in sys_tamago_$GOARCH.s for details.
func WakeG()
// Wake modifies a goroutine cached timer for time.Sleep (g.timer) to fire as
// soon as possible.
func Wake(gp uint)

编译

此类目标的编译将与标准 Go 二进制文件保持相同,而加载策略可能会因硬件而异,但无论如何都以完全独立于 Go 发行版的方式处理,根据需要使用标准标志,例如:

# Example for Cloud Hypervisory, QEMU and Firecracker KVMs
GOOS=tamago GOARCH=amd64 ${TAMAGO} build -ldflags "-T 0x10010000 -R 0x1000" main.go
# Example for USB armory Mk II
GOOS=tamago GOARM=7 GOARCH=arm ${TAMAGO} build -ldflags "-T 0x80010000 -R 0x1000" main.go
# Example for QEMU RISC-V sifive_u
GOOS=tamago GOARCH=riscv64 ${TAMAGO} build -ldflags "-T 0x80010000 -R 0x1000" main.go
# Example for Linux userspace
GOOS=tamago ${TAMAGO} build main.go

提案背景

此提案基于 TamaGo 项目的更新,该项目为 AMD64、ARM 和 RISCV64 目标带来 Go 的裸机执行。

虽然类似的提案(参见 #37503#46802)已经尝试过但没有成功,但这次努力是受到我们努力的巨大进步和变化的推动。

显著变化:

  1. 现在有完全测试的 Go 标准库支持,与 vanilla 发行版测试 集成。 AMD64, ARMRISCV64 架构的测试环境 在 Linux 下本地运行,或者通过 binfmt_misc 使用 qemu-user-static
  2. tamago 网络代码 允许外部定义单个 socket 函数附加 gVisor 或任何其他虚假网络栈 (fake networking stack),如果需要的话,这可以使其他 Go 架构受益,并允许替换现有的 js/wasip1 的虚假网络
  3. 由于支持 1. 和 2. 的实现,GOOS=tamago 允许将未修改的 Go 应用程序作为软隔离的 用户空间代码 与 OS 资源一起执行,例如网络和文件系统,与实际用户 OS 隔离。
  4. TamaGo 现在不再只关注 ARM 嵌入式系统,而是扩展到 AMD64 KVM 执行,例如 microVMs
  5. 总体 Go 发行版更改不包含硬件相关代码(例如,外围设备驱动程序),并且更改在不同的架构之间是统一的,例如,参见 amd64, arm, riscv64 架构的入口点的相同实现。

总之,GOOS=tamago 已被转换为通用实现,该实现允许执行而无需依赖操作系统调用,而是使用 统一的“外部世界”退出接口,无论它是为 测试用户空间执行真实硬件 还是 半虚拟化 实现的。

在 ARM 上,我们使用此框架实现了 引导加载程序Trusted Execution Environments 和完整的安全 OS 和 applet

由于最近的 amd64 移植,我们启用了 Cloud HypervisorFirecrackerQEMU 下的 Pure Go KVM。

这也使我们能够实现 UEFI 下的执行,从而启用了 100% Go EFI 应用程序和引导加载程序,例如 go-boot(目前已从我正在编写的 Thinkpad 上启动了 Linux)。

我们认为,采用这些紧凑的 Go 发行版更改不仅可以保留这个生态系统,而且可以扩展和创新整个 Go 语言,将其带到令人惊讶的,意想不到的,但理想的环境中。

我们认为维护该补丁的成本是合理的,并且在改善 Go 在架构和 OS 特定组件之间的抽象方面是有益的。

在主要的 Go 版本之间,唯一有些底层且敏感的组件是我们的异步 goroutine 唤醒功能,用于服务外部中断请求。

但是,可以以与现有 OS 信令一致的方式重新实现此功能,或者,仅需意识到并包含 GOOS=none,将其挂接到计时器结构中一个更简单标准化的接口,到目前为止,尚未纯粹为了避免污染非 tamago 架构而实现。