Go源码之命令行参数实现

在Go中我们可以很方便的通过os.Args获取命令行参数,下面来看下是如何实现的?

先看下Args声明,是一个string切片,是os包的一个全局变量:

@(src/os/proc.go:15)

var Args []string

Args在os包的init函数中通过调用runtime_args被赋值,代码如下:

@(src/os/proc.go:17)

func init() {
	...
	Args = runtime_args()
}

func runtime_args() []string 

再去看runtime_args时发现只有声明,没有实现,因为是在runtime包里面实现的。在runtime里面的实现函数是os_runtime_args,代码如下:

@(src/runtime/runtime.go:59)

//go:linkname os_runtime_args os.runtime_args
func os_runtime_args() []string { return append([]string{}, argslice...) }

os_runtime_args函数实现非常简单,就是把argslice的值返回,那么argslice是怎么被赋值的呢?先看下argslice的声明,如下:

@(src/runtime/runtime.go:50)

var argslice []string

也是一个string切片,并且是runtime包的一个全局变量。下在来看下argslice是如何被赋值的,代码如下:

@(src/runtime/runtime1.go:66)

func goargs() {
	...
	argslice = make([]string, argc)
	for i := int32(0); i < argc; i++ {
		argslice[i] = gostringnocopy(argv_index(argv, i))
	}
}

argc和argv是runtime包的全局变量,分别存储着命令行参数的个数和值,具体声明如下:

@(src/runtime/runtime1.go:49)

var (
	argc int32
	argv **byte
)

可以看到浓浓的C风格,因此要通过goargs函数传成更好用的Go切片。那argc和argv是何时被赋值的呢?我们继续深入,在args被直接赋值,代码如下:

@(src/runtime/runtime1.go:60)

func args(c int32, v **byte) {
	argc = c
	argv = v
	sysargs(c, v)
}

到这里还是Go代码,比较好懂,下面再深入就要涉及到汇编,代码如下:

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

	...
	MOVL	16(SP), AX		// copy argc
	MOVL	AX, 0(SP)
	MOVQ	24(SP), AX		// copy argv
	MOVQ	AX, 8(SP)
	CALL	runtime·args(SB)
	...

有点汇编基础的应该都可以看懂,就是先把参数argc和argv拷到栈上,后面在调用runtime.args时候就会读取到argc和argv,即func args(c int32, v **byte)的两个参数的由来。

上面通过一步步逆向推导看到了命令行参数的实现,但对整体的调用栈可能还有点模糊,下面通过图例来说明下:

 ------------------
| _rt0_arm64_linux |
 ------------------
		|
		V
 -----------------                           -------------
| runtime.rt0_g0  |                         |   os.init   |
 -----------------                           -------------
		|                                          |
		V                                          V
 -----------------                         -----------------                           
|  runtime.args   |                       | os.runtime_args |
 -----------------                         -----------------
		|                                          |
		V                                          V
 -------------------                       -----------------
| runtime.schedinit |                     | os_runtime_args |                
 -------------------                       -----------------
		|                                          |  
		V                                          V
 -----------------                           ------------
|  runtime.goargs |       ------------>     |  argslice  |
 -----------------                           ------------

_rt0_arm64_linux 是Go的真正执行入口,从这里会依次对参数做处理,最终处理好的结果放入argslice全局变量。在os包初始化时,会使用argslice对os.Args赋值。

runtime.goargs会先于os.init执行

猜你喜欢

转载自blog.csdn.net/fengshenyun/article/details/106908689
今日推荐