执行的顺序,这个问题搞不清楚,看源码真的一头雾水。当然这篇文章并不是介绍源码,只是看源码中遇到的一个问题。
当运行runc run
命令,go语言逻辑执行一揽子动作(配置检查,环境变量设置 etc.),最后调用runc init
命令。
但是你会发现源码里并没有这样的类似代码:
// default action is to start a container
var InitCommand = cli.Command{
Name: "init",
Usage: "some description",
ArgsUsage: `<container-id>
package main
import (
"os"
"runtime"
"strconv"
"github.com/opencontainers/runc/libcontainer"
// 引入cgo部分
_ "github.com/opencontainers/runc/libcontainer/nsenter"
"github.com/sirupsen/logrus"
)
// 是谁调用了这个函数 ?
func init() {
if len(os.Args) > 1 && os.Args[1] == "init" {
}
}
是谁调用了这个函数init()
函数 ? 我知道这个是init是runc init
入口点,但是就是想不清楚谁是init()
函数调用者。
原来按照golang约定,init函数是默认在main函数之前执行的。-_-|||
可以跑下面的程序验证下:
package main
import (
"fmt"
"os"
)
func main(){
fmt.Println("main function get call after init()")
}
// by default call before main by go convention
func init() {
if len(os.Args) > 1 && os.Args[1] == "init" {
fmt.Println("init get called")
}
}
那么现在可以搞清楚,runc init
命令运行时,init()
函数先于main()
执行,那cgo部分呢?
这个demo验证cgo,init,main三者的执行顺序。
package main
import (
"fmt"
"os"
_ "runcapitest/hook" // 其他包的init函数
_ "runcapitest/init" // cgo 部分
)
func main(){
fmt.Println("main function get call after init()")
}
// 一个包里可以有多个init函数,init()函数不保证执行执行顺序,但是保证全部都会执行。
func init() {
if len(os.Args) > 1 && os.Args[1] == "init" {
fmt.Println("init get called")
}
}
func init() {
if len(os.Args) > 1 && os.Args[1] == "init" {
fmt.Println("init 2 get called")
}
}
main函数使用_
方式导入hook package, 那么也会执行hook package中的init()函数。
package hook
import "fmt"
func init() {
fmt.Println("call in hook")
}
package init
import (
_ "runcapitest/nsenter" //仅引用了cgo
)
package nsenter
/*
#include <stdlib.h>
#include <stdio.h>
#cgo CFLAGS: -Wall
extern void nsexec(); // 引用外部nsexec.c的nsexec函数声明
void __attribute__((constructor)) init(void) {
nsexec(); // 调用nsexec.c的nsexec函数
}
*/
import "C"
__attribute__((constructor))
这个函数也挺有意思,也是C语言中类似go语言中的init函数,先于main执行。
#include <stdio.h>
#include <stdlib.h>
void nsexec(void){
puts("executed in cgo exec");
return;
}
ok! 验证程序写完了,测试一下:
[root@localhost runcapitest]# go build main.go
[root@localhost runcapitest]# ./main init
executed in cgo exec
call in hook
init get called
init 2 get called
main function get call after init()
看输出我们可以读出一下重要结论:
执行顺序:cgo > init() in go > main() in go
那现在就可以梳理出runc的执行顺序:
runc run
-> runc init
-> nsenter nsexec
函数cgo部分 -> init.go init()
函数 ->runc init
命令调用结束 -> runc run
后续流程