Go實現啟動引數載入
導讀 | 剛學Go的同學一定思考過 Go 程式的啟動過程,關於這個問題可以看饒大的文章Go程式是怎樣跑起來的。今天我們將問題縮小,來學習Go程式是怎麼載入啟動引數,以及如何進行引數解析。 |
學習過 C 語言的童鞋,一定對 argc 和 argv 不會陌生。
C 程式總是從主函式 main 開始執行的,而在帶引數的主函式中,依照慣例,會使用 argc 和 argv 的命名作為主函式引數。
其中,argc (argument count)代表的是 行引數個數,argv(argument value) 是用來存放指向引數的指標陣列。
#includeint 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 語言中,又該如何獲取 行引數呢?
同 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 的重新抽象。
Go彙編為了簡化彙編程式碼的編寫,引入了 PC、FP、SP、SB 四個偽暫存器。
四個偽暫存器加上其它的通用暫存器就是 Go 組合語言對 CPU 的重新抽象。當然,該抽象的結構也適用於其它非 X86 型別的體系結構。
回到正題,命令列引數的解析過程是程式啟動中的一部分內容。
以 amd64 系統為例,Go 程式的執行入口位於runtime/rt0_linux_amd64.s。
TEXT _rt0_amd64_linux(SB),NOSPLIT,$-8 JMP _rt0_amd64(SB)
_rt0_amd64函式實現於 runtime/asm_amd64.s
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 暫存器。
rt0_go函式完成了 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 又被折騰回了棧記憶體 0(SP)和 8(SP) 中。
args 函式位於runtime/runtime1.go中
var ( argc int32 argv **byte ) func args(c int32, v **byte) { argc = c argv = v sysargs(c, v) }
在這裡,argc 和 argv 分別被儲存至變數runtime.argc和runtime.argv。
在rt0_go函式中呼叫執行完args函式後,還會執行schedinit。
func schedinit() { ... goargs() ...
goargs實現於runtime/runtime1.go
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 的 string型別,最終儲存於runtime.argslice。
這裡有個知識點,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 的資訊儲存至runtime.argslice中,那聰明的你一定能猜到os.Args方法就是讀取的該slice。
在os/proc.go中,是它的實現
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
而runtime_args方法的實現是位於 runtime/runtime.go中的os_runtime_args函式
//go:linkname os_runtime_args os.runtime_args func os_runtime_args() []string { return append([]string{}, argslice...) }
在這裡實現了runtime.argslice的拷貝。至此,os.Args方法最終成功載入了命令列引數 argv 資訊。
本文我們介紹了 Go 可以利用os.Args解析程式啟動時的命令列引數,並學習了它的實現過程。
在載入實現的原始碼學習中,我們發現如果從一個點出發,去追溯它的實現原理,這個過程並不複雜,希望童鞋們不要懼怕研究原始碼。
os.Args方法將命令列引數儲存在字串切片中,通過遍歷即可提取它們。但在實際開發中我們一般不會直接使用os.Args方法,因為 Go 為我們提供了一個更好用的 flag 包。但鑑於篇幅原因,該部分的內容以後再寫了。
原文來自:
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69955379/viewspace-2853128/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- docker學習筆記-啟動映象輸入引數Docker筆記
- 滾動載入圖片(懶載入)實現原理
- Go 快速入門指南 - 變長引數Go
- 『學了就忘』Linux啟動引導與修復 — 69、啟動載入程式(grub)Linux
- php 自動類載入類 composer.json 實現自動載入PHPJSON
- 實際案例:如何實現報表回寫時引數聯動輸入資料
- 如何實現 Java SpringBoot 自動驗證入引數據的有效性JavaSpring Boot
- jenkins 實現二級聯動選擇引數Jenkins
- Spring Boot中實現輸入引數驗證教程Spring Boot
- 下拉表關聯非同步載入- 引數動態過濾非同步
- Go 如何實現熱重啟Go
- 實時重新載入go應用Go
- 檢視JVM預設引數及微調JVM啟動引數JVM
- SpringMVC實現引數校驗SpringMVC
- SpringBoot 引數別名實現Spring Boot
- 移動端無限滾動載入 js實現原理JS
- 怎麼啟動 VIM?14 個 VIM 啟動引數和啟動方法詳解
- 優雅的實現動態載入 css、jsCSSJS
- 『動善時』JMeter基礎 — 24、JMeter中使用“使用者引數”實現引數化JMeter
- 行動硬碟引數錯誤怎麼解決?行動硬碟開啟出現引數錯誤的修復方法硬碟
- 如何透過動態引數實現週報製作
- BIRT 中根據引數實現動態日期分組
- 鈑金件如何實現自動引數化設計
- vue實現首屏載入等待動畫 避免首次載入白屏尷尬Vue動畫
- kubernetes實踐之十五:Kubernetes叢集主要啟動引數說明
- go-可變引數Go
- Go 接收命令列引數Go命令列
- Dolphinscheduler不重啟載入Oracle驅動Oracle
- MongoDB啟動檔案配置引數詳解MongoDB
- 認識Tomcat核心元件及其啟動引數Tomcat元件
- Docker(十七)-修改Docker容器啟動配置引數Docker
- 2、flask-run啟動引數詳解Flask
- java 啟動命令 java -jar 如何追加引數JavaJAR
- 如何檢視docker run啟動引數命令Docker
- fixtrue基礎之params引數實現簡單引數化
- JVM效能最佳化 —— 類載入器,手動實現類的熱載入JVM
- 線上直播系統原始碼,實現翻頁載入、下拉滾動載入原始碼
- JS實現載入層JS