>  기사  >  백엔드 개발  >  Go가 시작 매개변수 로딩을 구현하는 방법

Go가 시작 매개변수 로딩을 구현하는 방법

Go语言进阶学习
Go语言进阶学习앞으로
2023-07-21 13:20:431674검색

Go를 막 배운 학생들은 Go 프로그램의 시작 과정에 대해 생각해 봤을 것입니다. 이 문제에 대해서는 Rao Da의 글 Go 프로그램 실행 방법을 읽어보세요. 오늘 우리는 문제의 범위를 좁히고 Go 프로그램이 시작 매개변수를 로드하는 방법과 매개변수를 구문 분석하는 방법을 알아봅니다.

C 매개변수 분석

C 언어를 공부한 아이들이라면 argc, argv에 익숙할 텐데요.

C 프로그램은 항상 주 함수 main에서 실행을 시작하며, 매개변수가 있는 주 함수에서는 관례에 따라 argc 및 argv의 이름이 주 함수 매개변수로 사용됩니다.

그 중 argc(인수 개수)는 명령줄 매개변수의 개수를 나타내고, argv(인수 값)는 매개변수를 저장하는 데 사용되는 포인터의 배열입니다.

#include <stdio.h>

int main(int argc, char *argv[])
{
 printf("argc = %d\n",argc);
 printf("argv[0] = %s, argv[1] = %s, argv[2] = %s \n", argv[0], argv[1], argv[2]);
 return 0;
}

위의 C 코드를 컴파일하고 실행하면 출력은 다음과 같습니다

$ gcc c_main.c -o main
$ ./main foo bar sss ddd
argc = 5
argv[0] = ./main, argv[1] = foo, argv[2] = bar

그럼 Go 언어에서는 명령줄 매개변수를 어떻게 얻나요?

os.Args loading

C와 마찬가지로 Go 프로그램도 메인 함수(사용자 수준)부터 실행을 시작하지만 메인 함수에는 argc, argv가 정의되어 있지 않습니다.

os.Args 함수를 통해 명령줄 매개변수를 얻을 수 있습니다.

package main

import (
 "fmt"
 "os"
)

func main() {
 for i, v := range os.Args {
  fmt.Printf("arg[%d]: %v\n", i, v)
 }
}

Go 함수 컴파일 및 실행

 $ go build main.go
 $ ./main foo bar sss ddd
arg[0]: ./main
arg[1]: foo
arg[2]: bar
arg[3]: sss
arg[4]: ddd

C와 마찬가지로 첫 번째 매개변수도 실행 파일을 나타냅니다.

加载实现

下文我们需要展示一些 Go 汇编代码,为了方便读者理解,先通过两图了解 Go 汇编语言对 CPU 的重新抽象。

X86/AMD64 架构

Go가 시작 매개변수 로딩을 구현하는 방법

Go 伪寄存器

Go가 시작 매개변수 로딩을 구현하는 방법

Go汇编为了简化汇编代码的编写,引入了 PC、FP、SP、SB 四个伪寄存器。

四个伪寄存器加上其它的通用寄存器就是 Go 汇编语言对 CPU 的重新抽象。当然,该抽象的结构也适用于其它非 X86 类型的体系结构。

回到正题,命令行参数的解析过程是程序启动中的一部分内容。

以 linux amd64 系统为例,Go 程序的执行入口位于<span style="font-size: 15px;">runtime/rt0_linux_amd64.s</span>

TEXT _rt0_amd64_linux(SB),NOSPLIT,$-8
	JMP	_rt0_amd64(SB)

<span style="font-size: 15px;">_rt0_amd64</span>函数实现于 <span style="font-size: 15px;">runtime/asm_amd64.s</span>

TEXT _rt0_amd64(SB),NOSPLIT,$-8
	MOVQ	0(SP), DI	// argc
	LEAQ	8(SP), SI	// argv
	JMP	runtime·rt0_go(SB)

看到 argc 和 argv 的身影了吗?在这里,它们从栈内存分别被加载到了 DI、SI 寄存器。

<span style="font-size: 15px;">rt0_go</span>函数完成了 runtime 的所有初始化工作,但我们这里仅关注 argc 和 argv 的处理过程。

