8.3  Python虚拟机的运行框架

当Python启动后,首先会进行Python运行时环境的

初始化。注意这里的运行时环境是一个与上一节剖析的执行环境不同的概念。运行时环境是一个全局的概念,而执行环境实际就是一个栈帧,是一个与某个Code

Block对应的概念。这里不明白两者的区别不要紧,在以后剖析运行时环境初始化时我们就能弄清楚两者的区别和联系。运行时环境的初始化过程非常地复杂,

后面将用单独的一章来剖析,这里假设初始化的动作已经完成,我们已经站在了Python虚拟机的门槛外,只需要轻轻推动一下第一张骨牌,整个执行过程就像

多米诺骨牌一样,一环扣一环地展开。

这个推动第一张骨牌的地方在一个名叫PyEval_EvalFramEx的函数中,这个函数实际上就是Python的虚拟机的具体实现,它是一个非常巨大的函数,因此我们在列出其中的源代码时和以前有些不同。

PyEval_EvalFrameEx首先会初始化一些变量,其中PyFrameObject对象中的PyCodeObject对象包含的重要信息都被照顾到了。当然,另一个重要的动作就是初始化了堆栈的栈顶指针,使其指向f->f_stacktop:

[PyEval_EvalFrameEx in ceval.c]

co = f->f_code;

names = co->co_names;

consts = co->co_consts;

fastlocals = f->f_localsplus;

freevars = 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域中保存着字节码指令和字节码指令的参数,Python虚拟机执行字节码指令序列的过程就是从头到尾遍历整个co_code、依次执行字节

码指令的过程。在Python的虚拟机中,利用3个变量来完成整个遍历过程。co_code实际上是一个PyStringObject对象,而其中的字符

数组才是真正有意义的东西,这也就是说,整个字节码指令序列实际上就是一个在C中普普通通的字符数组。因此,遍历过程中所使用的这3个变量都是char*

类型的变量:first_instr永远指向字节码指令序列的开始位置;next_instr永远指向下一条待执行的字节码指令的位置;f_lasti指

向上一条已经执行过的字节码指令的位置。图8-5展示了这3个变量在遍历中某时刻的情形:

图8-5  遍历字节码指令序列

那么这个一步一步的动作是如何完成的呢,我们来看一看

Python虚拟机执行字节码指令的整体架构,其实就是一个for循环加上一个巨大的switch/case结构,熟悉Windows

SDK编程的朋友可以想象一下Windows下那个巨大的消息循环,就是那样的结构:

[ceval.c]

/* Interpreter main loop */

PyObject* PyEval_EvalFrameEx(PyFrameObject *f,

int throwflag)

{

……

why = WHY_NOT;

……

for (;;) {

……

fast_next_opcode:

f->f_lasti = INSTR_OFFSET();

//获得字节码指令

opcode

= NEXTOP();

oparg

= 0;

//如果指令需要参数,获得指令参数

if

(HAS_ARG(opcode))

oparg = NEXTARG();

dispatch_opcode:

switch

(opcode) {

case

NOP:

goto fast_next_opcode;

case

LOAD_FAST:

……

}

}

注意,这只是一个极度简化之后的Python虚拟机的样子,如果想一睹Python虚拟机的尊容,请参考ceval.c中的源码。

在这个执行架构中,对字节码的一步一步地遍历是通过几个宏来实现的:

[PyEval_EvalFrameEx

in ceval.c]

#define INSTR_OFFSET()  (int(next_instr -

first_instr))

#define NEXTOP()

(*next_instr++)

#define NEXTARG()   (next_instr += 2,

(next_instr[-1]<<8) + next_instr[-2])

在对PyCodeObject对象的分析中我们说过,

Python的字节码有的是带参数的,有的是没有参数的,而判断是否带参字节码是通过HAS_ARG这个宏实现的。注意,对不同的字节码指令,由于存在是

否需要指令参数的区别,所以next_instr的位移可能是不同的。但是无论如何,next_instr总是指向Python下一条要执行的字节码,这

很像x86平台上的那个PC寄存器。

Python在获得了一条字节码指令和其需要的指令参数后,会对字节码指令利用switch进行判断,根据判断的结果选择不同的case语句,每一条字节码指令都会对应一个case语句。在case语句中,就是Python对字节码指令的实现。

在成功执行完一条字节码指令后,Python的执行流程会跳

转到fast_next_opcode处,或者是for循环处,不管如何,Python接下来的动作都是获得下一条字节码指令和指令参数,完成对下一条指

令的执行。如此一条一条地遍历co_code中包含的所有字节码指令,最终完成了对Python程序的执行。

需要提到的一点是那个名叫“why”的神秘变量,它指示了在

退出这个巨大的for循环时Python执行引擎的状态。因为Python执行引擎不一定每次执行都会正确无误,很有可能在执行到某条字节码的时候,产生

了错误,这就是我们熟悉的那个“异常”——exception。所以在Python退出了执行引擎的时候,就需要知道执行引擎到底是因为什么原因结束了对

字节码指令的执行。是正常结束呢?还是因为有错误发生,实在是执行不下去了?why义无反顾地担负起这一重任。关于why在Python虚拟机中作用的详

细剖析,我们留到剖析异常机制时详细讲述。

