在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执行