Proposal: Add bare metal support to Go
提议:为 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
(示例): 运行时前的 CPU 初始化
// cpuinit handles pre-runtime CPU initialization
TEXT cpuinit(SB),NOSPLIT|NOFRAME,$0
该函数需要在汇编中定义,因为在运行时前缺乏对原生 Go 语句的支持。
runtime.hwinit
(示例): 早期运行时硬件初始化
// Init takes care of the lower level initialization triggered early in runtime
// setup.
//
//go:linkname Init runtime.hwinit
func Init()
runtime.printk
(示例): 标准输出(例如,串行控制台)
// printk emits a single 8-bit character to standard output
//
//go:linkname printk runtime.printk
func printk(c byte)
runtime.initRNG
和runtime.getRandomData
(示例): 随机数生成初始化和检索
// 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)
runtime.nanotime1
(示例): 系统时间,以纳秒为单位
// 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
板级支持包或应用程序可以选择性地定义以下内容:
runtime.Bloc
(示例): 堆内存起始地址覆盖runtime.Exit
(示例): 运行时终止runtime.Idle
(示例): CPU 空闲时间管理- 通过 Go 自身的 net 包进行的网络 I/O 需要应用程序设置一个外部
Socket
函数 (示例, 驱动程序示例):
// 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)已经尝试过但没有成功,但这次努力是受到我们努力的巨大进步和变化的推动。
显著变化:
- 现在有完全测试的 Go 标准库支持,与 vanilla 发行版测试 集成。 AMD64, ARM 和 RISCV64 架构的测试环境 在 Linux 下本地运行,或者通过 binfmt_misc 使用 qemu-user-static。
- tamago 网络代码 允许外部定义单个 socket 函数 来附加 gVisor 或任何其他虚假网络栈 (fake networking stack),如果需要的话,这可以使其他 Go 架构受益,并允许替换现有的 js/wasip1 的虚假网络。
- 由于支持 1. 和 2. 的实现,
GOOS=tamago
允许将未修改的 Go 应用程序作为软隔离的 用户空间代码 与 OS 资源一起执行,例如网络和文件系统,与实际用户 OS 隔离。 - TamaGo 现在不再只关注 ARM 嵌入式系统,而是扩展到 AMD64 KVM 执行,例如 microVMs。
- 总体 Go 发行版更改不包含硬件相关代码(例如,外围设备驱动程序),并且更改在不同的架构之间是统一的,例如,参见 amd64, arm, riscv64 架构的入口点的相同实现。
总之,GOOS=tamago
已被转换为通用实现,该实现允许执行而无需依赖操作系统调用,而是使用 统一的“外部世界”退出接口,无论它是为 测试,用户空间执行,真实硬件 还是 半虚拟化 实现的。
在 ARM 上,我们使用此框架实现了 引导加载程序,Trusted Execution Environments 和完整的安全 OS 和 applet。
由于最近的 amd64 移植,我们启用了 Cloud Hypervisor,Firecracker 和 QEMU 下的 Pure Go KVM。
这也使我们能够实现 UEFI 下的执行,从而启用了 100% Go EFI 应用程序和引导加载程序,例如 go-boot(目前已从我正在编写的 Thinkpad 上启动了 Linux)。
我们认为,采用这些紧凑的 Go 发行版更改不仅可以保留这个生态系统,而且可以扩展和创新整个 Go 语言,将其带到令人惊讶的,意想不到的,但理想的环境中。
我们认为维护该补丁的成本是合理的,并且在改善 Go 在架构和 OS 特定组件之间的抽象方面是有益的。
在主要的 Go 版本之间,唯一有些底层且敏感的组件是我们的异步 goroutine 唤醒功能,用于服务外部中断请求。
但是,可以以与现有 OS 信令一致的方式重新实现此功能,或者,仅需意识到并包含 GOOS=none
,将其挂接到计时器结构中一个更简单标准化的接口,到目前为止,尚未纯粹为了避免污染非 tamago 架构而实现。