python3.5源码分析-启动与虚拟机

Python3源码分析
本文环境python3.5.2。
参考书籍<<Python源码剖析>>
python官网
Python3启动流程概述

本文基于python3分析其基本的运行过程。作为一门动态语言,python脚本在运行的过程中,实现了编译文件并执行编译文件的过程,这一过程都是基于c语言实现,首先开始介绍一下python3的基本信息。

Python3源码结构

在官网下载python3.5.2后,根据官网的文档目录介绍,主要目录的信息如下:

python文档组织结构

Doc目录主要是官方文档的说明。

Include目录主要包括了Python的运行的头文件。

Lib目录主要包括了用Python实现的标准库。

Objects目录包括了内建对象。

Parser目录包括了python编译相关代码,将python文件编译生成语法树等相关工作。

Programs目录主要包括了python的入口函数。

Python目录主要包括了Python动态运行时执行的代码,里面包括编译、字节码解释器等工作。

Python的启动流程

根据官网文档目录介绍,启动的时候是运行Python的脚本,Python启动是由Programs下的python.c文件中的main函数开始执行,根据平台的不同选择执行不同类型的main函数,在此已Linux为例;

int
main(int argc, char **argv)
{
     ...
    res = Py_Main(argc, argv_copy);
     ...
    return res;
}

该函数主要就是执行了Py_Main函数,并将该函数的执行结果返回。

继续查看Py_Main函数的执行,在位于Modules/main.c中找到Py_Main函数,

int
Py_Main(int argc, wchar_t **argv)
{
    ...
    Py_Initialize();                              // 初始化
    ...
    {
        ...
        if (sts == -1)
            sts = run_file(fp, filename, &cf);    // 执行python脚本
    }
    ...
    Py_Finalize();                                // 释放相关资源
    ...
    return sts;                                   // 返回执行结果
}

由此可看出,在调用初始化函数Py_Initialize之后,就会执行run_file方法,当Python脚本执行完成后,就调用Py_Finalize释放相关资源。分别来查看这些函数的执行。

Py_Initialize初始化函数

该函数位于Python/pylifecycle.c中,

void
Py_InitializeEx(int install_sigs)
{
    _Py_InitializeEx_Private(install_sigs, 1);
}

void
Py_Initialize(void)
{
    Py_InitializeEx(1);
}

直接调用了_Py_InitializeEx_Private方法,并且传入了两个都为1的参数,

void
_Py_InitializeEx_Private(int install_sigs, int install_importlib)
{
    ...
    _PyRandom_Init();                                                // random模块初始化

    interp = PyInterpreterState_New();                               // 初始化解释器
    if (interp == NULL)
        Py_FatalError("Py_Initialize: can't make first interpreter");

    tstate = PyThreadState_New(interp);                              // 初始化线程
    if (tstate == NULL)
        Py_FatalError("Py_Initialize: can't make first thread");
    (void) PyThreadState_Swap(tstate);                               // 设置tstate为当前运行线程

#ifdef WITH_THREAD
    /* We can't call _PyEval_FiniThreads() in Py_Finalize because
       destroying the GIL might fail when it is being referenced from
       another running thread (see issue #9901).
       Instead we destroy the previously created GIL here, which ensures
       that we can call Py_Initialize / Py_Finalize multiple times. */
    _PyEval_FiniThreads();                                           // 如果脚本中是多线程运行则创建gil锁

    /* Auto-thread-state API */
    _PyGILState_Init(interp, tstate);
#endif /* WITH_THREAD */

    _Py_ReadyTypes();                                                // 初始化所有类型,如long,list等
    ...

    interp->modules = PyDict_New();                                 // 设置解释器的modules为新建的Dict类型
    if (interp->modules == NULL)                                    // 如果字典没有生成成功则报错
        Py_FatalError("Py_Initialize: can't make modules dictionary");

    /* Init Unicode implementation; relies on the codec registry */
    if (_PyUnicode_Init() < 0)
        Py_FatalError("Py_Initialize: can't initialize unicode");
    if (_PyStructSequence_Init() < 0)
        Py_FatalError("Py_Initialize: can't initialize structseq");

    bimod = _PyBuiltin_Init();                                      // 内建对象的初始化,如map,None,True等
    if (bimod == NULL)
        Py_FatalError("Py_Initialize: can't initialize builtins modules");
    _PyImport_FixupBuiltin(bimod, "builtins");                      // 设置builtins为初始化后的mod
    interp->builtins = PyModule_GetDict(bimod);                     // 设置解释器的builtins字段为初始化后的内建对象
    if (interp->builtins == NULL)
        Py_FatalError("Py_Initialize: can't initialize builtins dict");
    Py_INCREF(interp->builtins);                                    // 增加内建对象的引用

    /* initialize builtin exceptions */
    _PyExc_Init(bimod);                                             // 导入内建的错误类型,如BaseException,Exception等

    sysmod = _PySys_Init();                                         // 初始化sys模块
    if (sysmod == NULL)
        Py_FatalError("Py_Initialize: can't initialize sys");
    interp->sysdict = PyModule_GetDict(sysmod);                     // 设置解释器的sys模块
    if (interp->sysdict == NULL)
        Py_FatalError("Py_Initialize: can't initialize sys dict");
    Py_INCREF(interp->sysdict);
    _PyImport_FixupBuiltin(sysmod, "sys");                          // 将初始化的sysmod设置成模块sys
    PySys_SetPath(Py_GetPath());                                    // 设置导入路径
    PyDict_SetItemString(interp->sysdict, "modules",
                         interp->modules);

    ...

    initmain(interp); /* Module __main__ */                         // 创建主main
    ...
}

