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/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Chrome 啟動引數列表Chrome
- eclipse 啟動引數Eclipse
- docker學習筆記-啟動映象輸入引數Docker筆記
- 滾動載入圖片(懶載入)實現原理
- 使用Webview實現app啟動引導頁WebViewAPP
- Go 快速入門指南 - 變長引數Go
- js動態載入實現提高網頁載入速度JS網頁
- MongoDB啟動引數介紹MongoDB
- linux核心啟動引數Linux
- chrome啟動引數設定Chrome
- Eclipse 的啟動引數Eclipse
- 系統引導載入器的簡單實現
- 『學了就忘』Linux啟動引導與修復 — 69、啟動載入程式(grub)Linux
- 實際案例:如何實現報表回寫時引數聯動輸入資料
- 下拉表關聯非同步載入- 引數動態過濾非同步
- java--jvm啟動的引數JavaJVM
- MongoDB啟動引數中文詳解MongoDB
- eclipse 啟動引數介紹Eclipse
- Go 如何實現熱重啟Go
- php 自動類載入類 composer.json 實現自動載入PHPJSON
- 實時重新載入go應用Go
- ZendFramework自動載入類的實現方法Framework
- 封裝ListView,實現自動載入更多封裝View
- 微信小程式實現滾動載入更多微信小程式
- django 實現滾動載入的功能薦Django
- 啟動頁無法載入
- 啟動載入廣告頁面
- Jmeter使用_time函式實現同一個介面引數傳入可以每次傳入不同的引數JMeter函式
- jenkins 實現二級聯動選擇引數Jenkins
- 檢視JVM預設引數及微調JVM啟動引數JVM
- Spring Boot中實現輸入引數驗證教程Spring Boot
- jvm-All日誌啟動引數JVM
- Oracle 啟動例程 STARTUP引數說明Oracle
- Mongodb啟動命令mongod引數說明MongoDB
- 拖動滾動條實現內容自動載入效果
- 如何實現 Java SpringBoot 自動驗證入引數據的有效性JavaSpring Boot
- 怎麼啟動 VIM?14 個 VIM 啟動引數和啟動方法詳解
- 移動端無限滾動載入 js實現原理JS