Skip to content

值得您信賴的旅遊品牌 | 團體旅遊、自由行的專家‎

機場接送

Menu
  • 首頁
  • 旅遊天地
  • 裝潢設計
  • 環保清潔
  • 發燒車訊
Menu

python3 源碼閱讀-虛擬機運行原理

Posted on 2021-01-272021-01-27 by admin

閱讀源碼版本python 3.8.3

參考書籍<<Python源碼剖析>>

參考書籍<<Python學習手冊 第4版>>

官網文檔目錄介紹

  1. Doc目錄主要是官方文檔的說明。
  2. Include:目錄主要包括了Python的運行的頭文件。
  3. Lib:目錄主要包括了用Python實現的標準庫。
  4. Modules: 該目錄中包含了所有用C語言編寫的模塊,比如random、cStringIO等。Modules中的模塊是那些對速度要求非常嚴格的模塊,而有一些對速度沒有太嚴格要求的模塊,比如os,就是用Python編寫,並且放在Lib目錄下的
  5. Objects:該目錄中包含了所有Python的內建對象,包括整數、list、dict等。同時,該目錄還包括了Python在運行時需要的所有的內部使用對象的實現。
  6. Parser:該目錄中包含了Python解釋器中的Scanner和Parser部分,即對Python源碼進行詞法分析和語法分析的部分。除了這些,Parser目錄下還包含了一些有用的工具,這些工具能夠根據Python語言的語法自動生成Python語言的詞法和語法分析器,將python文件編譯生成語法樹等相關工作。
  7. Programs目錄主要包括了python的入口函數。
  8. Python:目錄主要包括了Python動態運行時執行的代碼,裡面包括編譯、字節碼解釋器等工作。

1. 總體架構

  • Runtime Env:python運行時環境,初始化對象/類型系統(Object/Type structures),內存分配器(Memory Allocator) 和 運行時狀態信息 (Current state of Python)。運行時狀態維護了解釋器在執行字節碼時不同的狀態(如正常和異常)之間的切換動作,可以視為一個巨大而複雜的有窮狀態機。內存管理機制可參考另外一篇文章Python3 源碼閱讀 – 內存管理機制。

  • Python Core: 中間部分是python的核心—-解釋器(PyInterpreter), 也可以成為PVM。大致流程就是 先對.py程序進行此法分析,將文件輸入的源代碼或從命令行輸入的一行行python代碼切分一個個Token, 然後使用Parser進行語法分析,建立抽象語法樹(AST), Compiler根據AST生成字節碼指令集合,最後由Code Evaluator來執行這些字節碼。

  • File Groups: Python Lib庫和用戶自己的模塊包等源代碼文件

2. Run Python文件的啟動流程

Python啟動是由Programs下的python.c文件中的main函數開始執行

/* Minimal main program -- everything is loaded from the library */

#include "Python.h"
#include "pycore_pylifecycle.h"

#ifdef MS_WINDOWS
int
wmain(int argc, wchar_t **argv)
{
    return Py_Main(argc, argv);
}
#else
int
main(int argc, char **argv)
{
    return Py_BytesMain(argc, argv);
}
#endif
int
Py_Main(int argc, wchar_t **argv) {
    ...
    return pymian_main(&args);
}

static int
pymain_main(_PyArgv *args)
{
    PyStatus status = pymain_init(args);  // 初始化
    if (_PyStatus_IS_EXIT(status)) {
        pymain_free();
        return status.exitcode;
    }
    if (_PyStatus_EXCEPTION(status)) {
        pymain_exit_error(status);
    }

    return Py_RunMain();
}

2.1 初始化關鍵流程

  • 初始化一些與配置項 如:開啟utf-8模式,設置Python內存分配器
  • 初始化pyinit_core核心部分
    • 創建生命周期 pycore_init_runtime, 同時生成HashRandom
    • 初始化線程和解釋器並創建GIL鎖 pycore_create_interpreter
    • 初始化所有基礎類型,list, int, tuple等 pycore_init_types
    • 初始化sys模塊 _PySys_Create
    • 初始化內建函數或者對象,如map, None, True等 pycore_init_builtins
      • 其中包括內建的錯誤類型初始化 _PyBuiltins_AddExceptions

