iT邦幫忙

2021 iThome 鐵人賽

4
Software Development

予焦啦!Hoddarla 專案起步:使用 Golang 撰寫 RISC-V 作業系統的初步探索系列 第 31

予焦啦!結論與展望(一):Hoddarla 專案的過去、現在與未來

阮毋是喜愛虛華,阮只是環境來拖磨;
人客若叫阮,風雨嘛著行,為伊唱出留戀的情歌。
-- 流浪到淡水

終於進到結尾的部份了。過去三十天在鐵人賽官方規定的範疇內,筆者也算是半刻意地壓抑行文筆調,結果就是整體風格生硬死板;現在的話筆者當然就沒有什麼枷鎖,可以暢所欲言了。如果說這個系列是一齣公路電影,那接下來的結論與展望,就當作是額外收錄的幕後花絮吧。

過去

為什麼是 RISC-V 與 Golang?

捾著風琴、提著吉他,雙人牽做伙 ...

關於 Hoddarla 專案的雙軸線,RISC-V 與 Golang,筆者已經不太能夠考察其成形之原因了。手邊的資料翻來翻去,可以追溯到的是 2017 年初、年假時在日記裡面寫下的很粗略的想像:希望今年可以用 Golang 寫個作業系統跑在 RISC-V 上面。與其說這是個積極的目標,或許更可以解讀成是一種逃避。所逃避的,當然就是指兩者的主流對應:C 與 x86,作業系統的話當然就是 Linux 了。

當時想將之命名為 Ivix,一方面是因為 2017 的 XVII 可以重新組合,另一方面是因為感覺 -ix 是一種真正有在玩作業系統的感覺。現在想起來果然天真。

語言

C 語言的存在感實在是太強了。深厚的歷史(不是與人類文明史相比,而是與電腦科技史相比)、經過時間考驗的軟體專案們根本數不完、一流的效能、繁榮的生態圈 ... ,再加上任何讚美都是錦上添花。差不多是那個時候吧,也是 Jserv 老師的影響力遍及系統圈的時候,總是會一直在社群網站上看到各種邊邊角角的 C 語言問題;那些問題,就像賽揚強投每一球都投在好球帶框框上一樣,對於我這個玻璃心平庸打者來說,只是加深對於未來的焦慮。

焦慮之餘,心裡還會冒出一堆問號:學 C 語言,真的要學到那的地步才可以嗎?真的會有科技公司的面試官故意考未定義行為,然後還不肯承認嗎?嘿,說到底,真的有人會為了任何現實的理由,寫出那些程式碼嗎?總之,儘管 C 語言是我的母語,也幾乎可以說是我唯一的武器,但心裡實在是越來越不喜歡這個語言。推力自然形成。

至於 Golang 的拉力,當然就是簡潔、典範化,還有與之同時從一個 C 語言的平庸使用者的視角難以想像的強大功能與標準函式庫。典範化這一點其實在筆者心裡的潔癖佔了很大的因素,比方說,至今我們都還能夠在 Linux 文件當中看到這一段話:

First off, I'd suggest printing out a copy of the GNU coding standards,
and NOT read it.  Burn them, it's a great symbolic gesture.

多麼愚蠢的事情,空白和 tab 也可以戰?在 C 語言專案待太久,會讓人誤以為語言本身不該明訂這種規矩。

延伸閱讀:ISO C 語言標準是如何變得無法用於作業系統開發

很巧,我幾天前才看到這篇論文,描述 C 語言規格是如何將未定義行為委託給編譯器自行處理,而歷史悠久的 C 語言專案(尤其是作業系統)是如何因此建造在不穩顧的地基之上,往往只能仰賴編譯器的特殊實作。

後來的語言設計當然會試圖記取這教訓,其中也包含 Golang。

架構

筆者最早認真考慮也成為一個 OS 自幹者的時候,其實並沒有排除 x86 這個選項。事實上,為了表達我的認真程度,我自掏腰包印了四冊版的 Intel x86_64 手冊(花了超過四千元),然後招攬學弟妹們,「我們這暑假來開始 K 這個,然後我們自己試著能不能刻個 OS 出來吧!」

