golang websocket绑定用户_Golang学习深入Golang启动过程 - Go语言中文社区

golang websocket绑定用户_Golang学习深入Golang启动过程


 看书犯困,那是梦开始的地方。

地鼠们应该都知道Golang具有运行时,golang启动必然会先启动运行时,然后才是你写的main函数

广义上讲,你写的main函数是入口,但是往深了讲,真正的入口并不在这里

这篇文章我们要探讨下真正的入口

注意 我使用的mac系统,由于go运行时具有很多区分于各个平台的代码,所以一些地方会不一样

定位入口

下面的测试代码

package main

import (

"fmt"

)

func main() {

fmt.Println("haha")

}

编译它(-gcflags "-N -l"可以禁用优化)

go build -gcflags "-N -l" ./bin/test/

动用gdb神器

gdb test

接着输入 info files

可以看到入口地址了,我这里是 0x105cbf0

好了,可以退出gdb了,输入 quit

接下来动用神器 delve

dlv exec test

下个断点,断在刚才看到的程序入口

b *0x105cbf0

好了,可以看到下面的输出:

Breakpoint 1 set at 0x105cbf0 for _rt0_amd64_darwin() /usr/local/go/src/runtime/rt0_darwin_amd64.s:8

那么,入口就在 /usr/local/go/src/runtime/rt0darwinamd64.s:8 这里了

入口分析

该打开golang的源码了,我使用goland

src/runtime/rt0darwinamd64.s

TEXT _rt0_amd64_darwin(SB),NOSPLIT,$-8

JMP _rt0_amd64(SB) // /usr/local/go/src/runtime/rt0_darwin_amd64.s:8 指向了这里,跳向了另一个函数

找到 rt0amd64 这个函数

src/runtime/asm_amd64.s

// _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 // argument count的缩写,表示传入main函数的参数个数,压入DI寄存器

LEAQ 8(SP), SI // argv // argument vector的缩写,表示传入main函数的参数序列或指针,压入SI寄存器

JMP runtime·rt0_go(SB) // 调用 runtime·rt0_go 函数

找到runtime·rt0_go函数,这个函数比较长

src/runtime/asm_amd64.s

TEXT runtime·rt0_go(SB),NOSPLIT,$0

// copy arguments forward on an even stack

MOVQ DI, AX // argc // 取出参数个数,放入AX寄存器

MOVQ SI, BX // argv // 取出所有参数,放入BX寄存器

SUBQ $(4*8+7), SP // 2args 2auto

ANDQ $~15, SP

MOVQ AX, 16(SP)

MOVQ BX, 24(SP)

// create istack out of the given (operating system) stack.

// _cgo_init may update stackguard.

MOVQ $runtime·g0(SB), DI

LEAQ (-64*1024+104)(SP), BX

MOVQ BX, g_stackguard0(DI)

MOVQ BX, g_stackguard1(DI)

MOVQ BX, (g_stack+stack_lo)(DI)

MOVQ SP, (g_stack+stack_hi)(DI)

// find out information about the processor we're on

MOVL $0, AX

CPUID

MOVL AX, SI

CMPL AX, $0

JE nocpuinfo

// Figure out how to serialize RDTSC.

// On Intel processors LFENCE is enough. AMD requires MFENCE.

// Don't know about the rest, so let's do MFENCE.

CMPL BX, $0x756E6547 // "Genu"

JNE notintel

CMPL DX, $0x49656E69 // "ineI"

JNE notintel

CMPL CX, $0x6C65746E // "ntel"

JNE notintel

MOVB $1, runtime·isIntel(SB)

MOVB $1, runtime·lfenceBeforeRdtsc(SB)

notintel:

// Load EAX=1 cpuid flags

MOVL $1, AX

CPUID

MOVL AX, runtime·processorVersionInfo(SB)

nocpuinfo:

// if there is an _cgo_init, call it.

MOVQ _cgo_init(SB), AX

TESTQ AX, AX

JZ needtls

// arg 1: g0, already in DI

MOVQ $setg_gcc<>(SB), SI // arg 2: setg_gcc

#ifdef GOOS_android // 如果是安卓

MOVQ $runtime·tls_g(SB), DX // arg 3: &tls_g

// arg 4: TLS base, stored in slot 0 (Android's TLS_SLOT_SELF).

// Compensate for tls_g (+16).

MOVQ -16(TLS), CX

#else

MOVQ $0, DX // arg 3, 4: not used when using platform's TLS

MOVQ $0, CX

#endif

#ifdef GOOS_windows // 如果是windows

// Adjust for the Win64 calling convention.

MOVQ CX, R9 // arg 4

MOVQ DX, R8 // arg 3

MOVQ SI, DX // arg 2

MOVQ DI, CX // arg 1

#endif

