聊聊Go程序是如何运行的

写在文章开头

Go语言是一门编译语言,其工作过程即直接通过编译生成各大操作系统的机器码即可直接执行,所以这篇文章笔者就从底层汇编码的角度聊一聊Go语言是如何运行的。

在这里插入图片描述

Hi,我是 sharkChili ,是个不断在硬核技术上作死的 java coder ,是 CSDN的博客专家 ,也是开源项目 Java Guide 的维护者之一,熟悉 Java 也会一点 Go ,偶尔也会在 C源码 边缘徘徊。写过很多有意思的技术博客,也还在研究并输出技术的路上,希望我的文章对你有帮助,非常欢迎你关注我的公众号: 写代码的SharkChili

因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 “加群” 即可和笔者和笔者的朋友们进行深入交流。

在这里插入图片描述

go语言代码执行详解

最终执行代码

我们首先在goland上创建一个名为main.go的文件,代码格式指明未main包下的main方法,当go语言完成编译并启动后,就会执行到这段代码:

package mainimport ("fmt"
)func main() {fmt.Println("hello Go")
}

入口跳转

go语言是跨平台的语言,所以底层对各大平台的启动都做了特定的封装,以笔者的windows系统为例,其执行入口为rt0_windows_amd64.s,同理Linux系统则是rt0_linux_amd64.s,可以看到在任何平台它们都会通过汇编指令JMP跳转到_rt0_amd64方法:

//windows的入口代码_rt0_amd64_windows
TEXT _rt0_amd64_windows(SB),NOSPLIT,$-8JMP	_rt0_amd64(SB)//Linux入口代码_rt0_amd64_linux
TEXT _rt0_amd64_linux(SB),NOSPLIT,$-8JMP	_rt0_amd64(SB)

拷贝参数,启动协程

通过全局搜索看到这个方法的实现,这段代码会通过MOVQ将参数的数量argc拷贝到目标寄存器DI上,然后再通过LEAQ计算所有参数argv的偏移量地址并存储到SI上,然后再次跳转到runtime·rt0_go方法准备利用上述寄存的参数完成g0协程初始化。


TEXT _rt0_amd64(SB),NOSPLIT,$-8MOVQ	0(SP), DI	// argcLEAQ	8(SP), SI	// argvJMP	runtime·rt0_go(SB)

启动g0运行main方法

因为runtime·rt0_go也是汇编方法,所以全局搜索后我们再次定位到该方法的实现,在这里笔者给出几个核心步骤:

  1. 将参数入栈,用于后续的各种初始化和启动操作所用。
  2. 调用check进行程序启动前必要的检查操作。
  3. 拷贝上述入栈的参数进行系统参数初始化。
  4. 初始化全局调度器。
  5. 创建协程g0等待线程绑定并运行main方法。
  6. 启动线程即M绑定协程g0,执行main方法。

对应核心代码如下:


TEXT runtime·rt0_go(SB),NOSPLIT|TOPFRAME,$0// 参数入栈MOVQ	DI, AX		// argcMOVQ	SI, BX		// argvSUBQ	$(5*8), SP		// 3args 2autoANDQ	$~15, SPMOVQ	AX, 24(SP)MOVQ	BX, 32(SP)//......// 初始化协程g0栈等信息MOVQ	$runtime·g0(SB), DILEAQ	(-64*1024+104)(SP), BXMOVQ	BX, g_stackguard0(DI)MOVQ	BX, g_stackguard1(DI)MOVQ	BX, (g_stack+stack_lo)(DI)MOVQ	SP, (g_stack+stack_hi)(DI)//......//调用check进行运行时检查CALL	runtime·check(SB)//......//拷贝argc和argvMOVL	24(SP), AX		// copy argcMOVL	AX, 0(SP)MOVQ	32(SP), AX		// copy argvMOVQ	AX, 8(SP)CALL	runtime·args(SB)//完成系统参数初始化,例如系统字长,CPU核心数等信息初始化CALL	runtime·osinit(SB)//初始化调度器CALL	runtime·schedinit(SB)// runtime·mainPC代表我们程序执行的main函数的地址值,下述方法会通过创建一个协程g0来调用这个方法MOVQ	$runtime·mainPC(SB), AX		// entryPUSHQ	AXCALL	runtime·newproc(SB)POPQ	AX// 启动一个线程绑定上述的协程,自此调度器开始工作直接执行main方法CALL	runtime·mstart(SB)CALL	runtime·abort(SB)	// mstart should never returnRET