然後,那四本厚重的手冊,還躺在筆者實驗室的鐵櫃裡面,至少我離開時是如此;PDF 的話還可以搜尋,紙本規格書就只剩下前個世代的人會翻閱吧。至於那個計畫,一行程式碼都還沒有產出。因為 x86 啟動時所需的各種權限模式轉換的繁文縟節,直接勸退了當時的我們那個臭皮匠團。

所以當我第一次看到 RISC-V,不多關注一點,當然是說不過去的。印象中我應該也是在 RISC-V 已經有名到會出現在臉書牆上時,我才第一次接觸到。當然,也沒有說非常狂熱的投入,但就是隱約覺得,如果我真的要花時間讓系統軟體成為我的生命的一部分,我希望我能夠花在 RISC-V 上。至於 ARM?當然那時候 ARM 已經很有市占率,但也不知道為什麼,從來沒有起過那個念頭想要靠近它。

為什麼是 Hoddarla?

命名的經緯實在不足為外人道。總之是覺得,雖然沒有硬性規定,但軟體專案名稱幾乎一定是英文的原生字或是諧音字,我為什麼要繼續依循這種規則?實在有不甘心之感。如果往日文找,當然有機會多一些中二感,但台灣人有更好的選擇。

予焦啦,於是成為一個最後定案的選擇。因為一些台語詞,對於外國人來講應該不可能太好發音,所以應該找個簡單有力的。至於羅馬字的呈現,沒有選擇 Hodala 或是 Hodarla,是因為前者已經被飲料店使用,後者也已經被國內服飾品牌使用。我也忘記是其中哪一個,在東南亞某國的語彙裡面,但因為我沒有解析那些文本的能力,所以也不感貿然共用那些名字。最後就重複 d 表示短促的感覺?家人們都表示 8 個字元太長,但也沒辦法再砍了。

以結果來說,這個本來毫不相干的命名,慢慢咀嚼倒是有點意思。筆者素不飲酒,也並不欣賞各種酒品帶來的感受,會知道這個詞,也純粹是對於流浪到淡水這首歌很有親近感而已。如此矛盾,用來表示這個專案本身猶如醉漢勸酒的囈語,有一種無法迴避的滑稽感。

大致上的時間軸

人生浮沈起起落落毋免來煩惱,有時月圓有時也抹平 ...

