当前位置: 首页 > 工具软件 > runC > 使用案例 >

runc 源码分析笔记: runc init 执行顺序

段宏毅
2023-12-01

执行的顺序,这个问题搞不清楚,看源码真的一头雾水。当然这篇文章并不是介绍源码,只是看源码中遇到的一个问题。

当运行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函数之前执行的。-_-|||

可以跑下面的程序验证下:

  • main.go
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验证

这个demo验证cgo,init,main三者的执行顺序。

  • main.go
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")
	}
}
  • hook/hook.go

main函数使用_方式导入hook package, 那么也会执行hook package中的init()函数。

package hook

import "fmt"
func init() {
	fmt.Println("call in hook")
}
  • init/init.go
package init

import (
	_ "runcapitest/nsenter" //仅引用了cgo
)

  • nsenter/nsenter.go
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执行。

  • nsenter/nsenter.c
#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后续流程

 类似资料: