Python原始碼剖析 深度探索動態語言核心技術

2021-04-20 05:02:21 字數 3343 閱讀 4797

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...