從 2017 年初至今,也快要五年了。這一節紀錄一下與 Hoddarla 有關的大事記。

  • 2015 Q1 以 Golang programming 當作教材,擔任講師教授簡單的程式設計概念,但其實也只跑過 Go Playground 而已。
  • 2017 Q2 決定不唸博士了,把 RISC-V spec 看過一輪,也把 RISC-V 版本的計算機結構(算盤書)看了一半,希望可以有機會找到相關的職缺的準備工作吧。
  • 2017 Q3 加入晶心科技:由於我們當時剛好切換到以 RISC-V 作為主力研發的產品,所以筆者從當時開始,主要的業務內容就是 Linux/RISC-V。
  • 2018 Q3 原本打算參加鐵人賽,追蹤 Golang Hello World 程式的過程。大概寫了 10 篇左右,看苗頭不對而放棄參賽。
  • 2019 Q3 第三次參加鐵人賽,繼承去年的未竟,完成入吾 go 中系列。其實是很淺碟式的追蹤,也不是大部分 Golang 工程師會感興趣的部份,但對於 Hoddarla 助益甚大。作業系統概念、RISC-V 權限指令集與 Golang 執行期行為可說是 Hoddarla 專案的三大支柱;經過當時的鐵人賽系列,才真正奠基。
  • 2020 Q1
    • 終於鼓起勇氣面對 Cody 已經實作了 Biscuit 的事實,看完他的博士論文。
    • 開始動 Golang,啟動這個專案。
    • 打通 cmd/dist 工具對 opensbi/riscv64 系統組合的認識。
    • 可編出產物。
    • 面對既有 RISC-V 並不支援系統權限指令的窘境,決定分心處理非同步搶佔的實作,熟悉一下 Golang 的感覺。包含認領 issue 到 merge,總共是兩個月。
  • 2020 Q2
    • 沉浸在自己的小勝利裡面兩個月。
    • Q2 底回頭追蹤 Golang 的工具鏈裡面到底怎麼支援系統權限指令比較好。在本次的 Hoddarla 系列完全沒有提到這個部份的困難,因為是完全的踏實工作,不追蹤就看不到的。實際上做起來都是一兩行的修補而已。
    • Korean Fish, go to hell!!! 和這個專案完沒關係,但我有紀錄這一行在我的開發筆記裡面。
  • 2020 Q3
    • 開始操作 CSR,加入相關指令。
    • 練習些組合語言程式,大致上對應到使用暫存器除錯一文。
    • 遭遇些 OpenSBI 0.8 的亂流。
    • 遭遇些 ARCH Linux 上面的 GNU 工具鏈的亂流。
  • 2020 Q4
    • 開始處理 BSS 的問題
    • 開始釐清虛擬記憶體會遇到的問題。
    • 採取與這個系列完全不同的虛擬記憶體實作方式:SysMap 裡面完全放任,只決定回傳的虛擬記憶體位址。這麼做會使得後續 Golang 執行期真的要存取的時候,觸發頁表錯誤的例外;然後,實作非常寬容的頁表錯誤例外處理,只要 Golang 執行期有需求,就生物理頁面出來配置映射給它。筆者後來參賽之後,決定重做,也就變成了現在的第二章的樣子。要是使用這個方法的話,上下文之類的處理就得拉到更前面來做了。
    • 遭遇旅途拾貝當中遭遇到的組合語言問題
    • 遭遇 newosproc 的問題,如斷章:問題分析當中的分析。
    • 開始研究旗號機制
    • 開始研究 Golang 訊號
    • 實作簡單排程
  • 2021 Q1
    • 工作筆記的第一句話:It’s hard to believe its 2021 now. Rebasing makes me tired.
    • 過年 ... 放鬆。
  • 2021 Q2
    • 回頭處理時間函數 nanotimewalltime 的問題。當時也如現在一樣是暫解。
    • 實作計時器中斷
    • 實作上下文處理
    • 正式印出 Hello World!,對應到Hello World 與 Uart 機制觀察的前半部。
    • 開始為了今年的鐵人賽起跑,撰寫文章。重定基底與重新編校既有的稿件(約三、四篇),還有些地方重新安排順序,也不是簡單的工作。就算到了開賽日之後的整個系列期間,每日工作量可能也都與筆者第一次鐵人賽相仿吧。
    • 其實先前都是借用低位的虛擬位址,但為了讓 ethanol 看起來更像作業系統核心,研究了一下怎麼下參數給 Golang 連結器使用。大致對應到產出可執行檔
    • 改組語改得很煩,因為 Golang 的組件歸屬符號使用一個 Unicode 的點(.)符號,但這個符號的寬度是歸類在模糊寬度,導致一些終端模擬器把它當作一個字元寬,用 vim 編輯組語檔案的時候就會一直跑掉。為什麼這時候才面對,因為實在是太煩了。
    • 認真學習 RISC-V 計時器中斷、外部中斷、UART 的暫存器。其實這些東西,之前筆者都沒有深入研究。其實這次,以普通嚴格的等級檢視,也不算是有深入研究就是了。
  • 2021 Q3
    • 剛好在 10/6 的 00:20 分寫完當日的技術部份,正式結束 Hoddarla 專案在這次鐵人賽的技術部份,總共歷時 21 個月。

現在

這個章節用來回顧這系列以來欠下的技術債。要展望未來之前,得先看過一次這些問題才行。

第零章:基本開發工具

這個部份,筆者從 2020 年初起算,總共跑過兩次。兩次的方法其實都一樣,只是細節部份不同而已。第一次是,隨意搜尋未定義符號,隨意解消,也不太遵守原本的架構。比方說,幾乎所有的東西都塞在 signal_opensbi_riscv64.go 裡面之類的。這一次本戰系列文當中,就有特別以 js/wasm 系統組合作為模板來建構大部份符號的解消。

所以,以支援系統組合 opensbi/riscv64 的角度來說,這個部份還算乾淨,畢竟空殼就是空殼,能管理就好了。但是技術債仍然有,那就是有些 Golang 檔案是由 script 生成的,但筆者並沒有完全遵守那一套機制來做事情。如果真的要做到堂堂正正地成為一個 Golang 支援的系統組合,那麼就應該要全部依循那一套作法才行。

