一、虛擬機設計方案
1. 架構風格
Python虛擬機實際上是一個解釋器,對編譯后的字節碼進行解釋、執行。因此解釋器風格顯然是最適合本項目的。
2. 分解視圖
虛擬機輸入為字節碼.pyc文件,由字節碼文件加載器將二進制的.pyc文件加載到內存,由執行引擎解釋執行,輸出為字節碼文件的執行結果。虛擬機總共分為以下四個模塊:
- 字節碼加載器:python字節碼是按固定格式存放的二進制文件,而在虛擬機進程中,字節碼對象是codeobject類型,加載器的作用就是在類型體系模塊的支持下,將固定格式的二進制文件解析成內存中的codeobject類型對象,然后將codeobject交由執行引擎運行。
- 執行引擎:本質是虛擬機的解釋器部分,本次虛擬機設計為棧式執行,所以解釋器維護一個操作棧,字節碼所有的操作都在棧上執行。解釋器主要運行在一個很大switch分支循環中,每個分支分別為對應字節碼的解釋,直到字節碼執行結束跳出循環結束虛擬機。解釋器還維護一個個虛擬機操作數棧幀frame,每個frame對應一個函數或方法的調用,且第一個frame為第一個codeobject。每個frame中有一個指向調用者frame的指針、當前frame字節碼位置的pc、對應的codeobjcet等。在解釋器中,在要操作棧上產生的類型對象都在堆中產生,依賴於堆區的內存管理,虛擬機可以不用管理對象的析構,而交由堆區自動垃圾回收。
- 類型體系:在python中,一切都是對象,虛擬機有着一個龐大的類型體系。這個模塊也是最基本最重要的模塊,大致上有Integer、Float、String、List、Set、Dict、Tuple這7個基本類型,還有運行時的codeobject、frame,其他的內建類型type、function、method、slice、iterator、module等。還有一個特殊的類型object,它是虛擬機中大部分類型的父類。
- 堆區內存管理:管理類型對象的關鍵,對象的分配權力交給了堆,在堆中使用自動內存管理算法,如標記-清除、標記-復制等算法。
3. 依賴視圖
依賴視圖展現了軟件模塊之間的依賴關系。
各個模塊之間的依賴關系如下所示:
字節碼加載器部分分為兩個類,他們之間不存在依賴關系:
Type類存放了對象的類型信息和對應的方法,Object類依賴於Type類,其他的基本類型都繼承自Object類:
4. 執行視圖
執行視圖展示了系統運行時的時序結構特點。
5. 實現視圖
實現視圖是描述軟件架構與源文件之間的映射關系。
虛擬機源代碼目錄的結構如下所示:
pvm/
├── bin
├── include
│ ├── Code.h
│ ├── Dict.h
│ ├── Float.h
│ ├── GlobalVar.h
│ ├── Integer.h
│ ├── List.h
│ ├── Object.h
│ ├── OpCode.h
│ ├── Str.h
│ ├── Type.h
│ └── global.h
├── main.c
├── objects
│ ├── Code.c
│ ├── Dict.c
│ ├── Float.c
│ ├── Integer.c
│ ├── List.c
│ ├── Object.c
│ └── Str.c
├── runtime
└── utils
├── FileLoader.c
├── FileLoader.h
├── FileReader.c
├── FileReader.h
├── Log.c
└── Log.h
二、核心數據結構
Python2字節碼文件結構
|
Magic number (4B) |
Modified date (4B) |
‘c‘ (1B,表示接下來是CodeObject) |
|
Argcount (4B) |
Nlocals (4B) |
Stacksize (4B) |
Flags (4B) |
|
字節碼 |
‘s‘ (1B) |
字節碼長度 (4B) |
字節碼1,字節碼2 … |
|
常量表 |
‘(‘ (1B) |
常量個數 (4B) |
常量1,常量2 … |
|
變量表 |
‘(‘ (1B) |
變量個數 (4B) |
變量1,變量2 … |
|
參數列表 |
‘(‘ (1B) |
|
|
|
Cell var |
‘(‘ (1B) |
|
|
|
Free var |
‘(‘ (1B) |
|
|
|
文件名 |
‘s‘ (1B) |
名字長度 (4B) |
|
|
模塊名 |
‘t‘ (1B) |
模塊名長度 (4B) |
|
|
行號表(調試) |
‘s‘ (1B) |
|
|
三、運行環境和技術選型
Python是跨平台語言,其生成的字節碼文件在不同平台上具有相同的結構,因此虛擬機的運行環境可以是Windows,Linux,macOS。虛擬機的開發語言定為C語言。
四、核心工作機制
以運行一個內容為 “print(1 + 2 * 3)” 的python源文件為例,假設源文件經過編譯生成的字節碼是a.pyc,要執行這個文件,需要先讀取a.pyc中的字節碼列表和常量表。a.pyc的字節碼列表中有LOAD_CONST、BINARY_ADD、PRINT_ITEM、PRINT_NEWLINE等指令,常量表中有常量1和6,我們根據字節碼列表中的指令,在操作數棧上進行對應操作:指令為LOAD_CONST時從常量表取出數字放進棧里面,指令為BINARY_ADD時就從棧中取出數字相加,指令為PRINT_ITEM就打印計算結果,這樣就完成了對a.pyc的運行。