该函数主要进行了相关类型的初始化,并初始化了解释器,初始化了线程对象,并设置了相关的内建mod等初始化工作。当初始化工作完成后,就调用了run_file来解释并执行脚本。

run_file编译并执行

该函数位于main.c文件中,

static int
run_file(FILE *fp, const wchar_t *filename, PyCompilerFlags *p_cf)
{
    ...
    run = PyRun_AnyFileExFlags(fp, filename_str, filename != NULL, p_cf);   // 解释执行
    ...
    return run != 0;
}

真正调用了位于Python/pythonrun.c中的PyRun_AnyFileExFlags方法来解释执行,

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);       // 执行脚本
}

此处讨论的是脚本模式,此时就会进入PyRun_SimpleFileExFlags函数执行,

int
PyRun_SimpleFileExFlags(FILE *fp, const char *filename, int closeit,
                        PyCompilerFlags *flags)
{
    ...
    else {
        ...
        v = PyRun_FileExFlags(fp, filename, Py_file_input, d, d,
                              closeit, flags);                      // 在没有pyc的情况下执行
    }
    ...
}

此时就调用了PyRun_FileExFlags函数进行执行,

PyObject *
PyRun_FileExFlags(FILE *fp, const char *filename_str, int start, PyObject *globals,
                  PyObject *locals, int closeit, PyCompilerFlags *flags)
{
    ...

    mod = PyParser_ASTFromFileObject(fp, filename, NULL, start, 0, 0,
                                     flags, NULL, arena);                   // 解析传入的脚本,解析成AST
    ... 
    ret = run_mod(mod, filename, globals, locals, flags, arena);            // 将AST编译成字节码然后启动字节码解释器执行编译结果

    ...
}

查看run_mod函数,

static PyObject *
run_mod(mod_ty mod, PyObject *filename, PyObject *globals, PyObject *locals,
            PyCompilerFlags *flags, PyArena *arena)
{
    ...
    co = PyAST_CompileObject(mod, filename, flags, -1, arena);      // 将AST编译成字节码
    ... 
    v = PyEval_EvalCode((PyObject*)co, globals, locals);            // 解释执行编译的字节码
    ...
}

再将AST转换为字节码后,调用了PyEval_EvalCode来执行字节码,查看位于Python/ceval.c中的该函数,

PyObject *
PyEval_EvalCode(PyObject *co, PyObject *globals, PyObject *locals)
{
    return PyEval_EvalCodeEx(co,
                      globals, locals,
                      (PyObject **)NULL, 0,
                      (PyObject **)NULL, 0,
                      (PyObject **)NULL, 0,
                      NULL, NULL);
}
...

