iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 30
1
Software Development

入吾 Go 中:走訪 Go 語言內部實作系列 第 30

第三十天:繼續前進

開始


回首第一篇規劃這個系列方向時,我一股腦列出許多主題,現在看來當然是像是螞蟻要對抗巨人一般可笑。事實上,認真要追蹤那些主題的話,都可以寫成多於一整個系列的鐵人文篇幅吧。當時沒有想到這麼多,這是時間軸上由前往後、由後往前之間的差異。

最後一章名為開始,是因為後面的路還很長。筆者日常工作完全摸不到 GO 語言,此前完全只能說事業餘愛好者,這次鐵人賽雖然也算從頭到尾走過了 Hello World 的啟動,但對比已獲得的與已知不足的,這顯然也是走馬看花的小小郊遊罷了。

仍然充滿感謝。

感謝 GO 語言社群與前人的努力、

感謝親友的支持、感謝讀者的監督、

感謝一起並肩齊行的 IT 鐵人們每日發文的激勵作用 ... 而最感謝的還是 ITHome 願意主辦這樣的活動。讓有時覺得自己如同螺絲釘一般渺小的 IT 人有一個只跟自己比耐力的舞台。去年因故無法參賽,今年終於再回來了。

我對於 GO 語言仍然充滿好奇。固然,若說當今世上偉大的系統專案是巍峨的建築,C 語言仍是它們的紅磚:讓人信賴、習慣、有效率。但是進入到雲端時代的系統已經有了新的風貌:Docker、Kubernetes 等大型分散式系統及其基石,所使用的建築基本材料已經是 GO 而非 C。在語言設計上的優勢使得它編譯快速、就算與 C 語言對接也能呈現良好功能,但開發的方便性與易維護性使它在這類專案中廣為使用。

撰寫此篇的同時,GO 1.14 剛釋出,筆者日常業務的一部分的 CPU 架構 RISC-V 也進入了上游支援。GO 語言未來的延展性仍然是令人期待的。接下來列出系列文中留下的未解問題,當作之後繼續在學習 GO 語言的旅途中的指引。他們有些是關於 GO 語言的文化,有些則關於技術本身;有些系統問題龐大的可以專書探討,有些則是一些小觀念尚未打通。無論如何都是這系列中最真實的另外一面:系列文記載我所理解的部份,而這些問題代表我尚未了解的。

