聊聊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在…

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])

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,精简了外围电路。 该平台默认支持大…

青海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:…

mysql虚拟列Generated Column

目录​​​​​​​ 1、Generated Column简介 生成的列定义具有以下语法: 2、实践 2.1 存储格式为json字段增加索引 2.2 手机号后四位 3、虚拟列索引介绍 3.1 虚拟列索引的限制 3.1.1 Virtal Generated Column 4、阿里云数据库环境是否支持 下期扩展&…

大话适航(一)民航产业

0. 前言 eVTOL、飞行汽车和低空经济已成为热门话题,政府引导资本投入新赛道,也势必会吸引跨界厂商前来淘金。只说民用航空器整机制造,技术最接近的行业是军工,然后是无人机,还有汽车、农业机械等。“互联网”曾经掀起…

数据库应用:Linux 部署 GaussDB

目录 一、实验 1.环境 2.Linux 部署 GaussDB 3.Linux 使用 GaussDB 4.使用 GaussDB 进行表与索引操作 5.使用 GaussDB 进行视图操作 6.使用 GaussDB 进行联表查询 7.使用 GaussDB 进行外键关联 二、问题 1.运行python脚本报错 2. 安装GaussDB 报错 3. install 安装…

语音识别:whisper部署服务器,可远程访问,实时语音转文字(全部代码和详细部署步骤)

Whisper是OpenAI于2022年发布的一个开源深度学习模型,专门用于语音识别任务。它能够将音频转换成文字,支持多种语言的识别,包括但不限于英语、中文、西班牙语等。Whisper模型的特点是它在多种不同的音频条件下(如不同的背景噪声水…

[C语言]指针详解一、数组指针、二维数组传参、函数指针

一、数组指针 对一个数组&#xff0c;如果我们想要让一个指针指向这个数组&#xff0c;我们应该如何定义呢?我们知道一个数组定义本来就是一个指针&#xff0c;那为何要多定义一个数组指针呢?我们来看看下面这个代码就理解了 #include <stdio.h> int main() {int arr…

【基础】哪个厂家的零件更标准?

时间限制 : 1 秒 内存限制 : 128 MB 在统计描述中,方差用来计算每一个变量(观察值)与总体均数之间的差异。比如:甲乙 2 个厂商生产某零件,一批零件要求在尺寸合格的情况下,大小越一致越好,由于生产工艺的问题,零件生产厂商生产的零件不可能一模一样。 为了检测甲乙两…

AIX系统下挂载ISO镜像

我们需要将AIX的iso文件作为软件包的安装源挂载的系统目录中 首先我们查看系统下有哪些挂载文件 如何挂载一个系统iso镜像文件 loopmount -i /ftp/iso/LK4T_1807_11.iso -o "-V cdrfs -o ro " -m /mnt/iso 需要安装软件直接执行smit就可以了&#xff0c;在smit中…

phpStudy安装thinkCMF8时,如何解决服务器rewrite和APIrewrite不支持的问题

解决步骤&#xff1a; 一&#xff1a;服务器rewrite 点击后面的问号跳转到官方文档链接&#xff1a; 复制红框内的代码 打开phpstudy&#xff0c;找到配置的站点&#xff0c;点击管理&#xff0c;找到伪静态 点击确认保存即可。 phpstudy会自动重启站点。 此时&#xff0c;…

docker init 生成Dockerfile和docker-compose.yml —— 筑梦之路

官网&#xff1a;https://docs.docker.com/engine/reference/commandline/init/ 简介 docker init是一个命令行实用程序&#xff0c;可帮助初始化项目中的 Docker 资源。.dockerignore它根据项目的要求创建 Dockerfile、Compose 文件。这简化了为项目配置 Docker 的过程&#…

PHP反序列化---字符串逃逸(增加/减少)

一、PHP反序列化逃逸--增加&#xff1a; 首先分析源码&#xff1a; <?php highlight_file(__FILE__); error_reporting(0); class A{public $v1 ls;public $v2 123;public function __construct($arga,$argc){$this->v1 $arga;$this->v2 $argc;} } $a $_GET[v…