12-go进程启动过程

22 篇文章 1 订阅
1 篇文章 0 订阅

bootstrap顺序

// The bootstrap sequence is:
//
//	call osinit
//	call schedinit
//	make & queue new G
//	call runtime·mstart
//
// The new G calls runtime·main.

src\runtime\asm_amd64.s

go进程的启动过程在文件src\runtime\asm_amd64.s中可以看到。

1. main入口

汇编main入口比较简单,直接跳转到runtime·rt0_go。
对于amd64系统,不同的链接方式入口不同。有两种方式。

// _rt0_amd64 is common startup code for most amd64 systems when using
// internal linking. This is the entry point for the program from the
// kernel for an ordinary -buildmode=exe program. The stack holds the
// number of arguments and the C-style argv.
TEXT _rt0_amd64(SB),NOSPLIT,$-8
	MOVQ	0(SP), DI	// argc
	LEAQ	8(SP), SI	// argv
	JMP	runtime·rt0_go(SB)

// main is common startup code for most amd64 systems when using
// external linking. The C startup code will call the symbol "main"
// passing argc and argv in the usual C ABI registers DI and SI.
TEXT main(SB),NOSPLIT,$-8
	JMP	runtime·rt0_go(SB)

2. runtime·rt0_go

rt0_go 调用一系列runtime函数,进行初始化。包括

  1. runtime·check
  2. runtime·args
  3. runtime·osinit
  4. runtime·schedinit
  5. runtime·newproc
    创建一个goroutine,执行对象函数为runtime.main
  6. runtime·mstart
TEXT runtime·rt0_go(SB),NOSPLIT,$0
    // 1. 将参数入栈。
    // 2. 查找cpu信息。
    //      1.如果没有cpu信息,如果有_cgo_init就调用
    // 3. 设置m->g0 = g0
    // 4. 设置m0 to g0->m
    // 5. CALL	runtime·check(SB)	
    // 6. CALL	runtime·args(SB)
	// 7. CALL	runtime·osinit(SB)
	// 8. CALL	runtime·schedinit(SB)
	// 9. CALL  runtime·newproc创建一个goroutine启动进程。
	//      create a new goroutine to start program
	//      0.MOVQ	$runtime·mainPC(SB), AX		// entry == runtime·main
	//          将函数入口runtime·main放入AX,作为newproc执行函数
	//          DATA	runtime·mainPC+0(SB)/8,$runtime·main(SB)
	//      1.通过调用runtime·newproc启动一个goroutine
	//      2.CALL	runtime·newproc(SB)
	// 10.CALL	runtime·mstart 启动一个M,执行 runtime·main
	//      1.CALL	runtime·mstart(SB)
	//      
	//

3. osinit()

获取CPU个数。

func osinit() {
	ncpu = getproccount()   //通过封装系统调用,获取CPU个数
}

4. schedinit()

进行模块初始化,符号初始化,内存初始化,gc初始化,p初始化等。