留下來的問題


  • 所謂的 netpoll 系統是指什麼?顯然在創建檔案的時候很重要。
  • runtime.SetFinalizer 是什麼?在整個 GO 語言 runtime 中扮演何種角色?
  • 追蹤過程中發現搶佔是可以被關掉的,也就是說 GO 語言有非同步的搶佔引擎。其機制為何?
  • arg.(type) 這種功能被稱作 reflect。GO 語言的 reflect 是怎麼做的?
  • internal/race 是怎麼樣的函式庫?功能?
  • sync 是怎樣的函式庫?功能?
  • runtime.KeepAlive 大致上可以顧名思義。但為什麼它出現在讀寫之後?讀寫之前難道就沒有被 runtime 影響的危險嗎?
  • 處理管線錯誤訊號的時候有瞄到 sigpipe,GO 語言如何處理 signal?
  • .gopclntab 區段是什麼?
  • 初始化後什麼時候開始進入通用的 GO 語言部份?
  • import 關鍵字有時會引用多層結構,為什麼要這樣作?
  • 常常看見 internal 什麼什麼。內部的這個關鍵字的差異是什麼?這些函式庫不都是內部的的嗎?
  • unsafe 的用途。
  • sched_getaffinity 並沒有像之前 write 那樣最終導到 Syscall 去。
  • GO 命名的歷史淵源,還有為什麼 runtime 跟大家都不一樣?是否是 linker 之類的工具鏈限制使然?
  • goroutine 的構成,顯然是理解 GO 語言的關鍵。 g0gsignal 分別是怎麼來的?如何生成或指派的?
  • fn 函式?
  • 怎麼開啟具備 race 功能的編譯模式?
  • skipPC 的具體用途?
  • GO 語言抽象了所有不同架構,仍然保持 PCSPFPLR 等關鍵抽象暫存器,這些對於整個 stack trace 功能的具體實作為何?
  • goroutine 的構成,顯然是理解 GO 語言的關鍵。 g0gsignal 分別是怎麼來的?如何生成或指派的?
  • moduledata 有沒有別的意思?就是 symbol table 而已嗎?
  • heap.go 裡面看到很多 heap 的管理都有強調不能使用 heap 來管理 heap,這如何作到?
  • 記憶體初始化的細節?
  • 亂數為何需要在程序啟動的早期設置? fastrand 的用意為何? 為何選定特殊的魔術數字 1597334677
  • stackguard 顧名思義是 stack 的保護機制,GO 如何實作這個功能?
  • GC 如何影響 mg 的運作?
  • gp 之間的關係?
  • 為什麼 maps 要在 alginit 之後才能用?
  • atomic 系列函式是如何實作的?
  • 常常看到註解內有 //go:nosplit 這種實際上類似給編譯器的 hint,運作機制是?
  • 為什麼 windows 不用相關機制來處理參數?windows 還是可以有命令列程式吧?
  • gostringnocopy 函式裡面有一些魔幻的手法在轉換結構體與 string 型別變數,後面的指標機制怎麼實作?
  • 為什麼環境變數的陣列建構時不用 gostringnocopy
  • 兩個選項一起設置的話,印出的部份會互相干擾,這難道不是 bug 嗎?
  • schedtrace 真的有實際用途嗎?用在何處?
  • 為什麼 workbuf 的大小綁定 2K 呢?
  • allp 的處理是看到了,那 allmallg 呢?
  • rbp 在剛進入 newproc 函式時是 0,合理嗎?
  • xmm0 的操作是怎麼回事?
  • 為什麼不能直接傳入 func1 就好呢?這樣不是還能省一個記憶體的存取嗎?
  • 為什麼要傳 newproc 的回傳位址給將由無名函式呼叫的 newproc1 呢?
  • acquirem 的註解為何是與 p 是否被存取有關?心理需要更好的 model 來理解這些 GO 語言的抽象物件了...
  • acquiremreleasem 的語意應該要有 atomic 的感覺,為何這裡不需要呢?GO 語言有什麼確保不會發生 race condition 的假設?
  • goroutine 被認為輕量的理由?
  • 執行期排程器的整個運作中,一個 M 是如何面對非同步事件或是阻塞型的系統呼叫?
  • 為何現在偏好的 approach 就不會有空轉?還是會有額外的 M 生成,並且在空轉找不到工作之後還是得休眠不是嗎?
  • gfget 到底有沒有可能挨餓?
  • gfget 之中,從 gFree.stack 拿到 G 的情況下,那兩種不同的 flag 是什麼?什麼時候可以使用相關功能?
  • malg 使用 new 關鍵字配置所需的記憶體,相關機制為何?所取得的的記憶體應該會在 heap 上。
  • 關於 mcache,為何註解說是 per-P 結構,這裡卻是由 M 來提取呢?
  • 兩個 stackGuard 分別有什麼用呢?註解中是有解釋,但是還是有點抽象。
  • write barrier 的詳細定義、功能,與使用的情境。
  • SIGSEGV 是什麼時候註冊好的?
  • 為什麼函式名稱裡面會有特殊字元?(如 runtime·goexit)是不是這種函式就無法在 gdb 裡面定位?
  • labels 成員代表的意義?profiling 的使用方法?
  • 為什麼 runtime.main 會有特殊的待遇,不被算在系統 goroutine 裡面?
  • tracer 的使用方法?
  • GO 與 gdb 的聯動還算可用,也是 binutils 處理的轉換嗎?
  • go:nosplit 具體來說是在哪些條件下必須要下?
  • 什麼才算是系統堆疊?
  • stack growth prologue 具體來說是指什麼?
  • 看到很多註解的部份提到必須要有 P 才能下 write barrier,他們的關聯是什麼?
  • getggetcaller* 函式好像都沒有本體,所以應該是 compiler 生成的?相關的程式在哪裡呢?
  • minit 函式前得住解說不能配置記憶體是什麼意思?
  • getg().m.procid 賦值自 gettid,為什麼說是為了 debugger 的?
  • 接收到 signal 的時候,GO 語言的處理方式是?
  • 之前也曾經為此混亂過,總覺得有時候註解的文意裡面不會太區分 M、P 的概念。之後應該了解一下 wakep 函式與 newm 函式。
  • 之前也問過了,可是為什麼函式指標要傳程式碼的指標的指標?
  • 到底為什麼會有 xxx1 這種函式命名法?
  • GO 語言的 rwmutex 機制是什麼?
  • 系統監視者函式具體來說是作什麼的?
  • 之前也問過了,可是為什麼函式指標要傳程式碼的指標的指標?
  • 什麼叫做主要 goroutine?主要 OS thread?難道不是 runtime.g0runtime.m0 嗎?
  • defergo 都是很常用的非同步關鍵字。它們生效的機制是什麼?(使用 gdb 已知 go 可能會觸發 runtime.newproc
  • 為什麼 main.main 會沒辦法被 linker 定位?還是說 GO 的連結方式有順序性,所以才強調 when laying down the runtime

檢討

這是第三次參賽了。有別於 2016 的跨界的追尋:trace 30個基本 Linux 系統呼叫與 2017 的與妖精共舞:在 RISC-V 架構上使用 GO 語言實作 binutils 工具包,當時的每一日就像是用競走甚至慢跑一樣的速度前進,非常疲累;這次更多的是帶著自助旅遊的心情,對於能夠理解的部份就深入一些去追蹤,搜索過資料之後無法理解的部份也不強求,相對之下是比較輕鬆的。所以一開始擬的龐大計畫看起來也無須羞赧,有些在這一趟獲得的,就會有一些在下一趟獲得。

整體來說,GO 語言的 Hello World 程式與所有其他程式一樣得從 CPU/作業系統相依的部份開始,由於內建 goroutine 這種輕量級的抽象執行單位而必需要精確控管 context,為了控管 context 又必須在仍非常早期的時候注意記憶體的狀況,比方說我們四處看到的那些編譯器選項: stack 是否可以被分割、該函式是否不能使用 write barrier 等等。之後的初始化,大多還是 G-M-P 三項之力在協調的。單就 Hello World,我們看到的還不夠多,還有許多其他的特色完全沒有觸碰到。

但那豈不是讓人更興奮嗎?我想,每一年度的鐵人賽不只是這 30 天的衝刺而已;它理所當然地包含事前充滿期待的計畫,以及事後的滿足、懊悔(XD)與不甘心等等混雜的情感,以及對於明年的自己再度興起的期待。連結起來,就是身為技術人穿越時間與自己對話、學習的過程吧。

各位讀者,祝福你們都能夠在這個系列中獲得自己想要的東西,若是有不足的部份也歡迎你們批評指教。無論如何,我們明年再見!


上一篇
第二十九天:終點的 main.main
系列文
入吾 Go 中:走訪 Go 語言內部實作30

1 則留言

0
阿展展展
iT邦好手 1 級 ‧ 2019-11-30 14:52:20

恭喜完賽/images/emoticon/emoticon64.gif
go 的世界博大精深阿/images/emoticon/emoticon13.gif

我要留言

立即登入留言