CALL AX // 调用 _cgo_init 函数

// update stackguard after _cgo_init

MOVQ $runtime·g0(SB), CX

MOVQ (g_stack+stack_lo)(CX), AX

ADDQ $const__StackGuard, AX

MOVQ AX, g_stackguard0(CX)

MOVQ AX, g_stackguard1(CX)

#ifndef GOOS_windows

JMP ok

#endif

needtls:

#ifdef GOOS_plan9

// skip TLS setup on Plan 9

JMP ok

#endif

#ifdef GOOS_solaris

// skip TLS setup on Solaris

JMP ok

#endif

#ifdef GOOS_illumos

// skip TLS setup on illumos

JMP ok

#endif

#ifdef GOOS_darwin

// skip TLS setup on Darwin

JMP ok

#endif

LEAQ runtime·m0+m_tls(SB), DI

CALL runtime·settls(SB)

// store through it, to make sure it works

get_tls(BX)

MOVQ $0x123, g(BX)

MOVQ runtime·m0+m_tls(SB), AX

CMPQ AX, $0x123

JEQ 2(PC)

CALL runtime·abort(SB)

ok:

// set the per-goroutine and per-mach "registers"

get_tls(BX)

LEAQ runtime·g0(SB), CX // runtime·g0变量压入CX

MOVQ CX, g(BX)

LEAQ runtime·m0(SB), AX // runtime·m0压入AX

// save m->g0 = g0

MOVQ CX, m_g0(AX) // g0绑定到m0

// save m0 to g0->m // m0绑定到g0

MOVQ AX, g_m(CX)

CLD // convention is D is always left cleared

CALL runtime·check(SB) // 调用runtime包下的check函数

MOVL 16(SP), AX // copy argc

MOVL AX, 0(SP)

MOVQ 24(SP), AX // copy argv

MOVQ AX, 8(SP)

CALL runtime·args(SB) // 调用runtime包下的args函数,设置参数

CALL runtime·osinit(SB) // 调用runtime包下的osinit函数,获取cpu个数以及获取页大小

CALL runtime·schedinit(SB) // 调用runtime包下的schedinit函数

// create a new goroutine to start program

MOVQ $runtime·mainPC(SB), AX // entry mainPC方法(也就是runtime·main函数,是一个全局变量)压入AX寄存器

PUSHQ AX // 压入第二个参数到栈

PUSHQ $0 // arg size 压入第一个参数到栈

CALL runtime·newproc(SB) // 调用 newproc 函数

POPQ AX

POPQ AX

// start this M

CALL runtime·mstart(SB) // 调用 runtime·mstart函数

CALL runtime·abort(SB) // mstart should never return 结束

RET

// Prevent dead-code elimination of debugCallV1, which is

// intended to be called by debuggers.

MOVQ $runtime·debugCallV1(SB), AX

RET

接下来看看调度初始化函数 schedinit函数 吧

// The bootstrap sequence is:

//

// call osinit

// call schedinit

// make & queue new G

// call runtime·mstart

//

// The new G calls runtime·main.

func schedinit() {

// raceinit must be the first call to race detector.

// In particular, it must be done before mallocinit below calls racemapshadow.

_g_ := getg() // 获取当前g,就是g0

if raceenabled {

_g_.racectx, raceprocctx0 = raceinit() // 开启了race,就初始化race

}

sched.maxmcount = 10000 // 设置允许的最大的m的数量,即线程数量

tracebackinit()

moduledataverify() // 校验go可执行文件以及各个模块格式

stackinit() // 初始化栈池变量

mallocinit() // 向OS申请内存,初始化m的堆

fastrandinit() // must run before mcommoninit 初始化随机种子

mcommoninit(_g_.m) // 初始化m的一些信息

cpuinit() // must run before alginit 初始化cpu信息

alginit() // maps must not be used before this call 算法相关初始化

modulesinit() // provides activeModules 模块初始化

typelinksinit() // uses maps, activeModules 初始化各个模块的typelinks

itabsinit() // uses activeModules 初始化各个模块的itabs

msigsave(_g_.m) // 初始化m的signal mask

initSigmask = _g_.m.sigmask

goargs() // 参数放到argslice变量中

goenvs() // 环境变量放到envs中

parsedebugvars() // 初始化一系列debug相关的变量

gcinit() // 初始化gc

sched.lastpoll = uint64(nanotime()) // 初始化上次netpool执行时间

procs := ncpu // procs设置成cpu个数

if n, ok := atoi32(gogetenv("GOMAXPROCS")); ok && n > 0 { // 如果GOMAXPROCS有设置,则覆盖procs的值

procs = n

}

if procresize(procs) != nil { // 增加或减少p的实例个数,多了就清理多的p,少了就新建p

throw("unknown runnable goroutine during bootstrap")

}

// For cgocheck > 1, we turn on the write barrier at all times

// and check all pointer writes. We can't do this until after

// procresize because the write barrier needs a P.

if debug.cgocheck > 1 {

writeBarrier.cgo = true

writeBarrier.enabled = true

for _, p := range allp {

p.wbBuf.reset()

}

}

if buildVersion == "" {

// Condition should never trigger. This code just serves

// to ensure runtime·buildVersion is kept in the resulting binary.

buildVersion = "unknown"

}

if len(modinfo) == 1 {

// Condition should never trigger. This code just serves

// to ensure runtime·modinfo is kept in the resulting binary.

modinfo = ""

}

}

