簡介Python虛擬機中的Python運行環(huán)境
其實Python運行環(huán)境是一個全局性的概念,而執(zhí)行環(huán)境實際就是一個棧幀,是Code Block對應(yīng)的概念,兩者之間存在著本質(zhì)上的區(qū)別,在以后的運行操作過程中就可以了解到他們呢兩者之間的不同。
運行時環(huán)境的初始化過程非常地復(fù)雜,后面將用單獨的一章來剖析,這里假設(shè)初始化的動作已經(jīng)完成,我們已經(jīng)站在了Python虛擬機的門檻外,只需要輕輕推動一下***張骨牌,整個執(zhí)行過程就像多米諾骨牌一樣,一環(huán)扣一環(huán)地展開。
這個推動***張骨牌的地方在一個名叫PyEval_EvalFramEx的函數(shù)中,這個函數(shù)實際上就是Python的虛擬機的具體實現(xiàn),它是一個非常巨大的函數(shù),因此我們在列出其中的源代碼時和以前有些不同。
PyEval_EvalFrameEx首先會初始化一些變量,其中PyFrameObject對象中的PyCodeObject對象包含的重要信息都被照顧到了。當(dāng)然,另一個重要的動作就是初始化了堆棧的棧頂指針,使其指向f->f_stacktop:
- [PyEval_EvalFrameEx in ceval.c]
- co = f->f_code;
- names = co->co_names;
- coconsts = co->co_consts;
- ffastlocals = f->f_localsplus;
- ffreevars = 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域中保存著字節(jié)碼指令和字節(jié)碼指令的參數(shù),Python虛擬機執(zhí)行字節(jié)碼指令序列的過程就是從頭到尾遍歷整個co_code、依次執(zhí)行字節(jié)碼指令的過程。
在Python運行環(huán)境的虛擬機中,利用3個變量來完成整個遍歷過程。co_code實際上是一個PyStringObject對象,而其中的字符數(shù)組才是真正有意義的東西。這也就是說,整個字節(jié)碼指令序列實際上就是一個在C中普普通通的字符數(shù)組。因此,遍歷過程中所使用的這3個變量都是char*類型的變量:first_instr永遠(yuǎn)指向字節(jié)碼指令序列的開始位置;
next_instr永遠(yuǎn)指向下一條待執(zhí)行的字節(jié)碼指令的位置;f_lasti指向上一條已經(jīng)執(zhí)行過的字節(jié)碼指令的位置。展示了這3個變量在遍歷中某時刻的情形:
- [ceval.c]
- /* Interpreter main loop */
- PyObject* PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
- {
- ……
- why = WHY_NOT;
- ……
- for (;;) {
- ……
- fast_next_opcode:
- f->f_lasti = INSTR_OFFSET();
- //獲得字節(jié)碼指令
- opcode = NEXTOP();
- oparg = 0;
- //如果指令需要參數(shù),獲得指令參數(shù)
- if (HAS_ARG(opcode))
- oparg = NEXTARG();
- dispatch_opcode:
- switch (opcode) {
- case NOP:
- goto fast_next_opcode;
- case LOAD_FAST:
- ……
- }
- }
那么這個一步一步的動作是如何完成的呢,我們來看一看Python運行環(huán)境執(zhí)行字節(jié)碼指令的整體架構(gòu),其實就是一個for循環(huán)加上一個巨大的switch/case結(jié)構(gòu),熟悉Windows SDK編程的朋友可以想象一下Windows下那個巨大的消息循環(huán),就是那樣的結(jié)構(gòu)。在對PyCodeObject對象的分析中我們說過,Python的字節(jié)碼有的是帶參數(shù)的,有的是沒有參數(shù)的,而判斷是否帶參字節(jié)碼是通過HAS_ARG這個宏實現(xiàn)的。
注意,對不同的字節(jié)碼指令,由于存在是否需要指令參數(shù)的區(qū)別,所以next_instr的位移可能是不同的。但是無論如何,next_instr總是指向Python下一條要執(zhí)行的字節(jié)碼,這很像x86平臺上的那個PC寄存器。
Python在獲得了一條字節(jié)碼指令和其需要的指令參數(shù)后,會對字節(jié)碼指令利用switch進行判斷,根據(jù)判斷的結(jié)果選擇不同的case語句,每一條字節(jié)碼指令都會對應(yīng)一個case語句。在case語句中,就是Python對字節(jié)碼指令的實現(xiàn)。
在成功執(zhí)行完一條字節(jié)碼指令后,Python運行環(huán)境的執(zhí)行流程會跳轉(zhuǎn)到fast_next_opcode處,或者是for循環(huán)處,不管如何,Python接下來的動作都是獲得下一條字節(jié)碼指令和指令參數(shù),完成對下一條指令的執(zhí)行。如此一條一條地遍歷co_code中包含的所有字節(jié)碼指令,最終完成了對Python程序的執(zhí)行。
【編輯推薦】