开发者

图文详解Go程序如何编译并运行起来的

开发者 https://www.devze.com 2024-08-15 11:20 出处:网络 作者: RdrB1te
目录Go程序是如何编译的从hello RdrB1te开始Go 编译过程Go程序是如何运行起来的Go程序的入口?总结Go程序是如何编译的
目录
  • Go程序是如何编译的
    • 从hello RdrB1te开始
    • Go 编译过程
  • Go程序是如何运行起来的
    • Go程序的入口?
  • 总结

    Go程序是如何编译的

    从hello RdrB1te开始

    package main  
      
    import "fmt"  
      
    func main() {  
       fmt.Println("hello RdrB1te")  
    }
    

    不实际编译它,只输出它的编译过程:

    go build -n
    

    简单的编译过程分析:

    图文详解Go程序如何编译并运行起来的

    上面的过程确认了两个事情:

    • Runtime会永远随着用户代码一起编译
    • 在Windows平台上编译出来了一个exe的可执行文件

    Go 编译过程

    图文详解Go程序如何编译并运行起来的

    词法分析

    • 将源代码翻译成Token
    • Token是代码中的最小语义结构(如变量名、关键字、运算符等不可拆分的最小单元)

    句法分析

    • Token序列经过处理,变成语法树

    语义分析

    • 类型检查
    • 类型推断
    • 查看类型是否匹配
    • 函数调用内联
    • 逃逸分析

    中间码生成:

    • 为了处理不同平台的差异,先生成中间代码(SSA)

    查看从代码到中间码(SSA)生成的整个过程

    $env:GOSSAFUNC="main" # windows PowerShell
    export GOSSAFUNC=main # linu编程x
    go build
    

    会看到如下输出:

    图文详解Go程序如何编译并运行起来的

    用浏览器打开ssa.html文件:

    图文详解Go程序如何编译并运行起来的

    sources就是你的源代码,AST就是生成的语法树,genssa就是生成的与平台无关的中间码SSA,当然中间还有很多的其它步骤,这里不再列举,可以点击展开查看

    机器码生成:

    • 先生成Plan9汇编代码(与平台相关)
    • 最后编译为机器码
    • 输出的机器码为.a文件

    查看Plan9汇编代码

    go build -gcflags -S main.go
    

    链接:

    • 将各个包进行链接,包括runtime,最终生成可执行文件

    Go程序是如何运行起来的

    Go程序的入口?

    是下面的main方法吗?当然不是

    func main() {  
       fmt.Println("hello RdrB1te")  
    }
    

    是runtime包下面的rt0_xxx.s文件,下面以linux x86芯片架构上面运行的rt0_linux-amd64.s举例:

    TEXT _rt0_amd64_linux(SB),NOSPLIT,$-8
    	JMP	_rt0_amd64(SB)
    

    只要用了x86芯片架构都要进入到_rt0_amd64这个方法中去,这个方法调到了哪里呢,选中双击shift,打开在文件中查找:找到下面这行

    图文详解Go程序如何编译并运行起来的

    asm_amd64.s这个文件中的这段代码:

    TEXT _rt0_amd64(SB),NOSPLIT,$-8  
       MOVQ   0(SP), DI  // argc  
       LEAQ   8(SP), SI  // argv  
       JMP    runtimert0_go(SB)
    

    意思是读取命令行参数,复制参数数量argc和参数值argv到栈上,然后调用了http://www.devze.comruntime·rt0_go这个方法,这方法的位置就在这个文件的下面:

    TEXT runtimert0_go(SB),NOSPLIT|TOPFRAME,$0  
       // copy arguments forward on an even stack  
       MOVQ   DI, AX    // argc  
       MOVQ   SI, BX    // argv  
       SUBQ   $(5*8), SP    // 3args 2auto  
       ANDQ   $~15, SP  
       MOVQ   AX, 24(SP)  
       MOVQ   BX, 32(SP)  
      
       // create istack out of the given (operating system) stack.  
     js  // _cgo_init may update stackguard.   MOVQ   $runtimeg0(SB), DI
    

    上面这段的意思时初始化g0执行栈,g0是为了调度协程而产生的协程,g0是每个Go程序的第一个协程。继续往下面看,找到下面这段:

    	CALL	runtimecheck(SB)
    

    这行是第一次调用的go语言方法,要找到这个方法可以选中双击shift,找到下面这行:

    图文详解Go程序如何编译并运行起来的

    进入:

    func check(){
    
    }
    

    check方法主要是做运行时检测:

    • 检查各种类型的长度
    • 检查指针操作
    • 检查结构体字段的偏移量
    • 检查atomic原子操作
    • 检查CAS操作
    • 检查栈大小是否是2的幂次

    继续往下看,可以通过Ctrl+Alt+左右箭头进行快速跳转回退或前进,退到这个位置:

    CALL    runtimecheck(SB)  
      
    MOVL   24(SP), AX    // copy argc  
    MOVL   AX, 0(SP)  
    MOVQ   32(SP), AX    // copy argv  
    MOVQ   AX, 8(SP)  
    CALL   runtimeargs(SB)  
    CALL   runtimeosinit(SB)  
    CALL   runtimeschedinit(SB)  
      
    // create a new goroutine to start program  
    MOVQ   $runtimemainPC(SB), AX       // entry  
    PUSHQ  AX  
    CALL   runtimenewproc(SB)  
    POPQ   AX
    

    runtime·args(SB):参数初始化runtime.args,对命令行中的参数进行处理,参数数量赋值给argc int32,参数值复制给argv **byteruntime·osinit:判断操作系统,执行相应的初始化组件,供调度器初始化所用

    runtime·schedinit: 初始化Go调度器。初始化调度器会做哪些事情:

    • 全局栈空间内存分配
    • 加载命令行参数到 os.Args
    • 堆内存空间的初始化
    • 加载操作系统环境变量
    • 初始化当前系统线程
    • 垃圾回收器的参数初始化
    • 算法初始化(ma编程客栈p、hash)
    • 设置 process 数量

    继续往下看:

        // create a new goroutine to start program  
       MOVQ   $runtimemainPC(SB), AX       // entry  
       PUSHQ  AX  
       CALL   runtimenewproc(SB)  
       POPQ   AX  
      
       // start this M  
       CALL   runtimemstart(SB)  
      
       CALL   runtimeabort(SB)  // mstart should never return  
       RET  
      
    // mainPC is a function value for runtime.main, to be passed to newproc.  
    // The reference to runtime.main is made via ABIInternal, since the  
    // actual function (not the ABI0 wrapper) is needed by newproc.  
    DATA   runtimemainPC+0(SB)/8,$runtimemain<ABIInternal>(SB)
    

    MOVQ $runtime·mainPC(SB):取mainPC的地址,这个mainPC的地址就是runtime·main这个方法的地址

    CALL runtime·newproc:创建一个新的协程(主协程),执行runtime·main这个方法(主函数),放入调度器等待调度

    CALL runtime·mstart(SB):初始化一个M,用来调度主协程,主协程开始执行主函数。

    看下runtime·main这个方法里面干了什么,选中双击shift,找到下面这行:

    图文详解Go程序如何编译并运行起来的

    进入:

    // The main goroutine.
    func main() {  
       doInit(&runtime_inittask) // 执行runtime包中的init方法
       gcenable() // 启动GC垃圾回收器
       doInit(&main_inittask) //执行用户包依赖的init方法
       fn := main_main // 执行用户主函数main.mian() 
       fn()
    }
    

    按住ctrl进入main_main:

    //go:linkname main_main main.main
    func mai编程n_main()
    

    主协程执行主函数:

    • 执行runtime包中的init方法
    • 启动GC垃圾回收器
    • 执行用户包依赖的init方法
    • 执行用户主函数main.mian()

    总结

    • Go启动时经历了检查、各种初始化、初始化协程调度的过程
    • main.main()也是在协程中运行的

    到此这篇关于Go程序如何编译并运行起来的文章就介绍到这了,更多相关Go编译运行内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!

    0

    精彩评论

    暂无评论...
    验证码 换一张
    取 消

    关注公众号