扩展 m0代表主线程,是程序一启动就有的,g0代表0号协程,一启动就有,并与m0挂钩。一个go进程有一个全局的m0和一个全局的g0,每个普通m下又有一个g0,只有g0才负责调度。严格意义上讲,g0虽然也是使用g结构,但其实不是一个g,他没有启动函数,也不会被调度。

接下来看 newproc函数 吧

src/runtime/proc.go

func newproc(siz int32, fn *funcval) {

argp := add(unsafe.Pointer(&fn), sys.PtrSize)

gp := getg() // 获取当前goroutine的指针,函数没有相关源码,编译器会进行指令填充

pc := getcallerpc() // 获取伪寄存器PC的内容,函数也是由编译器填充

systemstack(func() {

newproc1(fn, argp, siz, gp, pc)

})

}

func newproc1(fn *funcval, argp unsafe.Pointer, narg int32, callergp *g, callerpc uintptr) {

_g_ := getg()

if fn == nil {

_g_.m.throwing = -1 // do not dump full stacks

throw("go of nil func value")

}

acquirem() // disable preemption because it can be holding p in a local var 独占m

siz := narg

siz = (siz + 7) &^ 7

// We could allocate a larger initial stack if necessary.

// Not worth it: this is almost always an error.

// 4*sizeof(uintreg): extra space added below

// sizeof(uintreg): caller's LR (arm) or return address (x86, in gostartcall).

if siz >= _StackMin-4*sys.RegSize-sys.RegSize {

throw("newproc: function arguments too large for new goroutine")

}

_p_ := _g_.m.p.ptr()

newg := gfget(_p_) // 从p的g列表中获取一个goroutine,没有的话就从全局g列表中抓取一批g放入p的g列表中,再从中获取

if newg == nil { // 一开始启动应该取不到

newg = malg(_StackMin) // 新建一个g

casgstatus(newg, _Gidle, _Gdead) // 等待g从idle到dead

allgadd(newg) // publishes with a g->status of Gdead so GC scanner doesn't look at uninitialized stack.

}

if newg.stack.hi == 0 {

throw("newproc1: newg missing stack")

}

if readgstatus(newg) != _Gdead {

throw("newproc1: new g is not Gdead")

}

totalSize := 4*sys.RegSize + uintptr(siz) + sys.MinFrameSize // extra space in case of reads slightly beyond frame

totalSize += -totalSize & (sys.SpAlign - 1) // align to spAlign

sp := newg.stack.hi - totalSize

spArg := sp

if usesLR {

// caller's LR

*(*uintptr)(unsafe.Pointer(sp)) = 0

prepGoExitFrame(sp)

spArg += sys.MinFrameSize

}

if narg > 0 {

memmove(unsafe.Pointer(spArg), argp, uintptr(narg))

// This is a stack-to-stack copy. If write barriers

// are enabled and the source stack is grey (the

// destination is always black), then perform a

// barrier copy. We do this *after* the memmove

// because the destination stack may have garbage on

// it.

if writeBarrier.needed && !_g_.m.curg.gcscandone {

f := findfunc(fn.fn)

stkmap := (*stackmap)(funcdata(f, _FUNCDATA_ArgsPointerMaps))

if stkmap.nbit > 0 {

// We're in the prologue, so it's always stack map index 0.

bv := stackmapdata(stkmap, 0)

bulkBarrierBitmap(spArg, spArg, uintptr(bv.n)*sys.PtrSize, 0, bv.bytedata)

}

}

}

memclrNoHeapPointers(unsafe.Pointer(&newg.sched), unsafe.Sizeof(newg.sched))

newg.sched.sp = sp

newg.stktopsp = sp

newg.sched.pc = funcPC(goexit) + sys.PCQuantum // +PCQuantum so that previous instruction is in same function

newg.sched.g = guintptr(unsafe.Pointer(newg))

gostartcallfn(&newg.sched, fn)

newg.gopc = callerpc

newg.ancestors = saveAncestors(callergp)

newg.startpc = fn.fn // 将mainPC方法(就是runtime·main方法)指定为这个协程的启动方法

if _g_.m.curg != nil {

newg.labels = _g_.m.curg.labels

}

if isSystemGoroutine(newg, false) { // 判断是不是系统协程(g启动函数包含runtime.*前缀的都是系统协程,除了runtime.main, runtime.handleAsyncEvent)

atomic.Xadd(&sched.ngsys, +1)

}

casgstatus(newg, _Gdead, _Grunnable) // 等待g从dead状态到runnable状态

if _p_.goidcache == _p_.goidcacheend {

// Sched.goidgen is the last allocated id,

// this batch must be [sched.goidgen+1, sched.goidgen+GoidCacheBatch].

// At startup sched.goidgen=0, so main goroutine receives goid=1.

_p_.goidcache = atomic.Xadd64(&sched.goidgen, _GoidCacheBatch)

_p_.goidcache -= _GoidCacheBatch - 1

_p_.goidcacheend = _p_.goidcache + _GoidCacheBatch

}

newg.goid = int64(_p_.goidcache) // 初始化g的唯一id

_p_.goidcache++

if raceenabled {

newg.racectx = racegostart(callerpc)

}

if trace.enabled {

traceGoCreate(newg, newg.startpc)

}

runqput(_p_, newg, true) // 将g指定为p的下一个运行的g

if atomic.Load(&sched.npidle) != 0 && atomic.Load(&sched.nmspinning) == 0 && mainStarted {

wakep()

}

releasem(_g_.m) // 放弃独占m

}

