第 12 章、系統軟體實作 作者:陳鍾誠 旗標出版社
第 12 章、系統軟體實作 12.1 簡介 12.2 組譯器實作 12.3 虛擬機實作 12.4 剖析器實作 12.5 編譯器實作 12.1 簡介 12.2 組譯器實作 12.3 虛擬機實作 12.4 剖析器實作 12.5 編譯器實作 12.6 整合測試
12.1 簡介 C 語言實作 目的 動態陣列 + 雜湊表格 (12.1 節) 組譯器 (12.2 節) 虛擬機器 (12.3 節) 12.1 簡介 C 語言實作 動態陣列 + 雜湊表格 (12.1 節) 組譯器 (12.2 節) 虛擬機器 (12.3 節) 剖析器 (12.4 節) 編譯器 (12.5 節) 目的 展示系統軟體的實作方法與技巧 讓讀者熟悉系統軟體的原理 培養開發系統軟體的能力 以實作印證理論, 以理論支持實作
實作程式列表
動態陣列 實作目的 標準 C 語言函式庫當中缺乏一些基本資料結構的相 關函數, 因此我們必須先設計出這些資料結構, 像是 動態陣列 (Array)、雜湊表 (Hash Table) 等 以便在後續的組譯器、虛擬機、剖析器與編譯器中, 可以很容易的利用這些結構存放像符號表、指令表、 詞彙串列、剖析樹等資料。
動態陣列的資料結構與函數
動態陣列新增 – ArrayAdd()
動態陣列的使用
動態陣列的測試結果
動態陣列的列舉 - ArrayEach()
雜湊表 實作目的 標準 C 語言函式庫當中缺乏一些基本資料結構的相 關函數, 因此我們必須先設計出這些資料結構, 像是 動態陣列 (Array)、雜湊表 (Hash Table) 等 以便在後續的組譯器、虛擬機、剖析器與編譯器中, 可以很容易的利用這些結構存放像符號表、指令表、 詞彙串列、剖析樹等資料。
雜湊表的資料結構與函數
雜湊函數的實作 將字串 key 中的每個位元組相加後, 對雜湊表的大小取 餘數後的結果 這個方法雖然不是很好的雜湊函數,但是簡單卻足夠 了。
雜湊表:取得對應鍵值的元素 - HashTableGet(table, key)
雜湊表:放入鍵值與元素 - HashTablePut(table, key, data)
12.2 組譯器實作 撰寫一個簡單的 CPU0 組譯器 – as0 印證組譯器的理論 學習組譯器的實作方式 使用方式 12.2 組譯器實作 撰寫一個簡單的 CPU0 組譯器 – as0 印證組譯器的理論 學習組譯器的實作方式 使用方式 as0 <asmFile> <objFile>
組譯範例:as0 ArraySum.asm0 ArraySum.obj0
組譯器的資料結構
組譯器的函數
組譯器的主要函數 – assemble(asmFile, objFile)
組譯器的第一階段 (計算符號位址)
計算指令大小 – AsmCodeSize()
組譯器的第2階段
指令轉機器碼 (J 型指令)
指令轉機器碼 (A 型指令)
資料轉二進位碼
12.3 虛擬機實作 當我們用 as0 組譯出了目的碼之後, 這個目的碼仍 然無法被執行 12.3 虛擬機實作 當我們用 as0 組譯出了目的碼之後, 這個目的碼仍 然無法被執行 這是因為筆者所使用的處理器並不是 CPU0, 而是 Intel 的 IA32 CPU 為核心的電腦。 為了讓讀者能執行 CPU0 的目的碼, 筆者只好繼續 撰寫一個虛擬機器 vm0 以便讓讀者執行 as0 所組譯出的目的碼, 本節將描 述 vm0 的使用方式與設計原理。
虛擬機的執行 vm0 ArraySum.obj0
執行後的傾印
虛擬機的資料結構與函數
虛擬機的最上層函數 – runObjFile(objFile)
虛擬機使用的位元操作函數
定義暫存器別名
虛擬機的主要函數 – Cpu0Run()
Cpu0Run() - 指令擷取階段
Cpu0Run() – 解碼階段
Cpu0Run() – 執行階段 (載入指令)
Cpu0Run() – 執行階段 (運算指令)
Cpu0Run() – 執行階段 (跳躍指令)
Cpu0Run() – 執行階段 (結尾)
虛擬機:傾印暫存器 - Cpu0Dump()
12.4 剖析器實作 C0 語言的剖析器 (Parser) 是編譯器與直譯器中的關鍵程式 作為 c0c 編譯器的語法剖析程式 12.4 剖析器實作 C0 語言的剖析器 (Parser) 是編譯器與直譯器中的關鍵程式 作為 c0c 編譯器的語法剖析程式 會呼叫詞彙掃描器 Scanner 取的詞彙
掃描器 詞彙掃描器 Scanner 是一個較為簡單的物件 用來取得下一個詞彙 (token) 提供剖析器呼叫使用
掃描器的使用方法
掃描器的資料結構
判斷詞彙的型態 – tokenToType(token)
掃描器的主要函數
剖析器 Parser.h 剖析器的資料結構與函數宣告 Parser.c 剖析器的程式實作
剖析器的資料結構與函數
剖析器的最上層函數 parse()
遞迴下降剖析法
剖析 for 迴圈語法
剖析器中的 next() 函數
剖析器中的 push(), pop() 函數
圖 12.1 遞迴下降剖析器的執行過程 … PROG BaseList BASE STMT 成 長 方 向 堆疊 (Stack) 圖 12.1 遞迴下降剖析器的執行過程 PROG BaseList BASE STMT 成 長 方 向 堆疊 (Stack) i : id = 0:number EXP ITEM FOR ( for …
12.5 編譯器實作 c0c 編譯器 執行方法 輸入:C0 語法的程式 輸出:CPU0 的組合語言 12.5 編譯器實作 c0c 編譯器 輸入:C0 語法的程式 輸出:CPU0 的組合語言 執行方法 c0c <c0File> <asmFile>
編譯的範例
編譯器的最上層函數 範例 12.24 的 compile(cFile, asmFile) 剖析器:parse(cText) 程式產生器: generate(parser->tree, asmFile)
程式碼產生器的最上層函數
程式碼產生器的資料結構與函數
程式碼產生器 GenCode() – 開頭
GenCode() – 處理 FOR
GenCode() – 處理 STMT
GenCode() – 處理 COND
GenCode() – 處理 EXP
GenCode() – 結尾
輸出中間碼 P-Code
P-Code 轉為組合語言 (前半段)
P-Code 轉為組合語言 (後半段)
輸出組合語言
12.6 整合測試 單一主程式, 以條件編譯的方式 方法是利用 C 語言的巨集編譯指令 12.6 整合測試 單一主程式, 以條件編譯的方式 編譯出 test, c0c, as0, vm0 等四個執行檔 方法是利用 C 語言的巨集編譯指令 #if … #elif…#endif
整個系統的主程式 (前半段)
整個系統的主程式 (後半段)
專案建置檔 makefile (第1部分)
專案建置檔 makefile (第2部分)
專案建置檔 makefile (第3部分)
建置執行過程 (1)
執行:編譯器 (剖析)
執行:編譯器 (中間碼產生)
執行:編譯器 (產生組合語言)
執行:組譯器 (第1階段:計算符號位址)
執行:組譯器 (第2階段:指令轉機器碼)
執行:組譯器 (輸出:目的碼)
執行:虛擬機
結語 系統軟體實作 本章以 C 語言實作了 動態陣列 雜湊表 編譯器 c0c test.c0 test.asm0 組譯器 as0 test.asm0 test.obj0 虛擬機 vm0 test.obj0
習題 12.1 請撰寫一個 C0 語言的程式 fib.c0, 可以利用 for 迴圈的方式算出費氏序列 中的 f(10) 的值, 費氏序列的規則為 f(n) = f(n-1)+f(n-2), 而且 f(0) = 1, f(1)=1。 12.2 請利用 c0c 編譯器, 將 fib.c0 編譯為組合語言 fib.asm0。 12.3 請利用 as0 組譯器, 將 fib.asm0 組譯為目的檔 fib.obj0。 12.4 請利用 vm0 虛擬機, 執行 fib.obj0, 並檢查看看 f(10) 的結果是否正確。 12.5 請為 C0 語言加上 if 條件的規則為 IF = ‘if’ ‘(’ COND ‘)’ BLOCK (‘ elseif’ BLOCK)* (else BLOCK)?, 然後修改本章的剖析器程式, 加入可以處理該規則的 程式。 12.6 繼續前一題, 請修改本章的程式碼產生器程式, 以產生上述的 if 規則之程 式。