8.3 python虛擬機器的執行框架
· 1.5 python物件的分類
·8.1 python虛擬機器中的執行環境
·8.2 名字、作用域和名字空間
·8.4 python執行時環境初探
·15.1 gil與執行緒排程
·15.2 初見python thread
8.3 python虛擬機器的執行框架
當python啟動後,首先會進行python執行時環境的 初始化。注意這裡的執行時環境是乙個與上一節剖析的執行環境不同的概念。執行時環境是乙個全域性的概念,而執行環境實際就是乙個棧幀,是乙個與某個code block對應的概念。這裡不明白兩者的區別不要緊,在以後剖析執行時環境初始化時我們就能弄清楚兩者的區別和聯絡。執行時環境的初始化過程非常地複雜, 後面將用單獨的一章來剖析,這裡假設初始化的動作已經完成,我們已經站在了python虛擬機器的門檻外,只需要輕輕推動一下第一張骨牌,整個執行過程就像 多公尺諾骨牌一樣,一環扣一環地展開。
這個推動第一張骨牌的地方在乙個名叫pyeval_evalframex的函式中,這個函式實際上就是python的虛擬機器的具體實現,它是乙個非常巨大的函式,因此我們在列出其中的源**時和以前有些不同。
pyeval_evalframeex首先會初始化一些變數,其中pyframeobject物件中的pycodeobject物件包含的重要資訊都被照顧到了。當然,另乙個重要的動作就是初始化了堆疊的棧頂指標,使其指向f->f_stacktop:
[pyeval_evalframeex in ceval.c]
co = f->f_code;
names = co->co_names;
consts = co->co_consts;
fastlocals = f->f_localsplus;
freevars = f->f_localsplus + co->co_nlocals;
first_instr = (unsigned char*)pystring_as_string(co->co_code);
next_instr = first_instr + f->f_lasti + 1;
stack_pointer = f->f_stacktop;
f->f_stacktop = null; /* remains null unless yield suspends frame */
前面我們說過,在pycodeobject物件的 co_code域中儲存著位元組碼指令和位元組碼指令的引數,python虛擬機器執行位元組碼指令序列的過程就是從頭到尾遍歷整個co_code、依次執行位元組 碼指令的過程。在python的虛擬機器中,利用3個變數來完成整個遍歷過程。co_code實際上是乙個pystringobject物件,而其中的字元 陣列才是真正有意義的東西,這也就是說,整個位元組碼指令序列實際上就是乙個在c中普普通通的字元陣列。因此,遍歷過程中所使用的這3個變數都是char* 型別的變數:first_instr永遠指向位元組碼指令序列的開始位置;next_instr永遠指向下一條待執行的位元組碼指令的位置;f_lasti指 向上一條已經執行過的位元組碼指令的位置。圖8-5展示了這3個變數在遍歷中某時刻的情形:
圖8-5 遍歷位元組碼指令序列
那麼這個一步一步的動作是如何完成的呢,我們來看一看 python虛擬機器執行位元組碼指令的整體架構,其實就是乙個for迴圈加上乙個巨大的switch/case結構,熟悉windows sdk程式設計的朋友可以想象一下windows下那個巨大的訊息迴圈,就是那樣的結構:
[ceval.c]
/* interpreter main loop */
pyobject* pyeval_evalframeex(pyframeobject *f, int throwflag)
}注意,這只是乙個極度簡化之後的python虛擬機器的樣子,如果想一睹python虛擬機器的尊容,請參考ceval.c中的原始碼。
在這個執行架構中,對位元組碼的一步一步地遍歷是通過幾個巨集來實現的:
[pyeval_evalframeex in ceval.c]
#define instr_offset() (int(next_instr - first_instr))
#define nextop() (*next_instr++)
#define nextarg() (next_instr += 2, (next_instr[-1]<<8) + next_instr[-2])
在對pycodeobject物件的分析中我們說過, python的位元組碼有的是帶引數的,有的是沒有引數的,而判斷是否帶參位元組碼是通過has_arg這個巨集實現的。注意,對不同的位元組碼指令,由於存在是 否需要指令引數的區別,所以next_instr的位移可能是不同的。但是無論如何,next_instr總是指向python下一條要執行的位元組碼,這 很像x86平台上的那個pc暫存器。
python在獲得了一條位元組碼指令和其需要的指令引數後,會對位元組碼指令利用switch進行判斷,根據判斷的結果選擇不同的case語句,每一條位元組碼指令都會對應乙個case語句。在case語句中,就是python對位元組碼指令的實現。
在成功執行完一條位元組碼指令後,python的執行流程會跳 轉到fast_next_opcode處,或者是for迴圈處,不管如何,python接下來的動作都是獲得下一條位元組碼指令和指令引數,完成對下一條指 令的執行。如此一條一條地遍歷co_code中包含的所有位元組碼指令,最終完成了對python程式的執行。
需要提到的一點是那個名叫「why」的神秘變數,它指示了在 退出這個巨大的for迴圈時python執行引擎的狀態。因為python執行引擎不一定每次執行都會正確無誤,很有可能在執行到某條位元組碼的時候,產生 了錯誤,這就是我們熟悉的那個「異常」——exception。所以在python退出了執行引擎的時候,就需要知道執行引擎到底是因為什麼原因結束了對 位元組碼指令的執行。是正常結束呢?還是因為有錯誤發生,實在是執行不下去了?why義無反顧地擔負起這一重任。關於why在python虛擬機器中作用的詳 細剖析,我們留到剖析異常機制時詳細講述。
變數why的取值範圍在ceval.c中被定義,其實也就是python結束位元組碼執行時的狀態:
[ceval.c]
/* status code for main loop (reason for stack unwind) */
enum why_code ;
現在,想必大家已經對python的執行引擎的大體框架了然 於胸了。在python的執行流程進入了pyeval_evalframeex中的那個for迴圈,取出第一條位元組碼之後,第一張多公尺諾骨牌已經被推倒, 命運不可阻擋地降臨了。一條接一條的位元組碼像潮水一樣洶湧而來,浩浩蕩蕩,橫無際涯。
首頁
libevent原始碼深度剖析
序幕 張亮上來當然要先誇獎啦,libevent 有幾個顯著的亮點 事件驅動 event driven 高效能 輕量級,專注於網路,不如ace那麼臃腫龐大 源 相當精煉 易讀 跨平台,支援windows linux bsd和mac os 支援多種i o多路復用技術,epoll poll dev pol...
libevent原始碼深度剖析
上來當然要先誇獎啦,libevent 有幾個顯著的亮點 事件驅動 event driven 高效能 輕量級,專注於網路,不如ace那麼臃腫龐大 源 相當精煉 易讀 跨平台,支援windows linux bsd和mac os 支援多種i o多路復用技術,epoll poll dev poll sel...
libevent原始碼深度剖析
序幕 張亮上來當然要先誇獎啦,libevent 有幾個顯著的亮點 事件驅動 event driven 高效能 輕量級,專注於網路,不如ace那麼臃腫龐大 源 相當精煉 易讀 跨平台,支援windows linux bsd和mac os 支援多種i o多路復用技術,epoll poll dev pol...