Python3.8 對Python解釋器的初始化做了重構PEP 587-Python初始化配置

2.2 run 相關源碼閱讀

int
Py_RunMain(void)
{
    int exitcode = 0;
	
    pymain_run_python(&exitcode);  //執行python腳本

	if (Py_FinalizeEx() < 0) {  // 釋放資源
        /* Value unlikely to be confused with a non-error exit status or
           other special meaning */
        exitcode = 120;
    }

    pymain_free();   // 釋放資源

    if (_Py_UnhandledKeyboardInterrupt) {
        exitcode = exit_sigint();
    }

    return exitcode;
}


static void
pymain_run_python(int *exitcode)
{   
    // 獲取一個持有GIL鎖的解釋器
    PyInterpreterState *interp = _PyInterpreterState_GET_UNSAFE();
    /* pymain_run_stdin() modify the config */
    ... // 添加sys_path等操作

    if (config->run_command) {
        // 命令行模式
        *exitcode = pymain_run_command(config->run_command, &cf); 
    }
    else if (config->run_module) {
        // 模塊名
        *exitcode = pymain_run_module(config->run_module, 1);
    }
    else if (main_importer_path != NULL) {
        *exitcode = pymain_run_module(L"__main__", 0);
    }
    else if (config->run_filename != NULL) {
        // 文件名
        *exitcode = pymain_run_file(config, &cf);
    }
    else {
        *exitcode = pymain_run_stdin(config, &cf);
    }

	...
}

/* Parse input from a file and execute it */ //Python/pythonrun.c
int
PyRun_AnyFileExFlags(FILE *fp, const char *filename, int closeit,
                     PyCompilerFlags *flags)
{
    if (filename == NULL)
        filename = "???";
    if (Py_FdIsInteractive(fp, filename)) {
        int err = PyRun_InteractiveLoopFlags(fp, filename, flags);  // 是否是交互模式
        if (closeit)
            fclose(fp);
        return err;
    }
    else
        return PyRun_SimpleFileExFlags(fp, filename, closeit, flags);   // 執行腳本
}

// 執行python .py文件
int
PyRun_SimpleFileExFlags(FILE *fp, const char *filename, int closeit,
                        PyCompilerFlags *flags)
{
    ...
    if (maybe_pyc_file(fp, filename, ext, closeit)) {
        FILE *pyc_fp;
        /* Try to run a pyc file. First, re-open in binary */
        ...
        v = run_pyc_file(pyc_fp, filename, d, d, flags);
    } else {
        /* When running from stdin, leave __main__.__loader__ alone */
        ...
        v = PyRun_FileExFlags(fp, filename, Py_file_input, d, d,
                              closeit, flags);
    }
    ...
}

PyObject *
PyRun_FileExFlags(FILE *fp, const char *filename_str, int start, PyObject *globals,
                  PyObject *locals, int closeit, PyCompilerFlags *flags)
{
    ...
    // // 解析傳入的腳本,解析成AST
    mod = PyParser_ASTFromFileObject(fp, filename, NULL, start, 0, 0,
                                     flags, NULL, arena); 
    ...
    // 將AST編譯成字節碼然後啟動字節碼解釋器執行編譯結果
    ret = run_mod(mod, filename, globals, locals, flags, arena);
    ...
}

// 查看run_mode
static PyObject *
run_mod(mod_ty mod, PyObject *filename, PyObject *globals, PyObject *locals,
            PyCompilerFlags *flags, PyArena *arena)
{
    ...
    // 將AST編譯成字節碼
    co = PyAST_CompileObject(mod, filename, flags, -1, arena);  
    ...

    // 解釋執行編譯的字節碼
    v = run_eval_code_obj(co, globals, locals);
    Py_DECREF(co);
    return v;
}

2.3 字節碼查看案例

新建test.py

def show(a):
    return  a


if __name__ == "__main__":
    print(show(10))

執行命令: python3 -m dis test.py