第一章:匍匐前進、起步除錯

GDB 其實還是可以用得很優雅才對,但是筆者在一開始成文之時沒有留意到,src/runtime/runtime-gdb.py 竟然可以提供那麼強大的除錯功能。所以應該是在本戰的系列文的某篇補上了吧。原本的參考來自官方部落格,使用 GDB 除錯 Golang 程式的讀者應該可以一試。

BSS 初始化也很有趣。筆者第一次清除 BSS 是起源於不同的錯誤,但現在也沒有什麼好考證的了。

這個部份主要在於操作和早期的理解,所以沒有什麼技術債。不欠債的好日子也到這裡而已了。

第二章:基本的虛擬記憶體

Golang 本身

筆者藏拙的跡象很明顯,那就是關於 Golang 實際上如何做記憶體管理都是輕輕帶過,因為其實筆者也不清楚其中內容,甚至比對 Linux 的記憶體管理的理解還要少。但這個部份絕對是至關重要的。Hoddarla 現在只跑一個很簡單的輸入輸出,而且應該還沒有任何動態的垃圾回收機制觸發,看起來是沒有資源的問題;但顯然在日後擴充之前,應該先設法觸發垃圾回收,看看有沒有其他非預期的問題。

守在 Sys* 系列呼叫對於移植系統組合到 Golang 上來說是足夠的沒有錯,但我們今天是需要自己實作出 mmap 系統呼叫的角色,這個部份該怎麼置放程式碼呢?筆者當然不想重蹈 Biscuit 覆轍,它們不但得加執行期之下的一層 Shim 層,還得修改執行期本身。以現在的 Hoddarla/ethanol 狀態來看,我們其實也在執行期組件當中置放了大量的程式碼,這其實不太理想。原先筆者打算多使用 go:linkname 的技巧,讓執行期組件裡面完全沒有 ethanol 核心程式碼,而是置放在 main 組件中,就像一般的 Golang 程式一樣,但執行起來有其難度,也許之後再嘗試吧。

欠債:

  • 垃圾回收狀況的確認
  • 新增的現有 runtime/ethanol 組件內容以及 runtime 規劃雜亂,是否有望僅留入口,將實作放在 main 之外專案本身的部份?

位址的配置

顯然現在的虛擬位址配置很陽春,實體位址配置也是如此,而且根本完全不能夠回收再利用。

欠債:

  • 配置的方法
  • 可回收的設計

DTB 剖析器

欠債:

  • 完整的 parsing 實作

頁表屬性

現在每一個頁表項都是讀、寫、執行全開的狀態,當然不是很理想。

欠債:

  • 頁表屬性的規劃

斷章

參數與環境變數還蠻理想的,雖然筆者沒有測試過參數的部份(也就是使用 os.Args 去存取),但環境變數的部份我們至少也試過除錯用的一個 GODEBUG。所以這個部份也還好。

第三章:基本的計時器中斷

理想上我們應該實作真正的計時器系統,否則日後若是多個執行緒都呼叫 sleep,沒有一個機制來記憶所有的到期時間的話,就沒有辦法精準地按照大家的計時需求來運作。

又,這個部份我們確立了使用 gsignal 來服務中斷的構想。只是,這樣不會有其他問題嗎?比方說我們在第五章已經看過的 malloc during signal,或許之後其他的事件也會觸發也不一定。由於我們與其他系統組合共用的 Golang 框架還是很多,很難說不會有非預期的狀況發生。

畢竟筆者自己就是 RISC-V 支援的非同步搶佔貢獻者。理想上,筆者其實想要接上非同步搶佔當作計時器中斷,因為兩者根本很像;在 linux/riscv64 系統組合中,非同步搶佔的原理是讓共常式自己發 SIGPREEMPT 給自己,再從訊號處理函數接出去,概念上幾乎可以類比。只是非同步搶佔的部份程式碼,在筆者最後一次確認的印相當中是在某個 Unix-like 系統才有的通用部份之中,所以 ethanol 說真的不是很容易可以挪用。

欠債:

  • 計時器系統
  • gsignal 機制與其他機制相容性的問題?
  • 引用非同步搶佔框架的可能性?

第四章:基本的排程機制

