本文經機器之心(微信公眾號:almosthuman2014)授權轉載,禁止二次轉載
選自Fueled by Coffee,作者:Takaya Saeki,編輯:Panda
前段時間,中國科學院大學的「一生一芯」計劃引發熱議,五位本科生帶著自己設計的處理器芯片正式畢業,被稱為「最硬核畢業證」。其實,東京大學信息科學系也有一個自制 CPU 的實踐課程。近日,微軟軟件工程師 Takaya Saeki 刊文回顧了五年前他們小組的 CPU 實驗項目:不僅通過自學自制了 CPU、C 編譯器,還成功移植了一個類 Unix 操作系統(Xv6)。雖然回顧的是五年前的往事,但這篇文章應該也能為芯片和操作系統人才培養工作帶來一些啟發。
所有這一切都源自一個學生實驗項目:CPU Experiment(CPU 實驗)。首先說說這個 CPU 實驗是什么。
CPU 實驗是東京大學信息科學系一個小有名氣的實踐課程,通常在大三的冬季進行。在該實驗中,學生會被分成小組,每組四、五個人。每一組都要設計一種自己的 CPU 架構,在 FPGA 上實現它,為該 CPU 構建一個 OCaml 子集編譯器,然后在該 CPU 上運行一個給定的光線追蹤程序。通常來說,CPU、FPU、CPU 模擬器和編譯器都各由一兩個人負責。我負責第 6 組的 CPU 部分。
這個實踐課程的有名之處在于對自學能力的高度期望。導師向學生們下達了任務目標:「把這個用 OCaml 寫的光線追蹤程序運行在你們用 FPGA 實現的 CPU 上」,然后就下課了。對于編寫 CPU 和編譯器的具體步驟,他不會多說。學生需要自己學習如何將學過的有關 CPU 和編譯器的一般知識轉化成實際成品,這將涉及到實際的電路和代碼。是的,這個實踐課程確實很難,但也很激動人心且極具教育意義。
在我們自己的 CPU 上運行操作系統
你可能已經注意到了,我還沒談到操作系統。我來稍微解釋一下。
通常來說,這個實驗會這樣進行。首先,做出一個能可靠工作的 CPU,不管計算速度如何。如果做出了 CPU 并成功運行了那個光線追蹤程序,就能得到這個實踐課程的學分。之后,你的團隊就自由了。通常來說,這些自由時間會被用于 CPU 提速。在過去的實驗中,學生做出過亂序 CPU、VLIEW CPU、多核 CPU 甚至超標量 CPU,確實很了不起。
但是,有些團隊則把更多精力放到了一些有趣任務上,比如運行游戲或將 CPU 與揚聲器連接來播放音樂。我們第 6 組也是一個熱愛娛樂的小組,而我們決定將目標設定為運行一個操作系統。
結果,其它一些小組也對這個想法產生了興趣。于是,一個包含 8 個人的聯合小組——Group X 成立了。我們的目標是:「在我們自己的 CPU 上運行 OS!」
盡管我負責第 6 組的 CPU 創建工作,但這一次我選擇當 Group X 的領導者。因此,本文主要是從 OS 團隊角度寫作的,不過我也會介紹 Group X 的整體成果。
Xv6
對于要移植的 OS,我們選擇了 Xv6,這是一個由 Unix v6 啟發的簡單操作系統,是 MIT 為教育目的構建的。不同于 Unix v6,Xv6 是用 ANSI C 編寫的,而且運行在 x86 架構上。Xv6 是一款教育用 OS,所以功能有些簡陋,但作為一款簡單的類 Unix 操作系統,功能已經足夠了。有關 Xv6 的更多信息可訪問其 GitHub 代碼庫:https://github.com/mit-pdos/xv6-public
挑戰
在移植 Xv6 時,光是軟件方面就有一大堆難題,因為我們在嘗試從頭開始構建一切。
1. 用于 Xv6 的 C 編譯器和工具鏈。
在 CPU 實驗中,我們通常會創建一個 ML 編譯器。很自然,這樣無法編譯 Xv6 的 C 代碼。
2. 操作系統需要 CPU 具備哪些功能?
特權保護?虛擬地址?中斷?是的,我們在課堂上已經獲得了對操作系統的整體理解,但那時候我們對各個 CPU 功能的具體作用還沒有真正的切身體會。
3. 模擬器呢?
我們已經在 CPU 實驗的核心任務部分做了一個模擬器,但那個模擬器很簡單,只能逐一執行指令,而且沒有中斷和虛擬地址轉換。
4.Xv6 的可移植性差
Xv6 很難移植。舉個例子,它假設 char 是 1 個字節,而 int 是 4 個字節,并會大量操作堆棧。好吧,我猜 Xv6 這個名字實際上來自 x86 和 Unix v6,所以這種設計當然很自然。
我們有過很多擔憂,但還是在 12 月份開始了 Group X 的 OS 移植項目。
接下來,我將大致按時間順序編寫我們的工作經歷。這個過程會有一點長,所以如果你想快些看到結果,請跳轉至「三月」部分。
十一月下旬:開始開發編譯器
我們找到答案的第一個問題是編譯器和工具鏈。有點意外的是,我們決定從頭開始寫 C89 編譯器。說老實話,我之前沒想到我們會選這條路。我記得我和 Yuichi(后來負責 Group X 的 CPU)一開始討論過移植 gcc 或 llvm。
但是,一位團隊成員 Keiichi 突然說他已經寫好了一個 C 編譯器并向我們展示了一個編譯器原型,其帶有一個簡單的解釋器和發射器。從頭開始寫工具鏈似乎更有意思,因此我們決定自己寫一個編譯器。
來自第 3 組的 Yuichi 和 Wataru 已經結束了那一年 CPU 實驗的核心任務,于是他們加入了 Keiichi,組成了 Group X 的編譯器團隊。后來我們將我們的編譯器命名為 Ucc。
十二月中旬:OS 團隊上線!
十二月初,我完成了自己的 CPU,第 6 組完成了 CPU 實驗的核心部分。于是我們開始做有趣的部分:Group X 的 OS 移植任務。這時候,第 6 組的我和 Shohei 開始了 Group X 的工作并組成了 OS 團隊。Masayoshi 也在那時候加入了進來。
實驗的核心任務:編寫一個 CPU
順便一提,我猜沒多少軟件工程師親自寫過 CPU,所以我也談談如何寫 CPU。
現如今,制作 CPU 并不意味著要在面包板上連接各種跳線,你可以完全使用硬件描述語言(HDL)編寫電路。然后你可以使用 Vivado 或 Quartus 將 HDL 合成到真實電路中。這個過程叫做邏輯綜合(logic synthesis),而不是編譯。
HDL 與編程語言既有相似之處,也有一些差異。你可以將其視為一個將寄存器的信號狀態映射成另一個信號狀態的函數,其可由時鐘或輸入信號觸發。如果你想體驗真正的反應式編程,我建議你試試 HDL。同時請務必記住,在寫 HDL 時要一直注意你寫的 HDL 的信號傳播會在某個時鐘切實地終止。否則,人類將難以理解你的電路的行為。
實際開發過程中最艱難的部分就是邏輯綜合,其所需的時間多得離譜。在開始執行綜合之后,我們往往需要等上多達 30 分鐘時間。所以開始綜合之后,我常常與其他也在等著綜合結束的 CPU 團隊成員玩《任天堂明星大亂斗 DX》。隨便說一下,我的角色是 Sheik。
十二月下旬到一月中旬:通過將 Xv6 移植到 MIPS 來學習
我們開始找到「操作系統需要 CPU 具備哪些功能?」這個問題的答案。
OS 團隊誕生之后,我們開始每周聚會,閱讀 Xv6 源代碼。
與此同時,我開始將 Xv6 移植到 MIPS。這樣做的部分原因是學習 OS 在實現層的工作方式,部分原因是似乎還沒人將 Xv6 移植到 MIPS 過。我在大約一周內完成了移植工作,直到調度器過程開始。在這個移植過程中,我花了大量精力研究 MIPS,并且為了了解 Xv6 的工作方式還大量研究了 x86。得益于此,我理解了中斷的相關機制以及實現層的內存管理單元(MMU)。這時候,對于 Xv6 所需的 CPU 功能,我已經有了扎實的理解。
另外,在一月中旬,我們也開始努力通過注釋掉各個部分來編譯 Xv6 的整體代碼。結果是在我們自制架構的模擬器上,Xv6 在引導順序中顯示出了第一條消息:
xv6.。.cpu0: starting.。。
與此同時,這意味著這時候 Ucc 已經成長到足以編譯大部分 Xv6 代碼了。真是太棒了!
二月:我們的 CPU GAIA 誕生!
在 MIPS 移植過程中,我完成了 PIC 的初始化,這個過程很痛苦。另外,我還完成了實現中斷處理程序的任務。結果,Xv6 向 MIPS 的移植工作剛完成,第一個用戶程序就開始開發了。
在這一經歷的基礎上,我為我們的自制 CPU 編寫了中斷和虛擬地址轉譯的規范草稿。為了簡單,我們決定忽略 Ring 保護等硬件特權機制。至于虛擬地址轉譯,我們決定使用 x86 那樣的硬件頁面游走法(hardware page-walking method)??雌饋砜赡芎茈y在硬件中實現這個功能,但我們認為如果我們犧牲掉速度并忽視 TLB 實現,可能就不會那么難。畢竟 Yuichi 后來做了一個很棒的 CPU 內核,不過它一開始就安裝了 TLB。
Yuichi 完成了我們的 CPU 的 ISA(指令集架構)的整體設計。他將我們的 CPU 命名為 GAIA。在典型的 CPU 實驗項目中,我們既不會實現中斷,也不會實現 MMU。但是,Yuichi 開始為 Xv6 實現它們,他是基于第 3 組的 CPU 的重構版本開發的。
接下來,進度加快了,所以我將開始按周進行說明。
第一周
Masayoshi 開始為我們的 CPU 實現真正的初始化,而不只是將引導順序注釋掉;而 Shohei 將 Xv6 的 x86 匯編重寫進了我們自制的架構中。我為我們的模擬器添加了中斷模擬,而這個模擬器是 Wataru 在 CPU 實驗的核心任務部分開發的;另外我還完成了對虛擬地址轉譯的支持。這能讓模擬器有足夠的功能來運行 OS。
第二周
我為我們的架構構建了一個原語鏈接器,以集成 Xv6 及其 binary blobs。Shohei 正在實現中斷處理程序,這部分很難。中斷很難理解,難以弄清流程、難以調試、難以開發。
當我將 Xv6 移植到 MIPS 時,我有 GDB,所以還能應付,但我們自己的模擬器沒有任何調試功能,所以調試起來肯定非常難。
Shohei 也頂不住這樣的難度,所以他為模擬器添加了一個反匯編器和調試 dump 函數。之后,OS 團隊又對這個模擬器的調試功能進行了快速升級,最后得到的模擬器看起來是這樣:
第三周
克服了許多困難之后,Xv6 的移植工作有所進展,但 Xv6 還是無法工作。
尤其是 Ucc 的規范為 char 和 int 都是 32 位,這帶來了許多問題。這不是 Ucc 的錯。事實上,C 規范僅要求 sizeof(char) == 1 且 sizeof(char) 《= sizeof(int),因此這是符合規范的。
但是,Xv6 是為 x86 編寫的,所以它假設 sizeof(int) == 4 并會將常量添加到指針的值,這會導致大量不一致。由這個問題帶來的漏洞很難查找,而且數量也很多,所以最后我們決定將 Ucc 的 char 規范改為 8 位。
在將 char 32 位問題委托給 Ucc 團隊之后,我為首次進入階段寫了初始化頁面,并嘗試通過試錯方法讓中斷能夠有效工作。
最重要的是,我們努力解決了第 4 個難題:Xv6 的可移植性差。
2 月 27-28 日
當我回頭看 Slack 時,我發現這一天我們進展頗豐。在 Ucc 團隊很快完成將 char 改為 8 位的工作之后,我們進行了大量調試。最后,我們的第一個用戶程序 init 可以工作了!
之后,我們在移植用戶過程應用方面的成果越來越多,這是我在移植到 MIPS 時沒有做過的事情。在這個過程中,很多漏洞都很難重現,中斷規范之中的不足之處也顯現了出來,但我們最終克服了困難,找到并修復了這些漏洞。
我們修復的一個有趣問題是緩存別名問題。GAIA CPU 選擇了虛擬地址作為緩存索引,而非物理地址。這讓 CPU 在查找緩存時能夠跳過虛擬地址轉譯。但是,由于這樣的設計,我們發現緩存之間會出現不一致問題,因為虛擬地址的多個緩存可以指向同一個物理地址。當一個虛擬地址的緩存更新之后,其它指向同一物理地址的虛擬地址的緩存卻沒有更新。
這個漏洞很難在硬件層面上低成本地修復,所以為了解決它,我們為我們的 Xv6 引入了 Page Coloring。這會為每個緩存行引入「顏色」并重新分配頁面,使得指向同一物理地址的虛擬地址總會有一樣的顏色。這意味著指向同一物理地址的虛擬地址總是僅有一個緩存。這能讓 Xv6 確保 GAIA 永遠不會讓多個緩存共用同一個物理地址。
三月:Xv6 跑起來!
3 月 1 日,Xv6 移植工作完成?,F在 Xv6 已經運行在模擬器上了!
娛樂少不了
一開始,移植 Xv6 是因為這很有趣,現在 Xv6 已經成功運行在模擬器上,那我們就要加把勁讓它更有趣。
首先,Masayoshi 用大約 4 個小時做了一個小火車以及運行在 Xv6 上的 sl 命令。
Shohei 則想做一個掃雷游戲。
在這期間,Yuichi 完成了 Group X 的 CPU 實現工作。真正的 CPU 的運行速度比模擬器快多了,這能讓我們更輕松地玩耍和開發游戲。這時候,我們創建了一個非常高質量的應用:2048。
這個 2048 的質量很高。Yuichi 老是在玩。順便提一句,這個 2048 使用的是 non-line buffering 輸入,這是 Xv6 原本沒有的功能。為了支持這一功能,ioctl 被添加進來作為 read 和 write 之外的另一個 devsw 動作,另外還添加了用來控制 ICANON 和 echo 的與 termios 相關的新功能。因此,唯一能以如此高的完成度玩 2048 的 Xv6 就運行在 GAIA 上。
另外,畢竟 Xv6 是由 Unix v6 啟發的,因此我猜想,添加 gtty 和 stty 系統調用是更像 Unix v6 的方法。不過,因為 Xv6 沒有 tty 的概念,所以我采用了 ioctl;而且事實上 Unix v7 就引入了 ioctl,所以這與歷史情況也接近。
現在,更酷的是,Keiichi 又為 Xv6-GAIA 做了一個小型匯編器,Shohei 還做了一個 mini vi。想想看你能用這兩個工具做什么。
這就是基于 FPGA 的交互式編程!這是 CPU 實驗的一個出色演示,因為其中一般不包括任何交互式程序。
最棒的演示
CPU 實驗實踐課程的原始任務是「在自制 CPU 上運行給定的光線追蹤程序」?,F在我們的 CPU 上有操作系統了,你知道該怎么做了嗎?我們決定在我們的 CPU 的 OS 上運行這個光線追蹤程序。我們遇到了一些問題,但我們在最終展示前一個小時里成功解決了它們。
因此,我們做了我們這個系的學生開玩笑時說的話:在一個 CPU 上運行一個操作系統,然后再在上面運行光線追蹤程序。
來自 2020 年的回顧
這段故事發生在 2015 年,本文也是我自己的博文的翻新稿。盡管現在讀來,我看到了當時自身技術經驗的不足,但我們當時做的事情實在很激動人心。
另外,你現在也可以在你的瀏覽器中體驗我們的 Xv6:https://nullpo-head.github.io/emcc-gaia-simu/xv6.html
實驗之后,我將我們的 GAIA 模擬器通過 Emscripten 移植到了 JavaScript。去試試看我們的 sl、掃雷和 2048 吧。
xv6.。.cpu0: startinginit: starting sh$
還要說一下,Xv6 向 MIPS 的移植工作在 CPU 實驗期間并沒有完成,而是在實驗之后一個月完成的。GitHub 代碼庫在這里:https://github.com/nullpo-head/xv6-mips
在我們 2015 年寫文介紹了 Group X 的工作之后,后來的學生繼續攻堅有關 OS 的新挑戰。
2018 年,一些學生在自制的 CPU 上運行了他們自己開發的 OS;2019 年,一組學生運行了他們開發的 OS,同時采用了 RISC-V 作為他們自制 CPU 的 ISA。此外,2020 年的一個小組終于在自制 CPU 上成功運行 Linux,同時 ISA 也采用了 RISC-V。
我相信未來還會有更多故事,也讓我們保持期待。從個人角度看,我很期待某天能看到某人在自己的 ISA 上運行 Linux,或在上面運行虛擬機。
人們常說要避免重新造輪子,但這個過程確實能讓人學到很多東西。這讓我認識到,我對它的理解其實沒有那么深,無法從頭開始實現它。而且,我推薦這個故事的另一個原因是這真的非常有趣!
我們的 CPU 實驗故事就到此為止了。如果你也有興趣重新發明輪子,可以試試自制 CPU 并移植 OS。
原文標題:東京大學版「一生一芯」:自制CPU、C編譯器,還成功運行了類Unix系統
文章出處:【微信公眾號:人工智能與大數據技術】歡迎添加關注!文章轉載請注明出處。
責任編輯:haq
-
cpu
+關注
關注
68文章
10854瀏覽量
211578 -
UNIX
+關注
關注
0文章
296瀏覽量
41480 -
編譯器
+關注
關注
1文章
1623瀏覽量
49108
原文標題:東京大學版「一生一芯」:自制CPU、C編譯器,還成功運行了類Unix系統
文章出處:【微信號:TheBigData1024,微信公眾號:人工智能與大數據技術】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論