从main入口开始谈golang

一、问题

1.我们知道一个可独立运行的golang程序,一定要有个main.main(),因为main()是程序的入口。可你知道为什么一定要求是main.main()吗?

2.main()在执行前,根据package的初始化顺序,会先初始化依赖的package,然后初始化main,这些操作是在什么时候完成的呢?

二、溯源

1.源码

以下代码位于go/src/runtime/proc.go,这些源码将会给我们答案。

//go:linkname main_inittask main..inittask
var main_inittask initTask

...

//go:linkname main_main main.main
func main_main()

...

// The main goroutine.
func main() {

    ...

    gcenable()//启动gc,sweeping and scavenging

    main_init_done = make(chan bool)

    ...

    doInit(&main_inittask)//此处进行初始化任务

    close(main_init_done)

    ...

    if isarchive || islibrary {
        // A program compiled with -buildmode=c-archive or c-shared
        // has a main, but it is not executed.
        return
    }
    fn := main_main // make an indirect call, as the linker doesn't know the address of the main package when laying down the runtime
    fn()
    ...
}

2.go:link

在正式理解源码之前我们需要学习下go:link,以下是官方说法:

//go:linkname localname [importpath.name]
The //go:linkname directive instructs the compiler to use “importpath.name” as the object file symbol name for the variable or function declared as “localname” in the source code. If the “importpath.name” argument is omitted, the directive uses the symbol's default object file symbol name and only has the effect of making the symbol accessible to other packages. Because this directive can subvert the type system and package modularity, it is only enabled in files that have imported "unsafe".

简单来说通过这种机制,可以实现调用其他包不能导出的内容。

3.main的入口问题

根据go:link,我们知道

//go:linkname main_main main.main
func main_main()//调用的就是我们熟悉的main package的main()

因此,

fn := main_main // make an indirect call, as the linker doesn't know the address of the main package when laying down the runtime
fn()//此处即为执行main(),正式进入我们自己开发的程序逻辑

代码中的fn()即为main(),执行main()即进入我们自己开发的程序逻辑,此处回答了问题1。

4.初始化问题

我们在go:link中提过

我们先看下main_inittask:main_main()即为main.main(),那var main_inittask initTask又是什么呢?我们的main中是没有initTask的,有的只是init,这两者有什么联系呢?问题比较复杂,已经涉及到compile中的内容,有兴趣的可以去看下位于go/src/cmd/compile/internal/gc/init.go中fninit相关的内容,此处不再详细说明。

// fninit makes an initialization record for the package.
// See runtime/proc.go:initTask for its layout.
// The 3 tasks for initialization are:
//   1) Initialize all of the packages the current package depends on.
//   2) Initialize all the variables that have initializers.
//   3) Run any init functions.
func fninit(n []*Node) {
    nf := initOrder(n)

    var deps []*obj.LSym // initTask records for packages the current package depends on
    var fns []*obj.LSym  // functions to call for package initialization

    // Find imported packages with init tasks.
    for _, s := range types.InitSyms {
        deps = append(deps, s.Linksym())
    }

    // Make a function that contains all the initialization statements.
    if len(nf) > 0 {//打包所有的init
        lineno = nf[0].Pos // prolog/epilog gets line number of first init stmt
        initializers := lookup("init")
        disableExport(initializers)
        fn := dclfunc(initializers, nod(OTFUNC, nil, nil))
        for _, dcl := range dummyInitFn.Func.Dcl {
            dcl.Name.Curfn = fn
        }
        fn.Func.Dcl = append(fn.Func.Dcl, dummyInitFn.Func.Dcl...)
        dummyInitFn.Func.Dcl = nil

        fn.Nbody.Set(nf)
        funcbody()

        fn = typecheck(fn, ctxStmt)
        Curfn = fn
        typecheckslice(nf, ctxStmt)
        Curfn = nil
        funccompile(fn)
        fns = append(fns, initializers.Linksym())
    }
    if dummyInitFn.Func.Dcl != nil {
        // We only generate temps using dummyInitFn if there
        // are package-scope initialization statements, so
        // something's weird if we get here.
        Fatalf("dummyInitFn still has declarations")
    }

    // Record user init functions.
    for i := 0; i < renameinitgen; i++ {
        s := lookupN("init.", i)
        fns = append(fns, s.Linksym())
    }

    if len(deps) == 0 && len(fns) == 0 && localpkg.Name != "main" && localpkg.Name != "runtime" {
        return // nothing to initialize
    }

    // Make an .inittask structure.
    sym := lookup(".inittask")
    nn := newname(sym)
    nn.Type = types.Types[TUINT8] // dummy type
    nn.SetClass(PEXTERN)
    sym.Def = asTypesNode(nn)
    exportsym(nn)
    lsym := sym.Linksym()
    ot := 0
    //以下封装了initTask对应的参数
    ot = duintptr(lsym, ot, 0) // state: not initialized yet
    ot = duintptr(lsym, ot, uint64(len(deps)))
    ot = duintptr(lsym, ot, uint64(len(fns)))
    for _, d := range deps {
        ot = dsymptr(lsym, ot, d, 0)
    }
    for _, f := range fns {
        ot = dsymptr(lsym, ot, f, 0)
    }
    // An initTask has pointers, but none into the Go heap.
    // It's not quite read only, the state field must be modifiable.
    ggloblsym(lsym, int32(ot), obj.NOPTR)
}

initTask包含了3个参数,状态,初始化deps,初始化方法数,根据这后两个参数可以完成初始化的递进进行,见doInit。如此我们可以说明第二个问题,同时也可以发现初始化确实是在main之前执行的。

// An initTask represents the set of initializations that need to be done for a package.
type initTask struct {
    // TODO: pack the first 3 fields more tightly?
    state uintptr // 0 = uninitialized, 1 = in progress, 2 = done
    ndeps uintptr
    nfns  uintptr
    // followed by ndeps instances of an *initTask, one per package depended on
    // followed by nfns pcs, one per init function to run
}

func doInit(t *initTask) {
    switch t.state {
    case 2: // fully initialized
        return
    case 1: // initialization in progress
        throw("recursive call during initialization - linker skew")
    default: // not initialized yet
        t.state = 1 // initialization in progress
        for i := uintptr(0); i < t.ndeps; i++ {//存在依赖,继续下一层
            p := add(unsafe.Pointer(t), (3+i)*sys.PtrSize)
            t2 := *(**initTask)(p)
            doInit(t2)
        }
        for i := uintptr(0); i < t.nfns; i++ {
            p := add(unsafe.Pointer(t), (3+t.ndeps+i)*sys.PtrSize)
            f := *(*func())(unsafe.Pointer(&p))
            f()//执行init
        }
        t.state = 2 // initialization done
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值