Biscuit 大方承認他們用的是 Golang 共常式的排程,所以應該是沒有另外再多開 m 物件出來了。關於這件事情,目前我們還保留很高的彈性,但是現在的作法很明顯的如果在多核心系統上面運行會是個災難。除了上鎖之外,是否還有什麼要小心的呢?所有的 mg 的互動?與 p 的互動?這些都是目前還沒有妥善準備的。說穿了,本戰當中的準備,只是確保背景的 sysmon 與頻道處理的執行緒能夠有機會執行而已。

執行期的鎖的部份也欠了些東西,就是時間的準確度。這裡必須一路從裝置樹拿到對的內容,然後再看看睡眠模式該怎麼樣觸發排程,好讓其他執行緒能夠取得運算資源;如果多核心系統加入進來之後,又該怎麼管理呢?這些筆者都還沒有具體的圖像。

欠債:

  • 往多核心延伸的考量
  • 保護排程的區間
  • 旗號的部份時間非常不精確

第五章:基本的外部中斷

這個部份最大的殘念反而不是 Hoddarla 專案本身,而是筆者沒有機會探索 QEMU 為何禁止 GDB 直接對 PLIC MMIO 暫存器寫入,並加在附錄裡。但也許這個對於專案本身也不是那麼重要。

結構上欠最多債的還是 PLIC 與 UART 如何能夠從 DTB 當中拆解出來的部份。筆者在撰寫這個部份時,有意識地讓外部中斷相關的實作位在 main 組件當中,因為這樣比較理想,但如何從執行期一路繼承到裝置樹所在的位置,那就比較困難了。

功能上,筆者其實沒有完整追蹤完一個完全體的 UART 驅動程式;讀者也能發現,在最後實作基本的命令列的時候,筆者並沒有確認線路狀態,而是一味從讀取暫存器中取值,這是過於簡單的驅動程式行為。又,驅動程式應該是可以分上下部的,在那之上還有 console 抽象層,而我們現在都還缺乏。

欠債:

  • 裝置樹剖析
  • 完整的 UART 驅動程式

未來

你來跳舞,我來念歌詩 ...

說到未來發展路線,筆者認為很複雜。回歸初衷嗎?但初衷就是 2017 年日記裡一句斷片,用 Golang 寫作業系統跑在 RISC-V 上,但什麼是作業系統?每個主題演講都會在這個問題上至少逗留一下子,輕鬆一點的打個哈哈說這個沒人說得算,認真一點的會回顧一下古往今來曾經有過的比較權威的定義。那我呢?修補完技術債,然後把系統呼叫刻一刻,然後可以跑既有的應用程式,看起來勉強搆得上人家 MIT 博士的 Biscuit 這樣嗎?像是 Hoddarla: Yet Another POSIX Kernel Done in Modified Golang -- Which Is Still Useless Compared to Linux 這樣嗎?

王道 POSIX 路線對於筆者來說,實在沒有什麼吸引力。既然 Hoddarla 原本是勸酒詞,自己也得先醉得厲害才行,那麼接下來的規劃有多麼荒謬,應該也都是沒有關係的吧。以下是一些好高騖遠的幻想,按優先權由高至低:

排除 C 語言相依性

先打造使用者空間,一樣用 Golang 作 ethanol/riscv64 系統組合,然後在上面發展生態系。

之所以將這個優先權排最高,是因為這是我最嚮往的事情:一個不需要看 gcc 臉色的作業系統發行版,一個使用者空間不會有一堆基本軟體包需要 configure; make; make intall,尤其是你知道那些所謂可攜性都已經沒有人在乎了的時候。相反的,任何人在任何電腦上,管他是目前最強大的 linux/amd64 系統組合或是 ios/arm64,只要有一個 Golang 工具鏈,就可以 bootstrap 整個 Hoddarla。

要做到這點的話,筆者有些小型目標可以先完成,比方說減少 GNU 工具鏈的依賴程度、減少 shell 的依賴程度;但很現實的是,GDB 和 QEMU 可能短期內都難以割捨。所以相關的延伸工具部份也得再重新實作就是了。

拋棄萬物即檔案