λ ppython3 -m dis test.py
  3           0 LOAD_CONST               0 (<code object show at 0x000000E7FC89E270, file "test.py", line 3>)
              2 LOAD_CONST               1 ('show')
              4 MAKE_FUNCTION            0
              6 STORE_NAME               0 (show)

  7           8 LOAD_NAME                1 (__name__)
             10 LOAD_CONST               2 ('__main__')
             12 COMPARE_OP               2 (==)
             14 POP_JUMP_IF_FALSE       28

  8          16 LOAD_NAME                2 (print)
             18 LOAD_NAME                0 (show)
             20 LOAD_CONST               3 (10)
             22 CALL_FUNCTION            1
             24 CALL_FUNCTION            1
             26 POP_TOP
        >>   28 LOAD_CONST               4 (None)

左邊3, 7, 8表示 test.py中的第一行和第二行,右邊表示python byte code

Include/opcode.h 發現總共有 163 個 opcode, 所有的 python 源文件(Lib庫中的文件)都會被編譯器翻譯成由 opcode 組成的 pyx 文件,並緩存在執行目錄,下次啟動程序如果源代碼沒有修改過,則直接加載這個pyx文件,這個文件的存在可以加快 python 的加載速度。普通.py文件如我們的test.py 是直接進行編譯解釋執行的,不會生成.pyc文件,想生成test.pyc 需要使用python內置的py_compile模塊來編譯該文件,或者執行命令python3 -m test.py python生成.pyc文件

嚴格意義上來說: 只有文件導入import 的情況下字節碼.pyc文件才會保存下來,__pycache__ — 《python學習手冊(第四版) Page40》

2.4 python中的code對象

字節碼在python虛擬機中對應的是PyCodeObject對象, .pyc文件是字節碼在磁盤上的表現形式。python編譯的過程中,一個代碼塊就對應一個code對象,那麼如何確定多少代碼算是一個Code Block呢? 編譯過程中遇到一個新的命名空間或者作用域時就生成一個code對象,即類或函數都是一個代碼塊,一個code的類型結構就是PyCodeObject, 參考Junnplus

/* Bytecode object */
typedef struct {
    PyObject_HEAD
    int co_argcount;            /* #arguments, except *args */     // 位置參數的個數,
    int co_posonlyargcount;     /* #positional only arguments */  
    int co_kwonlyargcount;      /* #keyword only arguments */
    int co_nlocals;             /* #local variables */
    int co_stacksize;           /* #entries needed for evaluation stack */
    int co_flags;               /* CO_..., see below */
    int co_firstlineno;         /* first source line number */
    PyObject *co_code;          /* instruction opcodes */
    PyObject *co_consts;        /* list (constants used) */
    PyObject *co_names;         /* list of strings (names used) */
    PyObject *co_varnames;      /* tuple of strings (local variable names) */
    PyObject *co_freevars;      /* tuple of strings (free variable names) */
    PyObject *co_cellvars;      /* tuple of strings (cell variable names) */
    /* The rest aren't used in either hash or comparisons, except for co_name,
       used in both. This is done to preserve the name and line number
       for tracebacks and debuggers; otherwise, constant de-duplication
       would collapse identical functions/lambdas defined on different lines.
    */
    Py_ssize_t *co_cell2arg;    /* Maps cell vars which are arguments. */
    PyObject *co_filename;      /* unicode (where it was loaded from) */
    PyObject *co_name;          /* unicode (name, for reference) */
    PyObject *co_lnotab;        /* string (encoding addr<->lineno mapping) See
                                   Objects/lnotab_notes.txt for details. */
    void *co_zombieframe;       /* for optimization only (see frameobject.c) */
    PyObject *co_weakreflist;   /* to support weakrefs to code objects */
    /* Scratch space for extra data relating to the code object.
       Type is a void* to keep the format private in codeobject.c to force
       people to go through the proper APIs. */
    void *co_extra;

    /* Per opcodes just-in-time cache
     *
     * To reduce cache size, we use indirect mapping from opcode index to
     * cache object:
     *   cache = co_opcache[co_opcache_map[next_instr - first_instr] - 1]
     */

    // co_opcache_map is indexed by (next_instr - first_instr).
    //  * 0 means there is no cache for this opcode.
    //  * n > 0 means there is cache in co_opcache[n-1].
    unsigned char *co_opcache_map;
    _PyOpcache *co_opcache;
    int co_opcache_flag;  // used to determine when create a cache.
    unsigned char co_opcache_size;  // length of co_opcache.
} PyCodeObject;
Field Content Type
co_argcount Code Block 的參數個數 PyIntObject
co_posonlyargcount Code Block 的位置參數個數 PyIntObject
co_kwonlyargcount Code Block 的關鍵字參數個數 PyIntObject
co_nlocals Code Block 中局部變量的個數 PyIntObject
co_stacksize Code Block 的棧大小 PyIntObject
co_flags N/A PyIntObject
co_firstlineno Code Block 對應的 .py 文件中的起始行號 PyIntObject
co_code Code Block 編譯所得的字節碼 PyBytesObject
co_consts Code Block 中的常量集合 PyTupleObject
co_names Code Block 中的符號集合 PyTupleObject
co_varnames Code Block 中的局部變量名集合 PyTupleObject
co_freevars Code Block 中的自由變量名集合 PyTupleObject
co_cellvars Code Block 中嵌套函數所引用的局部變量名集合 PyTupleObject
co_cell2arg N/A PyTupleObject
co_filename Code Block 對應的 .py 文件名 PyUnicodeObject
co_name Code Block 的名字,通常是函數名/類名/模塊名 PyUnicodeObject
co_lnotab Code Block 的字節碼指令於 .py 文件中 source code 行號對應關係 PyBytesObject
co_opcache_map python3.8新增字段,存儲字節碼索引與CodeBlock對象的映射關係 PyDictObject

