Golang程序的入口点和启动代码详解
Golang程序的入口点和启动代码详解
我是一名C程序员,了解C程序是如何编译并链接成可执行文件的。例如,在Windows和MSVC中,有一个mainCRTStartup函数,它是可执行文件的入口点。这个函数位于C运行时库中,它负责准备argc和argv,然后调用main函数。
对于Go语言,我不太清楚Go程序是如何编译并链接成可执行文件的。Go程序的入口点是什么?入口或启动函数做了什么?启动代码位于哪里?有人能给我一些启发或指出相关的资源吗?非常感谢!
我认为程一平是在询问Windows下Go程序的实际加载过程。
当你运行 main 包时,默认会执行一个 main 函数,例如,在 main 包中包含你的 main() 函数的文件,例如 main.go:
package main
func main() {
// 一切从这里开始...
}
要编译并运行你的应用程序: #: go run main.go 要编译并将你的应用程序构建为可执行文件: #: go build main.go
如果你的 main.go 使用了同一目录下的其他 Go 文件,你需要使用“go run .”。作为入口点,只能有一个 main() 函数。
main() 函数是你的启动函数,它只执行你指定的操作,这意味着如果需要,你需要在此处自行读取和准备参数。还有一个可选的 init() 函数,它会在 main() 之前被调用。
在Go语言中,程序的入口点不是由运行时库提供的,而是由Go工具链直接生成的。Go程序的入口点位于Go运行时内部,具体是runtime包中的rt0_*.s文件(汇编文件),根据目标平台不同而有所差异。
入口点位置
Go程序的入口点通常命名为_rt0_<arch>(例如_rt0_amd64_linux),它位于Go运行时库的汇编启动文件中。这些文件在Go源码树的src/runtime目录下,例如:
- Linux x86-64:
src/runtime/rt0_linux_amd64.s - Windows x86-64:
src/runtime/rt0_windows_amd64.s - macOS ARM64:
src/runtime/rt0_darwin_arm64.s
启动流程
入口点会执行以下关键步骤:
- 初始化运行时:调用
runtime·args和runtime·osinit设置命令行参数和操作系统相关初始化。 - 调度器初始化:调用
runtime·schedinit初始化Go调度器、内存分配器等核心组件。 - 创建主goroutine:通过
runtime·newproc创建执行main.main的goroutine。 - 启动调度器:调用
runtime·mstart开始调度,最终执行main.main。
代码示例
以下是一个简化的Linux x86-64入口点汇编代码(rt0_linux_amd64.s):
TEXT _rt0_amd64_linux(SB),NOSPLIT,$-8
MOVQ 0(SP), DI // argc
LEAQ 8(SP), SI // argv
JMP runtime·rt0_go(SB)
TEXT runtime·rt0_go(SB),NOSPLIT,$0
// 保存argc和argv
MOVQ DI, AX // argc
MOVQ SI, BX // argv
// 调用运行时初始化
CALL runtime·args(SB)
CALL runtime·osinit(SB)
CALL runtime·schedinit(SB)
// 创建main goroutine
MOVQ $runtime·mainPC(SB), AX
PUSHQ AX
CALL runtime·newproc(SB)
POPQ AX
// 启动调度器
CALL runtime·mstart(SB)
RET
其中runtime·mainPC指向runtime.main函数,该函数会进一步调用用户定义的main.main。
用户层视角
从用户代码角度看,程序从main包的main函数开始:
package main
func main() {
// 用户代码
}
但实际执行前,Go运行时已经完成了上述初始化工作。
编译链接
Go工具链(如go build)会直接将运行时和用户代码静态链接为一个独立的可执行文件,无需外部C运行时。可以通过go tool nm查看可执行文件的符号,找到入口点:
go tool nm ./program | grep '_rt0_'
总结:Go程序的入口点由运行时汇编代码定义,负责初始化Go运行时环境,最终调用用户编写的main.main函数。这种设计使得Go程序不依赖外部运行时库,生成独立的可执行文件。