這個時代,檔案有何意義?人們真的還需要檔案這種東西嗎?人們要的是以網路為骨幹的人際關係與數位物流、串流影音。人們要的是隨拿即有,但又不黏牙不佔空間。當網路已經成為基礎設施,家用遊戲主機、Windows PC、智慧型手機、掃地機器人、空氣清靜機都只是物理上外出執行任務的登陸艇,只要有需要,它們隨時會概念上回到母艦進行更新。

What is a file? 字典有云:A folder or box for holding loose papers together and in order for easy reference. 呃,可是我有 Google Doc,可以多人編輯,我為什麼需要數位實作的一種紙、或是數位實作的一種物件匣?我有付錢給 whatever 網路服務供應商,而且我也付錢給串流平台,我何必存整個影片或音樂在我的手機裡?

但關於這點,筆者現在只有破壞的動機,卻還沒有建設的計畫。真的要徹底背棄這個概念的話,我想我還需要多讀一些資料庫與檔案系統的書與實作。所以之後得研究一下吧。

參考其他的抽象層

以鐵人賽來抽樣,瀏覽器就是最熱門的抽象層,超過兩百人在上面寫程式吧。瀏覽器確實厲害,已經有太多 javascript 函式庫,可以魔術般地在不同瀏覽器上做出各種兼具視覺衝擊與實用性的成果,往日的可攜性軟體都比不上瀏覽器這種虛擬機器來得可攜。所以如果接下來要設計桌面環境,除了先前宣言的應該參考一下簡單版的 Oberon 如何做之外,應該也要參考瀏覽器提供的方便性與可開發性。

另一個則是雲原生。大家都在微服務、容器、分散式架構。如果能夠有個系統,在上面寫程式之後,編譯出來的東西就原生地可以彈性調度在多個物理節點之間、取用各式資源、且可組態可監控、它的輸出可以導向歸納整理為日誌,那我們是否就可以放下 OCI 層的抽象了?畢竟它也大量仰賴了各種系統呼叫與 namespace 與 cgroup。更具體一點的想像是,如果我寫程式最後可以編出一個東西,然後我可以把它像容器一樣調度呢?

徹底的 Demo RISC-V 用專案

雖說還在非常簡單的狀態,Hoddarla 事實上已經展示了所有作業系統模式(S-mode)的狀態與控制暫存器的基本使用方法。如果以這個精神延伸,繼續把才剛凍結的虛擬機器(HS-/VS-/VU-mode)規格也都用最基本的功能兜起來,似乎也蠻有趣的。

這個優先權最低,因為筆者心中的想像是,當我對這個專案的責任感與成就感消弭的比我想像中還要飛快,但又想要撈出來做個資源回收的時候,才會想要走的路線。

未來狂想之結論

很現實的是,如果先前的技術債沒有償還,這些也都是空談。總的來說,筆者估計如果接下來的精力持續消退,那比較可行的時程應該是,

項目\時間 2021 2022 2023 2024 2025 2026
還技術債 >>>>++ ++++++ ++++++
思考系統架構圖 >>>>++ ++++++ ++++++ ++++++ ++++++
實作 virtio-net driver >>>+++ +++<<<
實作 HTTP2/TCP/IP >>>+++ +++<<<
研究使用者空間 Golang ++++++ ++++++
實作所需之 system call ++++++ ++++++ ++++++

所以,就先抓 2025 Q4 釋出 Hoddarla 1.0 版吧!

補述:Hoddarla 的滾動式維護

上述的時間軸分類法適用於不同階段的項目,但 Hoddarla 專案本身的維護方法,卻是各個階段皆然。本節兩段更新的經驗,首先是描述 1.3 節到 2.0 節之間的更新。也就是,以上游的 master 分支(branch)為基準來講,從 ab4085ce84f8378b4ec2dfdbbc44c98cb92debe5 到 9e26569293c13974d210fd588ebfd29b857d8525 的改動。兩者相距約莫兩週。

不升版:每個小節的開發常態

在開始撰寫任一小節之前,筆者會確保 GOBASE 檔案內容存放一個 Golang 專案上游 master 分支的 commit ID。當開發者執行 make apply 時,首先會從上游拉取 Golang 專案,並重設(reset)到指定的提交(commit)序號。之後,patch 資料夾底下的所有修補(patch)就會在該提交之上建立起來。

打上所有修補之後,應當要能夠沒有任何錯誤地執行完建置工具鏈的流程,並且能夠編譯 opensbi/riscv64 的 ethanol 核心映像檔。