func schedinit() {
    //1. 获取当前的goroutine
    _g_ := getg()
    
    //2. tracebackinit()
    tracebackinit()
    
    //3.模块数据校验
    moduledataverify()
        //type moduledata struct { 定义了二进制映像文件信息,由链接器填写。包括:代码段、只读数据,数据段等信息
        
    //4. 栈初始化
    stackinit()
    
    //5. 内存分配器初始化
    mallocinit()
    
    //6.
    mcommoninit(_g_.m)
    
    //7. cpu初始化,
    //  1.cpu个数;2.从参数获取cpu特性
    cpuinit()
    
    //8. 初始化AES hash算法
    alginit()
    
    //9. 模块初始化
    //  src\runtime\symtab.go
    //  当一个module被动态链接器首次加载时,.init_array被调用。
    //  在一个Go进程声明周期内,有两种场景会发生module的加载。
    //  1.以-buildmode=shared 模式编译,进程初始化会加载。
    //  2.-buildmode=plugin模式编译,进程运行时加载。
    //  约束:1.modulesinit一次只能有一个goroutine调用。
    modulesinit()
    
    //10. 建立符号表
    typelinksinit()
    
    //11. itab初始化
    itabsinit()
    
    //12. 空函数
    msigsave(_g_.m)
    
    //13. 将参数都放入切片argslice
    // windows下什么也不做
    goargs()
    
    //14. 将所有参数放入切边envs
    goenvs()
    
    //15. 获取所有GODEBUG参数。
    //  1.更新memprofilerate参数
    //  2.设置GOTRACEBACK级别
    //  3.默认进行cgocheck=1,invalidptr=1
    //  4.根据参数设置debug参数dbgvars
    parsedebugvars()
    
    //16. gc初始化
    //  1.memstats.triggerRatio = 7 / 8.0
    //  2.默认触发GC的堆大小为4M。可以调整。
    /// 3.根据GOGC参数设置GC比例,
    //      比如100->4M*1
    //      比如200->4M*2
    //  4.以通过设置 GOGC=off 来彻底关闭 GC
    gcinit()
    
    //17. 设置proc个数
    默认为cpu个数(procs)。
    如果设置了环境变量,"GOMAXPROCS",则设置为环境变量设定值。
    
    //18. 改变p个数
    procresize(procs)
        //整个过程所有处理都是暂停了stop the world,并且sched被加锁
        //allp是一个slice保存所有的p。根据需要增长allp大小
        //初始化新创建的p
        //将所有可以运行的p放入,sched.runq队列头部
        

5. runtime.newproc

实际调用newproc1(具体见[13-goroutine创建过程](https://blog.csdn.net/svasticah/article/details/94345696))。

  1. 优先从本地(当前线程p对象),或者全局(sched)的gfree队列获取一个空闲g对象。
  2. 没有空闲g对象,则新创建一个g对象。 设置状态为 _Grunnable
  3. 以50%概率作为下个执行对象,否则放到待执行队列p.runq。如果队列已满,从本地队列抽取一半g对象放入全局队列。
  4. 唤醒一个闲置线程,如果没有闲置的,则新建一个线程。

6. runtime·mstart

通过schedule()实际执行之前准备好的goroutine,即runtime.main

func mstart1() {
    // 1.asminit()空函数
    // 2.minit() m初始化,主要是信号初始化
    // 3. mstartm0()//对于windows单独启动一个额外的M用于callback
    // 4. fn() //如果有mstartfn,则执行。
    // 5. schedule()  //找到一个可以运行的的goroutine并执行。
            1.如果处于gc等待状态sched.gcwaiting,则stop the world.
                1.gcstopm()
                    1.releasep() 将p与m解除关联
                    2.stopm() 停止执行当前的m,直到新的work可用
                2.goto top  //循环等待gcwaiting是否结束
            2.trace
            3.获取后台一个goroutine
                findRunnableGCWorker()
            4.如果获取失败,以一定几率从全局队列中获取一个goroutine
                1.几率schedtick%61 == 0
                2.从全局队列获取,globrunqget()
                3.获取过程会加锁, lock(&sched.lock)
            5.如果仍然没有可以运行goroutine,则阻塞等待。
                findrunnable()
                    1. 处于sched.gcwaiting状态,循环等待goto top
                    2. 如果处于唤醒等待状态,则准备一个goroutine
                    3. 如果有cgo处于yeild,则调用cgo
                    4. 再次尝试获取一个本地goroutine,成功则返回。
                    5. 再次尝试从全局队列中获取goroutine,成功则返回。
                    6. Poll network.
                        通过poll等待网络。
            6. 找到一个goroutine,则执行runtime·gogo
}

7.执行一个goroutine

// func gogo(buf *gobuf)
// restore state from Gobuf; longjmp
TEXT runtime·gogo(SB), NOSPLIT, $16-8
	MOVQ	buf+0(FP), BX		// gobuf
	MOVQ	gobuf_g(BX), DX
	MOVQ	0(DX), CX		// make sure g != nil
	get_tls(CX)
	MOVQ	DX, g(CX)
	MOVQ	gobuf_sp(BX), SP	// restore SP
	MOVQ	gobuf_ret(BX), AX
	MOVQ	gobuf_ctxt(BX), DX
	MOVQ	gobuf_bp(BX), BP
	MOVQ	$0, gobuf_sp(BX)	// clear to help garbage collector
	MOVQ	$0, gobuf_ret(BX)
	MOVQ	$0, gobuf_ctxt(BX)
	MOVQ	$0, gobuf_bp(BX)
	MOVQ	gobuf_pc(BX), BX
	JMP	BX

8. runtime.main

src\runtime\proc.go
// The main goroutine.

  1. 创建一个线程,执行sysmon服务。
  2. 执行main.init
  3. 执行main.main
func main() {
    1. newm(sysmon, nil)
        对于非wasm(WebAssembly )环境,创建一个m,执行sysmon。
        1. allocm() 创建一个m对象
        2. 对于cgo,通过asmcgocall(_cgo_thread_start, unsafe.Pointer(&ts))系统调用创建一个线程
        3. 非cgo,通过pthread_create方式创建线程。其他通过clone方式创建(linux)
        4. sysmon
            1. 常驻后台
            2. trigger libc interceptors if needed
            3. poll network if not polled for more than 10ms
            4. 夺回被系统调用阻塞的p,并且抢占长期运行的g
            5. 判断是否需要做gc
            6. 偶尔清理一下heap,
                1.在gc后5分钟没有使用,就归还给操作系统,scavengelimit := int64(5 * 60 * 1e9)
    2. runtime_init()
        执行默认.init函数
    3. gcenable()
        启动gc goroutine
    4. main_init
        执行main.init
    5. main_main
        执行main.main
}

9. 总结

  1. 创建runtime.main()goroutine
    通过runtime.newproc创建一个goroutine,执行对象为runtime.main()
  2. 执行runtime.main()
    通过runtime.mstart()调用m.mstartfnruntime.main()
  3. 启动sysmon线程
    runtime.main()中调用newm()创建一个线程,执行后台服务sysmon
  4. runtime.main() goroutine中执行.init()。
  5. runtime.main() goroutine中创建一个gc goroutine。
  6. runtime.main() goroutine中执行main.init()。
  7. runtime.main() goroutine中执行main.main(),真正进入业务进程。

10. 主要过程

  1. newm()
    创建一个系统线程sysmon。
  2. newproc()
    创建一个g对象,用来执行runtime.main
  3. mstart()
    用来通过runtime.main来进入main
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值