变量why的取值范围在ceval.c中被定义,其实也就是Python结束字节码执行时的状态:

[ceval.c]

/* Status code for main loop (reason for stack

unwind) */

enum why_code {

WHY_NOT =   0x0001, /* No error */

WHY_EXCEPTION = 0x0002, /* Exception occurred */

WHY_RERAISE =   0x0004, /* Exception re-raised by 'finally' */

WHY_RETURN =    0x0008, /* 'return' statement */

WHY_BREAK = 0x0010, /* 'break' statement */

WHY_CONTINUE =  0x0020, /* 'continue' statement */

WHY_YIELD = 0x0040  /* 'yield' operator */

};

现在,想必大家已经对Python的执行引擎的大体框架了然

于胸了。在Python的执行流程进入了PyEval_EvalFrameEx中的那个for循环,取出第一条字节码之后,第一张多米诺骨牌已经被推倒,

命运不可阻挡地降临了。一条接一条的字节码像潮水一样汹涌而来,浩浩荡荡,横无际涯。

python源码深度剖析_Python源码剖析——深度探索动态语言核心技术 | 学步园相关推荐

  1. python数字转对应中文_python中将阿拉伯数字转换成中文的实现代码 | 学步园

    复制代码 代码如下: #!/usr/bin/python #-*- encoding: utf-8 -*- import types class NotIntegerError(Exception): ...

  2. python socket接收图像 数据_python中socket接受数据的三种方法 | 学步园

    原位置:http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/408859 Description: An issue with socket ...

  3. python模拟浏览器下载文件_python第一个脚本,模拟浏览器下载文件 | 学步园

    用wget命令下载文件总是失败.用python脚本模拟浏览器下载,代码如下: #!/usr/bin/python import sys from urllib import FancyURLopene ...

  4. python 源码编译教程_python源码编译

    尝试通过源码自己编译 Python,使用的系统是 Ubuntu14.04 LTS. 首先去官网下载源码,地址:源码下载.下载完成之后,解压源码: 1tar -zxvf Python-2.7.12.tg ...

  5. python源码剖析_Python源码剖析 - 对象初探

    01 前言 对象是 python 中最核心的一个概念,在python的世界中,一切都是对象,整数.字符串.甚至类型.整数类型.字符串类型,都是对象. 02 什么是PyObject Python 中凡事 ...

  6. python定义类的程序_python扫码签到程序python中如何定义类

    什么是类? 用来描述具有相同的属性和方法的对象的集合.它定义了该集合中每个对象所共有的属性和方法.对象是类的实例. 什么是方法? 类中的函数即为方法 如何定义一个类? 定义类,语法格式如下: < ...

  7. python快乐编程百度云_Python趣码快乐编程

    Python的安装/2 n 下载安装相应版本的Python安装包/2 n 验证Python是否安装成功/4 n 编辑器PyCharm的安装和使用/5 n PyCharm的下载与安装/5 n PyCha ...

  8. python 字节码指令含义_python 字节码指令列表

    /*********************************************************** KVS:文件:opcode.h swith字节码指令的文件:ceval.c * ...

  9. python中的ascii码是啥_python ascii码到u

    如果您不了解底层机制,很难解释UnicodeErrors.你真的应该读其中的一个或两个 简而言之,Unicode码位是一个抽象的"东西",表示一个字符1.程序员喜欢使用它们,因为我 ...

最新文章

  1. ubuntu14.04上安装Mysql-5.7.11
  2. Windows自动删除n天前的文件的批处理脚本
  3. elfutils cc1: all warnings being treated as errors
  4. 基于实例数据详解准确率和召回率
  5. CoreOS coreos-assembler文档
  6. 本地windows下新建kafka生产消费数据
  7. C++学生信息管理系统5.0
  8. Python3初级知识整理
  9. StorageManager
  10. sap系统搭建教程_SAP系统和微信集成的系列教程之一:微信开发环境的搭建
  11. Mac Pycharm导入Pygame教程(超细)
  12. 微信支付委托代扣的服务商模式和直连模式
  13. 业绩爆发,押注“泛半导体”,TCL押对了吗?
  14. 易支付源码 28k支付第四方支付源码-Oreo支付系统
  15. ELDER-RAY (多头力度和空头力度)
  16. 解决LaTeX中的\pdfendlink ended up in different nesting level than \pdfstartlink.问题
  17. 【PHP发送邮件】PHP实现发送邮件
  18. 格里高利历java_java时间类简单总结
  19. JSF——JSF 标签
  20. 手机文档怎样通过计算机打印,手机里的文件怎么快速打印出来?

热门文章

  1. 《北风网网友录制Silverlight入门系列视频教程》共23课时/更新完毕[压缩包]
  2. 【入门向】Navicat for MySQL的入门简单使用
  3. 计算机网络之网络层7
  4. nginx 安装,配置
  5. html代码的魔方加密,魔方加密解密测试调试方法
  6. 毕业论文的页眉页脚?奇偶页不同?前言作为第一页?……?这样弄!
  7. Faiss(16):编译时添加对AVX512指令的支持
  8. 批量压缩多文件-批处理(四)
  9. 企业智能化转型meetup回顾|开源BI AI助力企业转型之旅三阶段
  10. 【Spark | SparkStreaming】