PyObject *
PyEval_EvalCodeEx(PyObject *_co, PyObject *globals, PyObject *locals,
           PyObject **args, int argcount, PyObject **kws, int kwcount,
           PyObject **defs, int defcount, PyObject *kwdefs, PyObject *closure)
{
    return _PyEval_EvalCodeWithName(_co, globals, locals,
                                    args, argcount, kws, kwcount,
                                    defs, defcount, kwdefs, closure,
                                    NULL, NULL);
}

直接调用了_PyEval_EvalCodeWithName函数来执行,

static PyObject *
_PyEval_EvalCodeWithName(PyObject *_co, PyObject *globals, PyObject *locals,
           PyObject **args, int argcount, PyObject **kws, int kwcount,
           PyObject **defs, int defcount, PyObject *kwdefs, PyObject *closure,
           PyObject *name, PyObject *qualname)
{
    ...

    retval = PyEval_EvalFrameEx(f,0);

    ...
}

最终调用了PyEval_EvalFrameEx来执行,该函数就是字节码解释器,实现方式就是一个for循序依次执行字节码,然后调用相关函数去执行,解释出来的脚本,然后返回执行结果。

至此,run_file的大致执行流程如上。

Python3虚拟机框架

通过分析Python的大致执行流程,有了初步了解Python是经历了多少步骤然后调用了哪些函数来进行执行。现在继续分析一下Python的虚拟机框架是如何来实现Python脚本的执行的。

首先我们编写一个简单的Python脚本;

def show():
    print("show")

def foo():
    print("foo")

show()
foo()

在终端命令行中,直接输入:

python -m dis test.py

终端上打印出该脚本的字节码,如下:

  1           0 LOAD_CONST               0 (<code object show at 0x10f2cc8a0, file "test.py", line 1>)
              3 LOAD_CONST               1 ('show')
              6 MAKE_FUNCTION            0
              9 STORE_NAME               0 (show)

  4          12 LOAD_CONST               2 (<code object foo at 0x10f2ccd20, file "test.py", line 4>)
             15 LOAD_CONST               3 ('foo')
             18 MAKE_FUNCTION            0
             21 STORE_NAME               1 (foo)

  7          24 LOAD_NAME                0 (show)
             27 CALL_FUNCTION            0 (0 positional, 0 keyword pair)
             30 POP_TOP

  8          31 LOAD_NAME                1 (foo)
             34 CALL_FUNCTION            0 (0 positional, 0 keyword pair)
             37 POP_TOP
             38 LOAD_CONST               4 (None)
             41 RETURN_VALUE

从中可以看到两个函数foo和show生成了两个code对象,code对象是什么作用呢?

Python的code对象

在Python的编译过程中,一个代码块就对应一个code对象,那Python如何定义一个代码块呢?当编译过程中遇到一个新的命令空间或者作用域时就生成一个code对象,即类或函数都是一个代码块,一个code的类型结构就是PyCodeObject;

typedef struct {
    PyObject_HEAD
    int co_argcount;        /* #arguments, except *args */      // 位置参数的个数
    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 */
    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) and co_firstlineno (used only in
       comparisons).  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.
    */
    unsigned char *co_cell2arg; /* Maps cell vars which are arguments. */
    PyObject *co_filename;  /* unicode (where it was loaded from) */        // 文件名
    PyObject *co_name;      /* unicode (name, for reference) */        
    int co_firstlineno;     /* first source line number */                  // 第一行源码行数
    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 */   // 弱引用
} PyCodeObject;

至此,可知脚本编译生成的字节码都存放在co_code中,其他属性字段都是存放了code在编译过程中保存的相关信息,这就是这个code对象在字节码解释器中运行时需要的局部变量,变量名等都是从code的相关字段中取值,有了PyCodeObject对象后,是如何在Python中被执行的呢?此时还需要将PyCodeObject对象转换成PyFrameObject对象。

Python的frame对象

在Python字节码执行的一级,会将PyCodeObject对象包装成PyFrameObject,一个PyCodeObject对应一个PyFrameObject对象,由此可知,test.py中存在三个PyFrameObject对象,除了show和foo外,还有一个是整个脚本的字节码所对应的PyFrameObject对象。

