详解Go程序的启动流程,你知道g0,m0是什么吗?

作者&投稿:阿谈 (若有异议请与网页底部的电邮联系)
~

自古应用程序均从HelloWorld开始,你我所写的Go语言亦然:

import"fmt"funcmain(){fmt.Println("helloworld.")}

这段程序的输出结果为helloworld.,就是这么的简单又直接。但这时候又不禁思考了起来,这个helloworld.是怎么输出来,经历了什么过程。

真是非常的好奇,今天我们就一起来探一探Go程序的启动流程。其中涉及到GoRuntime的调度器启动,g0,m0又是什么?

车门焊死,正式开始吸鱼之路。

Go引导阶段查找入口

首先编译上文提到的示例程序:

$GOFLAGS="-ldflags=-compressdwarf=false"gobuild

在命令中指定了GOFLAGS参数,这是因为在Go1.11起,为了减少二进制文件大小,调试信息会被压缩。导致在MacOS上使用gdb时无法理解压缩的DWARF的含义是什么(而我恰恰就是用的MacOS)。

因此需要在本次调试中将其关闭,再使用gdb进行调试,以此达到观察的目的:

$gdbawesomeProject(gdb)infofilesSymbolsfrom"/Users/eddycjy/go-application/awesomeProject/awesomeProject".Localexecfile:`/Users/eddycjy/go-application/awesomeProject/awesomeProject',filetypemach-o-x86-64.Entrypoint:0x1063c800x0000000001001000-0x00000000010a6acais.text...(gdb)b*0x1063c80Breakpoint1at0x1063c80:file/usr/local/Cellar/go/1.15/libexec/src/runtime/rt0_darwin_amd64.s,line8.

通过Entrypoint的调试,可看到真正的程序入口在runtime包中,不同的计算机架构指向不同。例如:

MacOS在src/runtime/rt0_darwin_amd64.s。

Linux在src/runtime/rt0_linux_amd64.s。

其最终指向了rt0_darwin_amd64.s文件,这个文件名称非常的直观:

Breakpoint1at0x1063c80:file/usr/local/Cellar/go/1.15/libexec/src/runtime/rt0_darwin_amd64.s,line8.

rt0代表runtime0的缩写,指代运行时的创世,超级奶爸:

darwin代表目标操作系统(GOOS)。

amd64代表目标操作系统架构(GOHOSTARCH)。

同时Go语言还支持更多的目标系统架构,例如:AMD64、AMR、MIPS、WASM等:

若有兴趣可到src/runtime目录下进一步查看,这里就不一一介绍了。

入口方法

在rt0_linux_amd64.s文件中,可发现_rt0_amd64_darwinJMP跳转到了_rt0_amd64方法:

TEXT_rt0_amd64_darwin(SB),NOSPLIT,$-8JMP_rt0_amd64(SB)...

紧接着又跳转到runtime·rt0_go方法:

TEXT_rt0_amd64(SB),NOSPLIT,$-8MOVQ0(SP),DI//argcLEAQ8(SP),SI//argvJMPruntime·rt0_go(SB)

该方法将程序输入的argc和argv从内存移动到寄存器中。

栈指针(SP)的前两个值分别是argc和argv,其对应参数的数量和具体各参数的值。

开启主线

程序参数准备就绪后,正式初始化的方法落在runtime·rt0_go方法中:

TEXTruntime·rt0_go(SB),NOSPLIT,$0...CALLruntime·check(SB)MOVL16(SP),AX//copyargcMOVLAX,0(SP)MOVQ24(SP),AX//copyargvMOVQAX,8(SP)CALLruntime·args(SB)CALLruntime·osinit(SB)CALLruntime·schedinit(SB)//createanewgoroutinetostartprogramMOVQ$runtime·mainPC(SB),AX//entryPUSHQAXPUSHQ$0//argsizeCALLruntime·newproc(SB)POPQAXPOPQAX//startthisMCALLruntime·mstart(SB)...

runtime.check:运行时类型检查,主要是校验编译器的翻译工作是否正确,是否有“坑”。基本代码均为检查int8在unsafe.Sizeof方法下是否等于1这类动作。

runtime.args:系统参数传递,主要是将系统参数转换传递给程序使用。

runtime.osinit:系统基本参数设置,主要是获取CPU核心数和内存物理页大小。

runtime.schedinit:进行各种运行时组件的初始化,包含调度器、内存分配器、堆、栈、GC等一大堆初始化工作。会进行p的初始化,并将m0和某一个p进行绑定。

runtime.main:主要工作是运行maingoroutine,虽然在runtime·rt0_go中指向的是$runtime·mainPC,但实质指向的是runtime.main。

runtime.newproc:创建一个新的goroutine,且绑定runtime.main方法(也就是应用程序中的入口main方法)。并将其放入m0绑定的p的本地队列中去,以便后续调度。

runtime.mstart:启动m,调度器开始进行循环调度。

在runtime·rt0_go方法中,其主要是完成各类运行时的检查,系统参数设置和获取,并进行大量的Go基础组件初始化。

初始化完毕后进行主协程(maingoroutine)的运行,并放入等待队列(GMP模型),最后调度器开始进行循环调度。

小结

根据上述源码剖析,可以得出如下Go应用程序引导的流程图:

在Go语言中,实际的运行入口并不是用户日常所写的mainfunc,更不是runtime.main方法,而是从rt0_*_amd64.s开始,最终再一路JMP到runtime·rt0_go里去,再在该方法里完成一系列Go自身所需要完成的绝大部分初始化动作。

其中整体包括:

运行时类型检查、系统参数传递、CPU核数获取及设置、运行时组件的初始化(调度器、内存分配器、堆、栈、GC等)。

运行maingoroutine。

运行相应的GMP等大量缺省行为。

涉及到调度器相关的大量知识。

后续将会继续剖析将进一步剖析runtime·rt0_go里的爱与恨,尤其像是runtime.main、runtime.schedinit等调度方法,都有非常大的学习价值,有兴趣的小伙伴可以持续关注。

Go调度器初始化

知道了Go程序是怎么引导起来的之后,我们需要了解GoRuntime中调度器是怎么流转的。

runtime.mstart

这里主要关注runtime.mstart方法:

funcmstart(){//获取g0_g_:=getg()//确定栈边界osStack:=_g_.stack.lo==0ifosStack{size:=_g_.stack.hiifsize==0{size=8192*sys.StackGuardMultiplier}_g_.stack.hi=uintptr(noescape(unsafe.Pointer(&size)))_g_.stack.lo=_g_.stack.hi-size+1024}_g_.stackguard0=_g_.stack.lo+_StackGuard_g_.stackguard1=_g_.stackguard0//启动m,进行调度器循环调度mstart1()//退出线程ifmStackIsSystemAllocated(){osStack=true}mexit(osStack)}

调用getg方法获取GMP模型中的g,此处获取的是g0。

通过检查g的执行栈_g_.stack的边界(堆栈的边界正好是lo,hi)来确定是否为系统栈。若是,则根据系统栈初始化g执行栈的边界。

调用mstart1方法启动系统线程m,进行调度器循环调度。

调用mexit方法退出系统线程m。

runtime.mstart1

这么看来其实质逻辑在mstart1方法,我们继续往下剖析:

funcmstart1(){//获取g,并判断是否为g0_g_:=getg()if_g_!=_g_.m.g0{throw("badruntime·mstart")}//初始化m并记录调用方pc、spsave(getcallerpc(),getcallersp())asminit()minit()//设置信号handlerif_g_.m==&m0{mstartm0()}//运行启动函数iffn:=_g_.m.mstartfn;fn!=nil{fn()}if_g_.m!=&m0{acquirep(_g_.m.nextp.ptr())_g_.m.nextp=0}schedule()}

调用getg方法获取g。并且通过前面绑定的_g_.m.g0判断所获取的g是否g0。若不是,则直接抛出致命错误。因为调度器仅在g0上运行。

调用minit方法初始化m,并记录调用方的PC、SP,便于后续schedule阶段时的复用。

若确定当前的g所绑定的m是m0,则调用mstartm0方法,设置信号handler。该动作必须在minit方法之后,这样minit方法可以提前准备好线程,以便能够处理信号。

若当前g所绑定的m有启动函数,则运行。否则跳过。

若当前g所绑定的m不是m0,则需要调用acquirep方法获取并绑定p,也就是m与p绑定。

调用schedule方法进行正式调度。

忙活了一大圈,终于进入到开题的主菜了,原来潜伏的很深的schedule方法才是真正做调度的方法,其他都是前置处理和准备数据。

由于篇幅问题,schedule方法会放到下篇再继续剖析,我们先聚焦本篇的一些细节点。

问题深剖

不过到这里篇幅也已经比较长了,积累了不少问题。我们针对在Runtime中出镜率最高的两个元素进行剖析:

m0是什么,作用是?

g0是什么,作用是?

m0

m0是GoRuntime所创建的第一个系统线程,一个Go进程只有一个m0,也叫主线程。

从多个方面来看:

数据结构:m0和其他创建的m没有任何区别。

创建过程:m0是进程在启动时应该汇编直接复制给m0的,其他后续的m则都是GoRuntime内自行创建的。

变量声明:m0和常规m一样,m0的定义就是varm0m,没什么特别之处。

g0

g一般分为三种,分别是:

执行用户任务的叫做g。

执行runtime.main的maingoroutine。

执行调度任务的叫g0。。

g0比较特殊,每一个m都只有一个g0(仅此只有一个g0),且每个m都只会绑定一个g0。在g0的赋值上也是通过汇编赋值的,其余后续所创建的都是常规的g。

从多个方面来看:

数据结构:g0和其他创建的g在数据结构上是一样的,但是存在栈的差别。在g0上的栈分配的是系统栈,在Linux上栈大小默认固定8MB,不能扩缩容。而常规的g起始只有2KB,可扩容。

运行状态:g0和常规的g不一样,没有那么多种运行状态,也不会被调度程序抢占,调度本身就是在g0上运行的。

变量声明:g0和常规g,g0的定义就是varg0g,没什么特别之处。

小结

在本章节中我们讲解了Go调度器初始化的一个过程,分别涉及:

runtime.mstart。

runtime.mstart1。

基于此也了解到了在调度器初始化过程中,需要准备什么,初始化什么。另外针对调度过程中最常提到的m0、g0的概念我们进行了梳理和说明。

总结

在今天这篇文章中,我们详细的介绍了Go语言的引导启动过程中的所有流程和初始化动作。

同时针对调度器的初始化进行了初步分析,详细介绍了m0、g0的用途和区别。在下一篇文章中我们将进一步对真正调度的schedule方法进行详解,这块也是个硬骨头了。




我的E52在安装完GO浏览器1.5.1后首次启动时提示内存不足,需要关闭一些...
有可能是你的一些下载文件存储在你的手机里面、而手机内存基本上都不多、所以你先看下 如果是的话 把文件全部移动到内存卡里面 、手机运行40M算高的了

go语言写入文件是什么意思啊?
golang怎么将控制台的错误输出写入文件你可以记录下错误,然后写入文件。或者在启动go程序的时候,把输出写入文件。像这样:.\/maina.log Go语言之log(如何将日志写到指定文件里面)对于Go语言的日志来说,如何将log写到指定的文件里面,下面是一个例子。output:output:【golang】小技巧-利用io.copy写...

windows to go u盘无法启动的解决方法图文详
windows to go u盘无法启动的解决方法图文详细介绍 问题1:u盘启动后只有左上角的光标在闪 打开wtg辅助工具,选择u盘,右键》手动执行命令》设置活动分区和写入磁盘引导 仍然不行?在程序界面右键》打开程序运行目录》打开bootice.exe,选择u盘,点击分区管理,查看 是否为活动分区,如果不是,点击激活 问题...

golang文件锁清除?
Golang语言的标记清除垃圾回收算法,为了防止GC扫描时内存变化引起的混乱。那么就需要STW,即StopTheWorld。具体在Golang语言中是指,在GC时先停止所有goroutine。再进行垃圾回收,等待垃圾回收结束后再恢复所有被停止的goroutine。 标记清除方法 启动STW,暂停程序的业务逻辑,找出不可达对象和可达对象。 将所有可达对象做标记...

怎么退出surfacegos模式
四、关闭安全启动 在安全启动的菜单中,选择关闭或禁用安全启动选项。保存更改并退出BIOS\/UEFI设置。此时,Surface Go应该已经退出了UEFI安全模式。解释:Surface Go的UEFI安全模式是一种保护机制,用于确保只能从可信的源头启动操作系统和应用程序。退出此模式通常涉及到修改BIOS或UEFI设置中的安全启动选项。在...

我的PSPgo破解程序是6.35的,我不小心把系统升级成了6.60。
变砖是主板坏了,开不了机,楼主可以放心,你的机器没有坏,但是现在6.60好像没有破解,只是不能玩游戏。你就等等大神们破解6.60吧,或者你上电玩巴士留意一下。

gin框架原理详解(gin框架是什么)
不管怎么样,使用Go开发,我们可以不用花太多时间在WEB服务环境搭建上,程序启动就直接可以提供WEB服务了。 packagemain import( "net\/http" "github.com\/gin-gonic\/gin" ) funcmain(){ router:=gin.Default() \/\/静态资源加载,本例为css,js以及资源图片 router.StaticFS("\/public",http.Dir("D:\/goproject\/src...

我进一个游戏要修改主页,我怎么不改主页进游戏呢?
4.找到c:\\windows\\system32下的 msconfig.exe 将其删除,然后从其它干净的系统中复制一个msconfig程序回来。可以下载,绝对好用。下载:upload\/08082818596562.rar 5.用安全卫士360修复一下IE,6.重新启动,绝对原创好用。修改GO2000的方法,同样也好用,一般只需进行前两部就行,如果进行第三步真有该...

ECU指的是什么
我说不是,因为ECU内有个替代程序又叫回家功能(GO HOME)。比如说水温传感器坏了,ECU就以80度的后备值作为喷油和点火的控制参数。使发动机得以继续运转。减少抛锚的机率。当然,那时候的动力性和经济性下降。你要及时去修,就如上述的水温传感器坏了,不及时修,那要开锅的。 说了替代程序再说故障码,故障码的好处是...

微服务架构下的熔断框架:hystrix-go
本文我们就介绍一个开源熔断框架:hystrix-go。熔断框架(hystrix-go) Hystrix是一个延迟和容错库,旨在隔离对远程系统、服务和第三方服务的访问点,停止级联故障并在故障不可避免的复杂分布式系统中实现弹性。hystrix-go旨在允许Go程序员轻松构建具有与基于Java的Hystrix库类似的执行语义的应用程序。所以本文就从使用开始到源...

新民市15228945775: 在数控中GO和G1是什么代码 -
挚洪功劳: GO是无条件跳转指令,当程序执行到GO时会去执行GO后面的程序号所在行的指令;如GO80表示执行N80行的程序;G1是直线进给指令,如G1 X30表示车具从当位位置将走到X30mm的位置;G0是快速定位指令,不能用于切削,只能是空刀快速移动;

新民市15228945775: 数控编程\GO X0 Z0 什么意思 -
挚洪功劳: 两轴联动快速定位到x0 z0坐标,x0是刀尖定位到卡盘中心,z0是最右侧端面.

新民市15228945775: 数控钻床程序中G0和G001是代表什么 -
挚洪功劳: G0是快速点定位的意思,是空行程. G01是直线插补,是钻孔的动作,是工进.

新民市15228945775: 三菱FX 3U plc程序中MOVP H1A2 U0\G22的意思是?MOVP H0FF00 U0\G0的意思是? -
挚洪功劳: MOVP是传送指令H是十六进制的意思,U0/GO是0号模块的第0号缓冲区,如MOVP H1A2 U0/GO就是将H1A1传到0号模块的0号缓冲区,一次类推,记得给好评哦

新民市15228945775: 数控车床,如果是程序gox0zo,下一行g1x20z20,走刀是先走x轴,还是z轴,还有如果走沟边角怎么走 -
挚洪功劳: 在一行的程序都是同时执行的,你所谓的g1 x20 z20走的是斜线,但是你的前个程序段是g0 x0 z0,那你的下个程序段应该是g1 x20 z-20这样才对 切槽,切槽刀要在未切断的情况下X向退回,看看你的倒角多大就Z向正负多少X进多少然后在割断 不懂加QQ834783078

新民市15228945775: 发那科车床编程子程序调用 -
挚洪功劳: ....你不是说切槽只编了一个子程序! 给你个列子 自己看吧 主程序 O2346 M3 S2000 T101 G0 X21.; Z-20.; M98 P1000; 第一次进入子程序 Z-30.; M98 P1000; 第二次进入子程序 G0 Z100.; M30;结束程序回到程序开头. 切槽子程序 O1000 GO X21.; G1 X16. F0.05; X21. F.5; M99;回到主程序

新民市15228945775: 10分 数控车床..关于编程代号..G01...全部详解? -
挚洪功劳: G0是快速定位 G01是直线插补 也就是刀的轨迹是直线 G02是顺时针圆弧插补 G03是逆时针圆弧插补 G90是内外圆柱车削循环 G92是螺纹车削循环 G94是端面车削循环 M0是暂停 M02程序结束 M3主轴正转 M4主轴逆转 M5主轴停止....这些是最基本的 对了哦 这是GSK广州数控 不是所有数控系统都一样的哦 有的是相反的 具体的根据实际情况而定 希望对有你有所帮助

新民市15228945775: 读取失败无法激活应用,请尝试重新启动G0桌面是什么原因
挚洪功劳: 你好 这个情况分两种. 一这个软件已被你卸载, 或者找不到对应的文件.无法映射到文件夹.你重新安装下这个软件,并把原新的卸载. 二:GO桌面这个软件出现故障(重新安装,或者先关闭GO桌面.用原生态桌面看下是否有这个情况.没有则是GO桌面软件 的问题.) 希望可以帮到你

新民市15228945775: 数控车床编程代码?该怎样入门 -
挚洪功劳: G代码 分组 功能*G00 01 定位(快速移动)*G01 01 直线插补(进给速度) G02 01 顺时针圆弧插补 G03 01 逆时针圆弧插补 G04 00 暂停,精确停止 G09 00 精确停止*G17 02 选择X Y平面 G18 02 选择Z X平面 G19 02 选择Y Z平面 G27 00 返回...

新民市15228945775: 法拉克数控宏程序命令解答! -
挚洪功劳: #1 =0;#2 =1; N10 G10 P0 L2 Z#1; (G10修改工件坐标系,P0对应的是外部坐标系,L2固定不变,Z#1Z=0) M98 P154;(M98 P154 调用子程序O154) #1 =#1-13; #2 =#2+1; IF [#2 LT 14] GOT010; (#2≥14 GOTO10 跳程序段N10) G0 X100 Z200; M30;

本站内容来自于网友发表,不代表本站立场,仅表示其个人看法,不对其真实性、正确性、有效性作任何的担保
相关事宜请发邮件给我们
© 星空见康网