虛擬機分兩部分:管理資源,執行指令。
執行指令部分:
協程,所有協程共享一個虛擬機。也可以理解成線程,但是同時只有一個線程正在執行。
- 維護運行棧:局部變量棧,函數調用棧。
- 維護狀態:stop,runing,finish,error。
- 提供接口:控制協程執行和暫停,通過棧傳入參數獲取結果。
- 讀取執行指令:實際比較簡單,就是讀取執行,判斷指令類型,做相應操作。
管理資源部分:
- 加載解析源代碼。
- 管理所有的協程:每次執行生成一個新的協程去執行。
- 管理對象生命周期:垃圾回收,對象池什么的。
變量棧
lua內部函數調用和外部函數調用,都依賴棧來傳遞參數和返回值。
傳遞參數時,參數緊跟函數對象壓入棧中,通過棧頂位置和函數對象位置可以知道參數了哪些參數。
傳遞返回值時,內部函數調用和外部擴展函數調用的處理是不一樣的。
- lua內部函數的return命令負責把返回值copy到函數對象的位置,并重新設置棧頂。
- 外部函數將返回參數壓入棧頂,通過返回值告訴lua返回值數量,lua的調用模塊負責復制copy返回值到函數對象所在的位置。
閉包實現
現在的腳本語言都支持閉包,并且使用的相當自然,程序員可能都沒有察覺。
function test_closure()
-- 這個num被Add和Print捕捉成閉包變量,生命周期延長了
local num = 0
local function Add()
num = num + 1
end
local function Print()
print(num)
end
return Add, Print
end
local Add,Print = test_closure()
Print() -- 0
Add()
Print() -- 1
閉包變量可以理解成子函數保存了父級函數的局部變量的指針。
閉包本身可以當成運行時函數,包含函數定義和運行環境變量兩部分
一種實現:閉包變量作為一個特殊對象分兩種狀態:
- 新建閉包時new一批閉包變量,是個指針容器,指向棧上的局部變量,出入開狀態。
- 當局部變量回收時,閉包變量copy局部變量,進入閉狀態。
(luna的閉包實現想討巧,去掉開閉狀態維護,然而有bug)
協程
lua支持協程,核心點是lua支持中斷當前協程的執行,等待后續恢復執行。中斷協程的執行需要保存當前所有函數調用信息,一個函數調用可以理解成一個函數幀。
存在一個問題:c->lua->c->lua(yield)
,中間插入了一次c調用,中斷協程時lua是沒有辦法保存c的狀態的。
故而lua提供了lua_callk
函數,用于處理這種情況,告訴lua這層c函數幀可以不保存??梢岳斫獬晌舱{用,當前函數幀可以忽略掉。
oms的處理是,每次調用,都是開一個新的協程去執行,不存在插入外部函數幀的問題。(寫oms的原因之一就是想試試這種方法)
一些想法:
- 同步式的寫代碼:腳本本身對與被中斷無感知,比如讀取數據庫,不需要回調函數去處理。
- 中斷方負責協程的繼續執行。
垃圾回收
lua是用的標記掃描的方式來回收的,實現簡單,還可以實現整理碎片內存的功能。
oms偷懶了,用C#之類的帶垃圾回收的語言實現,也就不需要再加一層垃圾回收了。
但是有個缺點,不好實現對象內存池。內存池的回收接口調用有問題,只能等C#回收對象是,調用析構函數處理了。