赞
踩
/* Minimal main program -- everything is loaded from the library */ #include "Python.h" #ifdef MS_WINDOWS int wmain(int argc, wchar_t **argv) //等价于int wmain(int argc, wchar_t *argv[]) { printf("hellow world\n");//这里我们加一句调试语句可以来验证window平台确实是走的这个分支 return Py_Main(argc, argv); } #else int main(int argc, char **argv) //等价于int main(int argc, char *argv[]) { return _Py_UnixMain(argc, argv); } #endif
源文件位于main.c
源码如下
static int pymain_main(_PyMain *pymain) { int res = pymain_init(pymain); if (res == 1) { goto done; } pymain_run_python(pymain); if (Py_FinalizeEx() < 0) { /* Value unlikely to be confused with a non-error exit status or other special meaning */ pymain->status = 120; } done: pymain_free(pymain); return pymain->status; }
可以看到,pymain_main函数主要做的事情为初始化python的核心配置即pymain_init,以及真正将python的源代码run起来的pymain_run_python函数。
static int pymain_init(_PyMain *pymain) { _PyCoreConfig local_config = _PyCoreConfig_INIT; _PyCoreConfig *config = &local_config; /* 754 requires that FP exceptions run in "no stop" mode by default, * and until C vendors implement C99's ways to control FP exceptions, * Python requires non-stop mode. Alas, some platforms enable FP * exceptions by default. Here we disable them. */ #ifdef __FreeBSD__ fedisableexcept(FE_OVERFLOW); #endif config->_disable_importlib = 0; config->install_signal_handlers = 1; _PyCoreConfig_GetGlobalConfig(config); int res = pymain_cmdline(pymain, config); if (res < 0) { _Py_FatalInitError(pymain->err); } if (res == 1) { pymain_clear_config(&local_config); return res; } pymain_init_stdio(pymain); PyInterpreterState *interp; pymain->err = _Py_InitializeCore(&interp, config); if (_Py_INIT_FAILED(pymain->err)) { _Py_FatalInitError(pymain->err); } pymain_clear_config(&local_config); config = &interp->core_config; if (pymain_init_python_main(pymain, interp) < 0) { _Py_FatalInitError(pymain->err); } if (pymain_init_sys_path(pymain, config) < 0) { _Py_FatalInitError(pymain->err); } return 0; }
可以看到首先是定义了一个类型为_PyCoreConfig的local_config对象,接着定义了一个类型为_PyCoreConfig *的config指针,接着初始化了_disable_importlib与install_signal_handlers的值,接着config进入了_PyCoreConfig_GetGlobalConfig中,这个函数主要是对需要忽略的环境和字符集做一些初始化,具体可以转到定义查看。
接着是pymain_cmdline函数,它的主要作用是将配置读入_PyCoreConfig 和_PyMain,初始化LC_CTYPE 语言环境和 Py_DecodeLocale(),主要有配置内容有命令行参数,全局环境变量,以及Py_xxx 全局配置变量。
在pymain_cmdline中比较重要的是还会设置内存分配器,截取部分代码如下图,其中PyMem_SetAllocator的定义位于obmalloc.c文件中
还有一个_Py_InitializeCore函数,用来初始化运行时python解释器的核心,它的定义位于pylifestyle.c文件中。
语句 PyInterpreterState *interp;可以去查看一下PyInterpreterState结构体的定义。
其中filename是在哪里进行分析且赋值给py_main->filename的呢,在解析命令行参数函数pymain_parse_cmdline_impl中,具体调用链如下,其中pymain_init里面内容很多这里就不过多的介绍了,感兴趣的可以去仔细研读源码
pymain_init->
pymain_cmdline->
pymain_cmdline_impl->
pymain_read_conf->
pymain_read_conf_impl ->
pymain_parse_cmdline->
pymain_parse_cmdline_impl
最重要的GIL全局锁的初始化就隐藏在pymain_cmdline_impl中的_PyRuntime_Initialize即为运行时的初始化,_PyRuntime_Initialize的函数的定义位于pylifestyle.c中。
_PyRuntime_Initialize的源码如下
_PyInitError _PyRuntime_Initialize(void) { /* XXX We only initialize once in the process, which aligns with the static initialization of the former globals now found in _PyRuntime. However, _PyRuntime *should* be initialized with every Py_Initialize() call, but doing so breaks the runtime. This is because the runtime state is not properly finalized currently. */ /*XXX 我们在这个过程中只初始化一次,这与现在在 _PyRuntime 中找到 的以前全局变量的静态初始化一致。 但是,_PyRuntime *应该*在每次 Py_Initialize() 调用时初始化,但这样做会破坏运行时。这是因为当前 未正确确定运行时状态 */ static int initialized = 0;//用来标记是否已经被初始化过了 if (initialized) { return _Py_INIT_OK(); } initialized = 1; return _PyRuntimeState_Init(&_PyRuntime); }
重点来看_PyRuntimeState_Init函数中的_PyRuntimeState_Init_impl函数,源代码位于文件pystate.c
static _PyInitError _PyRuntimeState_Init_impl(_PyRuntimeState *runtime) { memset(runtime, 0, sizeof(*runtime)); _PyGC_Initialize(&runtime->gc);//初始化垃圾回收器 _PyEval_Initialize(&runtime->ceval);//下面重点关注 runtime->gilstate.check_enabled = 1; /* A TSS key must be initialized with Py_tss_NEEDS_INIT in accordance with the specification. */ //根据规范,必须使用 Py_tss_NEEDS_INIT 初始化 TSS 密钥 Py_tss_t initial = Py_tss_NEEDS_INIT; runtime->gilstate.autoTSSkey = initial; //PyThread_allocate_lock分配线程锁,源码位于thread_pthread.h文件 runtime->interpreters.mutex = PyThread_allocate_lock(); if (runtime->interpreters.mutex == NULL) { return _Py_INIT_ERR("Can't initialize threads for interpreter"); } runtime->interpreters.next_id = -1; return _Py_INIT_OK(); }
_PyEval_Initialize用来初始化ceva运行时的状态,源码如下,位于文件ceval.c
void
_PyEval_Initialize(struct _ceval_runtime_state *state)
{
//初始化递归调用的深度,宏Py_DEFAULT_RECURSION_LIMIT默认1000,要小于该值,在python中可以用sys.setrecursionlimit(1000000)来修改这一个限制
state->recursion_limit = Py_DEFAULT_RECURSION_LIMIT;
//检查递归限制
_Py_CheckRecursionLimit = Py_DEFAULT_RECURSION_LIMIT;
//初始化gil锁
_gil_initialize(&state->gil);
}
接着看下_gil_initialize的定义
#define DEFAULT_INTERVAL 5000
static void _gil_initialize(struct _gil_runtime_state *state)
{
_Py_atomic_int uninitialized = {-1};
//locked检查是否已采用 GIL(如果未初始化,则为 -1)。
//这是原子性的,因为它可以在 ceval.c 中不加锁的情况下读取。
state->locked = uninitialized;
//interval是表示gil锁切换的间隔时间,单位是微秒,默认5000微妙
state->interval = DEFAULT_INTERVAL;
}
我们可以在main.c中看到该函数的定义,如下,其中的printf为我调试时候所打印
从上图中我们可以看到python真正执行的时候分为三种大的模式,为什么说是三种大的模式呢,因为其实在pymain_run_filename这种模式下是包括了命令行的交互式环境和我们python test.py这两种模式的,我们可以来验证一下,将上图加了调试代码后重新编译一下Cpython后来实验一下,实验结果如下:
pymain_run_filename的相关分析,见如下源码的注释
static void pymain_run_filename(_PyMain *pymain, PyCompilerFlags *cf) { /*进来后首先会判断,文件名是否为空,如果文件名为空 且stdin_is_interactive为真,则可以判断当前环境为交 互式环境 */ if (pymain->filename == NULL && pymain->stdin_is_interactive) { Py_InspectFlag = 0; /* do exit on SystemExit */ //pymain_run_startup函数的主要作用是检查当前系统或当前命令行窗口是否设置了PYTHONSTARTUP这个环境变量, //至于PYTHONSTARTUP环境变量是做什么的,它主要是用来在交互式环境下,提前导入一些常用的模块,这样就可 //以在我们切入到交互式环境后就可以不用手动导入模块,就可以使用对应模块相关的接口了,也可以理解为预加 //载模块,有点类似在__init__.py中导入模块后,被其它模块引入的效果 pymain_run_startup(cf); //pymain_run_interactive_hook函数,这个里面会检查钩子函数__interactivehook__是否能正常调用,如果不能 //正常调用,则进入交互式环境会失败,报Failed calling sys.__interactivehook__的错误 pymain_run_interactive_hook(); } //这里还会检查一次,我们在pymain_init中初始化的模块搜索路径是否为空 if (pymain->main_importer_path != NULL) { pymain->status = pymain_run_main_from_importer(pymain); return; } FILE *fp; //如果filename不为空,则说明当前是执行的文件,python test.py 或者python test.pyc if (pymain->filename != NULL) { //pymain_open_filename返回对应文件指针,即文件首地址 fp = pymain_open_filename(pymain); if (fp == NULL) { return; } } else { printf("come from Interactive window\n");//测试用 fp = stdin; //fp的值来自交互式环境的标准输入,可以理解为文件描述符 } pymain->status = pymain_run_file(fp, pymain->filename, cf);
接着我们来到pymain_run_file函数,顾名思义,用来跑文件的,这里不管是交互式环境还是真正的文件,都统统抽象为文件。
static int pymain_run_file(FILE *fp, const wchar_t *filename, PyCompilerFlags *p_cf) { PyObject *unicode, *bytes = NULL; const char *filename_str; int run; /* call pending calls like signal handlers (SIGINT) */ //Py_MakePendingCalls是用来调用被挂起的调用,比如信号处理程序 if (Py_MakePendingCalls() == -1) { PyErr_Print(); return 1; } if (filename) { //将文件名转为unicode对象 unicode = PyUnicode_FromWideChar(filename, wcslen(filename)); if (unicode != NULL) { //如果unicode对象不为空,则再将unicode对象转为bytes对象 bytes = PyUnicode_EncodeFSDefault(unicode); Py_DECREF(unicode);//减少对象unicode的引用计数。 对象必须不为 NULL } if (bytes != NULL) { filename_str = PyBytes_AsString(bytes);//将bytes转为字符串 } else { PyErr_Clear(); filename_str = "<encoding error>"; } } else { filename_str = "<stdin>"; } //PyRun_AnyFileExFlags作用是解析来自文件的输入并执行它,即真正的执行模块 run = PyRun_AnyFileExFlags(fp, filename_str, filename != NULL, p_cf); Py_XDECREF(bytes);//减少对象bytes的引用计数。 对象可以为 NULL return run != 0; }
接着我们去PyRun_AnyFileExFlags看看
int PyRun_AnyFileExFlags(FILE *fp, const char *filename, int closeit, PyCompilerFlags *flags) { //fp为文件指针、filename为转为字符串后的文件名、closeit为最原始的宽字符文件名是否为空、flags编译器标志 if (filename == NULL) filename = "???"; //用来判断是否为交互式环境 if (Py_FdIsInteractive(fp, filename)) { printf("this is Interactive\n"); /*这里的交互式终端就是我们的python回车后的命令行, PyRun_InteractiveLoopFlags里面其实是一个 do...while循环一直在那里接受我们交互式环境中的输入, 直到收到EOF(即python代码中的exit())就表示要退出 交互式环境了,具体的更详细的过程可以跳转到该函数的定 义中去了解*/ int err = PyRun_InteractiveLoopFlags(fp, filename, flags); if (closeit) fclose(fp); return err; } else printf("this is Real file\n"); return PyRun_SimpleFileExFlags(fp, filename, closeit, flags); }
重点看看PyRun_SimpleFileExFlags都做了些什么
PyRun_SimpleFileExFlags首先会判断文件是不是pyc文件,即是否为字节码文件,如果是则到run_pyc_file函数,且不会去解析抽象语法树AST了,如果不是pyc文件则到PyRun_FileExFlags函数
PyObject * PyRun_FileExFlags(FILE *fp, const char *filename_str, int start, PyObject *globals, PyObject *locals, int closeit, PyCompilerFlags *flags) { PyObject *ret = NULL; mod_ty mod; PyArena *arena = NULL; PyObject *filename; //解码文件名 filename = PyUnicode_DecodeFSDefault(filename_str); if (filename == NULL) goto exit; // /* 为编译阶段申请一块内存池 */ arena = PyArena_New(); if (arena == NULL) goto exit; /*PyParser_ASTFromFileObject从文件对象中解析抽象语法树, 该函数调用Parser/parsetok.c中的PyParser_ParseFileObject() 函数中的解析器构造节点对象,并调用AST树构造函数PyAST_FromNodeObject()逐节点对象创建AST树。 */ mod = PyParser_ASTFromFileObject(fp, filename, NULL, start, 0, 0, flags, NULL, arena); if (closeit) fclose(fp); if (mod == NULL) { goto exit; } //run_mod执行之前出来的抽象语法树,两个功能第一个生成代码对象 (PyAST_CompileObject()),第二个调用解释器循环 (PyEval_EvalCode())。 ret = run_mod(mod, filename, globals, locals, flags, arena); exit: Py_XDECREF(filename); if (arena != NULL) //释放之前申请的内存池 PyArena_Free(arena) return ret; }
让我们PyAST_CompileObject()先进入,这个函数在Python/compile.c. 它有两个重要的函数调用PySymtable_BuildObject()和compiler_mod()
PySymtable_BuildObject()用于生成符号表,它在Python/symtable.c第 251行定义。
compiler_mod()将 AST 转换为 CFG(控制流图),其中它里面还有一个很关键的函数调用compiler_body()它用来生成字节码,compiler_body有一个如下的代码段。
for (; i < asdl_seq_LEN(stmts); i++)
VISIT(c, stmt, (stmt_ty)asdl_seq_GET(stmts, i));
在这里,我们观察到我们遍历 ASDL 语句并调用宏VISIT,然后调用compiler_visit_expr(c, node).
字节码的发射由以下宏处理:
几个将发出字节码并命名为 的辅助函数,该函数compiler_xx()在何处提供xx帮助(列表、boolop 等)
第五步已经将文件对象转为了字节码
一旦字节码生成,下一步就是由解释器执行程序。回到文件Python/pythonrun.c,然后我们调用该函数PyEval_EvalCode(),它是 to 的包装函数PyEval_EvalCodeEx(),并且是另一个包装函数_PyEval_EvalCodeWithName()
让我们检查一下文件中定义的框架对象的结构Include/frameobject.h:
typedef struct _frame { PyObject_VAR_HEAD struct _frame *f_back; /* previous frame, or NULL */ PyCodeObject *f_code; /* code segment */ 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; PyObject *f_trace; /* Trace function */ char f_trace_lines; /* Emit per-line trace events? */ char f_trace_opcodes; /* Emit per-opcode trace events? */ /* Borrowed reference to a generator, or NULL */ PyObject *f_gen; 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;
在_PyEval_EvalCodeWithName(),它将创建一个框架_PyFrame_New_NoTrack(),并在底部调用该函数PyEval_EvalFrameEx()。
PyEval_EvalFrameEx()然后将调用eval_frame()PyThreadState 上的函数,它只是函数_PyEval_EvalFrameDefault()。这个函数也可以称为Python的虚拟机。
跟踪到函数_PyEval_EvalFrameDefault(),然后我们可以在第 930 行观察到一个无限循环(Cpython版本为3.7.8),然后它将生成操作码。我们可以跟踪它,你会看到它切换到相应的操作码块。
例如,运行 with 的代码a = 100将首先使用LOAD_CONST,然后LOAD_NAME,然后依此类推,我们可以用 观察python -m dis test.py。
Python 程序 由 解释器 python 命令执行, Python 解释器中包含一个 编译器 和一个 虚拟机 。 Python 解释器执行 Python 程序时,分为以下两步:
Python 源码的编译结果是代码对象 PyCodeObject ,对象中保存了 字节码 、 常量 以及 名字 等信息,代码对象与源码作用域一一对应。 Python 将编译生成的代码对象保存在 .pyc 文件中,以避免不必要的重复编译,提高效率。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。