圖文詳解Go程序如何編譯并運行起來的
Go程序是如何編譯的
從hello RdrB1te開始
package main import "fmt" func main() { fmt.Println("hello RdrB1te") }
不實際編譯它,只輸出它的編譯過程:
go build -n
簡單的編譯過程分析:
上面的過程確認了兩個事情:
- Runtime會永遠隨著用戶代碼一起編譯
- 在windows平臺上編譯出來了一個exe的可執(zhí)行文件
Go 編譯過程
詞法分析
- 將源代碼翻譯成Token
- Token是代碼中的最小語義結(jié)構(gòu)(如變量名、關(guān)鍵字、運算符等不可拆分的最小單元)
句法分析
- Token序列經(jīng)過處理,變成語法樹
語義分析
- 類型檢查
- 類型推斷
- 查看類型是否匹配
- 函數(shù)調(diào)用內(nèi)聯(lián)
- 逃逸分析
中間碼生成:
- 為了處理不同平臺的差異,先生成中間代碼(SSA)
查看從代碼到中間碼(SSA)生成的整個過程
$env:GOSSAFUNC="main" # windows powershell export GOSSAFUNC=main # linux go build
會看到如下輸出:
用瀏覽器打開ssa.html文件:
sources就是你的源代碼,AST就是生成的語法樹,genssa就是生成的與平臺無關(guān)的中間碼SSA,當(dāng)然中間還有很多的其它步驟,這里不再列舉,可以點擊展開查看
機器碼生成:
- 先生成Plan9匯編代碼(與平臺相關(guān))
- 最后編譯為機器碼
- 輸出的機器碼為.a文件
查看Plan9匯編代碼
go build -gcflags -S main.go
鏈接:
- 將各個包進行鏈接,包括runtime,最終生成可執(zhí)行文件
Go程序是如何運行起來的
Go程序的入口?
是下面的main方法嗎?當(dāng)然不是
func main() { fmt.Println("hello RdrB1te") }
是runtime包下面的rt0_xxx.s文件,下面以Linux x86芯片架構(gòu)上面運行的rt0_linux-amd64.s舉例:
TEXT _rt0_amd64_linux(SB),NOSPLIT,$-8 JMP _rt0_amd64(SB)
只要用了x86芯片架構(gòu)都要進入到_rt0_amd64
這個方法中去,這個方法調(diào)到了哪里呢,選中雙擊shift
,打開在文件中查找:找到下面這行
在asm_amd64.s
這個文件中的這段代碼:
TEXT _rt0_amd64(SB),NOSPLIT,$-8 MOVQ 0(SP), DI // argc LEAQ 8(SP), SI // argv JMP runtime·rt0_go(SB)
意思是讀取命令行參數(shù),復(fù)制參數(shù)數(shù)量argc
和參數(shù)值argv
到棧上,然后調(diào)用了runtime·rt0_go
這個方法,這方法的位置就在這個文件的下面:
TEXT runtime·rt0_go(SB),NOSPLIT|TOPFRAME,$0 // copy arguments forward on an even stack MOVQ DI, AX // argc MOVQ SI, BX // argv SUBQ $(5*8), SP // 3args 2auto ANDQ $~15, SP MOVQ AX, 24(SP) MOVQ BX, 32(SP) // create istack out of the given (operating system) stack. // _cgo_init may update stackguard. MOVQ $runtime·g0(SB), DI
上面這段的意思時初始化g0執(zhí)行棧,g0是為了調(diào)度協(xié)程而產(chǎn)生的協(xié)程,g0是每個Go程序的第一個協(xié)程。繼續(xù)往下面看,找到下面這段:
CALL runtime·check(SB)
這行是第一次調(diào)用的go語言方法,要找到這個方法可以選中雙擊shift
,找到下面這行:
進入:
func check(){ }
check方法主要是做運行時檢測:
- 檢查各種類型的長度
- 檢查指針操作
- 檢查結(jié)構(gòu)體字段的偏移量
- 檢查atomic原子操作
- 檢查CAS操作
- 檢查棧大小是否是2的冪次
繼續(xù)往下看,可以通過Ctrl+Alt+左右箭頭
進行快速跳轉(zhuǎn)回退或前進,退到這個位置:
CALL runtime·check(SB) MOVL 24(SP), AX // copy argc MOVL AX, 0(SP) MOVQ 32(SP), AX // copy argv MOVQ AX, 8(SP) CALL runtime·args(SB) CALL runtime·osinit(SB) CALL runtime·schedinit(SB) // create a new goroutine to start program MOVQ $runtime·mainPC(SB), AX // entry PUSHQ AX CALL runtime·newproc(SB) POPQ AX
runtime·args(SB)
:參數(shù)初始化runtime.args
,對命令行中的參數(shù)進行處理,參數(shù)數(shù)量賦值給argc int32
,參數(shù)值復(fù)制給argv **byte
runtime·osinit
:判斷操作系統(tǒng),執(zhí)行相應(yīng)的初始化組件,供調(diào)度器初始化所用runtime·schedinit
: 初始化Go調(diào)度器。初始化調(diào)度器會做哪些事情:
- 全局??臻g內(nèi)存分配
- 加載命令行參數(shù)到 os.Args
- 堆內(nèi)存空間的初始化
- 加載操作系統(tǒng)環(huán)境變量
- 初始化當(dāng)前系統(tǒng)線程
- 垃圾回收器的參數(shù)初始化
- 算法初始化(map、hash)
- 設(shè)置 process 數(shù)量
繼續(xù)往下看:
// create a new goroutine to start program MOVQ $runtime·mainPC(SB), AX // entry PUSHQ AX CALL runtime·newproc(SB) POPQ AX // start this M CALL runtime·mstart(SB) CALL runtime·abort(SB) // mstart should never return RET // mainPC is a function value for runtime.main, to be passed to newproc. // The reference to runtime.main is made via ABIInternal, since the // actual function (not the ABI0 wrapper) is needed by newproc. DATA runtime·mainPC+0(SB)/8,$runtime·main<ABIInternal>(SB)
MOVQ $runtime·mainPC(SB)
:取mainPC
的地址,這個mainPC
的地址就是runtime·main
這個方法的地址CALL runtime·newproc
:創(chuàng)建一個新的協(xié)程(主協(xié)程),執(zhí)行runtime·main
這個方法(主函數(shù)),放入調(diào)度器等待調(diào)度CALL runtime·mstart(SB)
:初始化一個M,用來調(diào)度主協(xié)程,主協(xié)程開始執(zhí)行主函數(shù)。
看下runtime·main
這個方法里面干了什么,選中雙擊shift
,找到下面這行:
進入:
// The main goroutine. func main() { doInit(&runtime_inittask) // 執(zhí)行runtime包中的init方法 gcenable() // 啟動GC垃圾回收器 doInit(&main_inittask) //執(zhí)行用戶包依賴的init方法 fn := main_main // 執(zhí)行用戶主函數(shù)main.mian() fn() }
按住ctrl
進入main_main
:
//go:linkname main_main main.main func main_main()
主協(xié)程執(zhí)行主函數(shù):
- 執(zhí)行runtime包中的init方法
- 啟動GC垃圾回收器
- 執(zhí)行用戶包依賴的init方法
- 執(zhí)行用戶主函數(shù)
main.mian()
總結(jié)
- Go啟動時經(jīng)歷了檢查、各種初始化、初始化協(xié)程調(diào)度的過程
main.main()
也是在協(xié)程中運行的
到此這篇關(guān)于Go程序如何編譯并運行起來的文章就介紹到這了,更多相關(guān)Go編譯運行內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go打印結(jié)構(gòu)體提升代碼調(diào)試效率實例詳解
這篇文章主要介紹了Go打印結(jié)構(gòu)體提升代碼調(diào)試效率實例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2024-02-02Go?Excelize?API源碼閱讀Close及NewSheet方法示例解析
這篇文章主要為大家介紹了Go?Excelize?API源碼閱讀Close及NewSheet方法示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-08-08