当前位置: 代码网 > it编程>前端脚本>Golang > 图文详解Go程序如何编译并运行起来的

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

2024年06月13日 Golang 我要评论
go程序是如何编译的从hello rdrb1te开始package main import "fmt" func main() { fmt.println("hello rdrb1

go程序是如何编译的

从hello rdrb1te开始

package main  
  
import "fmt"  
  
func main() {  
   fmt.println("hello rdrb1te")  
}

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

go build -n

简单的编译过程分析:

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

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

go 编译过程

词法分析

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

句法分析

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

语义分析

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

中间码生成:

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

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

$env:gossafunc="main" # windows powershell
export gossafunc=main # linux
go build

会看到如下输出:

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

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,打开在文件中查找:找到下面这行

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

text _rt0_amd64(sb),nosplit,$-8  
   movq   0(sp), di  // argc  
   leaq   8(sp), si  // argv  
   jmp    runtime·rt0_go(sb)

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

text runtime·rt0_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.  
   // _cgo_init may update stackguard.   movq   $runtime·g0(sb), di

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

	call	runtime·check(sb)

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

进入:

func check(){

}

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

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

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

call    runtime·check(sb)  
  
movl   24(sp), ax    // copy argc  
movl   ax, 0(sp)  
movq   32(sp), ax    // copy argv  
movq   ax, 8(sp)  
call   runtime·args(sb)  
call   runtime·osinit(sb)  
call   runtime·schedinit(sb)  
  
// create a new goroutine to start program  
movq   $runtime·mainpc(sb), ax       // entry  
pushq  ax  
call   runtime·newproc(sb)  
popq   ax

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

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

继续往下看:

    // create a new goroutine to start program  
   movq   $runtime·mainpc(sb), ax       // entry  
   pushq  ax  
   call   runtime·newproc(sb)  
   popq   ax  
  
   // start this m  
   call   runtime·mstart(sb)  
  
   call   runtime·abort(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   runtime·mainpc+0(sb)/8,$runtime·main<abiinternal>(sb)

movq $runtime·mainpc(sb):取mainpc的地址,这个mainpc的地址就是runtime·main这个方法的地址
call runtime·newproc:创建一个新的协程(主协程),执行runtime·main这个方法(主函数),放入调度器等待调度
call runtime·mstart(sb):初始化一个m,用来调度主协程,主协程开始执行主函数。

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

进入:

// 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 main_main()

主协程执行主函数:

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

总结

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

到此这篇关于go程序如何编译并运行起来的文章就介绍到这了,更多相关go编译运行内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com