0

0

Go 如何实现启动参数的加载

Go语言进阶学习

Go语言进阶学习

发布时间:2023-07-21 13:20:43

|

1718人浏览过

|

来源于Go语言进阶学习

转载

刚学 Go 的同学一定思考过 Go 程序的启动过程,关于这个问题可以看饶大的文章 Go 程序是怎样跑起来的。今天我们将问题缩小,来学习 Go 程序是怎么加载启动参数,以及如何进行参数解析。

C 参数解析

学习过 C 语言的童鞋,一定对 argc 和 argv 不会陌生。

C 程序总是从主函数 main 开始执行的,而在带参数的主函数中,依照惯例,会使用 argc 和 argv 的命名作为主函数参数。

其中,argc (argument count)代表的是命令行参数个数,argv(argument value) 是用来存放指向参数的指针数组。

#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 加载

同 C 一样,Go 程序也是从 main 主函数开始(用户层)执行,但主函数中并没有定义 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>方法,因为 Go 为我们提供了一个更好用的 flag 包。但鉴于篇幅原因,该部分的内容以后再写了。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

76

2026.03.11

Go高并发任务调度与Goroutine池化实践
Go高并发任务调度与Goroutine池化实践

本专题围绕 Go 语言在高并发任务处理场景中的实践展开,系统讲解 Goroutine 调度模型、Channel 通信机制以及并发控制策略。内容包括任务队列设计、Goroutine 池化管理、资源限制控制以及并发任务的性能优化方法。通过实际案例演示,帮助开发者构建稳定高效的 Go 并发任务处理系统,提高系统在高负载环境下的处理能力与稳定性。

38

2026.03.10

Kotlin Android模块化架构与组件化开发实践
Kotlin Android模块化架构与组件化开发实践

本专题围绕 Kotlin 在 Android 应用开发中的架构实践展开,重点讲解模块化设计与组件化开发的实现思路。内容包括项目模块拆分策略、公共组件封装、依赖管理优化、路由通信机制以及大型项目的工程化管理方法。通过真实项目案例分析,帮助开发者构建结构清晰、易扩展且维护成本低的 Android 应用架构体系,提升团队协作效率与项目迭代速度。

83

2026.03.09

JavaScript浏览器渲染机制与前端性能优化实践
JavaScript浏览器渲染机制与前端性能优化实践

本专题围绕 JavaScript 在浏览器中的执行与渲染机制展开,系统讲解 DOM 构建、CSSOM 解析、重排与重绘原理,以及关键渲染路径优化方法。内容涵盖事件循环机制、异步任务调度、资源加载优化、代码拆分与懒加载等性能优化策略。通过真实前端项目案例,帮助开发者理解浏览器底层工作原理,并掌握提升网页加载速度与交互体验的实用技巧。

97

2026.03.06

Rust内存安全机制与所有权模型深度实践
Rust内存安全机制与所有权模型深度实践

本专题围绕 Rust 语言核心特性展开,深入讲解所有权机制、借用规则、生命周期管理以及智能指针等关键概念。通过系统级开发案例,分析内存安全保障原理与零成本抽象优势,并结合并发场景讲解 Send 与 Sync 特性实现机制。帮助开发者真正理解 Rust 的设计哲学,掌握在高性能与安全性并重场景中的工程实践能力。

223

2026.03.05

PHP高性能API设计与Laravel服务架构实践
PHP高性能API设计与Laravel服务架构实践

本专题围绕 PHP 在现代 Web 后端开发中的高性能实践展开,重点讲解基于 Laravel 框架构建可扩展 API 服务的核心方法。内容涵盖路由与中间件机制、服务容器与依赖注入、接口版本管理、缓存策略设计以及队列异步处理方案。同时结合高并发场景,深入分析性能瓶颈定位与优化思路,帮助开发者构建稳定、高效、易维护的 PHP 后端服务体系。

458

2026.03.04

AI安装教程大全
AI安装教程大全

2026最全AI工具安装教程专题:包含各版本AI绘图、AI视频、智能办公软件的本地化部署手册。全篇零基础友好,附带最新模型下载地址、一键安装脚本及常见报错修复方案。每日更新,收藏这一篇就够了,让AI安装不再报错!

169

2026.03.04

Swift iOS架构设计与MVVM模式实战
Swift iOS架构设计与MVVM模式实战

本专题聚焦 Swift 在 iOS 应用架构设计中的实践,系统讲解 MVVM 模式的核心思想、数据绑定机制、模块拆分策略以及组件化开发方法。内容涵盖网络层封装、状态管理、依赖注入与性能优化技巧。通过完整项目案例,帮助开发者构建结构清晰、可维护性强的 iOS 应用架构体系。

246

2026.03.03

C++高性能网络编程与Reactor模型实践
C++高性能网络编程与Reactor模型实践

本专题围绕 C++ 在高性能网络服务开发中的应用展开,深入讲解 Socket 编程、多路复用机制、Reactor 模型设计原理以及线程池协作策略。内容涵盖 epoll 实现机制、内存管理优化、连接管理策略与高并发场景下的性能调优方法。通过构建高并发网络服务器实战案例,帮助开发者掌握 C++ 在底层系统与网络通信领域的核心技术。

34

2026.03.03

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Go 教程
Go 教程

共32课时 | 6.1万人学习

Go语言实战之 GraphQL
Go语言实战之 GraphQL

共10课时 | 0.9万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号