Go Runtime(一):运行时架构与程序启动
本文源码基线为 Go 1.26.2、Linux/amd64。源码链接固定到 go1.26.2 tag;其他操作系统、架构和构建模式的入口汇编会不同。 一个 Go 程序可以只有几行代码,但它的第一条指令并不属于 main.main。在用户函数开始前,可执行文件必须先建立线程本地状态、调度资源、栈和堆等运行环境。 本文先从 ELF 入口这个可观察事实出发,再沿 Linux/amd64 启动链追踪到 main.main。重点不是背诵初始化函数,而是回答:每一步补上了什么运行前提,编译器、链接器、runtime 和操作系统各自负责哪一段。 一个最小Go程序 以下程序表面上只有 goroutine、Channel 和一次函数返回: GOpackage main func main() { ch := make(chan int) go func() { ch <- 1 }() println(<-ch) } 这段程序的启动和运行依次经过编译、链接与操作系统装载。执行期间,runtime 把操作系统提供的线程、虚拟内存、信号和 I/O 事件转换成 Go 的调度、栈、堆和阻塞语义: TEXTGo源码 -> 编译器:机器码 + 类型/栈图/init task 等元数据 -> 链接器:用户包 + 标准库 + runtime -> ELF可执行文件 -> OS loader:建立初始线程和进程地址空间,跳到ELF入口 -> runtime启动:rt0 -> schedinit -> runtime.main | +-- 执行:G / M / P / runq +-- 栈:stack check / morestack / copystack +-- 内存:mcache / mcentral / mheap / GC +-- 等待:gopark / goready / timer / netpoll / syscall -> package init -> main.main 这里没有一个独立安装、解释字节码的“Go 虚拟机”:CPU 执行的是提前编译并链接好的本地机器码,runtime 也是可执行文件的一部分。标准库提供稳定 API,并在内部接入 runtime,例如 net.Conn 通过 internal/poll 使用 netpoll,sync 通过 runtime 等待设施挂起和唤醒 G。 ...