從這裡出發,就能夠開始撰寫新的小節,進行恰當的修改之後,在 Golang 專案內創造新的提交。再之後,在 hoddarla 專案內執行 make patch 時,新的提交就會成為新的修補,進入到 patch 資料夾。

升版:重定基底(rebase)

一段時間之後,總是得進行升版,否則會和 Golang 上游的發展逐漸脫節,導致難以維護的後果。Hoddarla 進行至今已經超過一年半,目前為止是以兩週到一個月不等的頻率升版。目前還沒有決定基底更新的策略,總之是都先取一個當下最新的提交作為新的基底。

當然,最常使用的工具就是 git rebase。每次升版的流程是:

$ git fetch origin master
remote: Enumerating objects: 109, done.
remote: Counting objects: 100% (109/109), done.
remote: Compressing objects: 100% (48/48), done.
remote: Total 109 (delta 63), reused 101 (delta 61), pack-reused 0
接收物件中: 100% (109/109), 205.96 KiB | 1.37 MiB/s, 完成.
處理 delta 中: 100% (63/63), 完成 11 個本機物件.
來自 https://github.com/golang/go
   c8f4e6152d..9e26569293  master         -> origin/master
   aa4da4f189..ab361499ef  dev.cmdgo      -> origin/dev.cmdgo
   c6d3d0b0ad..897970688b  dev.typeparams -> origin/dev.typeparams
$ git rebase origin/master

如果順利的話,基本上可以沿著前一小節的描述繼續進行,因為所有既有的修補都能夠順利的在新的基底上使用。但若像是 1.3 到 2.0 節時的更新,那問題就比較複雜一點了。續前指令:

升級實例之一:1.3 至 2.0 時的實戰

$ git rebase origin/master
自動合併 src/go/build/syslist.go
衝突(內容):合併衝突於 src/go/build/syslist.go
error: 不能套用 e57488ec62... 0.1 Toolchain
Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".
You can instead skip this commit: run "git rebase --skip".
To abort and get back to the state before "git rebase", run "git rebase --abort".
不能套用 e57488ec62... 0.1 Toolchain

go (master|REBASE 1/7)
$

如果讀者還沒有在常用的工作環境內啟用 git-prompt 工具,我會強力建議設置!像此時的狀況就能夠展示 git 本身的狀態為何,相當具有提示性。

基本上,遇到衝突當然是不太情願,但畢竟 Golang 是個跨系統的程式語言,所以衝突的狀況有很多種:

  1. 如果有新的作業系統、計算機架構或是新的組合出現,都有可能衝突到 hoddarla 專案這些早期的修補。
  2. 組件內部的重構,也可能必須針對 opensbi/riscv64 組合刪改。
  3. 廣義衝突:git apply 指令可以執行,但是無法建置工具鏈。
  4. 廣義衝突:可以建置工具鏈,但是無法編譯出成品。
  5. 廣義衝突:可以編譯出 ethanol 映像檔,但執行時有非預期行為。

幸好,升版時遭遇的衝突大多停留在前兩者的狀況,但每一次也是需要見招拆招。以這次為例,以下再分小節描述。

1/7 0.1 Toolchain

這裡的衝突算是蠻輕微的,只要觀察事發的檔案即可了解:

$ git diff
diff --cc src/go/build/syslist.go
index 60ac5511bd,dcb9f2bfb7..0000000000
--- a/src/go/build/syslist.go
+++ b/src/go/build/syslist.go
@@@ -7,5 -7,5 +7,10 @@@ package buil
  // List of past, present, and future known GOOS and GOARCH values.
  // Do not remove from this list, as these are used for go/build filename matching.

++<<<<<<< HEAD
 +const goosList = "aix android darwin dragonfly freebsd hurd illumos ios js linux nacl netbsd o
penbsd plan9 solaris windows zos "
 +const goarchList = "386 amd64 amd64p32 arm armbe arm64 arm64be ppc64 ppc64le loong64 mips mips