2.4.1 LOAD_CONST

// Python\ceval.c
PREDICTED(LOAD_CONST);     -> line 943: #define PREDICTED(op)           PRED_##op:
FAST_DISPATCH();           -> line 876 #define FAST_DISPATCH() goto fast_next_opcode

額外收穫: c 語言中 ##和# 號 在marco 里的作用可以參考 這篇

在宏定義里, ## 被稱為連接符(concatenator) , a##b 表示將ab連接起來

a 表示把a轉換成字符串,即加雙引號,

所以LONAD_CONST這個指領根據宏定義展開如下:

case TARGET(LOAD_CONST): {
    PRED_LOAD_CONST:
    PyObject *value = GETITEM(consts, oparg); // 獲取一個PyObject* 指針對象
    Py_INCREF(value);  // 引用計數加1
    PUSH(value);     // 把剛剛創建的PyObject* push到當前的frame的stack上, 以便下一個指令從這個 stack 上面獲取
    goto fast_next_opcode;

2.5 main_loop

// Python\ceval.c
main_loop:
    for (;;) {
        ...
            
        switch (opcode) {
 
        /* BEWARE!
           It is essential that any operation that fails must goto error
           and that all operation that succeed call [FAST_]DISPATCH() ! */
 
        case TARGET(NOP): {
            FAST_DISPATCH();
        }
 
        case TARGET(LOAD_FAST): {
            PyObject *value = GETLOCAL(oparg);
            if (value == NULL) {
                format_exc_check_arg(PyExc_UnboundLocalError,
                                     UNBOUNDLOCAL_ERROR_MSG,
                                     PyTuple_GetItem(co->co_varnames, oparg));
                goto error;
            }
            Py_INCREF(value);
            PUSH(value);
            FAST_DISPATCH();
        }
 
        case TARGET(LOAD_CONST): {
            PREDICTED(LOAD_CONST);
            PyObject *value = GETITEM(consts, oparg);
            Py_INCREF(value);
            PUSH(value);
            FAST_DISPATCH();
        }
        ...
    }
}

在 python 虛擬機中,解釋器主要在一個很大的循環中,不停地讀入 opcode, 並根據 opcode 執行對應的指令,當執行完所有指令虛擬機退出,程序也就結束了

2.6 總結

過程描述:

  1. python先把代碼(.py文件)編譯成字節碼,交給字節碼虛擬機,然後虛擬機會從編譯得到的PyCodeObject對象中一條一條執行字節碼指令,並在當前的上下文環境中執行這條字節碼指令,從而完成程序的執行。Python虛擬機實際上是在模擬操作中執行文件的過程。PyCodeObject對象中包含了字節碼指令以及程序的所有靜態信息,但沒有包含程序運行時的動態信息——執行環境(PyFrameObject),後面會繼續記錄執行環境的閱讀。
  2. 從整體上看:OS中執行程序離不開兩個概念:進程和線程。python中模擬了這兩個概念,模擬進程和線程的分別是PyInterpreterState和PyTreadState。即:每個PyThreadState都對應着一個幀棧,python虛擬機在多個線程上切換(靠GIL實現線程之間的同步)。當python虛擬機開始執行時,它會先進行一些初始化操作,最後進入PyEval_EvalFramEx函數,內部實現了一個main_loop它的作用是不斷讀取編譯好的字節碼,並一條一條執行,類似CPU執行指令的過程。函數內部主要是一個switch結構,根據字節碼的不同執行不同的代碼

3. Python中的Frame

如上所說,PyCodeObject對象只是包含了字節碼指令集以及程序的相關靜態信息,虛擬機的執行還需要一個執行環境,即PyFrameObject,也就是對系統棧幀的模擬。

3.1 堆和棧的認識

堆中存的是對象。棧中存的是基本數據類型和堆中對象的引用。一個對象的大小是不可估計的,或者說是可以動態變化的,但是在棧中,一個對象只對應了一個4btye的引用(堆棧分離的好處)

內存中的堆棧和數據結構堆棧不是一個概念,可以說內存中的堆棧是真實存在的物理區,數據結構中的堆棧是抽象的數據存儲結構。

內存空間在邏輯上分為三部分:代碼區,靜態數據區和動態數據區,動態數據區有分為堆區和棧區

  • 代碼區:存儲的二進制代碼塊,高級調度(作業調度)、中級調度(內存調度)、低級調度(進程調度)控制代碼區執行代碼的切換
  • 靜態數據區:存儲全局變量,靜態變量,常量,系統自動分配和回收。
  • 動態數據區:
    • 棧區(stack):存儲運行方法的形參,局部變量,返回值,有編譯器自動分配和回收,操作類似數據結構中的棧
    • 堆區(heap):new一個對象的引用或者地址存儲在棧區,該地址指向指向對象存儲在堆區中的真實數據。如c中的malloc函數,python中的Pymalloc

3.2 PyFrameObject對象

typedef struct _frame{  
    PyObject_VAR_HEAD //"運行時棧"的大小是不確定的, 所以用可變長的對象
    struct _frame *f_back; //執行環境鏈上的前一個frame,很多個PyFrameObject連接起來形成執行環境鏈表  
    PyCodeObject *f_code; //PyCodeObject 對象,這個frame就是這個PyCodeObject對象的上下文環境  
    PyObject *f_builtins; //builtin名字空間  
    PyObject *f_globals;  //global名字空間  
    PyObject *f_locals;   //local名字空間  
    PyObject **f_valuestack; //"運行時棧"的棧底位置  
    PyObject **f_stacktop;   //"運行時棧"的棧頂位置  
    //...  
    int f_lasti;  //上一條字節碼指令在f_code中的偏移位置  
    int f_lineno; //當前字節碼對應的源代碼行  
    //...  
      
    //動態內存,維護(局部變量+cell對象集合+free對象集合+運行時棧)所需要的空間  
    PyObject *f_localsplus[1];    
} PyFrameObject; 

如果你想知道 PyFrameObject 中每個字段的意義, 請參考 Junnplus’ blog 或者直接閱讀源代碼,了解frame的執行過程可以參考zpoint’blog.

名字空間實際上是維護着變量名和變量值之間關係的PyDictObject對象。
f_builtins, f_globals, f_locals名字空間分別維護了builtin, global, local的name與對應值之間的映射關係。

每一個 PyFrameObject對象都維護了一個 PyCodeObject對象,這表明每一個 PyFrameObject中的動態內存空間對象都和源代碼中的一段Code相對應。

3.2.1 棧幀的獲取,工作中會用到

可以通過sys._getframe([depth]), 獲取指定深度的PyFrameObject對象

>>> import sys
>>> frame = sys._getframe()
>>> frame
<frame object at 0x103ab2d48>

3.2.2 python中變量名的解析規則 LEGB

Local -> Enclosed -> Global -> Built-In

  • Local 表示局部變量

  • Enclosed 表示嵌套的變量

  • Global 表示全局變量

  • Built-In 表示內建變量

如果這幾個順序都取不到,就會拋出 ValueError

可以在這個網站python執行可視化網站,觀察代碼執行流程,以及變量的轉換賦值情況。

4. 額外收穫

意外收穫: 之前知道pythonGIL , 遇到I/O阻塞時會釋放gil,現在從源碼中看到了對應的流程

if (_Py_atomic_load_relaxed(&ceval->gil_drop_request)) {
    /* Give another thread a chance */
    if (_PyThreadState_Swap(&runtime->gilstate, NULL) != tstate) {
        Py_FatalError("ceval: tstate mix-up");
    }
    drop_gil(ceval, tstate);

    /* Other threads may run now */

    take_gil(ceval, tstate);

    /* Check if we should make a quick exit. */
    exit_thread_if_finalizing(runtime, tstate);

    if (_PyThreadState_Swap(&runtime->gilstate, tstate) != NULL) {
        Py_FatalError("ceval: orphan tstate");
    }
}
/* Check for asynchronous exceptions. */

參考:

python 源碼分析 基本篇

python虛擬機運行原理

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

※教你寫出一流的銷售文案?

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※網頁設計最專業,超強功能平台可客製化

好站推薦

  • 健康醫療 減重知識專區
  • 婚紗世界 婚紗攝影寫真網
  • 成人話題 未滿18請勿進入
  • 流行時尚 時下流行愛美情報
  • 理財資訊 當舖借貸信用卡各式理財方法
  • 生活情報 各行各業情報資訊
  • 科技資訊 工業電子3C產品
  • 網路資訊 新奇趣味爆笑內容
  • 美食分享 全台各式名產 伴手禮
  • 裝潢設計 買屋賣屋裝修一羅框
  • 視覺設計 T恤、團體服、制服、polo衫

近期文章

  • 奧方地產11.23億元競得廣州增城1宗商住用地
  • 廣州高新投資20.18億元競得廣州黃埔區1宗商業用地
  • 廣州開發投資聯合體5.91億元競得廣州黃埔1宗商業用地
  • 廣東開新睿智生物1961萬元競得廣州1宗工業用地
  • 廣州130.87億元出讓7宗地塊 龍湖、香江控股、越秀各競得1宗

標籤

USB CONNECTOR  南投搬家公司費用 古典家具推薦 台中一中住宿 台中一中民宿 台中室內設計 台中室內設計師 台中室內設計推薦 台中搬家 台中電動車 台北網頁設計 台東伴手禮 台東名產 地板施工 大圖輸出 如何寫文案 婚禮錄影 宜蘭民宿 家具工廠推薦 家具訂製工廠推薦 家具訂製推薦 實木地板 復刻家具推薦 新竹婚宴會館 木地板 木質地板 柚木地板 桃園機場接送 桃園自助婚紗 沙發修理 沙發換皮 海島型木地板 牛軋糖 租車 網站設計 網頁設計 網頁設計公司 貨運 超耐磨木地板 銷售文案 隱形鐵窗 電動車 馬賽克拼貼 馬賽克磁磚 馬賽克磚

彙整

  • 2021 年 3 月
  • 2021 年 2 月
  • 2021 年 1 月
  • 2020 年 12 月
  • 2020 年 11 月
  • 2020 年 10 月
  • 2020 年 9 月
  • 2020 年 8 月
  • 2020 年 7 月
  • 2020 年 6 月
  • 2020 年 5 月
  • 2020 年 4 月
  • 2020 年 3 月
  • 2020 年 2 月
  • 2020 年 1 月
  • 2019 年 12 月
  • 2019 年 11 月
  • 2019 年 10 月
  • 2019 年 9 月
  • 2019 年 8 月
  • 2019 年 7 月
  • 2019 年 6 月
  • 2019 年 5 月
  • 2019 年 4 月
  • 2019 年 3 月
  • 2019 年 2 月
  • 2019 年 1 月
  • 2018 年 12 月
©2021 值得您信賴的旅遊品牌 | 團體旅遊、自由行的專家‎ | Built using WordPress and Responsive Blogily theme by Superb