这个时候,还并没有创建m对应的系统线程,只是初始化了m的一些内存以及数据,初始化了cpu个数的p的相关数据,启动了一个新的goroutine(函数是runtime·main)

newproc执行完,接下来就是 mstart 了,mstart函数是每个m(即线程)启动后执行的第一个函数

func mstart() { // 每个m的启动函数

_g_ := getg()

osStack := _g_.stack.lo == 0

if osStack {

// Initialize stack bounds from system stack.

// Cgo may have left stack size in stack.hi.

// minit may update the stack bounds.

size := _g_.stack.hi

if size == 0 {

size = 8192 * sys.StackGuardMultiplier

}

_g_.stack.hi = uintptr(noescape(unsafe.Pointer(&size)))

_g_.stack.lo = _g_.stack.hi - size + 1024

}

// Initialize stack guard so that we can start calling regular

// Go code.

_g_.stackguard0 = _g_.stack.lo + _StackGuard

// This is the g0, so we can also call go:systemstack

// functions, which check stackguard1.

_g_.stackguard1 = _g_.stackguard0

mstart1()

// Exit this thread.

switch GOOS {

case "windows", "solaris", "illumos", "plan9", "darwin", "aix":

// Windows, Solaris, illumos, Darwin, AIX and Plan 9 always system-allocate

// the stack, but put it in _g_.stack before mstart,

// so the logic above hasn't set osStack yet.

osStack = true

}

mexit(osStack)

}

func mstart1() {

_g_ := getg()

if _g_ != _g_.m.g0 { // 判断是不是g0

throw("bad runtime·mstart")

}

// Record the caller for use as the top of stack in mcall and

// for terminating the thread.

// We're never coming back to mstart1 after we call schedule,

// so other calls can reuse the current frame.

save(getcallerpc(), getcallersp()) // 保存pc、sp信息到g0

asminit() // asm初始化

minit() // m初始化

// Install signal handlers; after minit so that minit can

// prepare the thread to be able to handle the signals.

if _g_.m == &m0 {

mstartm0() // 启动m0的signal handler

}

if fn := _g_.m.mstartfn; fn != nil {

fn()

}

if _g_.m != &m0 { // 如果不是m0

acquirep(_g_.m.nextp.ptr())

_g_.m.nextp = 0

}

schedule() // 第一轮调度。这个函数会阻塞

}

到这里,m0主线程已经开始调度了。这里可以可以联想一下,调度中会将前面新建的一个goroutine运行起来,那么就会执行runtime.main函数,这个我们下一篇再来揭晓。

总结一下启动过程:

  1. 入口:rt0amd64_darwin 汇编函数

  2. 初始化m0,g0,并挂钩

  3. runtime·check

  4. runtime·args

  5. runtime·osinit

  6. runtime·schedinit 初始化调度前的相关信息

  7. runtime·newproc 初始化m0的p,并且p下新建一个g,指定为p的下一个运行的g

  8. runtime·mstart m0开始调度,这里阻塞

  9. runtime·abort

下篇预告

goroutine的调度

a7847481e2628d81ae06628dfaff71de.png

戳↙【阅读原文

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/weixin_39558221/article/details/110209560
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2021-06-12 19:47:55
  • 阅读 ( 1262 )
  • 分类:Go

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