le mips64 mips64le mips64p32 mips64p32le ppc riscv riscv64 s390 s390x sparc sparc64 wasm "
++=======
+ const goosList = "aix android darwin dragonfly freebsd hurd illumos ios js linux nacl netbsd o
penbsd opensbi plan9 solaris windows zos "
+ const goarchList = "386 amd64 amd64p32 arm armbe arm64 arm64be ppc64 ppc64le mips mipsle mips6
4 mips64le mips64p32 mips64p32le ppc riscv riscv64 s390 s390x sparc sparc64 wasm "
++>>>>>>> e57488ec62 (0.1 Toolchain)

差異點在於 goosList 我們需要加上 opensbi,而 goarchList 中新增了一個 loong64 架構。這個不難處理,兼容即可。

修補的預設行為是不會更新提交當中的時間。如果這個行為無法接受的話,一開始的重定基底指令可以增加一個參數變為 git rebase --ignore-date origin/master

修改完 src/go/build/syslist.go 之後,

$ git rebase  --continue
[分離 HEAD b2c95baada] 0.1 Toolchain
 20 files changed, 56 insertions(+), 20 deletions(-)
 create mode 100644 src/runtime/internal/sys/zgoos_opensbi.go
成功重定基底並更新 refs/heads/master。

看起來第一步是成功了。

建置工具鏈

以這次的例子來講,也是成功建置,沒有問題。

試跑

...
Boot HART MIDELEG         : 0x0000000000000222
Boot HART MEDELEG         : 0x000000000000a109
HI0000000000000005
ffffff8000075d00
0000000080245ae8

仍然是當時還沒啟用虛擬記憶體的狀態。順利升級!

收尾

最後就是接續該小節的主題,繼續開始進行囉!

升級實例之二:2.4 至 2.5 時的實戰

在這兩篇的寫作之間,Golang 專案正在經歷 1.17 版的發行,也進了相當大型的變動,關於系統組合的管理。

搬家的 goarchgoos

這兩組搬到 src/internal 之下。筆者仍然得重新引入 opensbi,但是甚至無法執行 go run gengoos.go,因為變動太過劇烈的關係,這個指令會抱怨 runtime.convT2E 是不存在的符號。但這裡可以先編譯一支新的工具鏈,之後再繼續重定基底。

重定基底成功後,卻無法編譯工具鏈!

變成 2.4 時加入的 SFENCEVMA 指令不被認得!這也是沒辦法的,因為針對 master 分支重新編譯了工具鏈,所以這些我們在第 0 章已經實作完的部分就不在工具鏈的支援範圍了。

所以,這裡筆者先回溯到 0.4 去,重新編成工具鏈後,再重設到 2.4 的狀態,重新編成工具鏈。

鐵人賽之後

筆者也不知道這樣的滾動式維護模式可以維持多久。但相較於前述的 Biscuit,在初期就缺乏一個良好的維護規劃的情況下,專案只能隨著時間塵封在舊版本當中。雖然說學術論文導向的開發就是這樣子,但也難免唏噓。

鐵人賽期間,可以很簡單的以每個章節的內容來區分修補的範圍如何。但是鐵人賽之後,乃至於 Hoddarla 專案本身的未來目標,筆者期望還是能夠更能按照功能或是概念重新對應這些修補。並且,如果能夠研擬一套自動流程,就能夠以更高頻率進行這個升版的作業。

最不敢奢求的夢,當然就是 Hoddarla 計劃吸引足夠多的玩家並累積足夠的人氣,讓這些化外之修補也能夠進入上游。

結論

這個專案對我來說,是第一次執行時間切分地如此零碎(每天下班一、兩個小時,週末頂多三、四個小時),但時間尺度又橫跨一年半的專案。過程中累積的很多感覺,隨著鐵人賽完賽的興奮快速流失,但是那些過程中的痛苦,才是 Hoddarla 更真實的樣貌吧。

回顧完 Hoddarla 專案本身,明天的最後一篇結語章節,會分享我在其他鐵人系列當中學到的東西。總之,祝福各位鐵人都能完賽!但我還要再發一篇。各位讀者,我們明天再會!


上一篇
予焦啦!附錄:那些作業系統的巨人們與參考資料
下一篇
予焦啦!結論與展望(二):鐵人賽、正體中文科技寫作與雜談
系列文
予焦啦!Hoddarla 專案起步:使用 Golang 撰寫 RISC-V 作業系統的初步探索32

尚未有邦友留言

立即登入留言