TEXT runtime·rt0_go(SB),NOSPLIT|TOPFRAME,$0
	// copy arguments forward on an even stack
	MOVQ	DI, AX		// argc
	MOVQ	SI, BX		// argv
	SUBQ	$(4*8+7), SP		// 2args 2auto
	ANDQ	$~15, SP
	MOVQ	AX, 16(SP)
	MOVQ	BX, 24(SP)
	...
	MOVL	16(SP), AX		// copy argc
	MOVL	AX, 0(SP)
	MOVQ	24(SP), AX		// copy argv
	MOVQ	AX, 8(SP)
	CALL	runtime·args(SB)
	CALL	runtime·osinit(SB)
	CALL	runtime·schedinit(SB)
	...

经过一系列操作之后,argc 和 argv 又被折腾回了栈内存 <span style="font-size: 15px;">0(SP)</span><span style="font-size: 15px;">8(SP)</span> 中。

<span style="font-size: 15px;">args</span> 函数位于<span style="font-size: 15px;">runtime/runtime1.go</span>

var (
 argc int32
 argv **byte
)

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

在这里,argc 和 argv 分别被保存至变量<span style="font-size: 15px;">runtime.argc</span><span style="font-size: 15px;">runtime.argv</span>

<span style="font-size: 15px;">rt0_go</span>函数中调用执行完<span style="font-size: 15px;">args</span>函数后,还会执行<span style="font-size: 15px;">schedinit</span>

func schedinit() {
  ...
 goargs()
 ...

<span style="font-size: 15px;">goargs</span>实现于<span style="font-size: 15px;">runtime/runtime1.go</span>

var argslice []string

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

该函数的目的是,将指向栈内存的命令行参数字符串指针,封装成 Go 的 <span style="font-size: 15px;">string</span>类型,最终保存于<span style="font-size: 15px;">runtime.argslice</span>

这里有个知识点,Go 是如何将 C 字符串封装成 Go string 类型的呢?答案就在以下代码。

func gostringnocopy(str *byte) string {
 ss := stringStruct{str: unsafe.Pointer(str), len: findnull(str)}
 s := *(*string)(unsafe.Pointer(&ss))
 return s
}

func argv_index(argv **byte, i int32) *byte {
 return *(**byte)(add(unsafe.Pointer(argv), uintptr(i)*sys.PtrSize))
}

func add(p unsafe.Pointer, x uintptr) unsafe.Pointer {
 return unsafe.Pointer(uintptr(p) + x)
}

此时,Go 已经将 argc 和 argv 的信息保存至<span style="font-size: 15px;">runtime.argslice</span>中,那聪明的你一定能猜到os.Args方法就是读取的该slice。

<span style="font-size: 15px;">os/proc.go</span>中,是它的实现

var Args []string

func init() {
 if runtime.GOOS == "windows" {
  // Initialized in exec_windows.go.
  return
 }
 Args = runtime_args()
}

func runtime_args() []string // in package runtime

<span style="font-size: 15px;">runtime_args</span>方法的实现是位于 <span style="font-size: 15px;">runtime/runtime.go</span>中的<span style="font-size: 15px;">os_runtime_args</span>函数

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

在这里实现了<span style="font-size: 15px;">runtime.argslice</span>的拷贝。至此,<span style="font-size: 15px;">os.Args</span>方法最终成功加载了命令行参数 argv 信息。

요약

이 기사에서는 Go에서 <span style="font-size: 15px;">os.Args</span>解析程序启动时的命令行参数,并学习了它的实现过程。

在加载实现的源码学习中,我们发现如果从一个点出发,去追溯它的实现原理,这个过程并不复杂,希望童鞋们不要惧怕研究源码。

<span style="font-size: 15px;">os.Args</span>方法将命令行参数存储在字符串切片中,通过遍历即可提取它们。但在实际开发中我们一般不会直接使用<span style="font-size: 15px;">os.Args</span> os.Args는 프로그램이 시작될 때 명령줄 매개변수를 구문 분석하고 구현 프로세스를 학습합니다.

🎜로딩 구현의 소스 코드를 연구하는 동안 한 지점에서 시작하여 구현 원리를 추적하면 프로세스가 복잡하지 않다는 것을 알았습니다. . 🎜🎜🎜🎜os.Args🎜🎜method 변경 명령줄 매개변수 문자열 조각에 저장되어 있으며 순회를 통해 추출할 수 있습니다. 하지만 실제 개발에서는 일반적으로 직접 사용하지 않습니다🎜🎜os.Args🎜 🎜 메서드는 Go가 더 유용한 플래그 패키지를 제공하기 때문입니다. 다만, 공간상의 이유로 이 부분의 내용은 나중에 쓰겠습니다. 🎜🎜🎜🎜

위 내용은 Go가 시작 매개변수 로딩을 구현하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 Go语言进阶学习에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제