《電子技術(shù)應(yīng)用》
您所在的位置:首頁 > 電子元件 > 業(yè)界動態(tài) > 東京大學(xué)版「一生一芯」:自制CPU、C編譯器,還成功運行了類Unix系統(tǒng)

東京大學(xué)版「一生一芯」:自制CPU、C編譯器,還成功運行了類Unix系統(tǒng)

2020-10-08
來源:機器之心

  前段時間,中國科學(xué)院大學(xué)的「一生一芯」計劃引發(fā)熱議,五位本科生帶著自己設(shè)計的處理器芯片正式畢業(yè),被稱為「最硬核畢業(yè)證」。其實,東京大學(xué)信息科學(xué)系也有一個自制 CPU 的實踐課程。近日,微軟軟件工程師 Takaya Saeki 刊文回顧了五年前他們小組的 CPU 實驗項目:不僅通過自學(xué)自制了 CPU、C 編譯器,還成功移植了一個類 Unix 操作系統(tǒng)(Xv6)。雖然回顧的是五年前的往事,但這篇文章應(yīng)該也能為芯片和操作系統(tǒng)人才培養(yǎng)工作帶來一些啟發(fā)。

  所有這一切都源自一個學(xué)生實驗項目:CPU Experiment(CPU 實驗)。首先說說這個 CPU 實驗是什么。

  CPU 實驗是東京大學(xué)信息科學(xué)系一個小有名氣的實踐課程,通常在大三的冬季進行。在該實驗中,學(xué)生會被分成小組,每組四、五個人。每一組都要設(shè)計一種自己的 CPU 架構(gòu),在 FPGA 上實現(xiàn)它,為該 CPU 構(gòu)建一個 OCaml 子集編譯器,然后在該 CPU 上運行一個給定的光線追蹤程序。通常來說,CPU、FPU、CPU 模擬器和編譯器都各由一兩個人負責(zé)。我負責(zé)第 6 組的 CPU 部分。

  這個實踐課程的有名之處在于對自學(xué)能力的高度期望。導(dǎo)師向?qū)W生們下達了任務(wù)目標(biāo):「把這個用 OCaml 寫的光線追蹤程序運行在你們用 FPGA 實現(xiàn)的 CPU 上」,然后就下課了。對于編寫 CPU 和編譯器的具體步驟,他不會多說。學(xué)生需要自己學(xué)習(xí)如何將學(xué)過的有關(guān) CPU 和編譯器的一般知識轉(zhuǎn)化成實際成品,這將涉及到實際的電路和代碼。是的,這個實踐課程確實很難,但也很激動人心且極具教育意義。

  在我們自己的 CPU 上運行操作系統(tǒng)

  你可能已經(jīng)注意到了,我還沒談到操作系統(tǒng)。我來稍微解釋一下。

  通常來說,這個實驗會這樣進行。首先,做出一個能可靠工作的 CPU,不管計算速度如何。如果做出了 CPU 并成功運行了那個光線追蹤程序,就能得到這個實踐課程的學(xué)分。之后,你的團隊就自由了。通常來說,這些自由時間會被用于 CPU 提速。在過去的實驗中,學(xué)生做出過亂序 CPU、VLIEW CPU、多核 CPU 甚至超標(biāo)量 CPU,確實很了不起。

  但是,有些團隊則把更多精力放到了一些有趣任務(wù)上,比如運行游戲或?qū)?CPU 與揚聲器連接來播放音樂。我們第 6 組也是一個熱愛娛樂的小組,而我們決定將目標(biāo)設(shè)定為運行一個操作系統(tǒng)。

  結(jié)果,其它一些小組也對這個想法產(chǎn)生了興趣。于是,一個包含 8 個人的聯(lián)合小組——Group X 成立了。我們的目標(biāo)是:「在我們自己的 CPU 上運行 OS!」

  盡管我負責(zé)第 6 組的 CPU 創(chuàng)建工作,但這一次我選擇當(dāng) Group X 的領(lǐng)導(dǎo)者。因此,本文主要是從 OS 團隊角度寫作的,不過我也會介紹 Group X 的整體成果。

  Xv6

  對于要移植的 OS,我們選擇了 Xv6,這是一個由 Unix v6 啟發(fā)的簡單操作系統(tǒng),是 MIT 為教育目的構(gòu)建的。不同于 Unix v6,Xv6 是用 ANSI C 編寫的,而且運行在 x86 架構(gòu)上。Xv6 是一款教育用 OS,所以功能有些簡陋,但作為一款簡單的類 Unix 操作系統(tǒng),功能已經(jīng)足夠了。有關(guān) Xv6 的更多信息可訪問其 GitHub 代碼庫:https://github.com/mit-pdos/xv6-public

  挑戰(zhàn)

  在移植 Xv6 時,光是軟件方面就有一大堆難題,因為我們在嘗試從頭開始構(gòu)建一切。

  1. 用于 Xv6 的 C 編譯器和工具鏈。

  在 CPU 實驗中,我們通常會創(chuàng)建一個 ML 編譯器。很自然,這樣無法編譯 Xv6 的 C 代碼。

  2. 操作系統(tǒng)需要 CPU 具備哪些功能?

  特權(quán)保護?虛擬地址?中斷?是的,我們在課堂上已經(jīng)獲得了對操作系統(tǒng)的整體理解,但那時候我們對各個 CPU 功能的具體作用還沒有真正的切身體會。

  3. 模擬器呢?

  我們已經(jīng)在 CPU 實驗的核心任務(wù)部分做了一個模擬器,但那個模擬器很簡單,只能逐一執(zhí)行指令,而且沒有中斷和虛擬地址轉(zhuǎn)換。

  4.Xv6 的可移植性差

  Xv6 很難移植。舉個例子,它假設(shè) char 是 1 個字節(jié),而 int 是 4 個字節(jié),并會大量操作堆棧。好吧,我猜 Xv6 這個名字實際上來自 x86 和 Unix v6,所以這種設(shè)計當(dāng)然很自然。

  我們有過很多擔(dān)憂,但還是在 12 月份開始了 Group X 的 OS 移植項目。

  接下來,我將大致按時間順序編寫我們的工作經(jīng)歷。這個過程會有一點長,所以如果你想快些看到結(jié)果,請?zhí)D(zhuǎn)至「三月」部分。

  十一月下旬:開始開發(fā)編譯器

  我們找到答案的第一個問題是編譯器和工具鏈。有點意外的是,我們決定從頭開始寫 C89 編譯器。說老實話,我之前沒想到我們會選這條路。我記得我和 Yuichi(后來負責(zé) Group X 的 CPU)一開始討論過移植 gcc 或 llvm。

  但是,一位團隊成員 Keiichi 突然說他已經(jīng)寫好了一個 C 編譯器并向我們展示了一個編譯器原型,其帶有一個簡單的解釋器和發(fā)射器。從頭開始寫工具鏈似乎更有意思,因此我們決定自己寫一個編譯器。

  來自第 3 組的 Yuichi 和 Wataru 已經(jīng)結(jié)束了那一年 CPU 實驗的核心任務(wù),于是他們加入了 Keiichi,組成了 Group X 的編譯器團隊。后來我們將我們的編譯器命名為 Ucc。

  十二月中旬:OS 團隊上線!

  十二月初,我完成了自己的 CPU,第 6 組完成了 CPU 實驗的核心部分。于是我們開始做有趣的部分:Group X 的 OS 移植任務(wù)。這時候,第 6 組的我和 Shohei 開始了 Group X 的工作并組成了 OS 團隊。Masayoshi 也在那時候加入了進來。

  實驗的核心任務(wù):編寫一個 CPU

  順便一提,我猜沒多少軟件工程師親自寫過 CPU,所以我也談?wù)勅绾螌?CPU。

  現(xiàn)如今,制作 CPU 并不意味著要在面包板上連接各種跳線,你可以完全使用硬件描述語言(HDL)編寫電路。然后你可以使用 Vivado 或 Quartus 將 HDL 合成到真實電路中。這個過程叫做邏輯綜合(logic synthesis),而不是編譯。

  HDL 與編程語言既有相似之處,也有一些差異。你可以將其視為一個將寄存器的信號狀態(tài)映射成另一個信號狀態(tài)的函數(shù),其可由時鐘或輸入信號觸發(fā)。如果你想體驗真正的反應(yīng)式編程,我建議你試試 HDL。同時請務(wù)必記住,在寫 HDL 時要一直注意你寫的 HDL 的信號傳播會在某個時鐘切實地終止。否則,人類將難以理解你的電路的行為。

  實際開發(fā)過程中最艱難的部分就是邏輯綜合,其所需的時間多得離譜。在開始執(zhí)行綜合之后,我們往往需要等上多達 30 分鐘時間。所以開始綜合之后,我常常與其他也在等著綜合結(jié)束的 CPU 團隊成員玩《任天堂明星大亂斗 DX》。隨便說一下,我的角色是 Sheik。

  十二月下旬到一月中旬:通過將 Xv6 移植到 MIPS 來學(xué)習(xí)

  我們開始找到「操作系統(tǒng)需要 CPU 具備哪些功能?」這個問題的答案。

  OS 團隊誕生之后,我們開始每周聚會,閱讀 Xv6 源代碼。

  與此同時,我開始將 Xv6 移植到 MIPS。這樣做的部分原因是學(xué)習(xí) OS 在實現(xiàn)層的工作方式,部分原因是似乎還沒人將 Xv6 移植到 MIPS 過。我在大約一周內(nèi)完成了移植工作,直到調(diào)度器過程開始。在這個移植過程中,我花了大量精力研究 MIPS,并且為了了解 Xv6 的工作方式還大量研究了 x86。得益于此,我理解了中斷的相關(guān)機制以及實現(xiàn)層的內(nèi)存管理單元(MMU)。這時候,對于 Xv6 所需的 CPU 功能,我已經(jīng)有了扎實的理解。

  另外,在一月中旬,我們也開始努力通過注釋掉各個部分來編譯 Xv6 的整體代碼。結(jié)果是在我們自制架構(gòu)的模擬器上,Xv6 在引導(dǎo)順序中顯示出了第一條消息:

  xv6…cpu0:

  starting…

  與此同時,這意味著這時候 Ucc 已經(jīng)成長到足以編譯大部分 Xv6 代碼了。真是太棒了!

  二月:我們的 CPU GAIA 誕生!

  在 MIPS 移植過程中,我完成了 PIC 的初始化,這個過程很痛苦。另外,我還完成了實現(xiàn)中斷處理程序的任務(wù)。結(jié)果,Xv6 向 MIPS 的移植工作剛完成,第一個用戶程序就開始開發(fā)了。

  在這一經(jīng)歷的基礎(chǔ)上,我為我們的自制 CPU 編寫了中斷和虛擬地址轉(zhuǎn)譯的規(guī)范草稿。為了簡單,我們決定忽略 Ring 保護等硬件特權(quán)機制。至于虛擬地址轉(zhuǎn)譯,我們決定使用 x86 那樣的硬件頁面游走法(hardware page-walking method)??雌饋砜赡芎茈y在硬件中實現(xiàn)這個功能,但我們認為如果我們犧牲掉速度并忽視 TLB 實現(xiàn),可能就不會那么難。畢竟 Yuichi 后來做了一個很棒的 CPU 內(nèi)核,不過它一開始就安裝了 TLB。

  Yuichi 完成了我們的 CPU 的 ISA(指令集架構(gòu))的整體設(shè)計。他將我們的 CPU 命名為 GAIA。在典型的 CPU 實驗項目中,我們既不會實現(xiàn)中斷,也不會實現(xiàn) MMU。但是,Yuichi 開始為 Xv6 實現(xiàn)它們,他是基于第 3 組的 CPU 的重構(gòu)版本開發(fā)的。

  接下來,進度加快了,所以我將開始按周進行說明。

  第一周

  Masayoshi 開始為我們的 CPU 實現(xiàn)真正的初始化,而不只是將引導(dǎo)順序注釋掉;而 Shohei 將 Xv6 的 x86 匯編重寫進了我們自制的架構(gòu)中。我為我們的模擬器添加了中斷模擬,而這個模擬器是 Wataru 在 CPU 實驗的核心任務(wù)部分開發(fā)的;另外我還完成了對虛擬地址轉(zhuǎn)譯的支持。這能讓模擬器有足夠的功能來運行 OS。

  第二周

  我為我們的架構(gòu)構(gòu)建了一個原語鏈接器,以集成 Xv6 及其 binary blobs。Shohei 正在實現(xiàn)中斷處理程序,這部分很難。中斷很難理解,難以弄清流程、難以調(diào)試、難以開發(fā)。

  當(dāng)我將 Xv6 移植到 MIPS 時,我有 GDB,所以還能應(yīng)付,但我們自己的模擬器沒有任何調(diào)試功能,所以調(diào)試起來肯定非常難。

  Shohei 也頂不住這樣的難度,所以他為模擬器添加了一個反匯編器和調(diào)試 dump 函數(shù)。之后,OS 團隊又對這個模擬器的調(diào)試功能進行了快速升級,最后得到的模擬器看起來是這樣:

  第三周

  克服了許多困難之后,Xv6 的移植工作有所進展,但 Xv6 還是無法工作。

  尤其是 Ucc 的規(guī)范為 char 和 int 都是 32 位,這帶來了許多問題。這不是 Ucc 的錯。事實上,C 規(guī)范僅要求 sizeof(char) == 1 且 sizeof(char) <= sizeof(int),因此這是符合規(guī)范的。

  但是,Xv6 是為 x86 編寫的,所以它假設(shè) sizeof(int) == 4 并會將常量添加到指針的值,這會導(dǎo)致大量不一致。由這個問題帶來的漏洞很難查找,而且數(shù)量也很多,所以最后我們決定將 Ucc 的 char 規(guī)范改為 8 位。

  在將 char 32 位問題委托給 Ucc 團隊之后,我為首次進入階段寫了初始化頁面,并嘗試通過試錯方法讓中斷能夠有效工作。

  最重要的是,我們努力解決了第 4 個難題:Xv6 的可移植性差。

  2 月 27-28 日

  當(dāng)我回頭看 Slack 時,我發(fā)現(xiàn)這一天我們進展頗豐。在 Ucc 團隊很快完成將 char 改為 8 位的工作之后,我們進行了大量調(diào)試。最后,我們的第一個用戶程序 init 可以工作了!

  之后,我們在移植用戶過程應(yīng)用方面的成果越來越多,這是我在移植到 MIPS 時沒有做過的事情。在這個過程中,很多漏洞都很難重現(xiàn),中斷規(guī)范之中的不足之處也顯現(xiàn)了出來,但我們最終克服了困難,找到并修復(fù)了這些漏洞。

  我們修復(fù)的一個有趣問題是緩存別名問題。GAIA CPU 選擇了虛擬地址作為緩存索引,而非物理地址。這讓 CPU 在查找緩存時能夠跳過虛擬地址轉(zhuǎn)譯。但是,由于這樣的設(shè)計,我們發(fā)現(xiàn)緩存之間會出現(xiàn)不一致問題,因為虛擬地址的多個緩存可以指向同一個物理地址。當(dāng)一個虛擬地址的緩存更新之后,其它指向同一物理地址的虛擬地址的緩存卻沒有更新。

  這個漏洞很難在硬件層面上低成本地修復(fù),所以為了解決它,我們?yōu)槲覀兊?Xv6 引入了 Page Coloring。這會為每個緩存行引入「顏色」并重新分配頁面,使得指向同一物理地址的虛擬地址總會有一樣的顏色。這意味著指向同一物理地址的虛擬地址總是僅有一個緩存。這能讓 Xv6 確保 GAIA 永遠不會讓多個緩存共用同一個物理地址。

  三月:Xv6 跑起來!

  3 月 1 日,Xv6 移植工作完成。現(xiàn)在 Xv6 已經(jīng)運行在模擬器上了!

  娛樂少不了

  一開始,移植 Xv6 是因為這很有趣,現(xiàn)在 Xv6 已經(jīng)成功運行在模擬器上,那我們就要加把勁讓它更有趣。

  首先,Masayoshi 用大約 4 個小時做了一個小火車以及運行在 Xv6 上的 sl 命令。

  Shohei 則想做一個掃雷游戲。

  在這期間,Yuichi 完成了 Group X 的 CPU 實現(xiàn)工作。真正的 CPU 的運行速度比模擬器快多了,這能讓我們更輕松地玩耍和開發(fā)游戲。這時候,我們創(chuàng)建了一個非常高質(zhì)量的應(yīng)用:2048。

  這個 2048 的質(zhì)量很高。Yuichi 老是在玩。順便提一句,這個 2048 使用的是 non-line buffering 輸入,這是 Xv6 原本沒有的功能。為了支持這一功能,ioctl 被添加進來作為 read 和 write 之外的另一個 devsw 動作,另外還添加了用來控制 ICANON 和 echo 的與 termios 相關(guān)的新功能。因此,唯一能以如此高的完成度玩 2048 的 Xv6 就運行在 GAIA 上。

  另外,畢竟 Xv6 是由 Unix v6 啟發(fā)的,因此我猜想,添加 gtty 和 stty 系統(tǒng)調(diào)用是更像 Unix v6 的方法。不過,因為 Xv6 沒有 tty 的概念,所以我采用了 ioctl;而且事實上 Unix v7 就引入了 ioctl,所以這與歷史情況也接近。

  現(xiàn)在,更酷的是,Keiichi 又為 Xv6-GAIA 做了一個小型匯編器,Shohei 還做了一個 mini vi。想想看你能用這兩個工具做什么。

  這就是基于 FPGA 的交互式編程!這是 CPU 實驗的一個出色演示,因為其中一般不包括任何交互式程序。

  最棒的演示

  CPU 實驗實踐課程的原始任務(wù)是「在自制 CPU 上運行給定的光線追蹤程序」?,F(xiàn)在我們的 CPU 上有操作系統(tǒng)了,你知道該怎么做了嗎?我們決定在我們的 CPU 的 OS 上運行這個光線追蹤程序。我們遇到了一些問題,但我們在最終展示前一個小時里成功解決了它們。

  因此,我們做了我們這個系的學(xué)生開玩笑時說的話:在一個 CPU 上運行一個操作系統(tǒng),然后再在上面運行光線追蹤程序。

  來自 2020 年的回顧

  這段故事發(fā)生在 2015 年,本文也是我自己的博文的翻新稿。盡管現(xiàn)在讀來,我看到了當(dāng)時自身技術(shù)經(jīng)驗的不足,但我們當(dāng)時做的事情實在很激動人心。

  另外,你現(xiàn)在也可以在你的瀏覽器中體驗我們的 Xv6:https://nullpo-head.github.io/emcc-gaia-simu/xv6.html

  實驗之后,我將我們的 GAIA 模擬器通過 Emscripten 移植到了 JavaScript。去試試看我們的 sl、掃雷和 2048 吧。

  xv6…

  cpu0: starting

  init: starting sh

  $

  還要說一下,Xv6 向 MIPS 的移植工作在 CPU 實驗期間并沒有完成,而是在實驗之后一個月完成的。GitHub 代碼庫在這里:https://github.com/nullpo-head/xv6-mips

  在我們 2015 年寫文介紹了 Group X 的工作之后,后來的學(xué)生繼續(xù)攻堅有關(guān) OS 的新挑戰(zhàn)。

  2018 年,一些學(xué)生在自制的 CPU 上運行了他們自己開發(fā)的 OS;2019 年,一組學(xué)生運行了他們開發(fā)的 OS,同時采用了 RISC-V 作為他們自制 CPU 的 ISA。此外,2020 年的一個小組終于在自制 CPU 上成功運行 Linux,同時 ISA 也采用了 RISC-V。

  我相信未來還會有更多故事,也讓我們保持期待。從個人角度看,我很期待某天能看到某人在自己的 ISA 上運行 Linux,或在上面運行虛擬機。

  人們常說要避免重新造輪子,但這個過程確實能讓人學(xué)到很多東西。這讓我認識到,我對它的理解其實沒有那么深,無法從頭開始實現(xiàn)它。而且,我推薦這個故事的另一個原因是這真的非常有趣!

  我們的 CPU 實驗故事就到此為止了。如果你也有興趣重新發(fā)明輪子,可以試試自制 CPU 并移植 OS。



本站內(nèi)容除特別聲明的原創(chuàng)文章之外,轉(zhuǎn)載內(nèi)容只為傳遞更多信息,并不代表本網(wǎng)站贊同其觀點。轉(zhuǎn)載的所有的文章、圖片、音/視頻文件等資料的版權(quán)歸版權(quán)所有權(quán)人所有。本站采用的非本站原創(chuàng)文章及圖片等內(nèi)容無法一一聯(lián)系確認版權(quán)者。如涉及作品內(nèi)容、版權(quán)和其它問題,請及時通過電子郵件或電話通知我們,以便迅速采取適當(dāng)措施,避免給雙方造成不必要的經(jīng)濟損失。聯(lián)系電話:010-82306118;郵箱:aet@chinaaet.com。