第三十天:繼續前進
- Day: 30
- 發佈日期: 2019-10-15
- 原文: https://ithelp.ithome.com.tw/articles/10228328
開始
回首第一篇規劃這個系列方向時,我一股腦列出許多主題,現在看來當然是像是螞蟻要對抗巨人一般可笑。事實上,認真要追蹤那些主題的話,都可以寫成多於一整個系列的鐵人文篇幅吧。當時沒有想到這麼多,這是時間軸上由前往後、由後往前之間的差異。
最後一章名為開始,是因為後面的路還很長。筆者日常工作完全摸不到 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 語言的關鍵。
g0和gsignal分別是怎麼來的?如何生成或指派的? - fn 函式?
- 怎麼開啟具備 race 功能的編譯模式?
skipPC的具體用途?- GO 語言抽象了所有不同架構,仍然保持
PC、SP、FP、LR等關鍵抽象暫存器,這些對於整個 stack trace 功能的具體實作為何? - goroutine 的構成,顯然是理解 GO 語言的關鍵。
g0和gsignal分別是怎麼來的?如何生成或指派的? - moduledata 有沒有別的意思?就是 symbol table 而已嗎?
- 在
heap.go裡面看到很多 heap 的管理都有強調不能使用 heap 來管理 heap,這如何作到? - 記憶體初始化的細節?
- 亂數為何需要在程序啟動的早期設置?
fastrand的用意為何? 為何選定特殊的魔術數字1597334677? - stackguard 顧名思義是 stack 的保護機制,GO 如何實作這個功能?
- GC 如何影響
m與g的運作? g和p之間的關係?- 為什麼 maps 要在
alginit之後才能用? - atomic 系列函式是如何實作的?
- 常常看到註解內有
//go:nosplit這種實際上類似給編譯器的 hint,運作機制是? - 為什麼 windows 不用相關機制來處理參數?windows 還是可以有命令列程式吧?
gostringnocopy函式裡面有一些魔幻的手法在轉換結構體與string型別變數,後面的指標機制怎麼實作?- 為什麼環境變數的陣列建構時不用
gostringnocopy? - 兩個選項一起設置的話,印出的部份會互相干擾,這難道不是 bug 嗎?
schedtrace真的有實際用途嗎?用在何處?- 為什麼
workbuf的大小綁定 2K 呢? allp的處理是看到了,那allm和allg呢?rbp在剛進入newproc函式時是 0,合理嗎?- xmm0 的操作是怎麼回事?
- 為什麼不能直接傳入
func1就好呢?這樣不是還能省一個記憶體的存取嗎? - 為什麼要傳
newproc的回傳位址給將由無名函式呼叫的newproc1呢? acquirem的註解為何是與p是否被存取有關?心理需要更好的 model 來理解這些 GO 語言的抽象物件了...acquirem和releasem的語意應該要有 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,他們的關聯是什麼?
getg、getcaller*函式好像都沒有本體,所以應該是 compiler 生成的?相關的程式在哪裡呢?minit函式前得住解說不能配置記憶體是什麼意思?getg().m.procid賦值自gettid,為什麼說是為了 debugger 的?- 接收到 signal 的時候,GO 語言的處理方式是?
- 之前也曾經為此混亂過,總覺得有時候註解的文意裡面不會太區分 M、P 的概念。之後應該了解一下
wakep函式與newm函式。 - 之前也問過了,可是為什麼函式指標要傳程式碼的指標的指標?
- 到底為什麼會有
xxx1這種函式命名法? - GO 語言的 rwmutex 機制是什麼?
- 系統監視者函式具體來說是作什麼的?
- 之前也問過了,可是為什麼函式指標要傳程式碼的指標的指標?
- 什麼叫做主要 goroutine?主要 OS thread?難道不是
runtime.g0和runtime.m0嗎? defer和go都是很常用的非同步關鍵字。它們生效的機制是什麼?(使用 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)與不甘心等等混雜的情感,以及對於明年的自己再度興起的期待。連結起來,就是身為技術人穿越時間與自己對話、學習的過程吧。
各位讀者,祝福你們都能夠在這個系列中獲得自己想要的東西,若是有不足的部份也歡迎你們批評指教。無論如何,我們明年再見!