typedef struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */            // 上一个栈帧对象
    PyCodeObject *f_code;       /* code segment */                       // 对应的code对象
    PyObject *f_builtins;       /* builtin symbol table (PyDictObject) */ // 内建对象
    PyObject *f_globals;        /* global symbol table (PyDictObject) */  // 全局名称空间
    PyObject *f_locals;         /* local symbol table (any mapping) */    // 本地名称空间
    PyObject **f_valuestack;    /* points after the last local */         // 栈低
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;                                                // 栈顶
    ...

    int f_lasti;                /* Last instruction if called */          // 上一条执行命令
    /* Call PyFrame_GetLineNumber() instead of reading this field
       directly.  As of 2.3 f_lineno is only valid when tracing is
       active (i.e. when f_trace is set).  At other times we use
       PyCode_Addr2Line to calculate the line from the current
       bytecode index. */
    int f_lineno;               /* Current line number */                 // 当前行数
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */     // 动态内存
} PyFrameObject;

由此可知,多个code对象就对应多个PyFrameObject对象,每一个栈帧都是像函数调用那样嵌套调用执行,此时继续分析Frame对象是如何Python的运行环境中执行。

Python3运行时环境

Python在运行的初始化阶段,在_Py_InitializeEx_Private函数中初始化了interp和tstate;

interp = PyInterpreterState_New();
if (interp == NULL)
    Py_FatalError("Py_Initialize: can't make first interpreter");

tstate = PyThreadState_New(interp);
if (tstate == NULL)
    Py_FatalError("Py_Initialize: can't make first thread");

由此可知,interp对应的就是一个解释器对象,tstate对应在Python中就是一个线程对象,这两者是如何推动Python的执行呢?

typedef struct _is {

    struct _is *next;                           // 下一个解释器对象
    struct _ts *tstate_head;                    // 线程对象

    PyObject *modules;                          // 模块的列表
    PyObject *modules_by_index;                 
    PyObject *sysdict;                          // 系统模块列表
    PyObject *builtins;                         // 内建对象列表
    PyObject *importlib;                        // 导入模块的函数

    PyObject *codec_search_path;                
    PyObject *codec_search_cache;
    PyObject *codec_error_registry;
    int codecs_initialized;
    int fscodec_initialized;

#ifdef HAVE_DLOPEN
    int dlopenflags;
#endif
#ifdef WITH_TSC
    int tscdump;
#endif

    PyObject *builtins_copy;                    // 内建对象副本
} PyInterpreterState;

由PyInterpreterState定义可以看出,解释器对象提供基本的内建对象和模块的数据,再来查看PyThreadState对象的定义;

typedef struct _ts {
    /* See Python/ceval.c for comments explaining most fields */

    struct _ts *prev;                                       // 上一个线程对象
    struct _ts *next;                                       // 下一个线程对象
    PyInterpreterState *interp;                             // 依赖的解释器对象

    struct _frame *frame;                                   // 当前执行的帧
    ...
    long thread_id; /* Thread id where this tstate was created */  // 线程id
    ...

} PyThreadState;

主要定义了上下线程对象,当前锁对应的解释器对象,和当前正在执行的栈帧。此时的栈帧就是将PyCodeObject生成的栈帧,在Python的启动阶段的时候会调用_PyEval_EvalCodeWithName函数,该函数中就是调用PyFrame_New函数生成新的执行栈帧;

    static PyObject *
_PyEval_EvalCodeWithName(PyObject *_co, PyObject *globals, PyObject *locals,
           PyObject **args, int argcount, PyObject **kws, int kwcount,
           PyObject **defs, int defcount, PyObject *kwdefs, PyObject *closure,
           PyObject *name, PyObject *qualname)
{   
    ...
    f = PyFrame_New(tstate, co, globals, locals);
    ...
    retval = PyEval_EvalFrameEx(f,0);
    ...
}

此时就进入字节码解释器解释执行,当执行到另一个PyCodeObject对象时,就调用PyFrame_New生成一个对应的frame调用字节码解释器继续执行。该生成过程位于fast_function函数中,后文会对其进行详细分析。至此Python运行的大概原理与执行的方式基本分析完毕。

总结

Python的运行过程图可归纳如下所示,

python运行时架构图

本文只是简单的分析了启动过程和Python的一个大致的执行过程,其中细节或有疏漏错误的地方请批评指正,具体详细的过程如有兴趣可自行调试分析。

  • 5
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值