我们先来看看运行时检查的步骤,这段代码在runtime1.go上,从笔者贴出的代码不难看出,这个方法会在程序运行进行类型长度、CAS、指针、原子类的进行正确性的检查操作。

func check() {//......if unsafe.Sizeof(a) != 1 {throw("bad a")}//......if timediv(12345*1000000000+54321, 1000000000, &e) != 12345 || e != 54321 {throw("bad timediv")}//......if !atomic.Cas(&z, 1, 2) {throw("cas1")}//......m = [4]byte{1, 1, 1, 1}atomic.Or8(&m[1], 0xf0)if m[0] != 1 || m[1] != 0xf1 || m[2] != 1 || m[3] != 1 {throw("atomicor8")}//......*(*uint64)(unsafe.Pointer(&j)) = ^uint64(0)if j == j {throw("float64nan")}if !(j != j) {throw("float64nan1")}//......if _FixedStack != round2(_FixedStack) {throw("FixedStack is not power-of-2")}if !checkASM() {throw("assembly checks failed")}
}

检查之后就是osinit,它会获取当前操作系统核心数、系统字长等基本信息:

func osinit() {asmstdcallAddr = unsafe.Pointer(abi.FuncPCABI0(asmstdcall))setBadSignalMsg()loadOptionalSyscalls()disableWER()initExceptionHandler()initHighResTimer()timeBeginPeriodRetValue = osRelax(false)initLongPathSupport()ncpu = getproccount()physPageSize = getPageSize()// Windows dynamic priority boosting assumes that a process has different types// of dedicated threads -- GUI, IO, computational, etc. Go processes use// equivalent threads that all do a mix of GUI, IO, computations, etc.// In such context dynamic priority boosting does nothing but harm, so we turn it off.stdcall2(_SetProcessPriorityBoost, currentProcess, 1)
}

完成检查后就调用schedinit进行调度器初始化,从各个函数的语义即可知晓它会进行一次STW然后进行堆栈、cpu、环境变量、垃圾回收器等各个信息的初始化:

func schedinit() {// The world starts stopped.worldStopped()moduledataverify()stackinit()mallocinit()godebug := getGodebugEarly()initPageTrace(godebug) // must run after mallocinit but before anything allocatescpuinit(godebug)       // must run before alginitgoargs()goenvs()secure()parsedebugvars()gcinit()//......
}

最终就是创建一个线程绑定协程g0,调用$runtime·mainPC(SB)从而拿到g0协程的main方法,最终定位到我们实现的main包下的main方法main_main,这一点我们可以定位main_main的注释知晓(go:linkname main_main main.main),这个main_main指向的是被链接的main包下的main方法,也就是我们编写入口代码:

