python3.5源码分析-启动与虚拟机
Python3源码分析
本文环境python3.5.2。
参考书籍<<Python源码剖析>>
python官网
Python3启动流程概述
本文基于python3分析其基本的运行过程。作为一门动态语言,python脚本在运行的过程中,实现了编译文件并执行编译文件的过程,这一过程都是基于c语言实现,首先开始介绍一下python3的基本信息。
Python3源码结构
在官网下载python3.5.2后,根据官网的文档目录介绍,主要目录的信息如下:
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 becausedestroying the GIL might fail when it is being referenced fromanother running thread (see issue #9901).Instead we destroy the previously created GIL here, which ensuresthat 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为初始化后的modinterp->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设置成模块sysPySys_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;}elsereturn 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 09 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 021 STORE_NAME 1 (foo)7 24 LOAD_NAME 0 (show)27 CALL_FUNCTION 0 (0 positional, 0 keyword pair)30 POP_TOP8 31 LOAD_NAME 1 (foo)34 CALL_FUNCTION 0 (0 positional, 0 keyword pair)37 POP_TOP38 LOAD_CONST 4 (None)41 RETURN_VALUE
从中可以看到两个函数foo和show生成了两个code对象,code对象是什么作用呢?
Python的code对象
在Python的编译过程中,一个代码块就对应一个code对象,那Python如何定义一个代码块呢?当编译过程中遇到一个新的命令空间或者作用域时就生成一个code对象,即类或函数都是一个代码块,一个code的类型结构就是PyCodeObject;
typedef struct {PyObject_HEADint 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 forco_name (used in both) and co_firstlineno (used only incomparisons). This is done to preserve the name and line numberfor tracebacks and debuggers; otherwise, constant de-duplicationwould 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) SeeObjects/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_HEADstruct _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 itto the current stack top. */PyObject **f_stacktop; // 栈顶...int f_lasti; /* Last instruction if called */ // 上一条执行命令/* Call PyFrame_GetLineNumber() instead of reading this fielddirectly. As of 2.3 f_lineno is only valid when tracing isactive (i.e. when f_trace is set). At other times we usePyCode_Addr2Line to calculate the line from the currentbytecode 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_DLOPENint dlopenflags;
#endif
#ifdef WITH_TSCint tscdump;
#endifPyObject *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的一个大致的执行过程,其中细节或有疏漏错误的地方请批评指正,具体详细的过程如有兴趣可自行调试分析。
python3.5源码分析-启动与虚拟机相关推荐
- golang源码分析-启动过程概述
golang源码分析-启动过程概述 golang语言作为根据CSP模型实现的一种强类型的语言,本文主要就是通过简单的实例来分析一下golang语言的启动流程,为深入了解与学习做铺垫. golang代码 ...
- springboot集成mybatis源码分析-启动加载mybatis过程(二)
springboot集成mybatis源码分析-启动加载mybatis过程(二) 1.springboot项目最核心的就是自动加载配置,该功能则依赖的是一个注解@SpringBootApplicati ...
- 嵌入式之uboot源码分析-启动第二阶段学习笔记(下篇)
接上部分---->嵌入式之uboot源码分析-启动第二阶段学习笔记(上篇) 注:如下内容来自朱老师物联网大讲堂uboot课件 3.2.14 CFG_NO_FLASH (1)虽然NandFlash ...
- Python3.5源码分析-垃圾回收机制
Python3源码分析 本文环境python3.5.2. 参考书籍<<Python源码剖析>> python官网 Python3的垃圾回收概述 随着软硬件的发展,大多数语言都已 ...
- Python3.5源码分析-sys模块及site模块导入
Python3源码分析 本文环境python3.5.2. 参考书籍<<Python源码剖析>> python官网 Python3的sys模块初始化 根据分析完成builtins ...
- Python3.5源码分析-Dict概述
Python3源码分析 本文环境python3.5.2. 参考书籍<<Python源码剖析>> python官网 Python3的Dict对象 在生成d = {}和d['1'] ...
- Python3.5源码分析-List概述
Python3源码分析 本文环境python3.5.2. 参考书籍<<Python源码剖析>> python官网 Python3的List对象 list对象是一个变长对象,在运 ...
- Python3.5源码分析-内存管理
Python3源码分析 本文环境python3.5.2. 参考书籍<<Python源码剖析>> python官网 Python3的内存管理概述 python提供了对内存的垃圾收 ...
- Python3.5源码分析-内建模块builtins初始化
Python3源码分析 本文环境python3.5.2. 参考书籍<<Python源码剖析>> python官网 Python3模块初始化与加载 Python的模块分为内建的模 ...
最新文章
- Python 用户的三次登录机会
- 全球99家AI芯片公司,中国占15家!
- PostgreSQL client's startup packet different between logical and normal stream replication
- 服务器操作系统2008安装图解,IBM服务器windows2008操作系统安装步骤图文(13页)-原创力文档...
- 程序员法律考试笔记(1)-民法基本规定与调整范围
- boost::ratio_negate相关的测试程序
- 【原】TreeView+Checkbox级联操作(IE/FireFox测试通过)
- SylixOS下基于NUC970的NAND驱动
- 框架如何调用存储过程_如何在FastReport.Net中使用存储过程
- java socket编程聊天室_Java Socket通信之聊天室功能
- LeetCode 2197. 替换数组中的非互质数(栈)
- linux内核通俗理解,简洁明了!高手带你理解ARM-Linux的启动过程
- python经典程序实例-Python简单基础小程序的实例代码
- centos ipython tab键上下键不起作用
- kettle 下载地址
- pandas填充空数组_pandas | DataFrame基础运算以及空值填充
- 【统计】回归系数与相关系数的联系与区别
- 锐捷商通v6数据库服务器位置,热烈庆祝我校开通IPv6资源
- Redis面试完整版
- 笔记本电脑外接显示器无信号 其实是主板静电积压 完全可以不拆机放电