中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Go程序的啟動流程是什么

發布時間:2021-10-14 10:29:17 來源:億速云 閱讀:146 作者:iii 欄目:編程語言

這篇文章主要講解了“Go程序的啟動流程是什么”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“Go程序的啟動流程是什么”吧!

Go 引導階段

查找入口

首先編譯上文提到的示例程序:

$ GOFLAGS="-ldflags=-compressdwarf=false" go build

在命令中指定了 GOFLAGS 參數,這是因為在 Go1.11 起,為了減少二進制文件大小,調試信息會被壓縮。導致在 MacOS 上使用 gdb  時無法理解壓縮的 DWARF 的含義是什么(而我恰恰就是用的 MacOS)。

因此需要在本次調試中將其關閉,再使用 gdb 進行調試,以此達到觀察的目的:

$ gdb awesomeProject  (gdb) info files Symbols from "/Users/eddycjy/go-application/awesomeProject/awesomeProject". Local exec file:  `/Users/eddycjy/go-application/awesomeProject/awesomeProject', file type mach-o-x86-64.  Entry point: 0x1063c80  0x0000000001001000 - 0x00000000010a6aca is .text  ... (gdb) b *0x1063c80 Breakpoint 1 at 0x1063c80: file /usr/local/Cellar/go/1.15/libexec/src/runtime/rt0_darwin_amd64.s, line 8.

通過 Entry point 的調試,可看到真正的程序入口在 runtime 包中,不同的計算機架構指向不同。例如:

  • MacOS 在 src/runtime/rt0_darwin_amd64.s。

  • Linux 在 src/runtime/rt0_linux_amd64.s。

其最終指向了 rt0_darwin_amd64.s 文件,這個文件名稱非常的直觀:

Breakpoint 1 at 0x1063c80: file  /usr/local/Cellar/go/1.15/libexec/src/runtime/rt0_darwin_amd64.s, line 8.

rt0 代表 runtime0 的縮寫,指代運行時的創世,超級奶爸:

  • darwin 代表目標操作系統(GOOS)。

  • amd64 代表目標操作系統架構(GOHOSTARCH)。

同時 Go 語言還支持更多的目標系統架構,例如:AMD64、AMR、MIPS、WASM 等:

Go程序的啟動流程是什么

源碼目錄

若有興趣可到 src/runtime 目錄下進一步查看,這里就不一一介紹了。

入口方法

在 rt0_linux_amd64.s 文件中,可發現 _rt0_amd64_darwin JMP 跳轉到了 _rt0_amd64 方法:

TEXT _rt0_amd64_darwin(SB),NOSPLIT,$-8  JMP _rt0_amd64(SB) ...

緊接著又跳轉到 runtime·rt0_go 方法:

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

該方法將程序輸入的 argc 和 argv 從內存移動到寄存器中。

棧指針(SP)的前兩個值分別是 argc 和 argv,其對應參數的數量和具體各參數的值。

開啟主線

程序參數準備就緒后,正式初始化的方法落在 runtime·rt0_go 方法中:

TEXT runtime·rt0_go(SB),NOSPLIT,$0  ...  CALL runtime·check(SB)  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)   // create a new goroutine to start program  MOVQ $runtime·mainPC(SB), AX  // entry  PUSHQ AX  PUSHQ $0   // arg size  CALL runtime·newproc(SB)  POPQ AX  POPQ AX   // start this M  CALL runtime·mstart(SB)  ...
  • runtime.check:運行時類型檢查,主要是校驗編譯器的翻譯工作是否正確,是否有 “坑”。基本代碼均為檢查 int8 在 unsafe.Sizeof  方法下是否等于 1 這類動作。

  • runtime.args:系統參數傳遞,主要是將系統參數轉換傳遞給程序使用。

  • runtime.osinit:系統基本參數設置,主要是獲取 CPU 核心數和內存物理頁大小。

  • runtime.schedinit:進行各種運行時組件的初始化,包含調度器、內存分配器、堆、棧、GC 等一大堆初始化工作。會進行 p 的初始化,并將 m0  和某一個 p 進行綁定。

  • runtime.main:主要工作是運行 main goroutine,雖然在runtime·rt0_go  中指向的是$runtime·mainPC,但實質指向的是 runtime.main。

  • runtime.newproc:創建一個新的 goroutine,且綁定 runtime.main 方法(也就是應用程序中的入口 main  方法)。并將其放入 m0 綁定的p的本地隊列中去,以便后續調度。

  • runtime.mstart:啟動 m,調度器開始進行循環調度。

  • 在 runtime·rt0_go 方法中,其主要是完成各類運行時的檢查,系統參數設置和獲取,并進行大量的 Go 基礎組件初始化。

初始化完畢后進行主協程(main goroutine)的運行,并放入等待隊列(GMP 模型),最后調度器開始進行循環調度。

小結

根據上述源碼剖析,可以得出如下 Go 應用程序引導的流程圖:

Go程序的啟動流程是什么

Go 程序引導過程

在 Go 語言中,實際的運行入口并不是用戶日常所寫的 main func,更不是 runtime.main 方法,而是從 rt0_*_amd64.s  開始,最終再一路 JMP 到 runtime·rt0_go 里去,再在該方法里完成一系列 Go 自身所需要完成的絕大部分初始化動作。

其中整體包括:

  • 運行時類型檢查、系統參數傳遞、CPU 核數獲取及設置、運行時組件的初始化(調度器、內存分配器、堆、棧、GC 等)。

  • 運行 main goroutine。

  • 運行相應的 GMP 等大量缺省行為。

  • 涉及到調度器相關的大量知識。

后續將會繼續剖析將進一步剖析 runtime·rt0_go 里的愛與恨,尤其像是 runtime.main、runtime.schedinit  等調度方法,都有非常大的學習價值,有興趣的小伙伴可以持續關注。

Go 調度器初始化

知道了 Go 程序是怎么引導起來的之后,我們需要了解 Go Runtime 中調度器是怎么流轉的。

runtime.mstart

這里主要關注 runtime.mstart 方法:

func mstart() {  // 獲取 g0  _g_ := getg()   // 確定棧邊界  osStack := _g_.stack.lo == 0  if osStack {   size := _g_.stack.hi   if size == 0 {    size = 8192 * sys.StackGuardMultiplier   }   _g_.stack.hi = uintptr(noescape(unsafe.Pointer(&size)))   _g_.stack.lo = _g_.stack.hi - size + 1024  }  _g_.stackguard0 = _g_.stack.lo + _StackGuard  _g_.stackguard1 = _g_.stackguard0      // 啟動 m,進行調度器循環調度  mstart1()   // 退出線程  if mStackIsSystemAllocated() {   osStack = true  }  mexit(osStack) }
  • 調用 getg 方法獲取 GMP 模型中的 g,此處獲取的是 g0。

  • 通過檢查 g 的執行棧 _g_.stack 的邊界(堆棧的邊界正好是 lo, hi)來確定是否為系統棧。若是,則根據系統棧初始化 g  執行棧的邊界。

  • 調用 mstart1 方法啟動系統線程 m,進行調度器循環調度。

  • 調用 mexit 方法退出系統線程 m。

runtime.mstart1

這么看來其實質邏輯在 mstart1 方法,我們繼續往下剖析:

func mstart1() {  // 獲取 g,并判斷是否為 g0  _g_ := getg()  if _g_ != _g_.m.g0 {   throw("bad runtime·mstart")  }   // 初始化 m 并記錄調用方 pc、sp  save(getcallerpc(), getcallersp())  asminit()  minit()   // 設置信號 handler  if _g_.m == &m0 {   mstartm0()  }  // 運行啟動函數  if fn := _g_.m.mstartfn; fn != nil {   fn()  }   if _g_.m != &m0 {   acquirep(_g_.m.nextp.ptr())   _g_.m.nextp = 0  }  schedule() }
  • 調用 getg 方法獲取 g。并且通過前面綁定的 _g_.m.g0 判斷所獲取的 g 是否 g0。若不是,則直接拋出致命錯誤。因為調度器僅在 g0  上運行。

  • 調用 minit 方法初始化 m,并記錄調用方的 PC、SP,便于后續 schedule 階段時的復用。

  • 若確定當前的 g 所綁定的 m 是 m0,則調用 mstartm0 方法,設置信號 handler。該動作必須在 minit 方法之后,這樣 minit  方法可以提前準備好線程,以便能夠處理信號。

  • 若當前 g 所綁定的 m 有啟動函數,則運行。否則跳過。

  • 若當前 g 所綁定的 m 不是 m0,則需要調用 acquirep 方法獲取并綁定 p,也就是 m 與 p 綁定。

  • 調用 schedule 方法進行正式調度。

忙活了一大圈,終于進入到開題的主菜了,原來潛伏的很深的 schedule 方法才是真正做調度的方法,其他都是前置處理和準備數據。

由于篇幅問題,schedule 方法會放到下篇再繼續剖析,我們先聚焦本篇的一些細節點。

問題深剖

不過到這里篇幅也已經比較長了,積累了不少問題。我們針對在 Runtime 中出鏡率最高的兩個元素進行剖析:

  1. 鴻蒙官方戰略合作共建——HarmonyOS技術社區

  2. m0 是什么,作用是?

  3. g0 是什么,作用是?

m0

m0 是 Go Runtime 所創建的第一個系統線程,一個 Go 進程只有一個 m0,也叫主線程。

從多個方面來看:

  • 數據結構:m0 和其他創建的 m 沒有任何區別。

  • 創建過程:m0 是進程在啟動時應該匯編直接復制給 m0 的,其他后續的 m 則都是 Go Runtime 內自行創建的。

  • 變量聲明:m0 和常規 m 一樣,m0 的定義就是 var m0 m,沒什么特別之處。

g0

  • g 一般分為三種,分別是:

  • 執行用戶任務的叫做 g。

  • 執行 runtime.main 的 main goroutine。

執行調度任務的叫 g0。。

g0 比較特殊,每一個 m 都只有一個 g0(僅此只有一個 g0),且每個 m 都只會綁定一個 g0。在 g0  的賦值上也是通過匯編賦值的,其余后續所創建的都是常規的 g。

從多個方面來看:

數據結構:g0 和其他創建的 g 在數據結構上是一樣的,但是存在棧的差別。在 g0 上的棧分配的是系統棧,在 Linux 上棧大小默認固定  8MB,不能擴縮容。而常規的 g 起始只有 2KB,可擴容。

運行狀態:g0 和常規的 g 不一樣,沒有那么多種運行狀態,也不會被調度程序搶占,調度本身就是在 g0 上運行的。

變量聲明:g0 和常規 g,g0 的定義就是 var g0 g,沒什么特別之處。

感謝各位的閱讀,以上就是“Go程序的啟動流程是什么”的內容了,經過本文的學習后,相信大家對Go程序的啟動流程是什么這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

go
AI

江华| 紫云| 离岛区| 宜章县| 宜黄县| 百色市| 安达市| 晋江市| 沁水县| 永德县| 武鸣县| 方城县| 新巴尔虎左旗| 专栏| 漯河市| 沙雅县| 于田县| 东乌| 萍乡市| 公安县| 黑水县| 务川| 隆子县| 盐亭县| 延长县| 迁西县| 盘锦市| 哈尔滨市| 瑞安市| 常州市| 平凉市| 桐庐县| 济宁市| 精河县| 全椒县| 巍山| 东乌珠穆沁旗| 宁晋县| 张掖市| 江陵县| 昌平区|