// The main goroutine.
func main() {//我们实现的main包下的main方法fn := main_main // make an indirect call, as the linker doesn't know the address of the main package when laying down the runtimefn()}

以终为始印证观点

基于文章开头给出的代码断点,通过堆栈调用的信息可以看到,g0的main方法确实通过main_main定位我们编写的main方法并完成执行:

在这里插入图片描述

小结

碍于篇幅等原因,笔者对go程序的运行仅做了简单的介绍,总体来说go程序运行大体分为以下几个步骤:

  1. 参考拷贝并入栈
  2. 类型检查
  3. 系统信息初始化
  4. 主调度器初始化
  5. 创建协程g0分配main方法的调用
  6. 创建线程绑定g0
  7. 通过协程g0的main方法调用我们的main方法,程序启动并运行

我是 sharkchiliCSDN Java 领域博客专家开源项目—JavaGuide contributor,我想写一些有意思的东西,希望对你有帮助,如果你想实时收到我写的硬核的文章也欢迎你关注我的公众号: 写代码的SharkChili
因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 “加群” 即可和笔者和笔者的朋友们进行深入交流。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/755057.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

IntelliJ IDEA 设置运行时环境变量

背景 博主要测试langchain4j,运行时需要OPENAI_BASE_URL和OPENAI_API_KEY这两个环境变量的值。 临时设置 Run -> Edit Configurations -> Edit Environmental Variables 永久设置 在系统环境变量中设置,教程无数。 注意:windows在…

考研机试题

目录 头文件与STL动态规划最大数组子串和最长公共子序列最长连续公共子串最长递增子序列最大上升子序列和0-1背包多重背包多重背包问题 I整数拆分最小邮票最大子矩阵 数学问题朴素法筛素数线性筛素数快速幂 石子合并锯木棍并查集Dijkstra单源最短路Python进制转换(整数无限大)全…

MATLAB教程

目录 前言一、MATLAB基本操作1.1 界面简介1.2 搜索路径1.3 交互式命令操作1.4 帮助系统 二、MATLAB语言基础2.1 数据类型2.2 MATLAB运算2.2.1 算数运算2.2.2 关系运算2.2.3 逻辑运算 2.3 常用内部函数2.4 结构数据与单元数据 三、MATLAB程序设计3.1 M文件3.2 函数文件3.3 程序控…

c#接口 axios的get请求url过长时该怎么做

今天又碰到了,大托参数拼在url里用get传 1、改服务器最大字数限制(还是会错) 2、改post(有些还要跟着把[FromUri]改成[FromBody])

Dubbo如何支持集群容错?有哪些集群容错模式?Dubbo的路由策略是怎样的?如何根据路由规则选择服务提供者?

Dubbo如何支持集群容错?有哪些集群容错模式? Dubbo通过ClusterInvoker接口和相关的实现类来支持集群容错。ClusterInvoker在原有的Invoker基础上增加了集群容错的能力,使得在分布式调用过程中,当某个服务提供者出现故障或不可用时…

Android 性能优化——APP启动优化

一、APP启动流程 首先在《Android系统和APP启动流程》中我们介绍了 APP 的启动流程,但都是 FW 层的流程,这里我们主要分析一下在 APP 中的启动流程。要了解 APP 层的启动流程,首先要了解 APP 启动的分类。 1、启动分类 冷启动 应用从头开始…

windows查看局域网内所有已使用的IP IP扫描工具 扫描网段下所有的IP Windows环境下

推荐使用: Advanced IP Scanner 官网下载: https://www.advanced-ip-scanner.com/

sqllab第35-45关通关笔记

35关知识点: 宽字节注入数值型注入错误注入 payload:id1andextractvalue(1,concat(0x7e,database(),0x7e))0--联合注入 payload:id0unionselect1,database(),version()-- 36关知识点: 字符型注入宽字节注入错误注入 payload:id1%df%27andextractvalue(…

广州大彩科技新品发布:大彩科技COF系列2.4寸串口屏发布!

一、产品介绍 此次发布的是S系列平台2.4寸COF超薄结构串口屏,分辨率为240*320,该平台采用了Cortex-M3内核的处理器,内置了2Mbyte PSRAM和64Mbit FLASH,是专为小尺寸串口屏设计的MCU,精简了外围电路。 该平台默认支持大…

Docker 各种部署应用的详细参数

持续更新 Docker 部署pgsql命令 docker run -d \ --restartalways \ --name pgsql \ -v /data/apps/pgsql/data:/var/lib/postgresql/data \ -e POSTGRES_PASSWORDabc123 \ -p 15432:5432 postgres:14.10

开发常用的一些工具总结

开发常用的一些工具总结 记录一些常用的开发软件. Android 开发相关 : Android studio 安卓开发者必备的编辑器,也是我用过最好用的编辑器.还可以用来写JNI 和C.Android studio 插件 : GsonFormatLeakCanary 其他 VS Code :轻量级的开发工具,插件非常多,很好用,但是上手难度…

React+umi+dva 项⽬实战-lesson6

lesson4-react全家桶及原理解析.mov 项⽬实战 项⽬实战 课堂⽬标资源知识要点起步Generatorredux-sagaumi why umidvadva+umi 的约定安装Umi基本使⽤理解dva移动端cra项⽬简介课堂⽬标 掌握企业级应⽤框架 - umi掌握数据流⽅案 - dva掌握⽣成器函数 - generator掌握redux异步⽅…

青海200MW光伏项目 35kV开关站图像监控及安全警示系统

一、背景 随着我国新能源产业的快速发展,光伏发电作为清洁能源的重要组成部分,得到了国家政策的大力扶持。青海作为我国光伏资源丰富地区,吸引了众多光伏项目的投资建设。在此背景下,为提高光伏发电项目的运行效率和安全性能&…

【C++】堆区空间的申请和释放--- 2024.3.19

目录 C和C的区别(申请堆区空间)C中的new和delete结束语 C和C的区别(申请堆区空间) 在c语言中,在遇到需要申请一块堆区空间时,我们往往会使用malloc申请,使用free进行释放,但是为什么…

数据可信流通:从运维信任到技术信任

1.数据可信流通概念 "数据可信流通"通常指的是确保数据在不同系统、应用程序或者组织之间的传输和交换过程中的可信性、完整性和安全性。在数据流通的过程中,确保数据的真实性、完整性和保密性是非常重要的,尤其是涉及到敏感信息或者重要数据…

GateWay路由规则

Spring Cloud GateWay 帮我们内置了很多 Predicates功能,实现了各种路由匹配规 则(通过 Header、请求参数等作为条件)匹配到对应的路由 1 时间点后匹配 server:port: 8888 spring:application:name: gateway-servicecloud:nacos:discovery:…

使用verilog写一个模拟比特币挖矿游戏及testbench

设计模拟比特币挖矿游戏需要考虑到以下几个方面: 游戏目标和规则: 确定游戏的目标,例如挖取尽可能多的比特币或达到一定的挖矿目标。确定游戏的规则,例如通过计算难题来进行挖矿、使用特定的硬件设备等。确定状态及状态转移条件: 确定游戏中可能存在的状态,如等待开始、准…

Vue利用axios发送请求并代理请求

由于浏览器的同源策略,发送请求时常常遇到跨域问题,一种解决办法是让后端配置跨域,还有一种就是使用代理(与前端工程一起启动,同一个端口),因为代理不是通过浏览器发送的,所以不受同…

蓝桥杯历年真题省赛java b组 2017年第八届 k倍区间

一、题目 k倍区间 给定一个长度为N的数列&#xff0c;A1, A2, ... AN&#xff0c;如果其中一段连续的子序列Ai, Ai1, ... Aj(i < j)之和是K的倍数&#xff0c;我们就称这个区间[i, j]是K倍区间。 你能求出数列中总共有多少个K倍区间吗&#xff1f; 输入 ----- 第一…

rust - 计算文件的md5和sha1值

本文提供了一种计算文件md5和sha1的方法。 添加依赖 cargo add file-hashing cargo add md-5 cargo add sha1添加功能函数 use file_hashing::get_hash_file; use md5::Md5; use sha1::{Digest, Sha1}; use std::io::Error; use std::path::Path;pub fn md5<P: AsRef<…