├── .gitignore ├── 1-1:课程介绍和开发环境.md ├── 1-2:PHP编译运行调试.md ├── 1-3-0:数据结构-ZVAL.md ├── 1-3-1:数据结构-数组和字符串.md ├── 1-3-2:数据结构-SAPI.md ├── 1-3-3:数据结构-Function.md ├── 1-4-0:运行过程-PHPCLI执行过程.md ├── 1-4-1:运行过程-ZEND引擎编译.md ├── 1-4-2:运行过程-ZEND引擎执行.md ├── 1-5-0:扩展开发-介绍,生命周期,骨架生成.md ├── 1-5-1:扩展开发-结构,全局变量,ini配置,函数定义.md ├── 1-5-2:扩展开发-示例,测试,练习.md ├── LICENSE ├── README.md ├── beike ├── 备课pic │ ├── 2.0-单进程SAPI生命周期.png │ ├── 2.1-单进程SAPI生命周期.png │ ├── 2017-06-10 20-56-47屏幕截图.png │ ├── 2017-06-10 23-57-13屏幕截图.png │ ├── 2017-06-26 18-41-32屏幕截图.png │ ├── 2017-06-26 18-41-51屏幕截图.png │ ├── 2017-06-26 18-52-21屏幕截图.png │ ├── 2017-06-26 19-48-43屏幕截图.png │ ├── 2017-06-26 19-49-14屏幕截图.png │ ├── 2017-06-26 19-49-39屏幕截图.png │ ├── 2017-06-26 19-56-06屏幕截图.png │ ├── 2017-06-26 19-56-21屏幕截图.png │ ├── 2017-06-26 20-12-48屏幕截图.png │ ├── 2017-06-26 20-12-56屏幕截图.png │ ├── 2017-06-26 20-46-28屏幕截图.png │ ├── 3.1-Zend引擎哈希表结构和关系.png │ ├── 4.1 抽象语法树表现.png │ ├── 4.2 语法包含关系.png │ ├── PHP-AST.png │ ├── PHP内部架构.gif │ ├── PHP内部架构2.png │ ├── PHP内部架构3.gif │ ├── PHP内部架构4.jpg │ ├── PHP内部架构5.jpg │ ├── PHP扩展生命周期.png │ ├── PHP生命周期.jpg │ ├── PHP生命周期2.jpg │ ├── PHP生命周期3.jpg │ ├── PHP编译执行过程.jpg │ ├── ZEND引擎.jpeg │ ├── autotools流程.png │ ├── autotools编译流程.gif │ ├── autotools编译流程2.gif │ ├── callgrind,gprof.jpg │ ├── php-ast-18-638.jpg │ ├── text.txt │ ├── vld-opcode.png │ ├── zendMM.jpg │ └── 性能分析工具.png ├── 旁白 │ ├── 1-1 │ ├── 1-2.txt │ ├── 1-3.txt │ ├── 1-4-1 │ └── 1-5-1 ├── 绘图 │ ├── 1-3 SAPI讲解 │ ├── 1-3 SAPI讲解.png │ ├── 1-4 PHP编译执行过程 │ ├── 1-4 PHP编译执行过程.png │ ├── 1.3 php代码执行过程 │ ├── 1.3 php代码执行过程.png │ ├── 1.3PHP架构图 │ ├── 1.3PHP架构图.png │ └── zval ├── 课件知识点准备 │ ├── 1.1 │ ├── 1.3 │ └── 3.1- ├── 课程图标 │ ├── php-core.jpg │ ├── php-core.png │ └── php-core.xcf └── 资料 │ └── PHP-AST彻底解说20161103.pptx ├── code └── 1-5-2 │ └── pib │ ├── .gitignore │ ├── CREDITS │ ├── EXPERIMENTAL │ ├── config.m4 │ ├── config.w32 │ ├── php_pib.h │ ├── pib.c │ └── tests │ └── 001.phpt ├── image ├── 1-1课程安排脑图.png ├── 1-3PHP架构图.png ├── 1-3SAPI讲解.png ├── 1-3hashtable结构.png ├── 1-3zval.png ├── 1-4-1vld-opcode.png ├── 1-4-2C程序运行栈.png ├── 1-4-2linux程序运行内存分配图.jpg ├── 1-4-2op_array与execute_data关系.png ├── 1-4PHP编译执行过程.png ├── 1-4生成的AST.png ├── 1-5-0PHP扩展的开发过程.jpg ├── 1-5-0扩展的生命周期.png ├── 1-7PHP生命周期-线程.png ├── 1-7PHP生命周期-进程.png ├── 1-7PHP运行流程思维导图.png └── 1-7php代码执行过程.png └── 附:PHP底层编码规则.md /.gitignore: -------------------------------------------------------------------------------- 1 | temp.md 2 | video/* 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /1-1:课程介绍和开发环境.md: -------------------------------------------------------------------------------- 1 | # <>课程介绍 2 | 3 | 4 | ### 课程内容: 5 | PHP底层源代码学习,实现原理,PHP扩展知识学习,相关开发工具运用. 6 | 7 | 注:本教程均在LINUX下讲解.学习本课程人员最好有一定的PHP/LINUX/C基础知识. 8 | 9 | ### PHP内核学习交流:QQ群 276949503 10 | 11 | ### 最新课件下载地址:https://github.com/huqinlou0123/php-internals-extended-development-course 12 | 13 | ### 配套视频观看地址: http://edu.csdn.net/course/detail/6261 14 | 15 | ### 课程安排 16 | ![课程安排](./image/1-1课程安排脑图.png) 17 | 18 | ### 开发环境准备 19 | 20 | ubuntu 下载地址 http://mirrors.163.com/ubuntu-releases/17.10/ 21 | 22 | 安装JDK 23 | * sudo apt update 24 | * sudo apt install default-jdk 25 | 26 | eclipse 下载地址 https://www.eclipse.org/downloads/eclipse-packages/ 27 | 28 | eclipse 主题(可选):https://marketplace.eclipse.org/content/darkest-dark-theme#maint-content-area 29 | 30 | ## 安装构建依赖程序 31 | 在继续之前,您可能应该与包管理器一起安装一些基本构建依赖项(默认情况下,您可能已经安装了前三个): 32 | * gcc或其他一些编译器套件. 33 | * libc-dev,它提供C标准库,包括头文件. 34 | * make,这是PHP使用的构建管理工具. 35 | * autoconf(2.59或更高版本),用于生成配置脚本. 36 | * automake(1.4或更高),它生成 Makefile.in文件. 37 | * libtool帮助管理共享库. 38 | * bison(2.4或更高版本),用于生成PHP解析器. 39 | * re2c(可选),用于生成PHP词法分析器.由于git存储库已经包含一个生成的词法分析器,所以只需要修改它就需要re2c. 40 | 41 | 在Debian / Ubuntu上,您可以使用以下命令安装所有这些: 42 | `sudo apt-get install build-essential autoconf automake libtool bison re2c` 43 | 44 | 45 | ## 课程作者:胡勤楼(QQ393450418),80后. 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /1-2:PHP编译运行调试.md: -------------------------------------------------------------------------------- 1 | ## 下载php源代码 2 | * 官网下载:https://secure.php.net/downloads.php 3 | * 或GITHUB:git clone http://github.com/php/php-src 4 | --- 5 | 6 | 7 | ## 配置选项讲解:`./configure --help | less` 8 | 使用`--enable-NAME`和`--disable-NAME`开关编译哪些扩展和SAPI . 9 | 10 | 如果扩展或SAPI具有外部依赖关系,则需要使用`--with-NAME`和`--without-NAME`. 11 | 12 | 如果NAME所需的库 不在默认位置(例如,因为您自己编译),则可以使用`--with-NAME = DIR`指定其位置. 13 | 14 | 如果选项是`--enable-NAME`或`--with-NAME`说明该选项默认是关闭的.如果是`--disable-NAME`或`--without-NAME`说明该选项默认是开启的. 15 | 16 | [引用链接](https://segmentfault.com/q/1010000009174725) 17 | * enable 是启用 PHP 源码包自带,但是默认不启用的扩展,比如 ftp 和 exif 扩展。with 是指定扩展依赖的资源库的位置,如果是默认位置,就可以留空。 18 | * php 的扩展分为静态编译和动态编译两种,静态编译就是随着PHP的源码一起编译安装,也就是 --enable 和 --with 启用的扩展。动态编译就是在一个已经可以使用的 PHP 环境下,使用 phpize 命令来给 php 增加扩展的方式,这种方式就是生成的 so 文件。所以想要把扩展编译进 php 内核,就需要和 php 一起编译安装。 19 | * php源码包ext目录下的就是官方默认支持的扩展,还有一部分就是 pecl 库里的扩展,pecl 的一部分扩展也是官方支持的,另一部分是第三方开发者支持的。 20 | * 把扩展跟随php一起编译安装,也就是 configure 命令 21 | 22 | 23 | ##### ~~例如:~~ 24 | ./configure --prefix=/usr/local/php 25 | --enable-opcache \ 26 | --with-mysqli \ 27 | --with-pdo-mysql=mysqlnd \ 28 | --enable-mysqlnd \ 29 | --enable-inline-optimization \ 30 | --enable-sockets \ 31 | --enable-zip \ 32 | --enable-calendar \ 33 | --enable-bcmath \ 34 | --enable-soap \ 35 | --enable-fpm \ 36 | --with-zlib \ 37 | --with-iconv \ 38 | --with-gd \ 39 | --with-xmlrpc \ 40 | --enable-mbstring \ 41 | --without-sqlite3 \ 42 | --without-pdo-sqlite \ 43 | --with-curl \ 44 | --enable-ftp \ 45 | --with-mcrypt \ 46 | --enable-pcntl \ 47 | --with-freetype-dir \ 48 | --with-jpeg-dir \ 49 | --with-png-dir \ 50 | --disable-ipv6 \ 51 | --disable-debug \ 52 | --with-openssl \ 53 | --disable-maintainer-zts \ 54 | 55 | ##### 比如 configure 命令行可能看起来象这样: 56 | $ ./configure --prefix=/where/to/install/php --enable-debug --enable-maintainer-zts --enable-cgi --enable-cli --with-mysql=/path/to/mysql 57 | 58 | 59 | ## 我们要进行的配置和编译命令: 60 | ./configure --disable-all --enable-cli --enable-debug 61 | (--enable-debug启用调试模式,具有多重效果: 62 | 编译将使用 -g运行以生成包括行号、变量的类型和作用域、函数名字、函数参数和函数的作用域等源文件特性的调试信息. 63 | 另外使用-O0,会让gcc编译时不对代码优化. 64 | 此外,调试模式定义了 ZEND_DEBUG宏,它将启动引擎中的各种调试助手.除其他事项外,还将报告内存泄漏以及某些数据结构的不正确使用.) 65 | make -jN 66 | (N为CPU数量,作用:make --help查看) 67 | 68 | 69 | ## 导入eclipse并配置 70 | 1: 右击左侧栏目空白处,点击New -> Project -> c/c++ -> makefile project with existing code ->,再选择PHP源代码目录,同时选择GNU autotools toolchain -> finish 71 | 2: 点击菜单栏RUN->run configurations 在弹出窗口双击c/c++ application添加一个配置,在右侧标签点击search project 然后选择php并确定.点击arguments标签并添加如下参数. 72 | -r "echo 'run-test-ok';" 73 | 3:点击apply -> close. 74 | 4:运行测试,调试测试,切换透视图 75 | 76 | 77 | ## eclipse快捷键 78 | Ctrl+左键 或 F3 :跳到光标所在标识符的定义代码. 79 | Alt+左右方向键 :返回上一个/下一个阅读位置. 80 | Ctrl+H : 打开查找窗口. 81 | Ctrl+Shift+G :在工作空间中查找引用了光标所在标识符的位置.可以说是与F3相反的快捷键 82 | Ctrl+O :查看文件概要信息. 83 | ------------------ 84 | 调试快捷键 85 | F5进入当前方法,如果当前执行语句是函数调用,则会进入函数里面. 86 | F6单步执行程序,运行下一行代码 87 | F7退出当前方法,返回到调用层 88 | F8继续运行直到下一个断点 89 | ctrl+b:重新编译 90 | ctrl+f11:重新运行 91 | 92 | 93 | ## PHP内核源码目录结构 94 | php-744.1.4 95 | ├── build //源码编译相关文件 96 | └── ext //官方扩展目录,包括了绝大多数PHP的函数的定义和实现 97 | └── main //PHP核心基本文件,这里和Zend引擎不一样,Zend引擎主要实现语言最核心的语言运行环境. 98 | └── pear //“PHP 扩展与应用仓库”,包含PEAR的核心文件. 99 | └── sapi //包含了各种服务器抽象层的代码,例如apache的mod_php,cgi,fastcgi以及fpm等等接口. 100 | └── tests //PHP的测试脚本集合,包含PHP各项功能的测试文件 101 | └── TSRM //PHP的线程安全是构建在TSRM库之上的,PHP实现中常见的*G宏通常是对TSRM的封装,TSRM(Thread Safe Resource Manager)线程安全资源管理器. 102 | └── win32 //Windows平台相关的一些实现,比如sokcet的实现在Windows下和*Nix平台就不太一样,同时也包括了Windows下编译PHP相关的脚本. 103 | └── Zend //Zend引擎的实现目录,比如脚本的词法语法解析,opcode的执行以及扩展机制的实现等等. 104 | └── .gdbinit //gdb命令编写脚本 (gdb) source /home/laruence/package/php-5.2.14/.gdbinit (gdb) zbacktrace 105 | └── CODING_STANDARDS //PHP编码标准 106 | └── config.guess //由automake产生,两个用于目标平台检测的脚本 107 | └── config.log //configure执行时生成的日志文件 108 | └── config.nice //configure执行时生成,记录了上次执行configure时带的详细参数 109 | └── config.status //configure执行时生成,实际调用编译工具构建软件的shell脚本 110 | └── config.sub //由automake产生,两个用于目标平台检测的脚本 111 | └── configure //配置并生成makefile 112 | └── configure.in //autoreconf创建,开发者维护,用于生成configure 113 | └── CREDITS //开发人员名单 114 | └── EXTENSIONS //扩展说明(维护状态,维护人员,版本,适用系统..) 115 | └── LICENSE //发布协议 116 | └── php.ini-development //PHP开发环境示例配置文件 117 | └── php.ini-production //PHP生产环境示例配置文件 118 | └── README.EXT_SKEL //构建扩展脚本说明 119 | └── README.GIT-RULES //GIT提交时的规则 120 | └── README.namespaces //命名空间说明 121 | └── README.PARAMETER_PARSING_API //新的参数解析函数说明 122 | └── README.REDIST.BINS //PHP中引用到的其它程序协议说明 123 | └── README.RELEASE_PROCESS //PHP发布过程说明 124 | └── README.SELF-CONTAINED-EXTENSIONS//创建一个内建的PHP扩展 125 | └── README.STREAMS //PHP Streams(流概念) 说明 126 | └── README.SUBMITTING_PATCH //介绍如何提交PHP的增强功能或修补程序 127 | └── README.TESTING //测试说明(run-tests.php) 128 | └── README.TESTING2 //测试说明(server-tests.php) 129 | └── README.UNIX-BUILD-SYSTEM //PHP编译系统V5概述 130 | └── README.WIN32-BUILD-SYSTEM //WIN32编译说明 131 | └── run-test.php //测试脚本 132 | └── server-test.php //测试脚本 133 | └── sesrver-test-config.php //测试脚本 134 | └── UPGRADING //版本更新说明 135 | └── UPGRADING.INTERNALS //内部更新说明 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /1-3-0:数据结构-ZVAL.md: -------------------------------------------------------------------------------- 1 | # 介绍 2 | 3 | 4 | PHP是弱类型语言,在PHP代码中声明或使用变量的时候,并不需要强制指明其数据类型.在PHP内核中定义的变量类型: 5 | ```c 6 | /* Zend/zend_type.h : line 304 */ 7 | #define IS_UNDEF 0 8 | #define IS_NULL 1 9 | ... 10 | #define IS_PTR 17 11 | #define _IS_ERROR 20 12 | ``` 13 | 14 | 15 | 16 | 17 | ## zval 18 | PHP底层设计了一个zval(“Zend value”的缩写)的数据结构,可以用来表示任意类型的PHP值,因此,它可能是所有PHP中最重要的结构.本节介绍zvals及其使用背后的基本概念. 19 | 20 | ```c 21 | /* Zend/zend_type.h */ 22 | typedef union _zend_value { 23 | ... 24 | } zend_value; 25 | struct _zval_struct { 26 | ... 27 | }; 28 | ``` 29 | 虽然看起来变得好大, 但其实你仔细看, 全部都是联合体, 这个新的zval在64位环境下,现在只需要16个字节(2个指针size), 它主要分为俩个部分, value和扩充字段, 而扩充字段又分为u1和u2俩个部分, 其中u1是type info, u2是各种辅助字. 30 | 其中value部分, 是一个size_t大小(一个指针大小), 可以保存一个指针, 或者一个long, 或者一个double. 31 | 而type info部分则保存了这个zval的类型. 扩充辅助字段则会在多个其他地方使用, 比如next, 就用在取代Hashtable中原来的拉链指针, 这部分会在以后介绍HashTable的时候再来详解. 32 | 33 | ![zval的内存分布](./image/1-3zval.png) 34 | 35 | 从PHP7开始, 对于在zval的value字段中能保存下的值, 就不再对他们进行引用计数了, 而是在拷贝的时候直接赋值, 这样就省掉了大量的引用计数相关的操作, 这部分类型有: 36 | IS_LONG 37 | IS_DOUBLE 38 | 当然对于那种根本没有值, 只有类型的类型, 也不需要引用计数了: 39 | IS_NULL 40 | IS_FALSE 41 | IS_TRUE 42 | 而对于复杂类型, 一个size_t保存不下的, 那么我们就用value来保存一个指针, 这个指针指向这个具体的值, 引用计数也随之作用于这个值上, 而不在是作用于zval上了. 43 | 44 | 45 | 所以, 在PHP7开始, 我们移除了MAKE_STD_ZVAL/ALLOC_ZVAL宏, 不再支持存堆内存上申请zval. 函数内部使用的zval要么来自外面输入, 要么使用在栈上分配的临时zval. 46 | 47 | ### 操作zval 48 | 49 | 对zval的操作定义了很多宏,这维持了一个抽象层次,使意图更清晰,将来的PHP版本zval的内部改变了还可以兼容. 50 | ```c 51 | //不正确 52 | zval zv = ... 53 | if (zv_ptr->type == IS_LONG) { 54 | php_printf("此变量为长整形,值为 %ld\n", zv->value.lval); 55 | } else ... 56 | 57 | //推荐使用宏操作, 58 | zval zv = ... 59 | if (Z_TYPE zv ) == IS_LONG ) { 60 | php_printf (“此变量为长整形,值为 %ld \ n ” , Z_LVAL_P zv )); 61 | } else ... 62 | 63 | //宏有_P后缀表示操作的是zval指针 64 | Z_TYPE(zv); // 获取zval类型 = zv.type 65 | Z_TYPE_P(zv_ptr); // 获取指针指向的zval类型= zv_ptr->type 66 | 67 | ``` 68 | 69 | #### zval.value 相关宏 70 | ```code 71 | No. 名称 变量名 含义 访问宏 72 | 1 zend_long lval 整数(integer) Z_LVAL(zval) 73 | 2 double dval 浮動小数点数(float/double) Z_DVAL(zval) 74 | 3 zend_refcounted *counted 引用计数 Z_COUNTED(zval) 75 | 4 zend_string *str 字符串 Z_STR(zval) 76 | 5 zend_array *arr 数组 Z_ARR(zval) 77 | 6 zend_object *obj 对象 Z_OBJ(zval) 78 | 7 zend_resource *res 资源 Z_RES(zval) 79 | 8 zend_reference *ref 引用 Z_REF(zval) 80 | 9 zend_ast_ref *ast 抽象構文木 Z_AST(zval) 81 | 10 zval *zv zval指针 Z_INDIRECT(zval) 82 | 11 void *ptr 任意类型指针 Z_PTR(zval) 83 | 12 zend_class_entry *ce 类 Z_CE(zval) 84 | 13 zend_function *func 函数 Z_FUNC(zval) 85 | ``` 86 | #### zval 相关宏 87 | ```code 88 | ZVAL_UNDEF(z): 表示zval被销毁 89 | ZVAL_NULL(z): 设置为NULL 90 | ZVAL_FALSE(z): 设置为false 91 | ZVAL_TRUE(z): 设置为true 92 | ZVAL_BOOL(z, b): 设置为布尔型,b为IS_TRUE、IS_FALSE,与上面两个等价 93 | ZVAL_LONG(z, l): 设置为整形,l类型为zend_long,如:zval z; ZVAL_LONG(&z, 88); 94 | ZVAL_DOUBLE(z, d): 设置为浮点型,d类型为double 95 | ZVAL_STR(z, s): 设置字符串,将z的value设置为s,s类型为zend_string*,不会增加s的refcount,支持interned strings 96 | ZVAL_NEW_STR(z, s): 同ZVAL_STR(z, s),s为普通字符串,不支持interned strings 97 | ZVAL_STR_COPY(z, s): 将s拷贝到z的value,s类型为zend_string*,同ZVAL_STR(z, s),这里会增加s的refcount 98 | ZVAL_ARR(z, a): 设置为数组,a类型为zend_array* 99 | ZVAL_NEW_ARR(z): 新分配一个数组,主动分配一个zend_array 100 | ZVAL_NEW_PERSISTENT_ARR(z): 创建持久化数组,通过malloc分配,需要手动释放 101 | ZVAL_OBJ(z, o): 设置为对象,o类型为zend_object* 102 | ZVAL_RES(z, r): 设置为资源,r类型为zend_resource* 103 | ZVAL_NEW_RES(z, h, p, t): 新创建一个资源,h为资源handle,t为type,p为资源ptr指向结构 104 | ZVAL_REF(z, r): 设置为引用,r类型为zend_reference* 105 | ZVAL_NEW_EMPTY_REF(z): 新创建一个空引用,没有设置具体引用的value 106 | ZVAL_NEW_REF(z, r): 新创建一个引用,r为引用的值,类型为zval* 107 | ``` 108 | ### 在代码中查看zval 109 | 110 | 111 | 112 | 113 | 114 | ## 参考资料: 115 | https://github.com/pangudashu/php7-internal/blob/master/7/var.md 116 | https://net-newbie.com/phpext/7-zval.html 117 | https://github.com/laruence/php7-internal/blob/master/zval.md 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /1-3-1:数据结构-数组和字符串.md: -------------------------------------------------------------------------------- 1 | ## 数组/哈希表 2 | 3 | ### 数组结构 4 | ```c 5 | /* Zend/zend_type.h */ 6 | 7 | typedef struct _zend_array HashTable; 8 | //这个结构体64位平台占56个字节. 9 | struct _zend_array { 10 | zend_refcounted_h gc; //引用计数信息,与字符串相同 11 | union { 12 | struct { 13 | ZEND_ENDIAN_LOHI_4( 14 | zend_uchar flags,//zend_hash.h:line 38 15 | zend_uchar nApplyCount,//数组循环递归保护 16 | zend_uchar nIteratorsCount,//数组迭代深度 17 | zend_uchar consistency)//一致性状态 zend_hash.c line 38 18 | } v; 19 | uint32_t flags; //标志位 20 | } u; 21 | uint32_t nTableMask; //计算bucket索引时的掩码,等于nTableSize的负值(nTableMask = -nTableSize) 22 | Bucket *arData; //指向 Bucket 类型数据的指针,插入时只会在 arData 数组的结尾插入,而不会填充已经被删除的节点. 23 | uint32_t nNumUsed; //arData数组已经使用bucket数,每当添加一个新的数据时,此成员会加1. 24 | uint32_t nNumOfElements; //有效bucket数,nNumOfElements <= nNumUsed,因为删除的并不是直接从arData中移除 25 | uint32_t nTableSize; //hash表的大小,为2^n 26 | uint32_t nInternalPointer; //数值索引,用于HashTable遍历 27 | zend_long nNextFreeElement; //下一个空闲可用位置的数字索引 28 | dtor_func_t pDestructor; //析构函数,销毁时调用的函数指针 29 | }; 30 | typedef struct _Bucket { 31 | zval val; 32 | zend_ulong h; /* hash value (or numeric index) */ 33 | zend_string *key; /* string key or NULL for numerics */ 34 | } Bucket; 35 | 36 | //引用结构 37 | typedef struct _zend_refcounted_h { 38 | uint32_t refcount; /* reference counter 32-bit */ 39 | union { 40 | struct { 41 | ZEND_ENDIAN_LOHI_3( 42 | zend_uchar type, //zend_value的类型,与zval.u1.type一致 43 | zend_uchar flags, /* line:438 used for strings & objects */ 44 | uint16_t gc_info) /* GC信息,垃圾回收的过程会用到 keeps GC root number (or 0) and color */ 45 | } v; 46 | uint32_t type_info; 47 | } u; 48 | } zend_refcounted_h; 49 | ``` 50 | 51 | zval.value.arr将指向上面的这样的一个结构体, 由它实际保存一个数组, 引用计数部分保存在zend_refcounted_h结构中: 52 | 53 | 所有的复杂类型的定义, 开始的时候都是zend_refcounted_h结构, 这个结构里除了引用计数以外, 还有GC相关的结构. 从而在做GC回收的时候, GC不需要关心具体类型是什么, 所有的它都可以当做zend_refcounted*结构来处理. 54 | 55 | 插入元素时按顺序 依次插入数组,比如第一个元素在arData[0]、第二个在arData[1]...arData[nNumUsed]. 56 | 57 | 58 | 59 | ### 数据结构图片 60 | 61 | 当我们分配完整的arData内存时,会通过公式`tablesize * sizeof(bucket) + tablesize * sizeof(uint32)`计算它的大小 62 | 63 | ![](./image/1-3hashtable结构.png) 64 | 65 | ### 监视EG,CG,PG全局变量 66 | 67 | 68 | ### hash映射函数,碰撞处理 69 | hash表背后的概念非常简单:字符串键通过散列函数运行,该函数返回一个整数.然后,该整数用作“正常”数组的索引.问题是两个不同的字符串可能导致相同的哈希,因为可能的字符串的数量实际上是无限的,而散列受整数大小的限制.这样的hash表需要实现某种碰撞解决机制->链表法. 70 | 71 | 生成hash值:DJBX33A算法 72 | Zend/zend_string.h : line 325 73 | 74 | 75 | ### hashtable的操作 76 | 77 | ```c 78 | /* 79 | ht: 数组地址HashTable*,如果内部使用可以直接通过emalloc分配 80 | nSize: 初始化大小,只是参考值,这个值会被对齐到2^n,最小为8 81 | pHashFunction: 无用,设置为NULL即可 82 | pDestructor: 删除或更新数组元素时会调用这个函数对操作的元素进行处理,比如将一个字符串插入数组,字符串的refcount增加,删除时不是简单的将元素的Bucket删除就可以了,还需要对其refcount进行处理,这个函数就是进行清理工作的 83 | persistent: 是否持久化 84 | */ 85 | //hash初始化 86 | ALLOC_HASHTABLE(ht); 87 | zend_hash_init(ht, nSize, pHashFunction, pDestructor, persistent); 88 | //添加元素 89 | //key为zend_string 90 | zend_hash_update(ht, key, pData) //插入或更新元素,会增加key的refcount 91 | zend_hash_update_ind(ht, key, pData)//插入或更新元素,当Bucket类型为indirect时,将pData更新至indirect的值,而不是更新Bucket 92 | zend_hash_add(ht, key, pData) //添加元素,与zend_hash_update()类似,不同的地方在于如果元素已经存在则不会更新 93 | zend_hash_add_new(ht, key, pData) //直接插入元素,不管key存在与否,如果存在也不覆盖原来元素,而是当做哈希冲突处理,所有会出现一个数组中key相同的情况,慎用!!! 94 | //key为普通字符串:char* 95 | //与上面几个对应,这里的key为普通字符串,会自动生成zend_string的key 96 | zend_hash_str_update(ht, key, len, pData) 97 | zend_hash_str_update_ind(ht, key, len, pData) 98 | zend_hash_str_add(ht, key, len, pData) 99 | zend_hash_str_add_new(ht, key, len, pData) 100 | //key为数值索引 101 | zend_hash_index_add(ht, h, pData) //插入元素,h为数值 102 | zend_hash_index_add_new(ht, h, pData) //zend_hash_index_add_new 103 | zend_hash_index_update(ht, h, pData) //与zend_hash_add_new()类似 104 | //使用自动索引值 105 | zend_hash_next_index_insert(ht, pData) 106 | zend_hash_next_index_insert_new(ht, pData) 107 | //查找元素 108 | zend_hash_find(const HashTable *ht, zend_string *key); //根据zend_string key查找数组元素 109 | zend_hash_str_find(const HashTable *ht, const char *key, size_t len); //根据普通字符串key查找元素 110 | zend_hash_index_find(const HashTable *ht, zend_ulong h); //获取数值索引元素 111 | //判断元素是否存在 112 | zend_hash_exists(const HashTable *ht, zend_string *key); 113 | zend_hash_str_exists(const HashTable *ht, const char *str, size_t len); 114 | zend_hash_index_exists(const HashTable *ht, zend_ulong h); 115 | zend_hash_num_elements(ht) //获取数组元素数 116 | zend_array_count(HashTable *ht);//与zend_hash_num_elements()类似,会有一些特殊处理 117 | //删除元素 118 | zend_hash_del(HashTable *ht, zend_string *key); //删除key 119 | zend_hash_del_ind(HashTable *ht, zend_string *key); ////与zend_hash_del()类似,不同地方是如果元素类型为indirect则同时销毁indirect的值 120 | zend_hash_str_del(HashTable *ht, const char *key, size_t len); 121 | zend_hash_str_del_ind(HashTable *ht, const char *key, size_t len); 122 | zend_hash_index_del(HashTable *ht, zend_ulong h); 123 | zend_hash_del_bucket(HashTable *ht, Bucket *p); 124 | //销毁 125 | zend_hash_destroy(ht);//调用所有Bucket的析构函数并释放它们 126 | FREE_HASHTABLE(ht); 127 | 128 | //遍历 129 | //遍历获取所有val 130 | zval *val; 131 | ZEND_HASH_FOREACH_VAL(ht, val) { 132 | ... 133 | } ZEND_HASH_FOREACH_END(); 134 | 135 | //遍历获取所有的数值索引 136 | #define ZEND_HASH_FOREACH_NUM_KEY(ht, _h) \ 137 | ZEND_HASH_FOREACH(ht, 0); \ 138 | _h = _p->h; 139 | 140 | //遍历获取所有的key 141 | #define ZEND_HASH_FOREACH_STR_KEY(ht, _key) \ 142 | ZEND_HASH_FOREACH(ht, 0); \ 143 | _key = _p->key; 144 | 145 | //上面两个的聚合 146 | #define ZEND_HASH_FOREACH_KEY(ht, _h, _key) \ 147 | ZEND_HASH_FOREACH(ht, 0); \ 148 | _h = _p->h; \ 149 | _key = _p->key; 150 | 151 | //遍历获取数值索引key及value 152 | #define ZEND_HASH_FOREACH_NUM_KEY_VAL(ht, _h, _val) \ 153 | ZEND_HASH_FOREACH(ht, 0); \ 154 | _h = _p->h; \ 155 | _val = _z; 156 | 157 | //遍历获取key及value 158 | #define ZEND_HASH_FOREACH_STR_KEY_VAL(ht, _key, _val) \ 159 | ZEND_HASH_FOREACH(ht, 0); \ 160 | _key = _p->key; \ 161 | _val = _z; 162 | 163 | #define ZEND_HASH_FOREACH_KEY_VAL(ht, _h, _key, _val) \ 164 | ZEND_HASH_FOREACH(ht, 0); \ 165 | _h = _p->h; \ 166 | _key = _p->key; \ 167 | _val = _z; 168 | 169 | ``` 170 | 171 | ### 数组操作 172 | 将zend_hash API与zvals一起使用通常需要自己处理zval分配和初始化。所以PHP提供了专门针对这种用例的第二组API。也就是array api。 173 | 174 | | PHP语法 | C语法(arr是zval*) | 作用 | 175 | | -------- | ----- | ----- | 176 | | $arr = array(); | array_init(arr); | 初始化一个新数组 | 177 | | $arr[] = NULL; | add_next_index_null(arr); | 向数字索引的数组增加指定类型的值 | 178 | | $arr[] = 42; | add_next_index_long(arr, 42); | | 179 | | $arr[] = true; | add_next_index_bool(arr, 1); | | 180 | | $arr[] = 3.14; | add_next_index_double(arr, 3.14); | | 181 | | $arr[] = 'foo'; | add_next_index_string(arr, "foo", 1); | | 182 | | $arr[] = $myvar; | add_next_index_zval(arr, myvar); | | 183 | | $arr[0] = NULL; | add_index_null(arr, 0); | 向数组中指定的数字索引增加指定类型的值 | 184 | | $arr[1] = 42; | add_index_long(arr, 1, 42); | | 185 | | $arr[2] = true; | add_index_bool(arr, 2, 1); | | 186 | | $arr[3] = 3.14; | add_index_double(arr, 3, 3.14); | | 187 | | $arr[4] = 'foo'; | add_index_string(arr, 4, "foo", 1); | | 188 | | $arr[5] = $myvar; | add_index_zval(arr, 5, myvar); | | 189 | | $arr['abc'] = NULL; | add_assoc_null(arr, "abc"); | | 190 | | $arr['def'] = 711; | add_assoc_long(arr, "def", 711); | 向关联索引的数组增加指定类型的值 | 191 | | $arr['ghi'] = true; | add_assoc_bool(arr, "ghi", 1); | | 192 | | $arr['jkl'] = 1.44; | add_assoc_double(arr, "jkl", 1.44); | | 193 | | $arr['mno'] = 'baz'; | add_assoc_string(arr, "mno", "baz", 1); | | 194 | | $arr['pqr'] = $myvar; | add_assoc_zval(arr, "pqr", myvar); | . | 195 | 196 | 197 | 198 | 199 | 200 | 201 | ## 字符串 202 | ```c 203 | /* 204 | gc: 变量引用信息,比如当前value的引用数,所有用到引用计数的变量类型都会有这个结构,3.1节会详细分析 205 | h: 哈希值,数组中计算索引时会用到 206 | len: 字符串长度,通过这个值保证二进制安全 207 | val: 字符串内容,变长struct,分配时按len长度申请内存 208 | 事实上字符串又可具体分为几类:IS_STR_PERSISTENT(通过malloc分配的)、IS_STR_INTERNED(php代码里写的一些字面量,比如函数名、变量值)、IS_STR_PERMANENT(永久值,生命周期大于request)、IS_STR_CONSTANT(常量)、IS_STR_CONSTANT_UNQUALIFIED,这个信息通过flag保存:zval.value->gc.u.flags,后面用到的时候再具体分析. 209 | */ 210 | struct _zend_string { 211 | zend_refcounted_h gc; 212 | zend_ulong h; /* hash value */ 213 | size_t len; 214 | char val[1]; 215 | }; 216 | ``` 217 | ### 示例 218 | ```c 219 | zend_string *foo, *foo2; 220 | foo = zend_string_init("foo", strlen("foo"), 0); 221 | foo2 = zend_string_copy(foo); /* “foo”字符串引用数加1 */ 222 | 223 | /* 内部字符串,“foo”字符串引用变回为1,尽管有有三个地方在使用此字符串 */ 224 | foo = zend_new_interned_string(foo); 225 | 226 | /* foo指向内部字符串,这里释放操作没有任何作用。*/ 227 | zend_string_release(foo); 228 | 229 | /* foo2 指向内部字符串,这里同样没有作用 */ 230 | zend_string_release(foo2); 231 | 232 | /* 进程结束时,PHP将清理整个内部字符串空间,我们前面声明的"foo"将在此时释放 */ 233 | ``` 234 | 235 | ### 字符串的操作 236 | ```c 237 | //创建zend_string 238 | zend_string *zend_string_init(const char *str, size_t len, int persistent); 239 | 240 | //字符串复制,只增加引用 241 | zend_string *zend_string_copy(zend_string *s); 242 | 243 | //字符串拷贝,硬拷贝 244 | zend_string *zend_string_dup(zend_string *s, int persistent); 245 | 246 | //将字符串按len大小重新分配,会减少s的refcount,返回新的字符串 247 | zend_string *zend_string_realloc(zend_string *s, size_t len, int persistent); 248 | 249 | //延长字符串,与zend_string_realloc()类似,不同的是len不能小于s的长度 250 | zend_string *zend_string_extend(zend_string *s, size_t len, int persistent); 251 | 252 | //截断字符串,与zend_string_realloc()类似,不同的是len不能大于s的长度 253 | zend_string *zend_string_truncate(zend_string *s, size_t len, int persistent); 254 | 255 | //获取字符串refcount 256 | uint32_t zend_string_refcount(const zend_string *s); 257 | 258 | //增加字符串refcount 259 | uint32_t zend_string_addref(zend_string *s); 260 | 261 | //减少字符串refcount 262 | uint32_t zend_string_delref(zend_string *s); 263 | 264 | //释放字符串,减少refcount,为0时销毁 265 | void zend_string_release(zend_string *s); 266 | 267 | //销毁字符串,不管引用计数是否为0 268 | void zend_string_free(zend_string *s); 269 | 270 | //比较两个字符串是否相等,区分大小写,memcmp() 271 | zend_bool zend_string_equals(zend_string *s1, zend_string *s2); 272 | 273 | //比较两个字符串是否相等,不区分大小写 274 | #define zend_string_equals_ci(s1, s2) \ 275 | (ZSTR_LEN(s1) == ZSTR_LEN(s2) && !zend_binary_strcasecmp(ZSTR_VAL(s1), ZSTR_LEN(s1), ZSTR_VAL(s2), ZSTR_LEN(s2))) 276 | 277 | //其它宏,zstr类型为zend_string* 278 | #define ZSTR_VAL(zstr) (zstr)->val //获取字符串 279 | #define ZSTR_LEN(zstr) (zstr)->len //获取字符串长度 280 | #define ZSTR_H(zstr) (zstr)->h //获取字符串哈希值 281 | #define ZSTR_HASH(zstr) zend_string_hash_val(zstr) //计算字符串哈希值 282 | 283 | ZVAL_STR(zval, zstr); //将字符串存储到zval 284 | Z_STRVAL(zval) //获取ZVAL中的字符串 285 | Z_STRLEN(zval) //获取ZVAL中的字符串长度 286 | Z_STRHASH(zval) //获取ZVAL中的字符串哈希值 287 | ``` 288 | 289 | php_printf():打印格式化字符串到输出流。该函数内部使用spprintf(),会执行动态分配空间并它在将其发送到SAPI输出之后释放自身。 290 | 291 | spprintf(): 292 | ```c 293 | #include 294 | char *result; 295 | int r; 296 | time_t timestamp = time(NULL); 297 | r = spprintf(&result, 0, "Here is the date: %s", asctime(localtime(×tamp)));/* result 为 "Here is the date: Thu Jun 15 19:12:51 2017\n" */ 298 | efree(result); 299 | ``` 300 | strpprintf(): 301 | ```c 302 | zend_string *result; 303 | result = strpprintf(0, "You are using PHP %s", PHP_VERSION); 304 | /* Do something with result */ 305 | zend_string_release(result); 306 | ``` 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | ## 参考资料: 328 | http://blog.jpauli.tech/2016/04/08/hashtables.html 329 | https://github.com/pangudashu 330 | http://ju.outofmemory.cn/entry/197064 331 | http://www.phpinternalsbook.com 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | -------------------------------------------------------------------------------- /1-3-2:数据结构-SAPI.md: -------------------------------------------------------------------------------- 1 | # SAPI讲解 2 | 3 | **什么是SAPI:Server Application Programming Interface,它是PHP提供的一个接口机制,使得PHP可以和其他应用进行交互数据/协作;** 4 | 5 | ## PHP架构图 6 | ![PHP内核架构图](./image/1-3PHP架构图.png) 7 | 8 | ## 举例 9 | ![SAPI结构原理](./image/1-3SAPI讲解.png) 10 | 11 | 12 | ## sapi模块结构体 13 | ```c 14 | struct _sapi_module_struct { 15 | char *name; 16 | char *pretty_name; 17 | 18 | int (*startup)(struct _sapi_module_struct *sapi_module);//PHP被调用的时候,这个函数会被调用执行 19 | int (*shutdown)(struct _sapi_module_struct *sapi_module);//PHP调用结束的时候,这个函数会被调用执行 20 | 21 | int (*activate)(void);//每个request开始时会执行,进行一些初始化,资源分配的事务 22 | int (*deactivate)(void);//每个request结束时会执行 23 | 24 | size_t (*ub_write)(const char *str, size_t str_length);//这个hanlder告诉了Zend,如何输出数据,对于mod_php来说,这个函数提供了一个向response数据写的接口,而CLI只是简单的写到stdout 25 | void (*flush)(void *server_context);//这个是提供给zend的刷新缓存的函数句柄,对于CLI来说,只是简单的调用系统提供的fflush; 26 | zend_stat_t *(*get_stat)(void);//这部分用来让Zend可以验证一个要执行脚本文件的state,从而判断文件是否据有执行权限等等. 27 | char *(*getenv)(char *name, size_t name_len);//为Zend提供了一个根据name来查找环境变量的接口,对于mod_php5来说,当我们在脚本中调用getenv的时候,就会间接的调用这个句柄.CLI和CGI很类似,直接调用父级是Shell, 所以,只是简单的调用了系统提供的genenv: 28 | 29 | void (*sapi_error)(int type, const char *error_msg, ...) ZEND_ATTRIBUTE_FORMAT(printf, 2, 3);//错误处理函数 30 | 31 | int (*header_handler)(sapi_header_struct *sapi_header, sapi_header_op_enum op, sapi_headers_struct *sapi_headers);//这个函数会在我们调用PHP的header()函数的时候被调用. 32 | int (*send_headers)(sapi_headers_struct *sapi_headers);//这个函数会在要真正发送header的时候被调用,一般来说,就是当有任何的输出要发送之前: 33 | void (*send_header)(sapi_header_struct *sapi_header, void *server_context);//这个用来单独发送每一个header 34 | 35 | size_t (*read_post)(char *buffer, size_t count_bytes);//这个句柄指明了如何获取POST的数据,CLI是从stdin中读取POST DATA的, 36 | char *(*read_cookies)(void);//这个句柄指明了如何获取cookies的数据 37 | 38 | void (*register_server_variables)(zval *track_vars_array);//这个函数用以给$_SERVER变量中添加变量,CLI在此函数中注册了PHP_SELF,SCRIPT_FILENAME等server变量: 39 | void (*log_message)(char *message, int syslog_type_int);//用来输出错误信息,对于CLI来说,只是简单的输出到stderr 40 | double (*get_request_time)(void);//获取请求时间,只有apach2自定义了,默认为获取当前时间 41 | void (*terminate_process)(void);//没有任何SAPI用到,无用代码 42 | 43 | //STANDARD_SAPI_MODULE_PROPERTIES 44 | char *php_ini_path_override; 45 | void (*default_post_reader)(void); 46 | void (*treat_data)(int arg, char *str, zval *destArray); 47 | char *executable_location; 48 | int php_ini_ignore; 49 | int php_ini_ignore_cwd; 50 | int (*get_fd)(int *fd); 51 | int (*force_http_10)(void); 52 | int (*get_target_uid)(uid_t *); 53 | int (*get_target_gid)(gid_t *); 54 | unsigned int (*input_filter)(int arg, char *var, char **val, size_t val_len, size_t *new_val_len); 55 | void (*ini_defaults)(HashTable *configuration_hash); 56 | int phpinfo_as_text; 57 | char *ini_entries; 58 | const zend_function_entry *additional_functions; 59 | unsigned int (*input_filter_init)(void); 60 | };``` 61 | 62 | --- 63 | 64 | ## cli_sapi_module 的定义 65 | ```c 66 | static sapi_module_struct cli_sapi_module = { 67 | "cli", /* name */ 68 | "Command Line Interface", /* pretty name */ 69 | 70 | php_cli_startup, /* startup */ 71 | php_module_shutdown_wrapper, /* shutdown */ 72 | NULL, /* activate */ 73 | sapi_cli_deactivate, /* deactivate */ 74 | 75 | sapi_cli_ub_write, /* unbuffered write */ 76 | sapi_cli_flush, /* flush */ 77 | NULL, /* get uid */ 78 | NULL, /* getenv */ 79 | 80 | php_error, /* error handler */ 81 | sapi_cli_header_handler, /* header handler */ 82 | sapi_cli_send_headers, /* send headers handler */ 83 | sapi_cli_send_header, /* send header handler */ 84 | 85 | NULL, /* read POST data */ 86 | sapi_cli_read_cookies, /* read Cookies */ 87 | 88 | sapi_cli_register_variables, /* register server variables */ 89 | sapi_cli_log_message, /* Log message */ 90 | NULL, /* Get request time */ 91 | NULL, /* Child terminate */ 92 | 93 | STANDARD_SAPI_MODULE_PROPERTIES 94 | }; 95 | ``` 96 | ## 在代码中下断点定位一下调用过程 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /1-3-3:数据结构-Function.md: -------------------------------------------------------------------------------- 1 | ## 介绍 2 | 3 | 在PHP中,函数分为俩种: 4 | 一种是zend_internal_function, 这种函数是由扩展或者Zend/PHP内核提供的,用’C/C++’编写的,可以直接执行的函数. 5 | 另外一种是zend_user_function, 这种函数呢,就是我们经常在见的,用户在PHP脚本中定义的函数,这种函数最终会被ZE翻译成opcode array来执行 6 | 7 | zval : zend_function func 类型成员 8 | EG(function_table)是一个哈希表,记录的就是PHP中所有的函数. 9 | 10 | zend_internal_function,zend_function,zend_op_array这三种结构在一定程序上存在公共的元素, 于是这些元素以联合体的形式共享内存,并且在执行过程中对于一个函数,这三种结构对应的字段在值上都是一样的, 于是可以在一些结构间发生完美的强制类型转换.zend_op_array与zend_internal_function结构的起始位置都有common中的几个成员,common可以看作是op_array、internal_function的header,不管是什么哪种函数都可以通过zend_function.common.xx快速访问,zend_function可以与zend_op_array互换,zend_function可以与zend_internal_function互换,但是一个zend_op_array结构转换成zend_function是不能再次转变成zend_internal_function结构的,反之亦然. 11 | 12 | 13 | ### 结构 14 | ```c 15 | //zend_compile.h 16 | union _zend_function { 17 | zend_uchar type; /* 函数类型 */ 18 | uint32_t quick_arg_flags; 19 | struct { 20 | zend_uchar type; /* never used */ 21 | zend_uchar arg_flags[3]; /* bitset of arg_info.pass_by_reference */ 22 | uint32_t fn_flags; //作为方法时的访问类型等,如ZEND_ACC_STATIC等 23 | zend_string *function_name; //函数名称 24 | zend_class_entry *scope; //成员方法所属类,面向对象实现中用到 25 | union _zend_function *prototype;//函数原型 26 | uint32_t num_args; //参数数量 27 | uint32_t required_num_args; //必传参数数量 28 | zend_arg_info *arg_info; //参数信息 29 | } common; 30 | zend_op_array op_array; //函数实际编译为普通的zend_op_array 31 | zend_internal_function internal_function; 32 | }; 33 | //内部函数结构 34 | typedef struct _zend_internal_function { 35 | /* Common elements */ 36 | zend_uchar type; 37 | zend_uchar arg_flags[3]; /* bitset of arg_info.pass_by_reference */ 38 | uint32_t fn_flags; 39 | zend_string* function_name; 40 | zend_class_entry *scope; 41 | zend_function *prototype; 42 | uint32_t num_args; 43 | uint32_t required_num_args; 44 | zend_internal_arg_info *arg_info; 45 | /* END of common elements */ 46 | 47 | void (*handler)(INTERNAL_FUNCTION_PARAMETERS); //函数指针,展开:void (*handler)(zend_execute_data *execute_data, zval *return_value) 48 | struct _zend_module_entry *module; 49 | void *reserved[ZEND_MAX_RESERVED_RESOURCES]; 50 | } zend_internal_function; 51 | //用户自定义函数结构 52 | struct _zend_op_array { 53 | /* Common elements common是普通函数或类成员方法对应的opcodes快速访问时使用的字段*/ 54 | zend_uchar type; 55 | zend_uchar arg_flags[3]; /* bitset of arg_info.pass_by_reference */ 56 | uint32_t fn_flags; 57 | zend_string *function_name; 58 | zend_class_entry *scope; 59 | zend_function *prototype; 60 | uint32_t num_args; 61 | uint32_t required_num_args; 62 | zend_arg_info *arg_info; 63 | /* END of common elements */ 64 | 65 | uint32_t *refcount; 66 | 67 | uint32_t last; 68 | zend_op *opcodes; //opcode指令数组 69 | int last_var; //PHP代码里定义的变量数:op_type为IS_CV的变量,不含IS_TMP_VAR、IS_VAR的,编译前0,然后发现一个新变量这个值就加1 70 | uint32_t T; //临时变量数:op_type为IS_TMP_VAR、IS_VAR的变量 71 | zend_string **vars; //这个数组在ast编译期间配合last_var用来确定各个变量的编号,非常重要的一步操作//PHP变量名数组 72 | ... 73 | HashTable *static_variables; //静态变量符号表:通过static声明的 74 | ... 75 | int last_literal; //字面量数量 76 | zval *literals; //字面量(常量)数组,这些都是在PHP代码定义的一些值 77 | int cache_size; //运行时缓存数组大小 78 | void **run_time_cache; //运行时缓存,主要用于缓存一些znode_op以便于快速获取数据,后面单独介绍这个机制 79 | 80 | void *reserved[ZEND_MAX_RESERVED_RESOURCES]; 81 | }; 82 | //函数类型 83 | #define ZEND_INTERNAL_FUNCTION 1 //内置的函数 84 | #define ZEND_USER_FUNCTION 2 //用户函数 85 | #define ZEND_OVERLOADED_FUNCTION 3 //对象中__call相关 86 | #define ZEND_EVAL_CODE 4 //eval code 87 | #define ZEND_OVERLOADED_FUNCTION_TEMPORARY 5 //对象中__call相关 88 | 89 | ``` 90 | 91 | 92 | 93 | 94 | 95 | ## 用户自定义函数 96 | 97 | PHP在编译阶段将用户自定义的函数编译为独立的opcodes,保存在EG(function_table)中,调用时重新分配新的zend_execute_data(相当于运行栈),然后执行函数的opcodes,调用完再还原到旧的zend_execute_data,继续执行,关于zend引擎execute阶段后面会详细分析. 98 | zend_function的结构中的op_array存储了该函数中所有的操作,当函数被调用时,ZE就会将这个op_array中的opline一条条顺次执行, 并将最后的返回值返回. 从VLD扩展中查看的关于函数的信息可以看出,函数的定义和执行是分开的,一个函数可以作为一个独立的运行单元而存在. 99 | 100 | 101 | ### 函数参数 102 | 参数名称也在zend_op_array.vars中,编号首先是从参数开始的,所以按照参数顺序其编号依次为0、1、2... 103 | 参数的其它信息通过zend_arg_info结构记录: 104 | ```c 105 | typedef struct _zend_arg_info { 106 | zend_string *name; //参数名 107 | zend_string *class_name;//类名 108 | zend_uchar type_hint; //显式声明的参数类型,比如(array $param_1) 109 | zend_uchar pass_by_reference; //是否引用传参,参数前加&的这个值就是1 110 | zend_bool allow_null; //是否允许为NULL,注意:这个值并不是用来表示参数是否为必传的 111 | zend_bool is_variadic; //是否为可变参数,即...用法,与golang的用法相同,5.6以上新增的一个用法:function my_func($a, ...$b){...} 112 | } zend_arg_info; 113 | ``` 114 | 每个参数都有一个上面的结构,所有参数的结构保存在zend_op_array.arg_info数组中,这里有一个地方需要注意:zend_op_array->arg_info数组保存的并不全是输入参数,如果函数声明了返回值类型则也会为它创建一个zend_arg_info,这个结构在arg_info数组的第一个位置,这种情况下zend_op_array->arg_info指向的实际是数组的第二个位置,返回值的结构通过zend_op_array->arg_info[-1]读取. 115 | 116 | ### 编译过程 117 | zend引擎编译课程再讲解 118 | 119 | 120 | ## 内部函数 121 | 122 | 内部函数的定义非常简单,我们只需要创建一个普通的C函数,然后创建一个zend_internal_function结构添加到 EG(function_table) 123 | 124 | ### 解析函数参数 125 | ZEND_API int zend_parse_parameters(int num_args TSRMLS_DC, char *type_spec, ...) 126 | 第一个参数num_args表明表示想要接收的参数个数,我们经常使用ZEND_NUM_ARGS() 来表示对传入的参数“有多少要多少”. 127 | 第二参数应该总是宏 TSRMLS_CC . 128 | 第三个参数 type_spec 是一个字符串,用来指定我们所期待接收的各个参数的类型,有点类似于 printf 中指定输出格式的那个格式化字符串. 129 | 剩下的参数就是我们用来接收PHP参数值的变量的指针. 130 | 131 | 132 | ### PHP自带函数讲解 133 | ```c 134 | // ext/standard/string.c : line 2744 135 | static void php_ucfirst(char *str) 136 | { 137 | register char *r; 138 | r = str; 139 | *r = toupper((unsigned char) *r); 140 | } 141 | PHP_FUNCTION(ucfirst) 142 | { 143 | zend_string *str; 144 | 145 | ZEND_PARSE_PARAMETERS_START(1, 1) 146 | Z_PARAM_STR(str) 147 | ZEND_PARSE_PARAMETERS_END(); 148 | 149 | if (!ZSTR_LEN(str)) { 150 | RETURN_EMPTY_STRING(); 151 | } 152 | 153 | ZVAL_STRINGL(return_value, ZSTR_VAL(str), ZSTR_LEN(str)); 154 | php_ucfirst(Z_STRVAL_P(return_value)); 155 | } 156 | // ext/standard/php_string.h 157 | PHP_FUNCTION(ucfirst); 158 | ``` 159 | 160 | ### 注册新函数 161 | 在扩展课程再讲解 162 | 163 | ### 函数注册过程 164 | 165 | ## 测试 166 | ```php 167 | 到最后脚本被执行->得到执行结果,这个过程,可以分为如下几个阶段 7 | * 首先,Zend Engine(ZE),调用词法分析器,将我们要执行的PHP源文件,去掉空格 ,注释,分割成一个一个的token. 8 | * 然后,ZE会将得到的token forward给语法分析器,生成抽象语法树. 9 | * 然后,ZE调用zend_compile_top_stmt()函数将抽象语法树解析为一个一个的op code,opcode一般会以op array的形式存在,它是PHP执行的中间语言. 10 | * 最后,ZE调用zend_executor来执行op array,输出结果. 11 | 12 | ![ZEND引擎编译执行过程](./image/1-4PHP编译执行过程.png) 13 | 14 | #### PHP编译PHP代码时用到的工具 15 | re2c: 词法分析器,将输入分割为一个个有意义的词块,称为token,Zend/zend_language_scanner.l 文件是re2c的规则文件. 16 | bison: 语法分析器,确定词法分析器分割出的token是如何彼此关联的,Zend/zend_language_parser.y 文件是bison的规则文件. 17 | 18 | 19 | # CG变量 zend_global.h/_zend_compiler_globals 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | # Token 28 | ```php 29 | &$value) { 32 | if(is_array($value)&&(!empty($value[0]))){ 33 | $value[0] = token_name(intval($value[0])); 34 | } 35 | } 36 | print_r($token); 37 | ``` 38 | 39 | **输出:** 40 | ``` 41 | Array 42 | ( 43 | [0] => Array 44 | ( 45 | [0] => T_OPEN_TAG //TOKEN名称 46 | [1] => 1 //行号 48 | ) 49 | [1] => Array 50 | ( 51 | [0] => T_VARIABLE 52 | [1] => $str 53 | [2] => 1 54 | ) 55 | [2] => = 56 | [3] => Array 57 | ( 58 | [0] => T_CONSTANT_ENCAPSED_STRING 59 | [1] => "hello world" 60 | [2] => 1 61 | ) 62 | [4] => ; 63 | [5] => Array 64 | ( 65 | [0] => T_ECHO 66 | [1] => echo 67 | [2] => 1 68 | ) 69 | [6] => Array 70 | ( 71 | [0] => T_WHITESPACE 72 | [1] => 73 | [2] => 1 74 | ) 75 | [7] => Array 76 | ( 77 | [0] => T_VARIABLE 78 | [1] => $str 79 | [2] => 1 80 | ) 81 | [8] => ; 82 | ) 83 | ``` 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | # AST: 92 | #### PHP7之后的编译过程加了一层抽象语法树,使编译过程更清晰规范,易于优化,语法规则减少,编译速度变快,编译占用内存增加. 93 | 94 | #### 查看AST的工具: 95 | * https://github.com/nikic/PHP-Parser (PHP解析工具) 96 | * https://pecl.php.net/package/ast (扩展) 97 | * https://dooakitestapp.herokuapp.com/phpast/webapp/ (在线) 98 | 99 | 100 | ![](./image/1-4生成的AST.png) 101 | 102 | #### 相关数据结构:Zend/zend_ast.h 103 | ```c 104 | enum _zend_ast_kind { 105 | /* 特殊节点 special nodes */ 106 | ZEND_AST_ZVAL = 1 << ZEND_AST_SPECIAL_SHIFT, 107 | ZEND_AST_ZNODE, 108 | 109 | /* 声明节点 declaration nodes */ 110 | ZEND_AST_FUNC_DECL, 111 | ZEND_AST_CLOSURE, 112 | ZEND_AST_METHOD, 113 | ZEND_AST_CLASS, 114 | 115 | /* 列表节点 list nodes */ 116 | ZEND_AST_ARG_LIST = 1 << ZEND_AST_IS_LIST_SHIFT, 117 | ZEND_AST_ARRAY, 118 | ... 119 | ZEND_AST_USE, 120 | 121 | /* 普通节点 0 child nodes */ 122 | ZEND_AST_MAGIC_CONST = 0 << ZEND_AST_NUM_CHILDREN_SHIFT, 123 | ZEND_AST_TYPE, 124 | 125 | /* 1 child node */ 126 | ZEND_AST_VAR = 1 << ZEND_AST_NUM_CHILDREN_SHIFT, 127 | ZEND_AST_CONST, 128 | ... 129 | /* 4 child nodes */ 130 | ZEND_AST_FOR = 4 << ZEND_AST_NUM_CHILDREN_SHIFT, 131 | ZEND_AST_FOREACH, 132 | }; 133 | struct _zend_ast { 134 | zend_ast_kind kind; /* 节点类型 Type of the node (ZEND_AST_* enum constant) */ 135 | zend_ast_attr attr; /* 附加信息 Additional attribute, use depending on node type */ 136 | uint32_t lineno; /* 行号 Line number */ 137 | zend_ast *child[1]; /* 子节点 Array of children (using struct hack) */ 138 | }; 139 | /* Same as zend_ast, but with children count, which is updated dynamically */ 140 | typedef struct _zend_ast_list { 141 | zend_ast_kind kind; 142 | zend_ast_attr attr; 143 | uint32_t lineno; 144 | uint32_t children; 145 | zend_ast *child[1]; 146 | } zend_ast_list; 147 | /* Lineno is stored in val.u2.lineno */ 148 | //php脚本中的变量,文字,变量名,调用函数名等,总是终端叶节点 149 | typedef struct _zend_ast_zval { 150 | zend_ast_kind kind; 151 | zend_ast_attr attr; 152 | zval val; 153 | } zend_ast_zval; 154 | /* Separate structure for function and class declaration, as they need extra information. */ 155 | /*声明类型的始终有四个子节点 156 | AST_FUNC_DECL函数定义 157 | 1:AST_PARAM_LIST(参数),2:未使用, 158 | 3:AST_STMT_LIST(函数内部),4:AST_ZVAL(返回值类型) 159 | AST_CLOSURE匿名函数定义 160 | 1:AST_PARAM_LIST(参数),2:AST_CLOSURE_USES(use),3:AST_STMT_LIST(函数内部),4:AST_ZVAL(返回值类型) 161 | AST_METHOD方法定义 162 | 1:AST_PARAM_LIST(参数),2:未使用,3:AST_STMT_LIST(函数内部),4:AST_ZVAL(返回值类型) 163 | AST_CLASS类,匿名类,trait,接口定义 164 | 1:AST_ZVAL(继承源),2:AST_NAME_LIST(implements),3:AST_STMT_LIST(内部定义),4:未使用 165 | */ 166 | typedef struct _zend_ast_decl { 167 | zend_ast_kind kind; 168 | zend_ast_attr attr; /* Unused - for structure compatibility */ 169 | uint32_t start_lineno; 170 | uint32_t end_lineno; 171 | uint32_t flags; 172 | unsigned char *lex_pos; 173 | zend_string *doc_comment; 174 | zend_string *name; 175 | zend_ast *child[4]; 176 | } zend_ast_decl; 177 | ``` 178 | 179 | 180 | # OPCEODE: 181 | opcode是将PHP代码编译产生的Zend虚拟机可识别的指令,php7共有173个opcode,定义在zend_vm_opcodes.h中,这些中间代码会被Zend VM(Zend虚拟机)直接执行. 182 | 183 | 184 | #### opcode查看: 185 | * https://3v4l.org/UBstu/vld#output (在线) 186 | * https://pecl.php.net/package/vld (扩展) 187 | 188 | #### VLD输出: 189 | Finding entry points 190 | Branch analysis from position: 0 191 | Jump found. (Code = 62) Position 1 = -2 192 | filename: /in/MSj65 193 | function name: (null) 194 | number of ops: 3 195 | compiled vars: !0 = $str 196 | line #* E I O op fetch ext return operands 197 | ------------------------------------------------------------------------------------- 198 | 4 0 E > ASSIGN !0, 'hello+world' 199 | 6 1 ECHO !0 200 | 2 > RETURN 1 201 | 202 | 203 | ![](./image/1-4-1vld-opcode.png) 204 | 205 | 206 | #### 相关数据结构Zend/compile.h 207 | 参考:http://blog.csdn.net/phpkernel/article/details/5721384 208 | http://www.bo56.com/php7%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E4%B9%8Bcg%E5%92%8Ceg/ 209 | ```c 210 | struct _zend_op_array { 211 | //common是普通函数或类成员方法对应的opcodes快速访问时使用的字段,后面分析PHP函数实现的时候会详细讲 212 | ... 213 | zend_op *opcodes; //opcode指令数组 214 | int last_var; //PHP代码里定义的变量数:op_type为IS_CV的变量,不含IS_TMP_VAR、IS_VAR的,编译前0,然后发现一个新变量这个值就加1 215 | uint32_t T; //临时变量数:op_type为IS_TMP_VAR、IS_VAR的变量 216 | zend_string **vars; //这个数组在ast编译期间配合last_var用来确定各个变量的编号,非常重要的一步操作//PHP变量名数组 217 | ... 218 | HashTable *static_variables; //静态变量符号表:通过static声明的 219 | ... 220 | int last_literal; //字面量数量 221 | zval *literals; //字面量(常量)数组,这些都是在PHP代码定义的一些值 222 | int cache_size; //运行时缓存数组大小 223 | void **run_time_cache; //运行时缓存,主要用于缓存一些znode_op以便于快速获取数据,后面单独介绍这个机制 224 | 225 | void *reserved[ZEND_MAX_RESERVED_RESOURCES]; 226 | }; 227 | struct _zend_op { 228 | const void *handler; //对应执行的C语言function,即每条opcode都有一个C function处理 229 | znode_op op1; //操作数1 230 | znode_op op2; //操作数2 231 | znode_op result; //返回值 232 | uint32_t extended_value; //用来区别被重载的操作符 233 | uint32_t lineno; 234 | zend_uchar opcode; //opcode指令 235 | zend_uchar op1_type; //操作数1类型,为IS_CONST, IS_TMP_VAR, IS_VAR, IS_UNUSED, or IS_CV 236 | zend_uchar op2_type; //操作数2类型 237 | zend_uchar result_type; //返回值类型 238 | }; 239 | //操作数结构 240 | typedef union _znode_op { 241 | uint32_t constant; 242 | uint32_t var; 243 | uint32_t num; 244 | uint32_t opline_num; /* Needs to be signed */ 245 | uint32_t jmp_offset; 246 | } znode_op; 247 | ``` 248 | #### 操作数类型: 249 | ```c 250 | // Zend/zend_compile.h 251 | #define IS_CONST (1<<0) //1 252 | #define IS_TMP_VAR (1<<1) //2 253 | #define IS_VAR (1<<2) //4 254 | #define IS_UNUSED (1<<3) //8 255 | #define IS_CV (1<<4) //16 256 | ``` 257 | * IS_CONST:字面量,编译时就可确定且不会改变的值,比如:$a = "hello~",其中字符串"hello~"就是常量 258 | * IS_TMP_VAR:临时变量,比如:$a = "hello~" . time(),其中"hello~" . time()的值类型就是IS_TMP_VAR,再比如:$a = "123" + $b,"123" + $b的结果类型也是IS_TMP_VAR,从这两个例子可以猜测,临时变量多是执行期间其它类型组合现生成的一个中间值,由于它是现生成的,所以把IS_TMP_VAR赋值给IS_CV变量时不会增加其引用计数 259 | * IS_VAR:PHP变量,这个很容易认为是PHP脚本里的变量,其实不是,这里PHP变量的含义可以这样理解:PHP变量是没有显式的在PHP脚本中定义的,不是直接在代码通过$var_name定义的.这个类型最常见的例子是PHP函数的返回值,再如$a[0]数组这种,它取出的值也是IS_VAR,再比如$$a这种 260 | * IS_UNUSED:表示操作数没有用 261 | * IS_CV:PHP脚本变量,即脚本里通过$var_name定义的变量,这些变量是编译阶段确定的,所以是compile variable, 262 | 263 | 264 | #### opcode handler 的索引算法 265 | //zend_vm_execute.h -> /zend_vm_get_opcode_handler() 266 | 267 | 268 | #### 编译后的CG(active_op_array)结构图 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | ### 参考资料: 277 | PHP代码的编译:https://github.com/pangudashu/php7-internal 278 | PHP官网关于AST的RFC: 279 | https://wiki.php.net/rfc/ast_based_parsing_compilation_process 280 | https://wiki.php.net/rfc/abstract_syntax_tree 281 | AST彻底解说:https://www.slideshare.net/do_aki/php-ast 282 | VLD扩展使用指南:http://www.php-internals.com/book/?p=C-php-vld 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | -------------------------------------------------------------------------------- /1-4-2:运行过程-ZEND引擎执行.md: -------------------------------------------------------------------------------- 1 | 2 | # zend执行过程 3 | ## EG变量 4 | executor_globals是一个全局变量,存储着许多信息(当前上下文、符号表、函数/类/常量表、堆栈等),EG宏就是用于访问executor_globals的某个成员. 5 | ```c 6 | //zend_global.h/_zend_executor_globals 7 | struct _zend_executor_globals { 8 | ... 9 | zend_array symbol_table; /* PHP全局变量表:$_GET,$_POST等 main symbol table */ 10 | HashTable included_files; /* 已经引入的脚本 files already included */ 11 | 12 | JMP_BUF *bailout; /* try-catch保存的catch跳转位置 */ 13 | int error_reporting; 14 | int exit_status; 15 | 16 | HashTable *function_table; /* 全部已编译的function哈希表,包括内部函数,用户自定义函数,函数调用将从这里查找 function symbol table */ 17 | HashTable *class_table; /* 全部已编译的class哈希表,new class 时从此查找 class table */ 18 | HashTable *zend_constants; /* 常量符号表 constants table */ 19 | 20 | zval *vm_stack_top;//栈内存池剩余可用内存起始位置 21 | zval *vm_stack_end;//栈内存池结束位置 22 | zend_vm_stack vm_stack; /* 运行栈内存池,一块空白的内存,用于分配PHP执行期间的一些数据结构(zend_execute),局部变量从这里分配 */ 23 | 24 | struct _zend_execute_data *current_execute_data; /* 指向当前正在执行的运行栈,函数调用就是分配一个新的zend_execute_data,然后将EG(current_execute_data)指向新的结构继续执行,调用完毕再还原回去,类似汇编call,ret指令的作用 */ 25 | zend_class_entry *fake_scope; /* used to avoid checks accessing properties */ 26 | ... 27 | 28 | HashTable *in_autoload; /* 在类的自动加载过程中会使用到 */ 29 | zend_function *autoload_func; /* 自动加载回调函数:__autoload() */ 30 | zend_bool full_tables_cleanup; 31 | ... 32 | 33 | HashTable regular_list; 34 | HashTable persistent_list; /* 持久化符号表,request请求结束后不释放可以跨request共享,在php_module_shutdown()阶段清理,*/ 35 | 36 | ... 37 | }; 38 | //zend/zend_globals_macros.h 39 | # define EG(v) (executor_globals.v) 40 | ``` 41 | 42 | # zend_compile.h/_zend_execute_data 43 | zend_execute_data是执行过程中最核心的一个结构,每次函数的调用、include/require、eval等都会生成一个新的结构,它表示当前的作用域、代码的执行位置以及局部变量的分配等等,等同于机器码执行过程中stack的角色. 44 | 45 | ```c 46 | //64位机器上,占80个字节 47 | struct _zend_execute_data { 48 | const zend_op *opline;/* 指向当前执行的opcode,初始时指向zend_op_array起始位置executed opline */ 49 | zend_execute_data *call;/* 当前正在调用的子函数 current call */ 50 | zval *return_value;//返回值指针 51 | zend_function *func;/* 当前执行的函数自身(非函数调用时为空)executed function */ 52 | zval This;/* 这个值并不仅仅是面向对象的this,还有另外两个值也通过这个记录:call_info + num_args,分别存在zval.u1.reserved、zval.u2.num_args */ 53 | zend_execute_data *prev_execute_data;//调用时指向父级调用位置作用空间 54 | zend_array *symbol_table;//全局变量符号表 55 | #if ZEND_EX_USE_RUN_TIME_CACHE 56 | void **run_time_cache;/* cache op_array->run_time_cache */ 57 | #endif 58 | #if ZEND_EX_USE_LITERALS 59 | zval *literals;/* 字面量数组,与func.op_array->literals相同 //cache op_array->literals*/ 60 | #endif 61 | }; 62 | ``` 63 | 64 | ![op_array与execute_data关系](./image/1-4-2op_array与execute_data关系.png) 65 | 66 | ### 运行栈帧布局: 67 | #### linux程序运行内存分配 68 | ![linux程序运行内存分配图](./image/1-4-2linux程序运行内存分配图.jpg) 69 | 70 | #### C程序运行栈 71 | ![C程序运行栈](./image/1-4-2C程序运行栈.png) 72 | 73 | ### ZEND分配运行栈 74 | ```c 75 | //Zend/zend_execute.c : 2094 76 | /* 77 | * 栈帧布局,所有运行栈空间是同时分配的 / Stack Frame Layout (the whole stack frame is allocated at once) 78 | * ================== 79 | * 80 | * +=========================================================+ 81 | * EG(current_execute_data) -> | zend_execute_data | 82 | * +---------------------------------------------------------+ 83 | * EX_CV_NUM(0) ---------> | VAR[0] = ARG[1] | 参数(arguments) 84 | * | ... | 85 | * | VAR[op_array->num_args-1] = ARG[N] | 86 | * | VAR[num_args] = CV[num_args] | 局部变量(remaining CVs) 87 | * | ... | 88 | * | VAR[op_array->last_var-1] = CV[last_var-1] | 89 | * | VAR[op_array->last_var] = TMP[0] | 临时变量(TMP/VARs) 90 | * | ... | 91 | * | VAR[op_array->last_var+op_array->T-1] = TMP[T] | 92 | * | ARG[N+1] (extra_args) | 其余参数(extra arguments) 93 | * | ... | 94 | * +---------------------------------------------------------+ 95 | */ 96 | ``` 97 | #### 变量类型: 98 | ```c 99 | // Zend/zend_compile.h 100 | #define IS_CONST (1<<0) //1 101 | #define IS_TMP_VAR (1<<1) //2 102 | #define IS_VAR (1<<2) //4 103 | #define IS_UNUSED (1<<3) //8 104 | #define IS_CV (1<<4) //16 105 | ``` 106 | * IS_CONST:字面量,编译时就可确定且不会改变的值,比如:$a = "hello~",其中字符串"hello~"就是常量 107 | * IS_TMP_VAR:临时变量,比如:$a = "hello~" . time(),其中"hello~" . time()的值类型就是IS_TMP_VAR,再比如:$a = "123" + $b,"123" + $b的结果类型也是IS_TMP_VAR,从这两个例子可以猜测,临时变量多是执行期间其它类型组合现生成的一个中间值,由于它是现生成的,所以把IS_TMP_VAR赋值给IS_CV变量时不会增加其引用计数 108 | * IS_VAR:PHP变量,这个很容易认为是PHP脚本里的变量,其实不是,这里PHP变量的含义可以这样理解:PHP变量是没有显式的在PHP脚本中定义的,不是直接在代码通过$var_name定义的.这个类型最常见的例子是PHP函数的返回值,再如$a[0]数组这种,它取出的值也是IS_VAR,再比如$$a这种 109 | * IS_UNUSED:表示操作数没有用 110 | * IS_CV:PHP脚本变量,即脚本里通过$var_name定义的变量,这些变量是编译阶段确定的,所以是compile variable, 111 | 112 | 113 | 除了分配execute_data的存储空间外,还分配了CV(compiled variable,即PHP变量)、TMP_VAR(临时变量,例如执行if (!$a) echo 'a';,就需要一个临时变量来存储!$a的结果)的存储空间. 114 | EX宏是用于访问execute_data的成员 115 | 116 | 117 | 118 | 堆栈的结构体,堆栈的设计跟PHP 5类似: 119 | struct _zend_vm_stack { 120 | zval *top; /* 指向堆栈的顶端 */ 121 | zval *end; /* 指向堆栈的底端 */ 122 | zend_vm_stack prev; /* 指向上一个堆栈,当前堆栈剩余空间不足时,会向内存管理器申请新的内存创建新的堆栈 */ 123 | }; 124 | 125 | 126 | #### opcode执行时使用的寄存器优化 127 | ```c 128 | define ZEND_VM_FP_GLOBAL_REG "%r14" //栈帧指针(frame pointer)寄存器 129 | define ZEND_VM_IP_GLOBAL_REG "%r15" //指令指针寄存器 130 | 131 | register const zend_op* volatile opline __asm__(ZEND_VM_IP_GLOBAL_REG); 132 | register zend_execute_data* volatile execute_data __asm__(ZEND_VM_FP_GLOBAL_REG); 133 | ``` 134 | 135 | 136 | 137 | 138 | 139 | ## 参考资料: 140 | https://yangxikun.github.io/php/2016/11/04/php-7-func-call.html 141 | https://github.com/pangudashu/php7-internal/ 142 | https://nikic.github.io/2017/04/14/PHP-7-Virtual-machine.html 143 | 144 | -------------------------------------------------------------------------------- /1-5-0:扩展开发-介绍,生命周期,骨架生成.md: -------------------------------------------------------------------------------- 1 | ## 扩展开发前言 2 | 3 | 扩展作用:重新定义PHP行为对PHP进行HACK,提供内部函数或内部类,提升执行性能等 4 | 5 | --- 6 | 7 | ### PHP扩展分类 8 | PHP中的扩展分为两类:PHP扩展、Zend扩展,对内核而言这两个分别称之为:模块(module)、扩展(extension),我们主要介绍是PHP扩展,也就是模块. 9 | 10 | #### 加载区别: 11 | * PHP扩展(又名PHP“模块”)使用“extension = test.so”行加载到INI文件中 12 | * Zend扩展使用“zend_extension = test.so”行加载到INI文件中 13 | 14 | Zend扩展比PHP扩展更复杂,因为它们有更多的钩子,而且更接近Zend引擎及其虚拟机(整个PHP源代码中最复杂的部分). 15 | Zend扩展例子如:OPCache,XDebug,phpdbg,Zend扩展通常用来处理两种任务:调试器和剖析器.如果您的目标是“只是”向PHP 添加一些新概念(函数,类,常量等),那么您将使用PHP扩展,但如果需要更改PHP的当前行为,可能Zend扩展将会更好. 16 | 17 | ## PHP扩展生命周期 18 | ![PHP扩展生命周期](./image/1-5-0扩展的生命周期.png) 19 | 20 | ```code 21 | /* Zend扩展结构 -- zend_extension.h */ 22 | struct _zend_extension { 23 | ... /* 扩展基础信息 */ 24 | startup_func_t startup; // STARTUP() */ 25 | shutdown_func_t shutdown; // SHUTDOWN() 模块关闭 */ 26 | activate_func_t activate; // ACTIVE() 请求启动 */ */ 27 | deactivate_func_t deactivate; // DEACTIVATE() 请求关闭 */ 28 | message_handler_func_t message_handler; // MESSAGE_HANDLER() 在扩展注册后调用 */ 29 | op_array_handler_func_t op_array_handler; //在脚本编译后(zend compilation)后调用的钩子函数 */ 30 | statement_handler_func_t statement_handler; /* */ 31 | fcall_begin_handler_func_t fcall_begin_handler; /* 在处理opcode时调用 */ 32 | fcall_end_handler_func_t fcall_end_handler; /* */ 33 | op_array_ctor_func_t op_array_ctor; /* 构造OPArray时调用 */ 34 | op_array_dtor_func_t op_array_dtor; /* 销毁OPArray时调用 */ 35 | int (*api_no_check)(int api_no); /* API_NO_CHECK() 用来检测扩展是否兼容 */ 36 | int (*build_id_check)(const char* build_id); /* BUILD_ID_CHECK() */ 37 | ... 38 | DL_HANDLE handle; /* dlopen()返回句柄 */ 39 | int resource_number; /* 用于管理该扩展名的内部编号 */ 40 | }; 41 | /* php扩展(模块)结构 -- zend_modules.h */ 42 | struct _zend_module_entry { 43 | ... 44 | int (*module_startup_func)(INIT_FUNC_ARGS); /* MINIT() 模块初始化回调函数,通过PHP_MINIT_FUNCTION()或ZEND_MINIT_FUNCTION()宏完成定义 */ 45 | int (*module_shutdown_func)(SHUTDOWN_FUNC_ARGS); /* MSHUTDOWN() 模块关闭阶段回调的函数.,通过PHP_MSHUTDOWN_FUNCTION()或ZEND_MSHUTDOWN_FUNCTION()定义, */ 46 | int (*request_startup_func)(INIT_FUNC_ARGS); /* RINIT() 请求开始前回调函数,通过PHP_RINIT_FUNCTION()或ZEND_RINIT_FUNCTION()宏定义. */ 47 | int (*request_shutdown_func)(SHUTDOWN_FUNC_ARGS); /* RSHUTDOWN() 请求结束时回调函数,通过PHP_RSHUTDOWN_FUNCTION()或ZEND_RSHUTDOWN_FUNCTION()宏定义 */ 48 | void (*info_func)(ZEND_MODULE_INFO_FUNC_ARGS); /* PHPINFO() php_info展示的扩展信息处理函数,调用phpinfo()时触发此函数 */ 49 | ... 50 | void (*globals_ctor)(void *global); /* GINIT() This funtion is called to initialize a module's globals before any module_startup_func. */ 51 | void (*globals_dtor)(void *global); /* GSHUTDOWN() This funtion is called to deallocate a module's globals after any module_shutdown_func. */ 52 | int (*post_deactivate_func)(void); /* PRSHUTDOWN() 晚于RSHUTDOWN调用, post-RSHUTDOWN function */ 53 | ... 54 | }; 55 | ``` 56 | module_startup/module_shutdown:可用来注册/销毁类,全局变量,INI配置,常量 57 | request_startup/request_shutdown:可用来注册/销毁特定于每个请求的变量 58 | 59 | 混合扩展可以注册为zend扩展或php模块后,在启动函数(startup或minit)中再注册另一个结构. 60 | ```c 61 | #include "php.h" 62 | #include "Zend/zend_extensions.h" 63 | #include "php_pib.h" 64 | 65 | #define PRINT(what) fprintf(stderr, what "\n"); 66 | 67 | /* Declared as static, thus private */ 68 | static zend_module_entry pib_module_entry = { 69 | STANDARD_MODULE_HEADER, 70 | "pib", 71 | NULL, /* Function entries */ 72 | PHP_MINIT(pib), /* Module init */ 73 | PHP_MSHUTDOWN(pib), /* Module shutdown */ 74 | PHP_RINIT(pib), /* Request init */ 75 | PHP_RSHUTDOWN(pib), /* Request shutdown */ 76 | NULL, /* Module information */ 77 | "0.1", /* Replace with version number for your extension */ 78 | STANDARD_MODULE_PROPERTIES 79 | }; 80 | 81 | /* This line should stay commented 82 | ZEND_GET_MODULE(pib) 83 | */ 84 | 85 | zend_extension_version_info extension_version_info = { 86 | ZEND_EXTENSION_API_NO, 87 | ZEND_EXTENSION_BUILD_ID 88 | }; 89 | 90 | zend_extension zend_extension_entry = { 91 | "pib-zend-extension", 92 | "1.0", 93 | "PHPInternalsBook Authors", 94 | "http://www.phpinternalsbook.com", 95 | "Our Copyright", 96 | pib_zend_extension_startup, 97 | pib_zend_extension_shutdown, 98 | pib_zend_extension_activate, 99 | pib_zend_extension_deactivate, 100 | NULL, 101 | NULL, 102 | NULL, 103 | NULL, 104 | NULL, 105 | NULL, 106 | NULL, 107 | 108 | STANDARD_ZEND_EXTENSION_PROPERTIES 109 | }; 110 | 111 | static void pib_zend_extension_activate(void) 112 | { 113 | PRINT("Zend extension new request starting up"); 114 | } 115 | 116 | static void pib_zend_extension_deactivate(void) 117 | { 118 | PRINT("Zend extension current request is shutting down"); 119 | } 120 | 121 | static int pib_zend_extension_startup(zend_extension *ext) 122 | { 123 | PRINT("Zend extension is starting up"); 124 | 125 | /* When the Zend extension part will startup(), make it register 126 | a PHP extension by calling ourselves zend_startup_module() */ 127 | return zend_startup_module(&pib_module_entry); 128 | } 129 | 130 | static void pib_zend_extension_shutdown(zend_extension *ext) 131 | { 132 | PRINT("Zend extension is shutting down"); 133 | } 134 | 135 | static PHP_MINIT_FUNCTION(pib) 136 | { 137 | PRINT("PHP extension is starting up"); 138 | 139 | return SUCCESS; 140 | } 141 | 142 | static PHP_MSHUTDOWN_FUNCTION(pib) 143 | { 144 | PRINT("PHP extension is shutting down"); 145 | 146 | return SUCCESS; 147 | } 148 | 149 | static PHP_RINIT_FUNCTION(pib) 150 | { 151 | PRINT("PHP extension new request starting up"); 152 | 153 | return SUCCESS; 154 | } 155 | 156 | static PHP_RSHUTDOWN_FUNCTION(pib) 157 | { 158 | PRINT("PHP extension current request is shutting down"); 159 | 160 | return SUCCESS; 161 | } 162 | ``` 163 | 164 | #### zend引擎可修改的全局变量函数指针 165 | ```c 166 | /* AST, Zend/zend_ast.h: */ 167 | void (*zend_ast_process_t)(zend_ast *ast) 168 | /* Compiler, Zend/zend_compile.h: */ 169 | zend_op_array *(*zend_compile_file)(zend_file_handle *file_handle, int type) 170 | zend_op_array *(*zend_compile_string)(zval *source_string, char *filename) 171 | /* Executor, Zend/zend_execute.h: */ 172 | void (*zend_execute_ex)(zend_execute_data *execute_data) 173 | void (*zend_execute_internal)(zend_execute_data *execute_data, zval *return_value) 174 | /* GC, Zend/zend_gc.h: */ 175 | int (*gc_collect_cycles)(void) 176 | /* TSRM, TSRM/TSRM.h: */ 177 | void (*tsrm_thread_begin_func_t)(THREAD_T thread_id) 178 | void (*tsrm_thread_end_func_t)(THREAD_T thread_id) 179 | /* Error, Zend/zend.h: */ 180 | void (*zend_error_cb)(int type, const char *error_filename, const uint error_lineno, const char *format, va_list args) 181 | /* Exceptions, Zend/zend_exceptions.h: */ 182 | void (*zend_throw_exception_hook)(zval *ex) 183 | /* Lifetime, Zend/zend.h: */ 184 | void (*zend_on_timeout)(int seconds) 185 | void (*zend_interrupt_function)(zend_execute_data *execute_data) 186 | void (*zend_ticks_function)(int ticks) 187 | ``` 188 | ## 编码规则 189 | php-src/CODING_STANDARDS 190 | * 用来区别函数行为的数值,除了0,1外,尽量定义为常量. 191 | * 用PHP封装好的函数(emalloc(), efree(), estrdup())分配内存. 192 | * 变量名和函数名为小写字母加下划线组成,尽量短但不要用缩写. 193 | * 如果是同一父集下的函数,使用parent_*加前缀的命名方法,如(file_get_contents,file_put_contents). 194 | * 类名和类中的方法使用驼峰命名法命名,类名首字母大写,类的方法首字母小写(如 FooBar->getData() ). 195 | * 不要使用`// ...`风格的注释,使用`/* ... */`格式的注释。 196 | * 使用tab缩进(四个空格的空间). 197 | * 变量声明和语句块之间留一个空行, 逻辑语句块之间也要有空行, 函数与函数之间留一到两个空行. 198 | * 预处理语句(例如 #if)必须写在第一列,如果要缩进预处理语句也要把 # 号放在一行的开始, 紧接着是任意数量的空格 199 | 200 | ## 编译安装PHP 201 | ```shell 202 | make clean 203 | ./configure --prefix=/home/ll/workspace/C/php-7-1-8-install --disable-all --enable-cli --enable-debug 204 | make -j4 205 | make install 206 | ``` 207 | 208 | #### TODO::扩展的加载,和钩子函数的调用过程 209 | 210 | 211 | ## 编写PHP扩展(模块)的步骤: 212 | 1. 通过ext目录下ext_skel脚本生成扩展的基本框架:./ext_skel --extname; 213 | 2. 修改config.m4配置:设置编译配置参数、设置扩展的源文件、依赖库/函数检查等等; 214 | 3. 编写扩展要实现的功能:按照PHP扩展的格式以及PHP提供的API编写功能; 215 | 4. 生成configure:扩展编写完成后执行phpize脚本生成configure及其它配置文件; 216 | 5. 编译&安装:./configure、make、make install,然后将扩展的.so路径添加到php.ini中. 217 | 218 | `![PHP扩展的开发过程](./image/1-5-0PHP扩展的开发过程.jpg)` 219 | 220 | ## PHP扩展(模块)生成和编译 221 | 骨架生成器脚本位于php-src/ext/ext_skel中,其使用的模板存储在 php-src/ext/skeleton 222 | ### 基本用法 223 | ```code 224 | > php-src/ext/ext_skel 225 | ./ext_skel --extname=module [--proto=file] [--stubs=file] [--xml[=file]] 226 | [--skel=dir] [--full-xml] [--no-help] 227 | 228 | --extname=module module为你的扩展名称 (module is the name of your extension) 229 | --proto=file 从file原型文件创建一组PHP函数,方便开发基于库的扩展.(file contains prototypes of functions to create) 230 | --stubs=file generate only function stubs in file 231 | --xml generate xml documentation to be added to phpdoc-cvs 232 | --skel=dir 用于指定用一套修改过的框架文件来工作.(path to the skeleton directory) 233 | --full-xml generate xml documentation for a self-contained extension 234 | (not yet implemented) 235 | --no-help 指定此参数会造成 ext_skel 会在生成的文件里省略很多有用的注释。do not try to be nice and create comments in the code 236 | and helper functions to test if the module compiled 237 | 238 | > php-src/ext/ext_skel --extname=pib 239 | > cd php-src/ext 240 | > tree pib/ 241 | pib/ 242 | ├── config.m4 //UNIX 构建系统配置 243 | ├── config.w32 //Windows 构建系统配置 244 | ├── CREDITS //扩展描述文件,包含扩展名,开发者信息。默认生成时只带有扩展名 245 | ├── EXPERIMENTAL //实验功能说明 246 | ├── php_pib.h //包含附加的宏、原型和全局量 247 | ├── pib.c //扩展源文件 248 | ├── pib.php //测试文件 249 | └── tests //测试脚本目录 250 | └── 001.phpt //测试脚本。测试方法:php ../../run-tests.php ./pib/001.phpt (run-tests.php文件存在php源码根目录) 251 | ``` 252 | 253 | #### proto原型文件 254 | 255 | 原型文件有点类似 C 头文件,根据其中申明的函数,生成函数骨架代码和其他相关代码。如 pib.proto,内容为 `string pib_hello_world (string name)` 256 | 257 | 原型文件的格式,类似于 C 头文件中的函数申明的方式,返回值、函数名、形参类型、形参名。 参数用 () 包裹,多个参数以 , 分隔,函数申明末尾不需要以 ; 结尾,一行一个函数声明。 258 | 259 | 原型文件的生成依赖于 awk 脚本 ext/skeleton/create_stubs ,由其中 convert 函数可知,其支持的参数类型有 260 | ```code 261 | int,long 262 | bool,boolean 263 | double,float 264 | string 265 | array,object,mixed 266 | resource,handle 267 | ``` 268 | 269 | 270 | 271 | ## 修改config.m4 272 | config.m4是扩展的编译配置文件,它被include到configure.in文件中,最终被autoconf编译为configure,编写扩展时我们只需要在config.m4中修改配置即可. 273 | 274 | 首先修改 config.m4 ,去掉 PHP_ARG_ENABLE 和 --enable-pib 这两行前面的 dnl,dnl 是注释符号。修改后如下 275 | ```code 276 | PHP_ARG_ENABLE(pib, whether to enable pib support, 277 | dnl Make sure that the comment is aligned: 278 | [ --enable-pib Enable pib support]) 279 | ``` 280 | PHP_ARG_ENABLE函数第一参数表示扩展名,第二个和第三个在生成configure时的一些提示信息。--enable-extname表示不依赖第三方库,而--with-extname表示需要第三方库。 281 | 282 | PHP_NEW_EXTENSION(pib, pib.c, $ext_shared,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1)函数声明了这个扩展的名称、需要的源文件名、此扩展的编译形式。 283 | 如果是多个文件的话,就在第二个参数后加空格再填写你的源文件地址,需要换行的话记得反斜杠\。 284 | 285 | ### config.m4常用宏 286 | PHP在acinclude.m4中基于autoconf/automake的宏封装了很多可以直接使用的宏,下面介绍几个比较常用的宏: 287 | * `PHP_ARG_WITH(arg_name,check message,help info)`: 定义一个--with-feature[=arg]这样的编译参数,调用的是autoconf的AC_ARG_WITH,这个宏有5个参数,常用的是前三个,分别表示:参数名、执行./configure是展示信息、执行--help时展示信息,第4个参数为默认值,如果不定义默认为"no",通过这个宏定义的参数可以在config.m4中通过$PHP_参数名(大写)访问.比如:PHP_ARG_WITH(aaa, aaa-configure, help aa)后面通过$PHP_AAA就可以读取到--with-aaa=xxx设置的值了 288 | * `PHP_ARG_ENABLE(arg_name,check message,help info)`: 定义一个--enable-feature[=arg]或--disable-feature参数,--disable-feature等价于--enable-feature=no,这个宏与PHP_ARG_WITH类似,通常情况下如果配置的参数需要额外的arg值会使用PHP_ARG_WITH,而如果不需要arg值,只用于开关配置则会使用PHP_ARG_ENABLE. 289 | * `AC_MSG_CHECKING()/AC_MSG_RESULT()/AC_MSG_ERROR()`: ./configure时输出结果,其中error将会中断configure执行. 290 | * `AC_DEFINE(variable, value, [description])`: 定义一个宏,比如:AC_DEFINE(IS_DEBUG, 1, []),执行autoheader时将在头文件中生成:#define IS_DEBUG 1. 291 | * `PHP_ADD_INCLUDE(path)`: 添加include路径,即:gcc -Iinclude_dir,#include "file";将先在通过-I指定的目录下查找,扩展引用了外部库或者扩展下分了多个目录的情况下会用到这个宏. 292 | * `PHP_CHECK_LIBRARY(library, function [, action-found [, action-not-found [, extra-libs]]])`: 检查依赖的库中是否存在需要的function,action-found为存在时执行的动作,action-not-found为不存在时执行的动作,比如扩展里使用到线程pthread,检查pthread_create(),如果没找到则终止./configure执行: 293 | ```code 294 | PHP_ADD_INCLUDE(pthread, pthread_create, [], [ 295 | AC_MSG_ERROR([not find pthread_create() in lib pthread]) 296 | ]) 297 | ``` 298 | * `AC_CHECK_FUNC(function, [action-if-found], [action-if-not-found])`: 检查函数是否存在. (8)PHP_ADD_LIBRARY_WITH_PATH($LIBNAME, $XXX_DIR/$PHP_LIBDIR, XXX_SHARED_LIBADD): 添加链接库. 299 | * `PHP_NEW_EXTENSION(extname, sources [, shared [, sapi_class [, extra-cflags [, cxx [, zend_ext]]]]])`: 注册一个扩展,添加扩展源文件,确定此扩展是动态库还是静态库,每个扩展的config.m4中都需要通过这个宏完成扩展的编译配置. 300 | 301 | 更多autoconf及PHP封装的宏大家可以在用到的时候再自行检索,同时ext目录下有大量的示例可供参考. 302 | [参考链接](https://secure.php.net/manual/en/internals2.buildsys.configunix.php) 303 | 304 | 305 | 306 | 307 | 308 | ## 编译安装测试扩展 309 | ```shell 310 | cd ext/pib 311 | ../../../php-7-1-8-install/bin/phpize 312 | ./configure --with-php-config=../../../php-7-1-8-install/bin/php-config 313 | make 314 | 315 | cd ../../sapi/cli 316 | ./php -d extension='../../ext/pib/modules/pib.so' -r "echo confirm_pib_compiled('hello world');" 317 | ``` 318 | 319 | 在扩展目录(ext/pib/)中,phpize会根据config.m4生一个configure文件. 320 | 321 | 执行./configure后会生makefiles文件,再执行make . 然后扩展中modules目录下会发现一个pib.so文件 . 322 | 323 | make install 会将扩展复制到PHP安装路径的扩展目录当中 。 324 | 325 | 测试会输出Congratulations! You have successfully modified ext/pib/config.m4. Module hello world is now compiled into PHP. 326 | 327 | 328 | ### 发布前执行 329 | `phpize --clean` 330 | 331 | 332 | 333 | 334 | 335 | ## 参考资料: 336 | http://www.phpinternalsbook.com/index.html 337 | https://github.com/pangudashu/php7-internal/ 338 | https://secure.php.net/manual/en/internals2.structure.php 339 | http://www.laruence.com/2009/04/28/719.html 340 | https://andot.gitbooks.io/bped/c02s03.html 341 | https://github.com/lxy254069025/php-extension-book 342 | http://php.net/manual/zh/internals2.buildsys.configunix.php 343 | http://www.php-internals.com/book/?p=chapt11/11-02-00-extension-hello-world 344 | 345 | 346 | -------------------------------------------------------------------------------- /1-5-1:扩展开发-结构,全局变量,ini配置,函数定义.md: -------------------------------------------------------------------------------- 1 | ## PHP扩展(模块)结构 2 | 3 | 4 | 5 | ### zend_module_struct 6 | 扩展首先需要创建一个zend_module_entry结构,这个变量必须是全局变量,且变量名必须是:扩展名称_module_entry,内核通过这个结构得到这个扩展都提供了哪些功能 7 | ```c 8 | /* zend_modules.h */ 9 | /* 参考:https://secure.php.net/manual/en/internals2.structure.modstruct.php */ 10 | struct _zend_module_entry { 11 | unsigned short size; /* sizeof(zend_module_entry) */ 12 | unsigned int zend_api; /* ZEND_MODULE_API_NO */ /* STANDARD_MODULE_HEADER */ 13 | unsigned char zend_debug; /* 是否开启debug */ 14 | unsigned char zts; /* 是否开启线程安全 */ 15 | const struct _zend_ini_entry *ini_entry; /* 未使用 */ 16 | const struct _zend_module_dep *deps; /* 扩展依赖 */ 17 | 18 | const char *name; /* 扩展名称,不能重复 */ 19 | const struct _zend_function_entry *functions; /* 扩展提供的内部函数列表 */ 20 | int (*module_startup_func)(INIT_FUNC_ARGS); /* 扩展初始化回调函数,PHP_MINIT_FUNCTION或ZEND_MINIT_FUNCTION定义的函数 */ 21 | int (*module_shutdown_func)(SHUTDOWN_FUNC_ARGS); /* 扩展关闭时回调函数 */ 22 | int (*request_startup_func)(INIT_FUNC_ARGS); /* 请求开始前回调函数 */ 23 | int (*request_shutdown_func)(SHUTDOWN_FUNC_ARGS); /* 请求结束时回调函数 */ 24 | void (*info_func)(ZEND_MODULE_INFO_FUNC_ARGS); /* php_info展示的扩展信息处理函数 */ 25 | const char *version; /* 版本 */ 26 | 27 | //STANDARD_MODULE_PROPERTIES 28 | size_t globals_size; /* */ 29 | #ifdef ZTS /* */ 30 | ts_rsrc_id* globals_id_ptr; /* */ 31 | #else /* */ /* Globals management */ 32 | void* globals_ptr; /* */ 33 | #endif /* */ 34 | void (*globals_ctor)(void *global); /* */ 35 | void (*globals_dtor)(void *global); /* */ /* */ 36 | int (*post_deactivate_func)(void); /* Rarely used lifetime hook */ 37 | int module_started; /* 扩展已启动(内部使用) */ 38 | unsigned char type; /* 扩展类型(内部使用) */ /* STANDARD_MODULE_PROPERTIES_EX */ 39 | void *handle; /* dlopen()返回句柄 */ 40 | int module_number; /* 扩展的唯一编号 */ 41 | const char *build_id; /* build id,STANDARD_MODULE_PROPERTIES_EX */ 42 | }; 43 | ``` 44 | ```c 45 | zend_module_entry pib_module_entry = { 46 | STANDARD_MODULE_HEADER, 47 | "pib", 48 | pib_functions, 49 | PHP_MINIT(pib), 50 | PHP_MSHUTDOWN(pib), 51 | PHP_RINIT(pib), /* Replace with NULL if there's nothing to do at request start */ 52 | PHP_RSHUTDOWN(pib), /* Replace with NULL if there's nothing to do at request end */ 53 | PHP_MINFO(pib), 54 | PHP_PIB_VERSION, 55 | STANDARD_MODULE_PROPERTIES 56 | }; 57 | ``` 58 | 59 | 60 | ### 发布 61 | PHP编译时默认将所有符号隐藏,所以要获取扩展必须使用ZEND_GET_MODULE(your_ext)宏,会在头文件中有定义`PHP__API`. 62 | 63 | PHP在启动时会调用每个扩展中的get_module()函数,函数在头主文件`php_.c`(这里是 pib.c)中 64 | ```c 65 | #ifdef COMPILE_DL_PIB 66 | #ifdef ZTS 67 | ZEND_TSRMLS_CACHE_DEFINE() 68 | #endif 69 | ZEND_GET_MODULE(pib) 70 | #endif 71 | ``` 72 | 模块符号是在这里发布,当编译时加上`–enable-`时,`COMPILE_DL_`将被定义.模块被加载. 73 | 74 | ### 发布API 75 | 在头文件`php_.h`(这里是 php_pib.h)中 76 | ```c 77 | #ifdef PHP_WIN32 78 | # define PHP_PIB_API __declspec(dllexport) 79 | #elif defined(__GNUC__) && __GNUC__ >= 4 80 | # define PHP_PIB_API __attribute__ ((visibility("default"))) 81 | #else 82 | # define PHP_PIB_API 83 | #endif 84 | ``` 85 | 使用`PHP__API` (这里是PHP_PIB_API)宏将设置为公开,使其在其它扩展中可用. 86 | 87 | 88 | 89 | ## 定义全局变量 90 | ```c 91 | /* php_pib.h */ 92 | #define PIB_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(pib, v) /* 定义一个宏用于访问扩展的全局资源结构体 */ 93 | ZEND_BEGIN_MODULE_GLOBALS(pib)/* 展开后实际就是个普通的struct声明 */ 94 | zend_long open_cache; 95 | HashTable class_table; 96 | ZEND_END_MODULE_GLOBALS(pib) 97 | /* pib.c */ 98 | ZEND_DECLARE_MODULE_GLOBALS(pib) /* 创建一个此结构体的全局变量,会根据是否开启线程安全采用不同方式 */ 99 | ``` 100 | 接下来就可以在扩展中通过:PIB_G(opene_cache)、PIB_G(class_table)对结构体成员进行读写了. 101 | 102 | 103 | 104 | ## 注册函数 105 | 106 | ### zend_function_entry用于在扩展中注册函数到zend引擎 107 | 由扩展名声明的函数称为“内部”函数,与PHP用户定义的函​​数相反,内部函数在当前请求结束时不会反注册直到PHP退出. 108 | ```c 109 | #define INTERNAL_FUNCTION_PARAMETERS zend_execute_data *execute_data, zval *return_value 110 | typedef struct _zend_function_entry { 111 | const char *fname; /* 函数名 */ 112 | void (*handler)(INTERNAL_FUNCTION_PARAMETERS); /* 处理程序 */ 113 | const struct _zend_internal_arg_info *arg_info; /* 参数,推荐使用宏定义. */ 114 | uint32_t num_args; /* 参数个数 */ 115 | uint32_t flags; /* 函数标记 */ 116 | } zend_function_entry; 117 | struct _zend_module_entry { 118 | ... 119 | const struct _zend_function_entry *functions; /* 函数定义声明 */ 120 | ... 121 | /* ... */ 122 | }; 123 | ``` 124 | 125 | ### 一个简单声明定义示例 126 | ```c 127 | /* pib.c */ 128 | PHP_FUNCTION(fahrenheit_to_celsius) 129 | { 130 | } 131 | static const zend_function_entry pib_functions[] = 132 | { 133 | PHP_FE(fahrenheit_to_celsius, NULL) 134 | PHP_FE_END /* 末尾必须加这个 */ 135 | } 136 | zend_module_entry pib_module_entry = { 137 | STANDARD_MODULE_HEADER, 138 | "pib", 139 | pib_functions, 140 | NULL, 141 | NULL, 142 | NULL, 143 | NULL, 144 | NULL, 145 | "0.1", 146 | STANDARD_MODULE_PROPERTIES 147 | }; 148 | /* 函数定义声明宏展开为: */ 149 | void zif_fahrenheit_to_celsius(zend_execute_data *execute_data, zval *return_value) 150 | { 151 | } 152 | static const zend_function_entry pib_functions[] = 153 | { 154 | { "fahrenheit_to_celsius", zif_fahrenheit_to_celsius, ((void *)0), 155 | (uint32_t) (sizeof(((void *)0))/sizeof(struct _zend_internal_arg_info)-1), 0 }, 156 | } 157 | /* 展开后的函数句加上了'zif'前缀,代表 Zend内部函数,以防止名称冲突. */ 158 | ``` 159 | 160 | 通过使用PHP_FUNCTION()宏来定义函数,同时在php_pib.h头文件里声明这个函数: 161 | ```c 162 | /* pib.h */ 163 | PHP_FUNCTION (fahrenheit_to_celsius); 164 | ``` 165 | 166 | 查看扩展的反射信息 167 | ```shell 168 | /php/bin/php -dextension=pib.so --re pib 169 | ``` 170 | 171 | 172 | 173 | ### 函数参数和返回值 174 | 175 | #### 参数声明 176 | 177 | 参数声明不是强制性的,但高度推荐.反射API(reflection API)使用参数声明来获取有关该功能的信息. 178 | 179 | 参数声明的数据结构 180 | ```c 181 | typedef struct _zend_internal_arg_info { 182 | const char *name; /* 参数名 */ 183 | const char *class_name; 184 | zend_uchar type_hint; /* 显式声明的类型 */ 185 | zend_uchar pass_by_reference; /* 是否引用传参 */ 186 | zend_bool allow_null; /* 是否允许参数为NULL,类似"!"的用法 */ 187 | zend_bool is_variadic; /* 是否为可变参数 */ 188 | } zend_internal_arg_info; 189 | 190 | /* 使用宏定义参数(如果你不知道如何命名参数矢量符号,一个实践是使用 'arginfo_[function name]'模式): */ 191 | ZEND_BEGIN_ARG_INFO_EX(arginfo_fahrenheit_to_celsius, 0, 0, 1) 192 | ZEND_ARG_INFO(0, fahrenheit) 193 | ZEND_END_ARG_INFO(); 194 | 195 | /* 宏展开后 */ 196 | static const zend_internal_arg_info arginfo_fahrenheit_to_celsius[] = { 197 | { (const char*)(zend_uintptr_t)(1), ((void *)0), 0, 0, 0, 0 }, 198 | { "fahrenheit", ((void *)0), 0, 0, 0, 0 }, 199 | }; 200 | 201 | /* 声明参数相关宏,宏定义位置:Zend/zend_API.h: */ 202 | /* 203 | name: 参数数组名,注册函数PHP_FE(function, arg_info)会用到 204 | _unused: 保留值,暂时无用 205 | return_reference: 返回值是否为引用,一般很少会用到 206 | required_num_args: required参数数 207 | */ 208 | ZEND_BEGIN_ARG_INFO_EX(name, _unused, return_reference, required_num_args); 209 | 210 | /* pass_by_ref表示是否引用传参,name为参数名称 */ 211 | #define ZEND_ARG_INFO(pass_by_ref, name) { #name, NULL, 0, pass_by_ref, 0, 0 }, 212 | 213 | /* 只声明此参数为引用传参 */ 214 | #define ZEND_ARG_PASS_INFO(pass_by_ref) { NULL, NULL, 0, pass_by_ref, 0, 0 }, 215 | /* 显式声明此参数的类型为指定类的对象,等价于PHP中这样声明:MyClass $obj */ 216 | #define ZEND_ARG_OBJ_INFO(pass_by_ref, name, classname, allow_null) { #name, #classname, IS_OBJECT, pass_by_ref, allow_null, 0 }, 217 | /* 显式声明此参数类型为数组,等价于:array $arr */ 218 | #define ZEND_ARG_ARRAY_INFO(pass_by_ref, name, allow_null) { #name, NULL, IS_ARRAY, pass_by_ref, allow_null, 0 }, 219 | /* 显式声明为callable,将检查函数、成员方法是否可调 */ 220 | #define ZEND_ARG_CALLABLE_INFO(pass_by_ref, name, allow_null) { #name, NULL, IS_CALLABLE, pass_by_ref, allow_null, 0 }, 221 | /* 通用宏,自定义各个字段 */ 222 | #define ZEND_ARG_TYPE_INFO(pass_by_ref, name, type_hint, allow_null) { #name, NULL, type_hint, pass_by_ref, allow_null, 0 }, 223 | /* 声明为可变参数 */ 224 | #define ZEND_ARG_VARIADIC_INFO(pass_by_ref, name) { #name, NULL, 0, pass_by_ref, 0, 1 }, 225 | 226 | ``` 227 | 228 | ```c 229 | /* 这个参数数组声明完成后,我们需要将arginfo_fahrenheit_to_celsius添加到函数声明当中 */ 230 | PHP_FE(fahrenheit_to_celsius, arginfo_fahrenheit_to_celsius) 231 | ``` 232 | 233 | #### 解析参数:zend_parse_parameters() 234 | 235 | zend_parse_parameters()是将参数读取到Zend引擎栈的函数.你会告诉你要读取多少参数,以及你想要为你提供什么样的类型.该函数将根据PHP类型转换规则将参数转换为您要求的类型. 236 | 237 | 如果你使用zend_parse_parameters来接收参数,那么你是不需要ZEND_BEGIN_ARG_INFO_EX宏来进行入参数校验的。但如果使用的是zend_get_parameters那么就需要zend帮你自动校验了。记得在PHP_FE中加入校验名。 238 | 239 | 函数参数是从PHP用户空间传到函数的,它们与用户自定义函数完全相同,包括参数的分配方式、传参过程,也是按照参数次序依次分配在运行栈帧上,所以在扩展中定义的函数直接按照顺序从运行栈帧上读取对应的值即可,PHP中通过zend_parse_parameters()这个函数解析运行栈帧上保存的参数 240 | 241 | `zend_parse_parameters(int num_args, const char *type_spec, ...);` 242 | 243 | 第一个参数是给出运行时的参数数,通过ZEND_NUM_ARGS()获取:zend_execute_data->This.u2.num_args,前面曾介绍过zend_execute_data->This这个zval的用途. 244 | 245 | 然后我们传递一个“d”字符,表示要接收的每一个参数类型为double类型.zend引擎将读取到的参数写入到变量c当中. 246 | 247 | 解析参数时传递的字符规则在源码目录里的README.PARAMETER_PARSING_API文件中. 248 | 249 | 解析的过程就是按照type_spec指定的各个类型,依次从zend_execute_data上获取参数,然后将参数地址赋给目标变量. 250 | ```c 251 | static double php_fahrenheit_to_celsius(double f) 252 | { 253 | return ((double)5/9) * (double)(f - 32); 254 | } 255 | 256 | PHP_FUNCTION(fahrenheit_to_celsius) 257 | { 258 | double f; 259 | /* 在php7中,提供另一种获取参数的方式FAST_ZPP(Fast zend parameter parsing),是为了提高参数解析的性能。 */ 260 | #ifdef FAST_ZPP 261 | ZEND_PARSE_PARAMETERS_START(1, 1) 262 | Z_PARAM_DOUBLE(f) /* zend_API.h */ 263 | ZEND_PARSE_PARAMETERS_END(); 264 | #else 265 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "d", &f) == FAILURE) { 266 | return; 267 | } 268 | #endif 269 | /* 使用RETURN_***()宏,设置返回值并返回 */ 270 | RETURN_DOUBLE(php_fahrenheit_to_celsius(f)); /* #define RETURN_DOUBLE(d) { RETVAL_DOUBLE(d); return; } */ 271 | } 272 | ``` 273 | 274 | 275 | ##### 返回值相关宏 276 | ```c 277 | /* 返回布尔型,b:IS_FALSE、IS_TRUE */ 278 | #define RETURN_BOOL(b) { RETVAL_BOOL(b); return; } 279 | /* 返回NULL */ 280 | #define RETURN_NULL() { RETVAL_NULL(); return;} 281 | /* 返回整形,l类型:zend_long */ 282 | #define RETURN_LONG(l) { RETVAL_LONG(l); return; } 283 | /* 返回浮点值,d类型:double */ 284 | #define RETURN_DOUBLE(d) { RETVAL_DOUBLE(d); return; } 285 | /* 返回字符串,可返回内部字符串,s类型为:zend_string * */ 286 | #define RETURN_STR(s) { RETVAL_STR(s); return; } 287 | /* 返回内部字符串,这种变量将不会被回收,s类型为:zend_string * */ 288 | #define RETURN_INTERNED_STR(s) { RETVAL_INTERNED_STR(s); return; } 289 | /* 返回普通字符串,非内部字符串,s类型为:zend_string * */ 290 | #define RETURN_NEW_STR(s) { RETVAL_NEW_STR(s); return; } 291 | /* 拷贝字符串用于返回,这个会自己加引用计数,s类型为:zend_string * */ 292 | #define RETURN_STR_COPY(s) { RETVAL_STR_COPY(s); return; } 293 | /* 返回char *类型的字符串,s类型为char * */ 294 | #define RETURN_STRING(s) { RETVAL_STRING(s); return; } 295 | /* 返回char *类型的字符串,s类型为char *,l为字符串长度,类型为size_t */ 296 | #define RETURN_STRINGL(s, l) { RETVAL_STRINGL(s, l); return; } 297 | /* 返回空字符串 */ 298 | #define RETURN_EMPTY_STRING() { RETVAL_EMPTY_STRING(); return; } 299 | /* 返回资源,r类型:zend_resource * */ 300 | #define RETURN_RES(r) { RETVAL_RES(r); return; } 301 | /* 返回数组,r类型:zend_array * */ 302 | #define RETURN_ARR(r) { RETVAL_ARR(r); return; } 303 | /* 返回对象,r类型:zend_object * */ 304 | #define RETURN_OBJ(r) { RETVAL_OBJ(r); return; } 305 | /* 返回zval */ 306 | #define RETURN_ZVAL(zv, copy, dtor) { RETVAL_ZVAL(zv, copy, dtor); return; } 307 | /* 返回false */ 308 | #define RETURN_FALSE { RETVAL_FALSE; return; } 309 | /* 返回true */ 310 | #define RETURN_TRUE { RETVAL_TRUE; return; } 311 | ``` 312 | 313 | 314 | 315 | #### 传递引用参数,修改上面代码: 316 | ```c 317 | ZEND_BEGIN_ARG_INFO_EX(arginfo_fahrenheit_to_celsius, 0, 0, 1) 318 | /* 这里1表示通知引擎必须通过引用传递该参数 */ 319 | ZEND_ARG_INFO(1, fahrenheit) 320 | ZEND_END_ARG_INFO(); 321 | 322 | /* 当我们收到参数时,我们使用“z”参数类型,告诉我们要将其赋予zval *,接收到的zval为将是IS_REFERENCE类型,我们需要取消引用然后修改. */ 323 | PHP_FUNCTION(fahrenheit_to_celsius) 324 | { 325 | double result; 326 | zval *param; 327 | if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", ¶m) == FAILURE) { 328 | return; 329 | } 330 | ZVAL_DEREF(param); 331 | convert_to_double(param); 332 | ZVAL_DOUBLE(param, php_fahrenheit_to_celsius(Z_DVAL_P(param))); 333 | } 334 | 335 | /* a reference (by that we mean a &$php_reference) is a heap allocated zval stored into a zval container */ 336 | /* 引用(&$php_var)是在堆中分配的zval然后存储在了zval容器中. */ 337 | ``` 338 | 339 | 340 | 341 | ## ini配置 342 | 扩展中一般会把php.ini配置映射前面介绍的全局变量(资源) 343 | ```c 344 | PHP_INI_BEGIN() 345 | /* 将php.ini中的pib.opene_cache值映射到PIB_G()结构中的open_cache,类型为zend_long,默认值109,则可以这么定义: */ 346 | STD_PHP_INI_ENTRY("pib.open_cache", "109", PHP_INI_ALL, OnUpdateLong, open_cache, zend_pib_globals, pib_globals) 347 | /* 其它规则 */ 348 | ... 349 | PHP_INI_END(); 350 | /* 上面的定义展开后: */ 351 | static const zend_ini_entry_def ini_entries[] = { 352 | { 353 | "pib.open_cache", 354 | OnUpdateLong, 355 | (void *) XtOffsetOf(zend_pib_globals, open_cache), //XtOffsetOf这个宏在linux环境下展开就是offsetof(),用来获取一个结构体成员的offset 356 | (void*)&pib_globals, 357 | NULL, 358 | "109", 359 | NULL, 360 | PHP_INI_ALL, 361 | sizeof("pib.open_cache")-1, 362 | sizeof("109")-1 363 | }, 364 | { NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, 0, 0} 365 | } 366 | /* 367 | STD_PHP_INI_ENTRY(name,default_value,modifiable,on_modify,property_name,struct_type,struct_ptr) 368 | name: php.ini中的配置标识符 369 | default_value: 默认值,注意不管转化后是什么类型,这里必须设置为字符串 370 | modifiable: 可修改等级,ZEND_INI_USER为可以在php脚本中修改,ZEND_INI_SYSTEM为可以在php.ini中修改,还有一个ZEND_INI_PERDIR,ZEND_INI_ALL表示三种都可以,通常情况下设置为ZEND_INI_ALL、ZEND_INI_SYSTEM即可 371 | on_modify: 函数指针,用于指定发现这个配置后赋值处理的函数,默认提供了5个:OnUpdateBool、OnUpdateLong、OnUpdateLongGEZero、OnUpdateReal、OnUpdateString、OnUpdateStringUnempty,支持可以自定义 372 | property_name: 要映射到的结构struct_type中的成员 373 | struct_type: 映射结构的类型 374 | struct_ptr: 映射结构的变量地址 375 | 376 | 这个宏展开后生成一个zend_ini_entry_def结构: 377 | typedef struct _zend_ini_entry_def { 378 | const char *name; 379 | int (*on_modify)(zend_ini_entry *entry, zend_string *new_value, void *mh_arg1, void *mh_arg2, void *mh_arg3, int stage); 380 | void *mh_arg1; //映射成员所在结构体的偏移:offsetof(type, member-designator)取到 381 | void *mh_arg2; //要映射到结构的地址 382 | void *mh_arg3; 383 | const char *value;//默认值 384 | void (*displayer)(zend_ini_entry *ini_entry, int type); 385 | int modifiable; 386 | uint name_length; 387 | uint value_length; 388 | } zend_ini_entry_def; 389 | */ 390 | ``` 391 | 392 | #### 将ini配置注册到全局变量: 393 | ```c 394 | /* php_pib.h */ 395 | #define PIB_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(pib, v) 396 | ZEND_BEGIN_MODULE_GLOBALS(pib) 397 | zend_long open_cache; 398 | ZEND_END_MODULE_GLOBALS(pib) 399 | 400 | /* pib.c */ 401 | ZEND_DECLARE_MODULE_GLOBALS(pib) 402 | 403 | PHP_INI_BEGIN() 404 | STD_PHP_INI_ENTRY("pib.open_cache", "109", PHP_INI_ALL, OnUpdateLong, open_cache, zend_pib_globals, pib_globals) 405 | PHP_INI_END(); 406 | 407 | PHP_MINIT_FUNCTION(pib) 408 | { 409 | REGISTER_INI_ENTRIES(); 410 | return SUCCESS; 411 | } 412 | 413 | PHP_MSHUTDOWN_FUNCTION(pib) 414 | { 415 | UNREGISTER_INI_ENTRIES(); 416 | return SUCCESS; 417 | } 418 | 419 | zend_module_entry pib_module_entry = { 420 | STANDARD_MODULE_HEADER, 421 | "pib", 422 | NULL,//pib_functions, 423 | PHP_MINIT(pib), 424 | PHP_MSHUTDOWN(pib), 425 | NULL,//PHP_RINIT(pib), 426 | NULL,//PHP_RSHUTDOWN(pib), 427 | NULL,//PHP_MINFO(pib), 428 | "1.0.0", 429 | STANDARD_MODULE_PROPERTIES 430 | }; 431 | 432 | ``` 433 | #### ini声明和读取: 434 | ```c 435 | /* hello.c */ 436 | PHP_INI_BEGIN() 437 | PHP_INI_ENTRY("hello.yell", "0", PHP_INI_ALL, NULL) 438 | PHP_INI_END() 439 | 440 | PHP_MINIT_FUNCTION(hello) { 441 | REGISTER_INI_ENTRIES(); 442 | return SUCCESS; 443 | } 444 | 445 | PHP_MSHUTDOWN_FUNCTION(hello) { 446 | UNREGISTER_INI_ENTRIES(); 447 | return SUCCESS; 448 | } 449 | 450 | /* function hello(string $name): bool */ 451 | PHP_FUNCTION(hello) { 452 | int i = 0; 453 | zend_string *name; 454 | 455 | ZEND_PARSE_PARAMETERS_START(1, 1) 456 | Z_PARAM_STR(name) 457 | ZEND_PARSE_PARAMETERS_END(); 458 | 459 | if (INI_BOOL("hello.yell") == 1) { 460 | while (name->val[i]) { 461 | name->val[i] = toupper(name->val[i]); 462 | i++; 463 | } 464 | php_printf("HELLO %s!\n", ZSTR_VAL(name)); 465 | } else { 466 | php_printf("Hello %s\n", ZSTR_VAL(name)); 467 | } 468 | 469 | RETURN_TRUE; 470 | } 471 | 472 | zend_module_entry hello_module_entry = { 473 | STANDARD_MODULE_HEADER, 474 | PHP_HELLO_EXTNAME, 475 | hello_functions, /* Function entries */ 476 | PHP_MINIT(hello), /* Module init */ 477 | PHP_MSHUTDOWN(hello), /* Module shutdown */ 478 | NULL, /* Request init */ 479 | NULL, /* Request shutdown */ 480 | NULL, /* Module information */ 481 | PHP_HELLO_VERSION, 482 | STANDARD_MODULE_PROPERTIES 483 | }; 484 | 485 | ``` 486 | 487 | ## 参考资料: 488 | http://www.phpinternalsbook.com/index.html 489 | https://github.com/pangudashu/php7-internal/ 490 | https://secure.php.net/manual/en/internals2.structure.php 491 | https://secure.php.net/manual/en/internals2.funcs.php 492 | http://www.cunmou.com/phpbook/7.2.md 493 | 494 | -------------------------------------------------------------------------------- /1-5-2:扩展开发-示例,测试,练习.md: -------------------------------------------------------------------------------- 1 | ## 示例 2 | 3 | ### 用PHP实现 4 | ```php 5 | autotools project 16 | make -j4 17 | make install 18 | 19 | 在make 完以后,在sapi/cli目录里就已经有了php的可以执行文件. 执行一下命令: 20 | ./sapi/cli/php -v 21 | 22 | 〜/ php-src> ./buildconf# 只有从git构建才需要 23 | 〜/ php-src> ./configure 24 | 〜/ php-src> make -jN 25 | 对于快速构建,将N替换为可用的CPU核心数(请参阅grep “cpu cores” / proc / cpuinfo)。 26 | 27 | 要创建一个仅包含最小扩展数量的构建,请使用--disable-all选项: 28 | 有两个开关,开发扩展或PHP工作时应始终指定: 29 | --enable-debug启用调试模式,具有多重效果:编译将使用 -g运行以生成调试符号,另外使用最低优化级别 -O0。这将使PHP慢得多,但使用像 gdb这样的工具进行调试更加可预测。此外,调试模式定义了 ZEND_DEBUG宏,它将启动引擎中的各种调试助手。除其他事项外,还将报告内存泄漏以及某些数据结构的不正确使用。 30 | --enable-maintainer-zts可以实现线程安全。该开关将定义 ZTS宏,这又将使得PHP使用的整个TSRM(线程安全资源管理器)机器。为PHP编写线程安全扩展非常简单,但只有确保启用此开关。 31 | 32 | 33 | 34 | 35 | PHP源码阅读工具 36 | 使用VIM + Ctags /Eclipse 37 | 38 | "##"被称为 连接符(concatenator) 39 | "#"是一种预处理运算符,它的功能是将其后面的宏参数进行 字符串化操作,将语言符号(Token)转化为字符串 40 | 41 | 将宏定义为空, 当于从代码中删除。 42 | 43 | 44 | PG():字段很大一部分是与php.ini文件中的配置项对应的。 在PHP启动并读取php.ini文件时就会对这些字段进行赋值, 而用户空间的ini_get()及ini_set()函数操作的一些配置也是对这个全局变量进行操作的。 45 | 其他地方也存在很多类似的宏,这些宏和PG宏一样,都是为了将线程安全进行封装,同时通过约定的 G 命名来表明这是全局的 46 | 47 | 48 | 49 | 在编译过程中,函数列表、类列表等都存放在编译时的全局变量中, 在准备执行过程时,会将这些列表赋值给执行的全局变量中,如:EG(function_table) = CG(function_table); 50 | 51 | 52 | 53 | CGI:服务器监听,然后fork子进程,在子进程中popen请求的cgi程序,然后返回。 54 | 55 | FastCGI消息通讯样例:一行表示一个数据包。下面的例子来自于官网 56 | {FCGI_BEGIN_REQUEST, 1, {FCGI_RESPONDER, 0}} 57 | {FCGI_PARAMS, 1, "\013\002SERVER_PORT80\013\016SERVER_ADDR199.170.183.42 ... "} 58 | {FCGI_STDIN, 1, "quantity=100&item=3047936"} 59 | {FCGI_STDOUT, 1, "Content-type: text/html\r\n\r\n\n ... "} 60 | {FCGI_END_REQUEST, 1, {0, FCGI_REQUEST_COMPLETE}} 61 | 62 | opcode处理函数的命名是有以下规律的: 63 | ZEND_[opcode]_SPEC_(变量类型1)_(变量类型2)_HANDLER 64 | 65 | 常量标记 66 | 除了CONST_CS标记,常量的flags字段通常还可以用CONST_PERSISTENT和CONST_CT_SUBST。 67 | 68 | //_zend_compiler_globals 编译时信息,包括函数表等 69 | zend_compiler_globals *compiler_globals; 70 | //_zend_executor_globals 执行时信息 71 | zend_executor_globals *executor_globals; 72 | //_php_core_globals 主要存储php.ini内的信息 73 | php_core_globals *core_globals; 74 | //_sapi_globals_struct SAPI的信息 75 | sapi_globals_struct *sapi_globals; 76 | 77 | 78 | 79 | 当执行一个拥有参数的用户自定义的函数时,其实它相当于赋值一个操作,即$s = $a; 只是这个赋值操作的引用计数会执行两次,除了给函数自定义的符号表,还有一个是给函数栈。 80 | 81 | 82 | 83 | 从函数直接返回值的宏,此宏设置返回值后直接return 84 | RETURN_RESOURCE(resource) 返回一个资源。 85 | 设置函数返回值的宏: 86 | RETVAL_RESOURCE(resource) 设定返回值为指定的一个资源。 87 | 88 | 89 | 90 | PHP提供了一个hook,我们可以在启动PHP前指定USE_ZEND_ALLOC环境变量为0,即关闭内存管理功能。 这样所有的内存分配都会直接向操作系统申请,这样valgrind就可以帮助我们定位问题。 91 | http://www.php-internals.com/book/?p=chapt06/06-07-memory-leaks 92 | 如果PHP内存管理打开了之后,如果发生了这种异常情况下的长跳转,PHP会将标志位:CG(unclean_shutdown)设置为true, 在请求结束后会将所有的内存进行释放 93 | 94 | 95 | 96 | 用户在PHP中定义的变量我们都可以在一个HashTable中找到,当用户在PHP中调用一个函数或者类的方法时,内核会创建一个新的符号表并激活之. 97 | struct _zend_executor_globals { 98 | ... 99 | HashTable symbol_table; 100 | HashTable *active_symbol_table; 101 | ... 102 | }; 103 | 104 | 105 | 106 | --enable-debug 最典型的功能便是在每一个请求结束后给出这一次请求中内存的泄漏情况。编译出来的php则会嵌入gdb或其它文件需要的所有调试信息。 107 | 108 | 109 | 110 | 二进制安全(binary safe)是什么意思? https://www.zhihu.com/question/28705562/answer/84320871 111 | sds.h/sds.c struct sdshdr { 112 | int len; 113 | int free; 114 | char buf[]; 115 | }; 116 | 因为有了对字符串长度定义len, 所以在处理字符串时候不会以零值字节(\0)为字符串结尾标志. 二进制安全就是输入任何字节都能正确处理, 即使包含零值字节. 117 | 118 | c中的strlen函数就不算是binary safe的,因为它依赖于特殊的字符'\0'来判断字符串是否结束,所以对于字符串str = "1234\0123"来说,strlen(str)=4而在php中,strlen函数是binary safe的,因为它不会对任何字符(包括'\0')进行特殊解释,所以在php中,strlen(str)=8所以,我理解的二进制安全的意思是:只关心二进制化的字符串,不关心具体格式.只会严格的按照二进制的数据存取。不会妄图已某种特殊格式解析数据。 119 | 120 | 121 | zend_parse_parameters中的(|)参数,这个参数之前的参数被认为是必须的,之后的便认为是非必须的 122 | 默认参数: 123 | ZEND_FUNCTION(sample_hello_world) { 124 | char *name; 125 | int name_len; 126 | char *greeting = "Mr./Mrs."; 127 | int greeting_len = sizeof("Mr./Mrs.") - 1; 128 | 129 | 130 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|s", 131 | &name, &name_len, &greeting, &greeting_len) == FAILURE) { 132 | RETURN_NULL(); 133 | } 134 | php_printf("Hello "); 135 | PHPWRITE(greeting, greeting_len); 136 | php_printf(" "); 137 | PHPWRITE(name, name_len); 138 | php_printf("!\n"); 139 | } 140 | 141 | 142 | zend_get_parameters 以zval为载体来接收参数 143 | 144 | 145 | EG() 这个宏可以用来访问符号表,函数,资源信息和常量。 146 | CG() 用来访问核心全局变量。 147 | PG() PHP全局变量。我们知道php.ini会映射一个或者多个PHP全局结构。举几个使用这个宏的例子:PG(register_globals), PG(safe_mode), PG(memory_limit) 148 | FG() 文件全局变量。大多数文件I/O或相关的全局变量的数据流都塞进标准扩展出口结构。 149 | SG() php 150 | 151 | 152 | 使用./ext_skel指令 解析zlib头: 153 | ./ext_skel extname=sample8 \ proto=/usr/local/include/zlib/zlib.h 154 | 155 | 156 | 157 | 158 | 159 | 第三方扩展和与PHP捆绑在一起的扩展没有根本区别。 160 | ./config.nice --enable-apcu 161 | 您应该始终在构建扩展时指定--with-php-config选项 162 | 163 | 164 | 165 | 扩展对5个主要因素非常敏感。如果他们不匹配,扩展将无法使用: 166 | PHP Api版本 167 | Zend Module Api 版本 168 | Zend Extension Api 版本 169 | Debug mode 170 | Thread safety 171 | 172 | 173 | 174 | 使用Z_TYPE_P()宏来检索类型标签,并使用Z_LVAL_P()获取long(整数)值。所有访问宏都具有_P后缀或没有后缀的变体。你使用哪一个取决于你是使用zval还是zval * 175 | 176 | 177 | 将字符串赋值给zval变量 178 | ZVAL_STRING (return_value , “hello world!” ); 179 | 180 | 使用ZSTR_VAL()宏访问字符数组。ZSTR_LEN()允许访问长度信息 181 | zend_string相关的宏都以ZSTR_**()开始 182 | 183 | 当字符串被内联时,它的GC标志被改变以添加IS_STR_INTERNED标志 184 | When a string is interned, its GC flags are changed to add the IS_STR_INTERNED flag 185 | 186 | 187 | 可以通过查看在编译PHP时生成的main / internal_functions.c来查找静态编译扩展的列表。 188 | 189 | 190 | GCC 4.0支持一个新的选项,用于设置源文件中符号的缺省可见性。这个选项是-fvisibility=vis ,您可以用它来设置当前编译的符号的可见性。这个选项的值可以是default(缺省)或者hidden(隐藏),设置为default时,没有显式标识为hidden的符号都处理为可见;设置为hidden时,没有显式标识为可见的符号都处理为隐藏。如果您在编译中没有指定-fvisibility选项,编译器会自行处理为缺省的可见性。 191 | 192 | 193 | 194 | 永远记住在每个--FILE--部分中包括关闭PHP标签。 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 龙宗法 226 | 3704 02 1956 0728 4339 227 | 228 | 王朝兰 229 | 3704 02 1964 0717 704x 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | -------------------------------------------------------------------------------- /beike/备课pic/vld-opcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dc236621911/php-internals-extended-development-course/c33c55c1f9a1b65a3335ad5cbcc81a6cee84de21/beike/备课pic/vld-opcode.png -------------------------------------------------------------------------------- /beike/备课pic/zendMM.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dc236621911/php-internals-extended-development-course/c33c55c1f9a1b65a3335ad5cbcc81a6cee84de21/beike/备课pic/zendMM.jpg -------------------------------------------------------------------------------- /beike/备课pic/性能分析工具.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dc236621911/php-internals-extended-development-course/c33c55c1f9a1b65a3335ad5cbcc81a6cee84de21/beike/备课pic/性能分析工具.png -------------------------------------------------------------------------------- /beike/旁白/1-1: -------------------------------------------------------------------------------- 1 | 大家好,欢迎大家来学习,PHP内核原理和扩展开发这门课程。 2 | 3 | 大家都知道PHP是以简单便捷的开发体验流行于当今世界 4 | 5 | 平时PHP开发人员大部分的开发工作也并不会接触到PHP的底层C源码, 6 | 7 | 但随着PHP开发人员开发经验积累和项目需求等原因,或早或晚的都会需要学习到这部分知识, 8 | 9 | 这也是成为一个高级PHP开发人员的必备知识。 10 | 11 | 我们这门课程就会系统的围绕PHP底层进行详细讲解,希望可以帮到各位同学。 12 | 13 | 14 | 15 | 16 | 17 | 为什么 录制本门课程 18 | 19 | PHP是一门开源的语言,PHP的发展必然少不了一些贡献者, 20 | 21 | 据我了解目前国内和国外还没有一份系统的介绍PHP底层知识的视频课程,目前这一份应该是第一份比较完善的课程。学习了本门课程,既可以丰富自己的知识,或者你也可以为PHP底层做一贡献。 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /beike/旁白/1-2.txt: -------------------------------------------------------------------------------- 1 | DEBUG显示的是我们的当前DEBUG实例的状态信息 2 | 3 | 这是显示的是我们的项目,当前调试进程和它的PID,里面是线程,再里面是调用函数栈 4 | 5 | 6 | Variables窗口显示的是变量名称类型和对应的值,还有显示断点信息的窗口,显示寄存器信息的窗口 7 | 8 | 9 | 10 | 这个中间窗口显示的是当前运行的代码 11 | 12 | 13 | 这个中间右侧的窗口会列出当前文件代码的概要信息。 14 | 15 | 16 | 下面这里还有控制台窗口,如果程序在调试过程中有输出会在这里显示。 17 | 18 | 19 | 20 | 21 | 我们单步调试一下测试一下 22 | 23 | 24 | 切换透视图 25 | 26 | 27 | 28 | 我们打开编辑器来讲一下几个重要的目录 29 | 30 | 31 | build目录里的内容是用来实现编译PHP的文件 32 | 33 | ext目录 官方扩展目录,包括了绝大多数PHP的函数的定义和实现 34 | 35 | main目录里面主要实现PHP的核心基本代码 36 | 37 | sapi目录 包含了各种服务器抽象层的代码 像cli,cgi,fpm等相关实现代码都放在里面 38 | 39 | tests 目录里面是PHP各项功能的测试文件 40 | 41 | TSRM目录 里面包含的是实现PHP的线程安全代码 42 | 43 | win32 里面是Windows平台下PHP的一些实现代码 44 | 45 | Zend Zend引擎的实现目录,比如脚本的词法语法解析,opcode的执行以及扩展机制的实现等等 46 | 47 | 48 | 还有一些其它的文件或者文件夹的作用,大家可以参考这个文档了解一下。 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /beike/旁白/1-3.txt: -------------------------------------------------------------------------------- 1 | 大家好,这节课我们来讲一下php中的SAPI的概念 2 | 3 | 4 | 5 | PHP可以不同环境下和不同软件配合工作,比较常见的比如可以在终端命令行直接执行,或者以fpm模式和nginx配合使用,或者可以配置为apache模块执行等。 6 | 7 | 对应的大家也可以看到PHP源代码的sapi目录,这里有具体的一些实现文件夹 8 | 9 | PHP和这些不同的软件配合,须要有一套机制和其它应用程序进行交互和协作。这一套机制就被称为是SAPI 10 | 11 | 12 | 13 | 14 | 15 | 举个例子,比如我们要运行这个test.php文件,这里面有两行代码... 16 | 17 | 大家都知道$_POST变是是PHP为你事先自动为你准备好了的,大家思考一下,在fpm和nginx配合的情况下这个post数据要如何生成, 18 | 在终端命令行要如何生成,这里获取POST的方法肯定是不一样的。 19 | 20 | 21 | 同样的,第二行代码的打印输出变量,要打印到哪里,输出到浏览器还是终端,这也是PHP需要区别对待的。 22 | 23 | 24 | 所以PHP为它们设计了sapi_module_struct的结构体,来配置定义这些模式中不同的行为。 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 我们看这个结构体的名字SAPI模块结构体,每个SAPI的实现都会对应一个这样的结构体, 36 | 37 | 38 | 我们来搜索一下看哪里用到了这个结构体 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 每个SAPI的实现都定义了一个sapi_module_struct结构体. 52 | 53 | 我们就在代码中搜索一下这个结构体来看一下。 54 | 55 | 56 | 57 | sapi.h中是这个结构体的定义、 58 | 59 | 每个SAPI的实现目录中都定义了一个这个结构体类型的变量。 60 | 61 | 62 | 63 | 我们来看一下这个结构体中具体有哪些成员以及他们的作用是什么。 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 我们在代码中看一下不同的SAPI的定义 76 | 77 | 78 | 可以看到不同的SAPI里的函数指定成员,被定义为了不同的函数 79 | 80 | 81 | 按住ctrl点左键或者按F3可以查看目标的具体定义 82 | 83 | 84 | 返回 85 | 86 | 87 | 88 | 最后我们下个断点验证一下,这个函数是不是被调用了。 89 | 90 | 91 | 92 | 听不懂不要紧,自己练习一下,可以往下面继续听 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 这么多代码如何学习? 115 | 116 | 你可以一步一步调试执行,一行一行研究它的实现,这是一个不太科学的方法。 117 | 118 | 我们这里先学习它的整体思想,然后讲再把它重要的点逐个讲一下,它再多的代码我们再研究起来也会脑子里有思路了。 119 | 120 | 121 | 122 | 123 | 124 | 125 | 我们先来讲一下一个最基本的PHP输入输出过程,CLI模式下面输出hello world 126 | 127 | 执行php -r "echo hello world" 这代码之后PHP会做什么事情 128 | 129 | 130 | 131 | 讲之前我们先了解一个概念:PHP里的SAPI是什么意思。 132 | 133 | 134 | 135 | 调试一下什么时候调用了sapi_module.ub_write(context.out.data, context.out.used); 136 | 137 | 138 | 139 | 140 | 141 | 我们看一下这个结构体, 142 | 我们看一下哪里用到了这个结构体,ctrl+alt+g 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 首先任何可执行的C程序都需要一个main函数,PHP也不例外 155 | 156 | PHP的main函数里面主要执行了以下几步 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | -------------------------------------------------------------------------------- /beike/旁白/1-4-1: -------------------------------------------------------------------------------- 1 | ```c 2 | /* 3 | 特殊节点 4 | ZEND_AST_ZVAL / ZEND_AST_ZNODE 5 | 包含ZVAL的节点,ZVAL为PHP中的变量,用来表示文字,变量名,调用函数名等,总是叶节点. 6 | 由zend_ast_create_zval / zend_ast_create_zval_from_str / zend_ast_create_zval_from_long创建*/ 7 | typedef struct _zend_ast_zval { 8 | zend_ast_kind kind; 9 | zend_ast_attr attr; 10 | zval val; /* Lineno is stored in val.u2.lineno */ 11 | } zend_ast_zval; 12 | 13 | /* 14 | 定义节点 15 | ZEND_AST_FUNC_DECL / ZEND_AST_CLOSURE / ZEND_AST_METHOD / ZEND_AST_CLASS 16 | 始终保留4个子元素(即使为NULL),由zend_ast_create_decl创建 17 | 4个子元素的含义: 18 | AST_FUNC_DECL函数定义: 19 | 1:AST_PARAM_LIST(虚拟参数),2:未使用,3:AST_STMT_LIST(函数内部),4:AST_ZVAL(返回类型) 20 | AST_CLOSURE匿名函数定义: 21 | 1:AST_PARAM_LIST(虚拟参数),2:AST_CLOSURE_USES(使用),3:AST_STMT_LIST(函数内部),4:AST_ZVAL 22 | AST_METHOD方法定义: 23 | 1:AST_PARAM_LIST(虚拟参数),2:未使用,3:AST_STMT_LIST(方法内部),4:AST_ZVAL(返回类型) 24 | AST_CLASS类,匿名类,trait,接口定义 25 | 1:AST_ZVAL(继承源),2:AST_NAME_LIST(implements),3:AST_STMT_LIST(内部),4:未使用 26 | */ 27 | typedef struct _zend_ast_decl { 28 | zend_ast_kind kind; 29 | zend_ast_attr attr; /* Unused */ 30 | uint32_t start_lineno; 31 | uint32_t end_lineno; 32 | uint32_t flags; 33 | unsigned char *lex_pos; 34 | zend_string *doc_comment; 35 | zend_string *name; 36 | zend_ast *child[4]; 37 | } zend_ast_decl; 38 | 39 | /* 40 | 列表节点 41 | 具有可变长度子节点的节点,由zend_ast_create_list创建/使用zend_ast_list_add添加子项 42 | */ 43 | typedef struct _zend_ast_list { 44 | zend_ast_kind kind; 45 | zend_ast_attr attr; 46 | uint32_t lineno; 47 | uint32_t children; 48 | zend_ast *child[1]; 49 | } zend_ast_list; 50 | 51 | /* 52 | 除特殊,定义和列表之外的所有普通节点 53 | 由Zend_ast_create(不含attr)/ zend_ast_create_ex / zend_ast_create_binary_op / zend_ast_create_assign_op / zend_ast_create_cast创建 54 | */ 55 | ``` 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 编译过程有工具 75 | 76 | 77 | 78 | 79 | opcode:当前个数 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /beike/旁白/1-5-1: -------------------------------------------------------------------------------- 1 | 这节课讲一下扩展开发中代码的知识要点。 2 | 3 | 学习扩展编写的相关网站 4 | 5 | 我们使用脚本工具生成一个带注释的扩展架构进行讲解。 6 | 7 | 每个扩展都有一个zend_module_struct,我们前面已经见过几次了,不过还没有讲,像身份证,PHP在启动时就会收集加载扩展的身份证信息 8 | 9 | 还记不记得这个结构成员对应的生命周期。 10 | 11 | 12 | 13 | 参考ext/json扩展 14 | 15 | -------------------------------------------------------------------------------- /beike/绘图/1-3 SAPI讲解: -------------------------------------------------------------------------------- 1 | 7Vpbc5s6EP41zLQP8XCxwTwmjtM+pOdkJudMHxkBAjQViIIc2/31lUBgMHJ9i6nt2C/At9JKWkn77cpSjEm8+JKBNPpGfIgVXfUXivGo6Lo21E324MiyRMaWVgJhhnxRaAW8ol9QgKpAZ8iHeasgJQRTlLZBjyQJ9GgLA1lG5u1iAcHtVlMQwg7w6gHcRb8jn0YC1Ux7JfgKURiJpse6VQpc4P0IMzJLRHuKbgTFrxTHoNIlBppHwCfzBmRMFWOSEULLt3gxgZjbtjJbWe9pg7TudwYTuksFvazwBvAMVj02Mav64KM33j+6FDYxf854px4oXNA7gFGYKMY9K4FhQFdS9haKZ6ElT0GyjgWE9U2mmgvu8mIxcM3aOF10NVOY00EapZU2NrZSYbsRBsvaZnAxsDZ6+rFWK7yqa2xW42aSTp/JEN3jxgfilL2UvTSedprETotnP7zEzdPiW5VCQwoZxt64Pv7tvPz7+t9Os31ltkgzlFAn+yRs8vnKbGA81WPecTdvHZveGpNe8Azknlxj4nmEKHxNgcelc8bLDItojIU4QBhPCCZZUZexEjQ9j3stmpEfsCHxLdtV1bq9N5gxo22kF60mLRYMQBJDmi1ZEVFhJGhuWQUG4nveIFVbYFGDT7WhCCCAIPKwVr0iM/Yi+EzObYaM23baozfoBt2gK4Q633sFpPpQFpDKyGKHzr18feGC6Vi5t5R7Q5maij1VmDOcPikPE2U84cjY5lJeRuPIn6mizSrvyBVQ80fQUiRcYZuWAcy6vaO4wqjIoSILtUsWpikhC7NKpo4hi+E5J0LSdRek8faQYs8E4RRB1CEpUAXkFFDk8dogRU5M/BmGDluFM4+r9ELkNASroNraFlVtsNZZ2mBnX1ctnjS+C5hpmqr3cx3XYI/BYPDhxlzsBTbzLkqcmevMM+bkP+LcN+0Q4FkeyZ1w0zAb/OQ52Ou9jpV62j99H0n1t5ycDALfSUlOz29T3RZJv+axHg92FLK+SM98JdY44YGQD+A4kB4Imd4YukHd3nFB/ngtyNe7Qb5hSYJ8XR8dH+SbG4P8zvwdlxO2N8b2477Lm0erPY+m0Z1HXZXM4+gdcjWrl1zt0LzMw+hwT3duDLklAcNrCdgaQ1xPNiZ12vuR5Up5uUJ2Ts42LKELsFpf4cQFj7kMLtk++sAJm/xUtrnsayuJdO6Wul1DVH56U/zz//Pz+W2o27K4JWuXGOQbltljsmZLovw1E+YRSPlrvAj5PcRBefWvfDg+mSdtI3JjIA/gZ+BC/EJyRBFhRR5dQimJGwXuy6X6SEla27i6B6jLrK4WPybJCA+YC613tvo+06Cv58yWLcm1JP+L2cdPgtbPBcFDLwOymQbLT3/dVx3a/T96lMsayuc9e37pV5lG1c7o5SqTJrvLdEnO8FS+cDTSTuUL2efqHnYha1x2N6a/AQ== -------------------------------------------------------------------------------- /beike/绘图/1-3 SAPI讲解.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dc236621911/php-internals-extended-development-course/c33c55c1f9a1b65a3335ad5cbcc81a6cee84de21/beike/绘图/1-3 SAPI讲解.png -------------------------------------------------------------------------------- /beike/绘图/1-4 PHP编译执行过程: -------------------------------------------------------------------------------- 1 | 7Vpbc6M2FP41mm0fkuEq4NEXvJ1pdzfT7PT2ksEg23TBogLHzv76SiBsJIvawZA6mew+BI5AiO985ztHBwNzku4+kiBbfcIRSoChRTtgToFh6JYB6R9meaosrqNXhiWJI37RwXAff0fcqHHrJo5QLlxYYJwUcSYaQ7xeo7AQbAEheCtetsCJ+NQsWKIjw30YJMfW3+OoWHGrDr3DwE8oXq74o13DqQbmQfhtSfBmzZ8HDHNR/quG06Cei79ovgoivG2YTB+YE4JxUR2luwlKGLY1bNV9s5bR/boJWhfn3GBUNzwGyQbVK4YJvXUcxY9sfcUTxwT+s2GLGhdoV9wESbxcA3NEr0jQojiM0qMl/1vOssB0HcxPCSbCNAyYkP1vmhSPYxPc5CVB2NN0N9u1Py3PgrVsmzcMQZrRg9IMzFm2yuqLKEBz+UZqU81HzdVLHZlLxETr/wNiHU61zeyMmJUXhM5YzqZ9WFHuYHqwxSSJPogv/w7fEXwoXOFy+WRQqAwBIoP6iKokPRlvV3GB7rMgZCNbKtTUtirShJ7pDJY4SSZ7TEwEIy1it9H14m+oMeJ5UHdtOrIkQRRTYWmMRbZjetF+HU3h4Vr0iAh1V8PEhegjwikqyBO9hI86XBN5zrD56fYgwHvZXDW0V9e4MeCiv9zPfBA+esC1T62DZqsOcvY8W5la+MaTQRuh9twQYu0EeUSWvBk+2CIfoKUghDUUISwFISRkyzSPIg7f89CNAuRS3VGgC0MXzRcNdKcxoSVOjKnWTdeYsFdXIe+gACKtH+QtXYTeOUYeOgrkodsD8vA08mgdjViVR8/CJMjzOBTB/nuTZryoNPQ9yHUlZ/wXSDnekBAJxRGKhGLxGLUGLLYCldpGUBIU8aNYYqqQ4k+4w3EpPdwppuSUvRjWU1RL53c1az5pIksTJ9I9aaIiIEtUHE1UOm7/2mf50r3cl9RB5OkPeqLdenQ+bvizNBjm3nCHSEwXhlg0aM9zefW6Qh64EpfLnoKysp3rch1KAW0P5vKapYpUepS7uubWapbvlDoPaIfCTYEe8pDEWfHDj6eLp3lrqmRlpUg+UZ3XeI0kKecmXotOQ8otRsEx09qY7idHfCCNoyhpS8J91E6SOOj6sWSrcqWsIV0EW3d6L55aiiK/dHaZCkfPqoka24mODLSUC/Qd4FlgrP/lf2Y7pN8+sc2SD4HrgZEDfBeMdOBOuu99VIs9vQO57KWaYXVOPCn3QKSfzcwlJdZigWCoLLEix5trPZVKtiTRtvuSVaquSrCDiO0vaBevl2LcnRbYweKORtYMuFMWa3R/7tnAt4HrAhcyi+cDVtJMVDHyq29MTpC3nzDt/G6KRbOQTNBOiMb+OgYD72MGCLJ9OXMqyCgxLo8x76XS211AchZk/gyMJ8C7khRXhtqoQ6iN4xyvX2WsUbLm6D3aeLSZ2kumNKN9/9BzuE1wmsVsf0ZZel0hZ4OxRgNsdP+VVZhsYZDHoaeX5aVLo+7L3eTL1L+OQCprxrDEEz0UOHvIi/SszdjbihTr3BalBy8PFMu+PFCUzvz65Wf/c6vrJCcd91ma2+dVkLHr0t2SfSe+XSR4G64CUtymm6SIbyIcbtISfqV7JZ9CODZms34850ofG6jIHXlOV31t6KPDabdLXJVnunqOCUZbznqbfjMVrenh/Ka3+u2yiDsh5W/Tdbbxkq5TfeB7Zif6GHe6Jy5Ya1q/1TSLn1edaWjW43Jj+tDPBkIv2wYtnexW6JufKupGQLOXXSeIK2lmO64kubJfz21mu1CeSFpLf81s+4zPgK+NNtdCB0sXlRxaHekg6YqlW4PRQVVyvdNhCDpYcuumKx1syx6MDj18qm6ng3arGbZAB2d/3koHXaRDecNZCaRGqZlA6s7XtVDEESliyB8xz6WI19bgGIAiqm9jr4giV+J61zFFdTCNbq53LClZ1L/o7d/1ULXLe3f9pa63u5aNsuth/d36YtfT08MvtqvLDz+LN/1/AQ== -------------------------------------------------------------------------------- /beike/绘图/1-4 PHP编译执行过程.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dc236621911/php-internals-extended-development-course/c33c55c1f9a1b65a3335ad5cbcc81a6cee84de21/beike/绘图/1-4 PHP编译执行过程.png -------------------------------------------------------------------------------- /beike/绘图/1.3 php代码执行过程: -------------------------------------------------------------------------------- 1 | 7VpLc6M4EP41qto9xAWI5xE/MnOYrUpVDrt7cmGQgR2MGCHH9vz6lUDYEsiOUyGZR+xUxdDq1qP7Q/21MICzzf4TiarsL5ygAlhGsgdwDizLtC2XfXHJoZX4gdcKUpInQukkeMy/IyE0hHSbJ6hWFCnGBc0rVRjjskQxVWQRIXinqq1xoY5aRSkaCB7jqBhK/84Tmgmp6Qanhs8oTzMxtG+J9a2i+GtK8LYU4wELrptP27yJur7EQussSvBOEsEFgDOCMW2vNvsZKrhvO7e1dvdnWo/zJqik1xhYrcFTVGzF0qusYoI7Aril+23LZzJFcYaZIGPG/HuHCXPosVUshR4699WU4K9Hz7FFTTO6KdilyS7Zkiuut9mnHDyTdYF3cRYROqkp+7/kOrssp+iximKuuGNqTLbOi2KGC0yaMWDiID+xeX/NYFKLb62g6x7nJTtD+OcJEYr2kkg45xPCG0TJgamIVmg4rUkHZLu93UmocEUsMwkQnSwSQEyPPZ+CwS5EPPSxsQexGTq658sW++3XkgGrVF3P150zlH+JVqh4wHVOc8xU5itMKd5ICmGRp7yB4uroYTmcA58bzWckn3ue4nPT0TjdGfrcHsHnzsDnKuad2e8MdqiC3e22O9nxxhuh3dWg3S349pLkT+wybXeaVrQifQnrXqN3M72Z3kxvpjfTtzFdY5Zs5GzYUULecFc3vD5kCqZf7YHEF9teNlFe/vGnNGjb3eW59LJvw7VRItKtLpVK2bifVSPkr2Ogyapu7KPV+jjeq7KqGahZ1eoopZRVoa/JqrYxQlr1bySypRem/24k0h5WVQOno4QVnOIWE5rhFJdRsThJpydkG2oA/kOUHkTNHG0pZqJTD18w93ajx5xHDv9w+4ljwU7wr+iwuXlAJGfLQ0QIzzqeT/ei2wkqIpo/qfWzzonC9AHnzdYhwmUHgRIu6DkTA9o2K6wd37UMT+2wxlsSI9GHXN72uvUCZ+JZtgm9AJq8P/VJvDwII+gpooNBQkKig6RWcYX6/NIc2+8v7eKc+/qWc1nfC9xL+uyinfEJusewXYVms3v2TmhO8LLbnusqKrvNOS5yZTuX28YqmyqCY1TXZ/b63gaPTFY4ebqtJnA9GI1UNnUOPwV4WK9ajvU2e4053GsePj+AhQP8EAT3YOEDH4LpHCxcEPogNHlTMAeB11wYILwXyqy1OfKJNtyT5aqupCwsh7KOqny5wcm2QHdH/baF17Tb6gIIzjIJVe3+3Dx6HVZZJWayFGNfSRveA31rh/9p6UXzGQt9ve1Ciz77jdA3PC/RxPgFgPQdMIVgEfDbcK4HRx8ZhoyCbEs5ZXkWgh8WH3ZHNRV8aOnn6/GhOV/+GPTz+GR0T6WreSo1Tre8C9Tpav7pjUk3JRoZBJZMI82JYTnnqOS1LPWss1uOJ1Jq9xKooWQqLxmZmr6U3nm98s6Bz9A7x32Vvm2OS+/gkN79dMcMN9Ob6U9h+rrzLlY29Uuk3/LEy+29NLW8K0+8RuEcUPfedBhRxkNZ6TP1OA+dmsC3tPUQp5YEfduimi511c0P55Br5MbagCZesBqLzbBiWQ1o8I41BjxfYyjEnsXRD0DoNZFlAZ01ZQerNmwQhLw1mIFw9kxZIMu+ozJZIjYsizzJy3SJ9i+w/ognIa5nKDiBmjf3b3YSAq97g6x97gdl57OHIMrG8CsVnT9kw7BNHRDMUYDAbk+/l2oJ8OlHaXDxPw== -------------------------------------------------------------------------------- /beike/绘图/1.3 php代码执行过程.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dc236621911/php-internals-extended-development-course/c33c55c1f9a1b65a3335ad5cbcc81a6cee84de21/beike/绘图/1.3 php代码执行过程.png -------------------------------------------------------------------------------- /beike/绘图/1.3PHP架构图: -------------------------------------------------------------------------------- 1 | 7Vpbj6M2FP41lrYPjQCDMY+ETXZX6lajTaVKfRkZMBctxBQ8k0x//R5zSYCQvXTYmTYiD4xzzvlsxt+x+Q4Owl5+fFeyIvkoQp4hQwuPCL9FhqGbBoE/yvLUWKitN4a4TMM26GzYpf/w1qi11oc05NUgUAqRybQYGgOx3/NADmysLMVhGBaJbDhqwWJ+YdgFLLu0/pmGMmmtOnHOjvc8jZN2aGrYjcNnwee4FA/7djxk4Kj+NO6cdX21/2iVsFAceia8QdgrhZBNKz96PFNz201bg9te8Z7uu+R7+T0AowE8suyBd3dMMoCuw/QRmrFqdia/HFug44m4BbpAF+gCXaALdIEu0OdA/bEhEqBsQDjJp1askb8fROf4taqlpAsBOi2OZ2fXy937u954TV8Xt9Eb0xgMZdTKjivtpIP7kKSS7woWKO8BlDDYEplnrTtKs8wTmShrLA4Zp1EA9kqW4jPveUhAuR+BJy5ZmIJy6/lszgjXTvfyyEvJj1fFnn6SkCDNuci5LJ8gpAN08rpV5Se5fThrXKezJT15a1lmq61bXR2f+j5rS2i08nJaappXpea/5tgwpzhGGwutXeSYaGOrq0tVA660c9HtDyXBT7nLNwwSJ+HI8PZxuocAr0pgwlar1S+vlqEWp6E5laHU8DEh0xnq2KFm2zNlqDHMUNO6SFDqTCQo1Z6fn9ZSCi3QBbpAF+gCXaCvDB0bnif7h700tj92nz7WmK+Krapg+5MNZCSox/V2oCfdDaKOalCrlpoUraEN4pPUKlSrgzfI1Ws5qiFKVLDjqODz2INxflyczsjJjIIyiiIjmCx5QuIT64qgjKLQqcXmHIISW8OSB1+WPJRMKEpzDklJ5i95pvN75959UH7IOcdD9G2djm6bc+4audvpomjtIYf0spkgCplq1fAtcjEM+KZIivtchFCgREUO1yBL/+NFyhx5Y49KZesyb+ypSoTMkDb2RNqMZrBKWKGa+TFWJ3Cr5tBrVWUcJra8lwdxf2BP97V5OJ1qWtKAZb8xn2d3okplKvbg84WUIu8FuFkaK4cUxWm2u8MwY2r+tfoDnlJI1vbqzPTqAo8KQ32Cj8llPAMddKFjTAfVXo8O56U2VfWuEHZEV0NrWm+fW0TxjW97zpBXbOILXrE1Qayu289ntnv1+ELUggjbqmeievzpiGrqGXrT7FpjdidWLe5euQ2W7Qyvf3X9KrnzaHt/tG5BlLu1Ll/ryLG+IZ6HbP/vmTV18p3MzrAh68ZLLdu/Nr8D5PYX6ngbPhUj39qG7TnoxC9M5+VivXmCLUpfkeCfcBL3VYI36oSLt0yP5JRqeGp/VqUrVuXnTRM/XtnWBPG2OQ/x8PX8Q8La1/u1Jt58AQ== -------------------------------------------------------------------------------- /beike/绘图/1.3PHP架构图.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dc236621911/php-internals-extended-development-course/c33c55c1f9a1b65a3335ad5cbcc81a6cee84de21/beike/绘图/1.3PHP架构图.png -------------------------------------------------------------------------------- /beike/绘图/zval: -------------------------------------------------------------------------------- 1 | 5VdNj5swEP01HFuBDTQck3R3e6lUKYfuHh2YgLUGU8dJSH99DYzD56qrKk2lJpfgN+P5eG8sjEPXefWkWJl9lQkIh7hJ5dDPDiGeT0LzVyPnFllEn1ogVTxBpw7Y8J+AoIvogSewHzhqKYXm5RCMZVFArAcYU0qehm47KYZZS5bCBNjETEzR7zzRGaJeGHWGL8DTDFMvCPa3ZfFrquShwHwOobvm15pzZmNho/uMJfLUg+iDQ9dKSt0+5dUaRM2tpa3d9/iG9VK3gkK/ZwNpNxyZOICtuKlLny0XTTdQ+7sOXZ0yrmFTsri2noz6Bst0LszKM487LsRaCqmavTQMV+TR5F5N68JSj6A0VD0I63wCmYNWZ+NirXY4cKa8CNenTqGIIpb1xCHWkeFUpJfYHTPmAcmZJ4rOEBUKXbcsTUt9xsIfB2kNH/bNbC+NA/HLqjOapxT/myhbC7gWMRVtx14Ga9NZeKSVoVIPBdlrJV/BSlLIAkYqIcQETwuzjI1AYPBVLQw3J2KJhpwnSZ1mdgKuIG80UjeYquvPiXsFbf1baRvSOxXXc8m/kze4mbz+nco7OruBeztxw1uJ69Wv+LtUd3J4/Zk379/Sd/H7CwoUybK+83WUTVi2lzjT5coQoc7P9XXmY2CXL3i7gYrrnsmsXjBOmxWSyb1xQuxeHlQMg+uVZioF3YOmXPeoDGaotJgCwTQ/DouY4xczfJO8OQUoZRAMlSThSKK2eNzVvzeOAoXuKJA3CtS2PAnUyH1p+10TEF1/AqzKPY17w/CMG7vJaGz/xwR4owmgfzoBZDQB9GoTYJbdB1Dr3n1l0odf -------------------------------------------------------------------------------- /beike/课件知识点准备/1.1: -------------------------------------------------------------------------------- 1 | 2 | ubuntu:详细介绍,重点推荐使用 3 | 链接:https://www.zhihu.com/question/30816866/answer/153948993 4 | 我也来回答一波:本人大三,面临毕业与找工作,给自己定的方向是python,目前 5 | 刚刚入门,16年末狠下心来,卸了windows10,装了ubuntu16,感觉整个世界都清净了,更加专注于学习,ubuntu真的使我的学习效率和热情提 6 | 高了不少,为数不多的日子,希望自己能变得更强吧。 7 | 8 | 链接:https://www.zhihu.com/question/30816866/answer/97994549 9 | > 从没接触过的新手学Linux,把日常工作的电脑从win转到Ubuntu是否合理?答:不合理,学习都是循序渐进的。不要直接切换, 10 | 推荐先在Windows中的虚拟机中跑Ubuntu,然后再玩双启动(Windows+Ubuntu),最后才是单系统Ubuntu。 11 | > 都有哪些坑?答:以下只谈日常工作相关。坑是自己错误使用Linux造成的,Linux不是Windows的替代品,同样的,深蓝的性能再 12 | 好,也运行不了孤岛危机。- 没有MS Office,所有的替代品都会出现格式问题。结果就是影响工作效率,比如客户发来的文档在你 13 | 的电脑上显示是乱的,或者客户看你的文档时候格式是乱的。- 没有客户端QQ,和客户沟通不方便;使用Web QQ,但是bug多 14 | ,功能少。- 公司需要使用A软件,但是A软件只有Windows版。 15 | 最后再附赠一个解决方案:做任何事情都是有目的的,“我要切换到Linux”是结果,不是目的。所以请分析一下的正确和错误的逻辑,然后 16 | 找到自己的目的/目标吧。 17 | 正确的逻辑:- Windows下的batch好难用!咦,Linux下Shell真强大,还附赠了Python,竟然还有Perl。我好想切换到Lin 18 | ux下,因为这些工具会极大的提升我的工作效率。- 我正在学习C语言,刚好看到几个优秀的开源项目,代码都值得学习,那就先 19 | 编译一下吧,咦,怎么只有Makefile?我还是切换到Linux下学习吧。 20 | 错误的逻辑:- Linux看起来好高端啊,我也装一个来玩玩。哇!3D桌面真炫,我想一直使用这个桌面。 - 我现在想看电影了,需要装一个 21 | 播放器。Linux下的播放器真难用。 - 我现在想玩游戏了,需要装一个xx游戏。Linux玩不了xx游戏,真垃圾。编辑于 2016-04-29 22 | 23 | 24 | clion 之前在windows上用很卡,现在linux速度正常 25 | 26 | 27 | PHP源码下载 28 | 29 | 30 | 31 | 32 | 常用代码: 33 | #define ZEND_FN(name) zif_##name 34 | 在C语言的宏中,"##"被称为 连接符(concatenator),它是一种预处理运算符, 用来把两个语言符号(Token)组合成单个语言符号。 35 | #define STR(x) #x 36 | "#"是一种预处理运算符,它的功能是将其后面的宏参数进行 字符串化操作 , 简单说就是在对它所引用的宏变量通过替换后在其左右各加上一个双引号, 用比较官方的话说就是将语言符号(Token)转化为字符串 37 | 38 | 39 | //宏定义中的do-while循环 40 | #define ALLOC_ZVAL(z) 41 | do { 42 | (z) = (zval*)emalloc(sizeof(zval_gc_info)); 43 | GC_ZVAL_INIT(z); 44 | } while (0) 45 | 46 | 47 | `#line 838 "Zend/zend_language_scanner.c"` 48 | #line 预处理 通过这条指令, 可以保证文件名是固定的,不会被C 源码编译过程中会产生一些中间文件代替,有利于进行调试分析。 49 | 50 | 51 | #ifdef ZTS // 编译时开启了线程安全则使用线程安全库 52 | # define PG(v) TSRMG(core_globals_id, php_core_globals *, v) 53 | extern PHPAPI int core_globals_id; 54 | #else 55 | # define PG(v) (core_globals.v) // 否则这其实就是一个普通的全局变量 56 | extern ZEND_API struct _php_core_globals core_globals; 57 | #endif 58 | 全局变量为一个结构体 _php_core_globals,这个全局变量很大一部分是与php.ini文件中的配置项对应的 59 | 60 | 61 | 62 | 63 | 64 | 65 | php-744.1.4 66 | ├── appveyor 67 | ├── build //源码编译相关文件 68 | └── ext //官方扩展目录,包括了绝大多数PHP的函数的定义和实现 69 | └── main //PHP核心基本文件,这里和Zend引擎不一样,Zend引擎主要实现语言最核心的语言运行环境。 70 | └── netware 71 | └── pear //“PHP 扩展与应用仓库”,包含PEAR的核心文件。 72 | └── sapi //包含了各种服务器抽象层的代码,例如apache的mod_php,cgi,fastcgi以及fpm等等接口。 73 | └── scripts 74 | └── tests //PHP的测试脚本集合,包含PHP各项功能的测试文件 75 | └── travis 76 | └── TSRM //PHP的线程安全是构建在TSRM库之上的,PHP实现中常见的*G宏通常是对TSRM的封装,TSRM(Thread Safe Resource Manager)线程安全资源管理器。 77 | └── win32 //Windows平台相关的一些实现,比如sokcet的实现在Windows下和*Nix平台就不太一样,同时也包括了Windows下编译PHP相关的脚本。 78 | └── Zend //Zend引擎的实现目录,比如脚本的词法语法解析,opcode的执行以及扩展机制的实现等等。 79 | ├── RFCS 80 | ├── tests 81 | └── ... 82 | └── .appveyor.yml 83 | └── .editorconfig 84 | └── .gdbinit //gdb命令编写脚本 (gdb) source /home/laruence/package/php-5.2.14/.gdbinit (gdb) zbacktrace 85 | └── .gitignore 86 | └── .travis.yml 87 | └── acinclude.m4 88 | └── aclocal.m4 89 | └── buildconf 90 | └── buildconf.bat 91 | └── CODING_STANDARDS //PHP编码标准 92 | └── config.guess //由automake产生,两个用于目标平台检测的脚本 93 | └── config.log //configure执行时生成的日志文件 94 | └── config.nice //configure执行时生成,记录了上次执行configure时带的详细参数 95 | └── config.status //configure执行时生成,实际调用编译工具构建软件的shell脚本 96 | └── config.sub //由automake产生,两个用于目标平台检测的脚本 97 | └── configure //配置并生成makefile 98 | └── configure.in //autoreconf创建,开发者维护,用于生成configure 99 | └── CONTRIBUTING.md 100 | └── CREDITS //开发人员名单 101 | └── EXTENSIONS //扩展说明(维护状态,维护人员,版本,适用系统..) 102 | └── footer 103 | └── generated_lists 104 | └── genfiles 105 | └── header 106 | └── INSTALL 107 | └── install-sh 108 | └── LICENSE //发布协议 109 | └── ltmain.sh 110 | └── makedist 111 | └── Makefile.frag 112 | └── Makefile.gcov 113 | └── Makefile.global 114 | └── makerpm 115 | └── missing 116 | └── mkinstalldirs 117 | └── NEWS 118 | └── php.gif 119 | └── php.ini-development //PHP开发环境示例配置文件 120 | └── php.ini-production //PHP生产环境示例配置文件 121 | └── php7.spec.in 122 | └── README.EXT_SKEL //构建扩展脚本说明 123 | └── README.GIT-RULES //GIT提交时的规则 124 | └── README.input_filter 125 | └── README.MAILINGLIST_RULES 126 | └── README.md 127 | └── README.namespaces //命名空间说明 128 | └── README.NEW-OUTPUT-API 129 | └── README.PARAMETER_PARSING_API //新的参数解析函数说明 130 | └── README.REDIST.BINS //PHP中引用到的其它程序协议说明 131 | └── README.RELEASE_PROCESS //PHP发布过程说明 132 | └── README.SELF-CONTAINED-EXTENSIONS //创建一个内建的PHP扩展 133 | └── README.STREAMS //PHP Streams(流概念) 说明 134 | └── README.SUBMITTING_PATCH //介绍如何提交PHP的增强功能或修补程序 135 | └── README.TESTING //测试说明(run-tests.php) 136 | └── README.TESTING2 //测试说明(server-tests.php) 137 | └── README.UNIX-BUILD-SYSTEM //PHP编译系统V5概述 138 | └── README.WIN32-BUILD-SYSTEM //WIN32编译说明 139 | └── run-test.php //测试脚本 140 | └── server-test.php //测试脚本 141 | └── sesrver-test-config.php //测试脚本 142 | └── snapshot 143 | └── stamp-h.in 144 | └── stub.c 145 | └── UPGRADING //版本更新说明 146 | └── UPGRADING.INTERNALS //内部更新说明 147 | └── vcsclean 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 。 189 | -------------------------------------------------------------------------------- /beike/课件知识点准备/1.3: -------------------------------------------------------------------------------- 1 | 词法解析:生成记号流 2 | Lex: 3 | Flex: 4 | re2c:zend_language_scanner.l 5 | Jlex:java 6 | 7 | 语法解析器:检源程序语法是否正确,生成抽象语法树 8 | ANTLR 9 | Yacc:LR(1)分析算法 10 | 词法解析规则中的语义动作代码中 $$ 表示产生式:号左侧的非终结符,$1...$n对应:号右侧按顺序的每个TOKEN 11 | Bison:zend_language_parser.y 12 | Lemon:类似于更加著名的“YACC”和“BISON”。但与yacc或bison不兼容 13 | 14 | 15 | 表 3.3 各类解析器生成器 16 | 软件名 能够生成解析器的语言 可处理语法的范围 17 | ANTLR Java、C、C++ 等多数语言 LL(*) 18 | JavaCC Java LL(k) 19 | jay Java LALR(1) 20 | yacc C LALR(1) 21 | bison C LALR(1) 22 | kmyacc C、Java、JavaScript、Perl LALR(1) 23 | Lemon C LALR(1) 24 | Parse::RecDecendent Perl LL(1) 25 | Racc Ruby LALR(1) 26 | Parsec Haskell LL(k) 27 | happy Haskell LALR(1) 28 | 有的像 LALR(1) 这样,标注了 (1) 这样的数字。这表示能够超前扫描的 token 数,其中 (k) 或 (*) 表示能够超前扫描任意个数。 29 | 30 | 31 | 只要你在Unix环境中写过程序,你必定会邂逅神秘的Lex&YACC,就如GNU/Linux用户所熟知的Flex&Bison,这里的Flex就是由Vern Paxon实现的一个Lex,Bison则是GNU版本的YACC. 32 | bison用$$表示规则左边的对象,用$1 $2 $3 等依次表示规则右边的对象。 33 | 他把编译器解释成“把一个装着程序的字符串切成词,把词组装成树,然后把树弄成线性的”这么个过程,可以说通常情况下,编译器就是这么个过程。 34 | 35 | 链接:https://www.zhihu.com/question/21755487/answer/82872281 36 | 说点编译相关知识在前端或其他方面的应用: 37 | - babel 是一个非常有趣的 js compiler,基于 es-ast 的编译器还有很多,如 jscodeshift 依赖的 recast 等。理解了代码被 tokenizer 解析到 ast 再到 output 才能更好的应用,比如实现代码重构、特殊编译等。 38 | - 模版引擎,解析这样的 {{ content }},还有这样的 {% for i in [0..10] %} 语句肯定是需要有parse的过程。如果你有兴趣,可以看看vue的实现 vuejs/vue - JavaScript 39 | - parse 常用的数据通讯格式格式如解析 json/proto。在早期一些老浏览器不支持JSON内置对象的时候,JSON的parse和stringify用的第三方库 douglascrockford/JSON-js: JSON in JavaScript 40 | - 以及 GraphQL 的实现,你可以理解他就是用一个请求的scheme来减少创建格式各样api的形式,对于整个scheme的处理,当然也要parser graphql-js/parser.js at master · graphql/graphql-js · GitHub 41 | - Node.JS 的引擎:v8、Chakra,我想他们改进的不仅仅是 JavaScript 的执行速度和语法吧 42 | 不知道举得几个栗子符不符合题主胃口..我之前也认为学习编译原理可能真的要写一个能把一个xx语言 parse 完 execute 成功才算ok,那个才叫编译器神马的... 43 | 也是接触到这些东西之后开启了新世界的大门,原来“编译”这个概念应用如此之广 44 | 45 | 46 | 新的解析器构建手法 47 | 几年前,说起解析器人们首先还是会想起 yacc(LALR),但最近解析器界出现了新的流派。 48 | 其中之一是和 LL 解析器、LR 解析器风格截然不同的 Packrat 解析器。无论哪款 Packrat 解析器,都具有如下特征。 49 | 1. 支持无限的超前扫描 50 | 2. 无需区分扫描器和解析器,可以一并编写 51 | 3. 内存的消耗量和字符串的长度(代码的长度)成比例 52 | 4. 语法无二义性(不会发生空悬 else 的问题) 53 | Packrat 解析器可以说是今后的潜力股。 54 | 其二是出现了直接使用编程语言来描述语法的方法。例如 parser combinator 技术就是其中之一。 55 | JavaCC 用不同于 Java 的形式来描述语法,之后再生成代码。如果使用 parser combinator 这样的技术的话,就可以用 Java 等编程语言直接表示语法。利用这样的手法,解析器生成器就成为了单纯的程序库,因此无需导入像 JavaCC 这样的额外的工具,这是它的主要优点。 56 | 无论上述哪种变化,都是以能够更简单、方便地使用解析器为共同目标。 57 | 58 | 参考《自制编程语言》2.1章节 59 | 在规则区块中遵循这样的书写方式:一个正则表达式的后面紧跟若干个空格,后接C代码。如果输入的字符串匹配正则表达式,则执行后面的C代码。这里的C代码部分称为动作(action)。 60 | 61 | 2.2.3 yacc 62 | yacc是自动生成语法分析器的工具,输入扩展名为 .y的文件,就会输出语法分析器的C语言代码。bison则是GNU项目所发布的yacc的功能扩充版。 63 | 64 | -------------------------------------------------------------------------------- /beike/课件知识点准备/3.1-: -------------------------------------------------------------------------------- 1 | 变量值的存储 2 | Z_TYPE_P(z)=IS_BOOL/LONG; Z_LVAL_P(z)=((b)!=0); 3 | 4 | EG宏:一个用于保存在运行时数据的全局结构体。excute globals 5 | EX宏: 6 | CG宏:compiler globals 7 | Searching 19192 files for "# define CG" 8 | /home/ll/workspace/C/php-7.1.4/Zend/zend_globals_macros.h: 9 | 32 /* Compiler */ 10 | 33 #ifdef ZTS 11 | 34: # define CG(v) ZEND_TSRMG(compiler_globals_id, zend_compiler_globals *, v) 12 | 35 #else 13 | 36: # define CG(v) (compiler_globals.v) 14 | 37 extern ZEND_API struct _zend_compiler_globals compiler_globals; 15 | 38 #endif 16 | 2 matches in 1 file 17 | SG宏:sapi全局变量 18 | 19 | Zend为每个函数(准确的说是zend_op_array)分配了一个私有的符号表来保存该函数的静态变量。(http://www.php-internals.com/book/?p=chapt03/03-04-static-var) 20 | 21 | 在ZE执行的过程中,有四个全局的变量,这些变量都是用于ZE运行时所需信息的存储: 22 | //_zend_compiler_globals 编译时信息,包括函数表等 23 | zend_compiler_globals *compiler_globals; 24 | //_zend_executor_globals 执行时信息 25 | zend_executor_globals *executor_globals; 26 | //_php_core_globals 主要存储php.ini内的信息 27 | php_core_globals *core_globals; 28 | //_sapi_globals_struct SAPI的信息 29 | sapi_globals_struct *sapi_globals; 30 | 31 | 32 | http://www.php-internals.com/book/?p=chapt03/03-06-02-var-scope 33 | 对于全局变量,Zend引擎有一个_zend_executor_globals结构,该结构中的symbol_table就是全局符号表, 其中保存了在顶层作用域中的变量。同样,函数或者对象的方法在被调用时会创建active_symbol_table来保存局部变量。 当程序在顶层中使用某个变量时,ZE就会在symbol_table中进行遍历, 同理,如果程序运行于某个函数中,Zend引擎会遍历查询与其对应的active_symbol_table, 而每个函数的active_symbol_table是相对独立的,由此而实现的作用域的独立。 34 | 展开来看,如果我们调用的一个函数中的变量,ZE使用_zend_execute_data来存储 某个单独的op_array(每个函数都会生成单独的op_array)执行过程中所需要的信息 35 | 36 | 37 | //PHP内部函数在解析参数 38 | ZEND_API int zend_parse_parameters(int num_args TSRMLS_DC, char *type_spec, ...) 39 | 第一个参数num_args表明表示想要接收的参数个数,我们经常使用ZEND_NUM_ARGS() 来表示对传入的参数“有多少要多少”。 40 | 第二参数应该总是宏 TSRMLS_CC 。 41 | 第三个参数 type_spec 是一个字符串,用来指定我们所期待接收的各个参数的类型,有点类似于 printf 中指定输出格式的那个格式化字符串。 42 | 剩下的参数就是我们用来接收PHP参数值的变量的指针。 43 | 44 | 45 | //匿名函数 46 | check with-path 24 | dnl SEARCH_PATH="/usr/local /usr" # you might want to change this 25 | dnl SEARCH_FOR="/include/pib.h" # you most likely want to change this 26 | dnl if test -r $PHP_PIB/$SEARCH_FOR; then # path given as parameter 27 | dnl PIB_DIR=$PHP_PIB 28 | dnl else # search default path list 29 | dnl AC_MSG_CHECKING([for pib files in default path]) 30 | dnl for i in $SEARCH_PATH ; do 31 | dnl if test -r $i/$SEARCH_FOR; then 32 | dnl PIB_DIR=$i 33 | dnl AC_MSG_RESULT(found in $i) 34 | dnl fi 35 | dnl done 36 | dnl fi 37 | dnl 38 | dnl if test -z "$PIB_DIR"; then 39 | dnl AC_MSG_RESULT([not found]) 40 | dnl AC_MSG_ERROR([Please reinstall the pib distribution]) 41 | dnl fi 42 | 43 | dnl # --with-pib -> add include path 44 | dnl PHP_ADD_INCLUDE($PIB_DIR/include) 45 | 46 | dnl # --with-pib -> check for lib and symbol presence 47 | dnl LIBNAME=pib # you may want to change this 48 | dnl LIBSYMBOL=pib # you most likely want to change this 49 | 50 | dnl PHP_CHECK_LIBRARY($LIBNAME,$LIBSYMBOL, 51 | dnl [ 52 | dnl PHP_ADD_LIBRARY_WITH_PATH($LIBNAME, $PIB_DIR/$PHP_LIBDIR, PIB_SHARED_LIBADD) 53 | dnl AC_DEFINE(HAVE_PIBLIB,1,[ ]) 54 | dnl ],[ 55 | dnl AC_MSG_ERROR([wrong pib lib version or lib not found]) 56 | dnl ],[ 57 | dnl -L$PIB_DIR/$PHP_LIBDIR -lm 58 | dnl ]) 59 | dnl 60 | dnl PHP_SUBST(PIB_SHARED_LIBADD) 61 | 62 | PHP_NEW_EXTENSION(pib, pib.c, $ext_shared,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1) 63 | fi 64 | -------------------------------------------------------------------------------- /code/1-5-2/pib/config.w32: -------------------------------------------------------------------------------- 1 | // $Id$ 2 | // vim:ft=javascript 3 | 4 | // If your extension references something external, use ARG_WITH 5 | // ARG_WITH("pib", "for pib support", "no"); 6 | 7 | // Otherwise, use ARG_ENABLE 8 | // ARG_ENABLE("pib", "enable pib support", "no"); 9 | 10 | if (PHP_PIB != "no") { 11 | EXTENSION("pib", "pib.c", PHP_EXTNAME_SHARED, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1"); 12 | } 13 | 14 | -------------------------------------------------------------------------------- /code/1-5-2/pib/php_pib.h: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | PHP Version 7 | 4 | +----------------------------------------------------------------------+ 5 | | Copyright (c) 1997-2017 The PHP Group | 6 | +----------------------------------------------------------------------+ 7 | | This source file is subject to version 3.01 of the PHP license, | 8 | | that is bundled with this package in the file LICENSE, and is | 9 | | available through the world-wide-web at the following url: | 10 | | http://www.php.net/license/3_01.txt | 11 | | If you did not receive a copy of the PHP license and are unable to | 12 | | obtain it through the world-wide-web, please send a note to | 13 | | license@php.net so we can mail you a copy immediately. | 14 | +----------------------------------------------------------------------+ 15 | | Author: | 16 | +----------------------------------------------------------------------+ 17 | */ 18 | 19 | /* $Id$ */ 20 | 21 | #ifndef PHP_PIB_H 22 | #define PHP_PIB_H 23 | 24 | extern zend_module_entry pib_module_entry; 25 | #define phpext_pib_ptr &pib_module_entry 26 | 27 | #define PHP_PIB_VERSION "0.1.0" 28 | 29 | #ifdef PHP_WIN32 30 | # define PHP_PIB_API __declspec(dllexport) 31 | #elif defined(__GNUC__) && __GNUC__ >= 4 32 | # define PHP_PIB_API __attribute__ ((visibility("default"))) 33 | #else 34 | # define PHP_PIB_API 35 | #endif 36 | 37 | #ifdef ZTS 38 | #include "TSRM.h" 39 | #endif 40 | 41 | 42 | #define PIB_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(pib, v) 43 | 44 | #if defined(ZTS) && defined(COMPILE_DL_PIB) 45 | ZEND_TSRMLS_CACHE_EXTERN() 46 | #endif 47 | 48 | #endif 49 | 50 | 51 | /* 52 | * Local variables: 53 | * tab-width: 4 54 | * c-basic-offset: 4 55 | * End: 56 | * vim600: noet sw=4 ts=4 fdm=marker 57 | * vim<600: noet sw=4 ts=4 58 | */ 59 | -------------------------------------------------------------------------------- /code/1-5-2/pib/pib.c: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | PHP Version 7 | 4 | +----------------------------------------------------------------------+ 5 | | Copyright (c) 1997-2017 The PHP Group | 6 | +----------------------------------------------------------------------+ 7 | | This source file is subject to version 3.01 of the PHP license, | 8 | | that is bundled with this package in the file LICENSE, and is | 9 | | available through the world-wide-web at the following url: | 10 | | http://www.php.net/license/3_01.txt | 11 | | If you did not receive a copy of the PHP license and are unable to | 12 | | obtain it through the world-wide-web, please send a note to | 13 | | license@php.net so we can mail you a copy immediately. | 14 | +----------------------------------------------------------------------+ 15 | | Author: | 16 | +----------------------------------------------------------------------+ 17 | */ 18 | 19 | /* $Id$ */ 20 | 21 | #ifdef HAVE_CONFIG_H 22 | #include "config.h" 23 | #endif 24 | 25 | #include "php.h" 26 | #include "php_ini.h" 27 | #include "ext/standard/info.h" 28 | #include "php_pib.h" 29 | 30 | #define TEMP_CONVERTER_TO_FAHRENHEIT 2 31 | #define TEMP_CONVERTER_TO_CELSIUS 1 32 | 33 | static int le_pib; 34 | 35 | /* {{{ arginfo */ 36 | /* 华氏转摄氏函数参数 */ 37 | ZEND_BEGIN_ARG_INFO_EX(arginfo_fahrenheit_to_celsius, 0, 0, 1) 38 | ZEND_ARG_TYPE_INFO(0, fahrenheit, IS_DOUBLE, 0) 39 | ZEND_END_ARG_INFO(); 40 | /* 摄氏转华氏函数参数 */ 41 | ZEND_BEGIN_ARG_INFO_EX(arginfo_celsius_to_fahrenheit, 0, 0, 1) 42 | ZEND_ARG_TYPE_INFO(0, celsius, IS_DOUBLE, 0) 43 | ZEND_END_ARG_INFO(); 44 | /* 双向转函数参数 */ 45 | ZEND_BEGIN_ARG_INFO_EX(arginfo_temperature_converter, 0, 0, 1) 46 | ZEND_ARG_TYPE_INFO(0, temperature, IS_DOUBLE, 0) 47 | ZEND_ARG_INFO(0, mode) 48 | ZEND_END_ARG_INFO(); 49 | /* 数组批量华氏转摄氏函数参数 */ 50 | ZEND_BEGIN_ARG_INFO_EX(arginfo_multiple_fahrenheit_to_celsius, 0, 0, 1) 51 | ZEND_ARG_ARRAY_INFO(0, temperatures, 0) 52 | ZEND_END_ARG_INFO(); 53 | /* }}} */ 54 | 55 | static double php_fahrenheit_to_celsius(double f) 56 | { 57 | return ((double)5/9) * (double)(f - 32); 58 | } 59 | static double php_celsius_to_fahrenheit(double c) 60 | { 61 | return (((double)9/5) * c) + 32 ; 62 | } 63 | 64 | 65 | /* {{{ proto double fahrenheit_to_celsius(double f) 66 | */ 67 | PHP_FUNCTION(fahrenheit_to_celsius) 68 | { 69 | int argc = ZEND_NUM_ARGS(); 70 | double f; 71 | 72 | if (zend_parse_parameters(argc, "d", &f) == FAILURE) 73 | return; 74 | RETURN_DOUBLE(php_fahrenheit_to_celsius(f)); 75 | } 76 | /* }}} */ 77 | 78 | /* {{{ proto double celsius_to_fahrenheit(double c) 79 | */ 80 | PHP_FUNCTION(celsius_to_fahrenheit) 81 | { 82 | int argc = ZEND_NUM_ARGS(); 83 | double c; 84 | 85 | if (zend_parse_parameters(argc, "d", &c) == FAILURE) 86 | return; 87 | RETURN_DOUBLE(php_celsius_to_fahrenheit(c)); 88 | } 89 | /* }}} */ 90 | 91 | /* {{{ proto string temperature_converter(double t, long mode) 92 | */ 93 | PHP_FUNCTION(temperature_converter) 94 | { 95 | int argc = ZEND_NUM_ARGS(); 96 | double t; 97 | zend_long mode = TEMP_CONVERTER_TO_CELSIUS; 98 | zend_string *result; 99 | 100 | /* 接收参数时|l表示此参数为long类型,并且为可选参数 */ 101 | if (zend_parse_parameters(argc, "d|l", &t, &mode) == FAILURE) 102 | return; 103 | 104 | switch (mode) 105 | { 106 | case TEMP_CONVERTER_TO_CELSIUS: 107 | /* strpprintf用来构建一个zend_string */ 108 | result = strpprintf(0, "华氏 %.2f 度, 摄氏 %.2f 度", t, php_fahrenheit_to_celsius(t)); 109 | RETURN_STR(result); 110 | case TEMP_CONVERTER_TO_FAHRENHEIT: 111 | result = strpprintf(0, "摄氏 %.2f 度, 华氏 %.2f 度", t, php_celsius_to_fahrenheit(t)); 112 | RETURN_STR(result); 113 | default: 114 | php_error(E_WARNING, "转换模式参数错误, 可接受的转换模式值为 1 或 2"); 115 | } 116 | } 117 | /* }}} */ 118 | 119 | /* {{{ proto array multiple_fahrenheit_to_celsius(array temperatures) 120 | */ 121 | PHP_FUNCTION(multiple_fahrenheit_to_celsius) 122 | { 123 | int argc = ZEND_NUM_ARGS(); 124 | HashTable *temperatures; 125 | zval *data; 126 | 127 | if (zend_parse_parameters(argc, "h", &temperatures) == FAILURE) 128 | return; 129 | 130 | if (zend_hash_num_elements(temperatures) == 0) { 131 | return; 132 | } 133 | array_init_size(return_value, zend_hash_num_elements(temperatures)); 134 | ZEND_HASH_FOREACH_VAL(temperatures, data) 135 | zval dup; 136 | ZVAL_COPY_VALUE(&dup, data); 137 | convert_to_double(&dup); 138 | add_next_index_double(return_value, php_fahrenheit_to_celsius(Z_DVAL(dup))); 139 | ZEND_HASH_FOREACH_END(); 140 | 141 | } 142 | /* }}} */ 143 | 144 | 145 | 146 | 147 | PHP_MINIT_FUNCTION(pib) 148 | { 149 | /* 根据要注册的常量类型,使用REGISTER_XXX_CONSTANT(),API和宏位于 Zend/zend_constants.h中. */ 150 | REGISTER_LONG_CONSTANT("TEMP_CONVERTER_TO_CELSIUS",TEMP_CONVERTER_TO_CELSIUS, CONST_CS|CONST_PERSISTENT); 151 | /* CONST_CS--区分大小写的常量,CONST_PERSISTENT--跨请求的持久常量 */ 152 | REGISTER_LONG_CONSTANT("TEMP_CONVERTER_TO_FAHRENHEIT",TEMP_CONVERTER_TO_FAHRENHEIT,CONST_CS|CONST_PERSISTENT); 153 | return SUCCESS; 154 | } 155 | 156 | 157 | PHP_MSHUTDOWN_FUNCTION(pib) 158 | { 159 | return SUCCESS; 160 | } 161 | 162 | 163 | 164 | PHP_RINIT_FUNCTION(pib) 165 | { 166 | #if defined(COMPILE_DL_PIB) && defined(ZTS) 167 | ZEND_TSRMLS_CACHE_UPDATE(); 168 | #endif 169 | return SUCCESS; 170 | } 171 | 172 | 173 | 174 | PHP_RSHUTDOWN_FUNCTION(pib) 175 | { 176 | return SUCCESS; 177 | } 178 | 179 | 180 | PHP_MINFO_FUNCTION(pib) 181 | { 182 | php_info_print_table_start(); 183 | php_info_print_table_header(2, "pib support", "enabled"); 184 | php_info_print_table_end(); 185 | 186 | } 187 | 188 | 189 | const zend_function_entry pib_functions[] = { 190 | PHP_FE(fahrenheit_to_celsius, arginfo_fahrenheit_to_celsius) 191 | PHP_FE(celsius_to_fahrenheit,arginfo_celsius_to_fahrenheit) 192 | PHP_FE(temperature_converter, arginfo_temperature_converter) 193 | PHP_FE(multiple_fahrenheit_to_celsius, arginfo_multiple_fahrenheit_to_celsius) 194 | PHP_FE_END 195 | }; 196 | 197 | 198 | zend_module_entry pib_module_entry = { 199 | STANDARD_MODULE_HEADER, 200 | "pib", 201 | pib_functions, 202 | PHP_MINIT(pib), 203 | PHP_MSHUTDOWN(pib), 204 | PHP_RINIT(pib), 205 | PHP_RSHUTDOWN(pib), 206 | PHP_MINFO(pib), 207 | PHP_PIB_VERSION, 208 | STANDARD_MODULE_PROPERTIES 209 | }; 210 | 211 | 212 | #ifdef COMPILE_DL_PIB 213 | #ifdef ZTS 214 | ZEND_TSRMLS_CACHE_DEFINE() 215 | #endif 216 | ZEND_GET_MODULE(pib) 217 | #endif 218 | 219 | -------------------------------------------------------------------------------- /code/1-5-2/pib/tests/001.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check for pib presence 3 | --SKIPIF-- 4 | 5 | --FILE-- 6 | 20 | --EXPECT-- 21 | pib extension is available 22 | -------------------------------------------------------------------------------- /image/1-1课程安排脑图.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dc236621911/php-internals-extended-development-course/c33c55c1f9a1b65a3335ad5cbcc81a6cee84de21/image/1-1课程安排脑图.png -------------------------------------------------------------------------------- /image/1-3PHP架构图.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dc236621911/php-internals-extended-development-course/c33c55c1f9a1b65a3335ad5cbcc81a6cee84de21/image/1-3PHP架构图.png -------------------------------------------------------------------------------- /image/1-3SAPI讲解.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dc236621911/php-internals-extended-development-course/c33c55c1f9a1b65a3335ad5cbcc81a6cee84de21/image/1-3SAPI讲解.png -------------------------------------------------------------------------------- /image/1-3hashtable结构.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dc236621911/php-internals-extended-development-course/c33c55c1f9a1b65a3335ad5cbcc81a6cee84de21/image/1-3hashtable结构.png -------------------------------------------------------------------------------- /image/1-3zval.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dc236621911/php-internals-extended-development-course/c33c55c1f9a1b65a3335ad5cbcc81a6cee84de21/image/1-3zval.png -------------------------------------------------------------------------------- /image/1-4-1vld-opcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dc236621911/php-internals-extended-development-course/c33c55c1f9a1b65a3335ad5cbcc81a6cee84de21/image/1-4-1vld-opcode.png -------------------------------------------------------------------------------- /image/1-4-2C程序运行栈.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dc236621911/php-internals-extended-development-course/c33c55c1f9a1b65a3335ad5cbcc81a6cee84de21/image/1-4-2C程序运行栈.png -------------------------------------------------------------------------------- /image/1-4-2linux程序运行内存分配图.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dc236621911/php-internals-extended-development-course/c33c55c1f9a1b65a3335ad5cbcc81a6cee84de21/image/1-4-2linux程序运行内存分配图.jpg -------------------------------------------------------------------------------- /image/1-4-2op_array与execute_data关系.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dc236621911/php-internals-extended-development-course/c33c55c1f9a1b65a3335ad5cbcc81a6cee84de21/image/1-4-2op_array与execute_data关系.png -------------------------------------------------------------------------------- /image/1-4PHP编译执行过程.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dc236621911/php-internals-extended-development-course/c33c55c1f9a1b65a3335ad5cbcc81a6cee84de21/image/1-4PHP编译执行过程.png -------------------------------------------------------------------------------- /image/1-4生成的AST.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dc236621911/php-internals-extended-development-course/c33c55c1f9a1b65a3335ad5cbcc81a6cee84de21/image/1-4生成的AST.png -------------------------------------------------------------------------------- /image/1-5-0PHP扩展的开发过程.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dc236621911/php-internals-extended-development-course/c33c55c1f9a1b65a3335ad5cbcc81a6cee84de21/image/1-5-0PHP扩展的开发过程.jpg -------------------------------------------------------------------------------- /image/1-5-0扩展的生命周期.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dc236621911/php-internals-extended-development-course/c33c55c1f9a1b65a3335ad5cbcc81a6cee84de21/image/1-5-0扩展的生命周期.png -------------------------------------------------------------------------------- /image/1-7PHP生命周期-线程.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dc236621911/php-internals-extended-development-course/c33c55c1f9a1b65a3335ad5cbcc81a6cee84de21/image/1-7PHP生命周期-线程.png -------------------------------------------------------------------------------- /image/1-7PHP生命周期-进程.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dc236621911/php-internals-extended-development-course/c33c55c1f9a1b65a3335ad5cbcc81a6cee84de21/image/1-7PHP生命周期-进程.png -------------------------------------------------------------------------------- /image/1-7PHP运行流程思维导图.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dc236621911/php-internals-extended-development-course/c33c55c1f9a1b65a3335ad5cbcc81a6cee84de21/image/1-7PHP运行流程思维导图.png -------------------------------------------------------------------------------- /image/1-7php代码执行过程.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dc236621911/php-internals-extended-development-course/c33c55c1f9a1b65a3335ad5cbcc81a6cee84de21/image/1-7php代码执行过程.png -------------------------------------------------------------------------------- /附:PHP底层编码规则.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | --- 4 | # **译文::** 5 | --- 6 | 7 | --- 8 | 9 | # PHP 源码编码标准翻译 10 | 11 | 任何想要添加或更改PHP源码的开发者都应该遵循这个文档下面列出的几个标准。此文件从PHP v3.0版本的开发末期阶段被添加,PHP的代码还没有完全遵循下面的标准, 但是会一直朝着这方向发展, 现在PHP5已经发布了, 大部分已经遵循了下面的规则 12 | 13 | --- 14 | 15 | ## 代码实现规则 16 | 17 | 1. **在源文件和技术手册中对你的代码做好描述** 18 | 19 | 2. **函数不应该释放传入的指针资源** 20 | 例如 `function int mail(char *to, char *from)` 不应该释放参数 to 或 from . 21 | 22 | 除了以下情况: 23 | * 函数本身被设计为释放资源 例如 efree() . 24 | * 给定的函数参数包含一个布尔值, 这个参数被用来控制是否释放,如果为真 则函数释放参数, 如果为假 则不释放 . 25 | * 与底层解析器程序紧密集成的令牌缓存或内存拷贝开销最小的语法分析器生成器(GNU bison)代码,(可以理解为C语言内嵌的一些汇编代码) . 26 | 27 | 3. **在同一个模块(module)中和其它函数耦合和彼此依赖的函数,应做好注释并用static声明。最好避免有这种情况** 28 | 29 | 4. **尽量使用definitions和macros,使常量有一个有意义的名字方便操作。除了0和1分别被当作false, true时可以例外,所有其它使用数值常量指定不同行为的情况都应该使用#define** 30 | 31 | 5. **当编写和字符串处理相关的函数时,要知道PHP会记住每个字符串的长度属性(使用zend_parse_parameters_ex获取到的字符串长度),不要用strlen来计算它的长度(如果不是\0结尾的字符串strlen的计算是不准确的) .** 32 | 这样编写的函数能充分利用长度属性,对效率和二进制安全都有好处。 33 | 34 | 使用函数改变字符串并获得他们的新长度的同时,应该返回新的长度,这样就不用strlen重新计算了(例如 php_addslashes()) . 35 | 36 | 6. **绝对不要使用 strncat()函数. 如果你确定知道你在做什么. 请再三查看该函数的使用手册, 想清楚再使用. 即使这样也请尽量避免使用.** 37 | 38 | 7. **在PHP的源码部分使用宏 PHP_* , 在Zend引擎源码部分使用宏 ZEND_*** 39 | 40 | 尽管几乎所有的宏 PHP_* 只是宏 ZEND_* 的一个别名. 但是它可以使代码的阅读性更好, 更容易理解你调用的是哪种宏 . 41 | 42 | 8. **当注释掉一段代码的时候不要只使用 #if 0 来声明.** 43 | 44 | 用git的账号加上_0 来代替, 例如 #if FOO_0, FOO表示你的git账号. 这使得更容易跟踪代码被注释掉了的原因, 尤其是在捆绑库的时候. 45 | 46 | 9. **不要定义不可用的函数** 47 | 48 | 例如 一个库缺少一个函数或者这个函数没有定义响应的PHP版本 . 如果在使用方法的时候也没有报出方法不存在的运行时错误 , 用户需要使用 function_exists() 去测试这个方法是否存在 . 49 | 50 | 10. **emalloc(), efree(), estrdup() 等函数是对标准C库的一个封装.** 51 | 52 | 这些方法实现了一个内部安全的机制,以确保回收没有被释放的内存 , 而且这些函数在debug模式下还提供了非常有用的分配和溢出的信息 . 53 | 54 | 在绝大多数情况下, 引擎中使用的内存必须使用 emalloc()函数来分配 . 55 | 56 | 只有在有限情况下才可能需要用malloc()等C库函数来控制或释放内存 比如使用第三方库 , 或者当这块内存的生命周期需要在多个请求中生存的时候 . 57 | 58 | --- 59 | 60 | ## 用户函数/方法名编码规则约定 61 | 62 | 1. **用户级函数的函数名应该用封闭的PHP_FUNCTION()宏** 63 | 64 | 函数名应该是小写的,用字母和下划线组合,尽量缩短函数名 ,但也不要用缩写,会降低函数名本身的的可读性 : 65 | ```code 66 | Good: 67 | 'mcrypt_enc_self_test' 68 | 'mysql_list_fields' 69 | 70 | Ok: 71 | 'mcrypt_module_get_algo_supported_key_sizes' 72 | (could be 'mcrypt_mod_get_algo_sup_key_sizes'?) 73 | 'get_html_translation_table' 74 | (could be 'html_get_trans_table'?) 75 | 76 | Bad: 77 | 'hw_GetObjectByQueryCollObj' 78 | 'pg_setclientencoding' 79 | 'jf_n_s_i' 80 | ``` 81 | 2. **如果函数是父集的一部分, 父集名称应该包含在这个函数名中** 82 | 83 | 并且应该明确该函数在父集中的关联性, 应该用 parent_* 形式来命名 , 例如和foo相关的一组函数集 : 84 | ```code 85 | Good: 86 | 'foo_select_bar' 87 | 'foo_insert_baz' 88 | 'foo_delete_baz' 89 | 90 | Bad: 91 | 'fooselect_bar' 92 | 'fooinsertbaz' 93 | 'delete_foo_baz' 94 | ``` 95 | 3. **功能型内部使用的函数名应该加前缀_php_** 96 | 97 | 紧随其后的应该是一个单词或用下划线分割的一组词, 并用小写字母描述这个方法 . 98 | 如果可以的话, 这里尽量加static关键字 [这条可以去参考PHP源码里的_php_stream_write_buffer函数] . 99 | 100 | 4. **变量名必须是有意义的. 避免使用一个字母的变量名** 101 | 102 | 除非这个变量名没有实际意义或微不足道的意义. 例如for循环中的i . 103 | 104 | 5. **变量名应该用小写字母. 词与词之间用下划线分割** 105 | 106 | 6. **方法名如'studlyCaps'(可参考驼峰命名法) 命名约定.** 107 | 108 | 尽量简化名字的字母个数,开头的字母名字是小写的并且名字新词的首字母是大写的 : 109 | ```code 110 | Good: 111 | 'connect()' 112 | 'getData()' 113 | 'buildSomeWidget()' 114 | 115 | Bad: 116 | 'get_Data()' 117 | 'buildsomewidget' 118 | 'getI()' 119 | ``` 120 | 121 | 7. **类应该给出描述性的名称. 尽可能避免使用缩写.** 122 | 123 | 每个词的类名应该用大写字母开始. 没有下划线分隔符(CamelCaps从一个大写字母开始) , 类名应该有"父集"的名称前缀(例如扩展的名称[可参考PDO的命名规则]) : 124 | ```code 125 | Good: 126 | 'Curl' 127 | 'FooBar' 128 | 129 | Bad: 130 | 'foobar' 131 | 'foo_bar' 132 | ``` 133 | --- 134 | 135 | ## 内部函数命名约定 136 | 137 | 1. **为了避免冲突,外部API函数应该用'php_modulename_function()' 这种形式命名** 138 | 139 | 并且小写,用下划线分割. 对外暴露的API必须定义在你自己的 'php_modulename.h'头文件中. 例如 : `PHPAPI char *php_session_create_id(PS_CREATE_SID_ARGS);` 140 | 141 | 不对外暴露的api不要定义在你的'php_modulename.h' 头文件中,而且应该用 static关键字修饰. 例如 : `static int php_session_destroy(TSRMLS_D)` 142 | 143 | 2. **主模块的源文件必须命名为 'modulename.c'.[ modulename 是你自己起的模块的名字 ]** 144 | 145 | 3. **主模块的头文件被包含在其他的源文件中,必须命名为'php_modulename.h'.** 146 | 147 | --- 148 | 149 | ## 语法和缩进 150 | 151 | 1. **不要使用C++风格的注释(也就是 // 注释)** 152 | 153 | 用C风格的注释去代替它. PHP是用C写的, 并且意图在任何 ANSI-C 编译器下编译 . 154 | 155 | 尽管许多编译器能够支持在C代码中使C++风格的注释, 但是为了确保你的代码兼容所有编译器最好还是使用C风格的注释 . 156 | 157 | 唯一例外的是在win32下的代码, 因为Win32 使用的是 MS-Visual C++ ,这个编译器是明确支持在C代码中使用C++风格注释的 . 158 | 159 | 2. **使用K&R 风格. 当然 我们不能也不想强迫任何人用或不用这种风格** 160 | 161 | 但是 最起码, 当你写的代码提交到PHP的核心 或 标准模块之一 时, 请保持 K&R 风格 . 162 | 163 | 这种风格可以应用到所有事物中去, 上到函数声明语法 下到缩进 注释风格 . 164 | 165 | 也可去看缩进风格 Indentstyle: http://www.catb.org/~esr/jargon/html/I/indent-style.html 166 | 167 | 3. **慷慨的空格和括号** 168 | 169 | 变量的声明和语句块之间留一个空行, 同样在代码逻辑语句块之间也要有空行. 170 | 171 | 两个函数之间留一个空行,两个更好 : 172 | ```code 173 | if (foo) { 174 | bar; 175 | } 176 | 177 | to: 178 | 179 | if(foo)bar; 180 | ``` 181 | 182 | 4. **使用tab制表符进行缩进,一个tab占用四个空格** 183 | 184 | 5. **预处理语句(例如 #if)必须写在第一列,如果要缩进预处理语句也要把 # 号放在一行的开始, 紧接着是任意数量的空格.** 185 | 186 | --- 187 | 188 | ## 测试 189 | 190 | 1. **PHP扩展应该用*.phpt 来测试. 请阅读 README.TESTING.** 191 | 192 | --- 193 | 194 | ## 文档和可折叠钩子(Documentation and Folding Hooks) 195 | 196 | 为了确保在线文档与代码保持一致 , 每个用户级函数在有它的用户级函数原型之前,应该有一行简单的对函数的描述 . 197 | 198 | 它看起来应该是这样的: 199 | ```c 200 | /* {{{ proto int abs(int number) 201 | Returns the absolute value of the number */ 202 | PHP_FUNCTION(abs) 203 | { 204 | ... 205 | } 206 | /* }}} */ 207 | ``` 208 | 209 | 在Emacs 和 vim 中 符号 {{{ 在折叠模式下 默认是折叠符号 (set fdm=marker) . 210 | 211 | 在处理大文件的时候折叠是非常有用的, 因为你能快速滚动文件并展开你希望工作的功能 , 符号 }}} 在每个函数的结尾标志着折叠的结束, 并且应该在单独的一行 . 212 | 213 | 那里的"原型(proto)"关键字只是辅助doc/genfuncsummary脚本生成所有函数摘要的 , 该关键字在函数原型的前面允许我们放在折叠块里,不影响函数摘要 . 214 | 215 | 可选参数像下面这样写: 216 | ```c 217 | /* {{{ proto object imap_header(int stream_id, int msg_no [, int from_length [, int subject_length [, string default_host]]]) 218 | Returns a header object with the defined parameters */ 219 | ``` 220 | 没错, 请保持原型在单独的一行, 即使这行可能会很长. 221 | 222 | --- 223 | 224 | ## 新的或实验性的函数 225 | 226 | 在首次公开一套新的函数集实现时 , 为了减少相关问题有人建议在这个函数的目录包含一个有“实验”标签的文件 , 并且这个函数在最初实现的时候要遵循下面的标准前缀约定 . 227 | 228 | EXPERIMENTAL文件应包括以下信息: 229 | * 任何编辑信息(已知的bugs, 模块的未来方向) 230 | * 不适合用git comments的当前的状态记录 231 | 232 | 一般新特性应该提交到PECL或实验分支,除非有特殊原因会直接添加到核心发布 233 | 234 | --- 235 | 236 | ## 别名 & 遗留文档(Aliases & Legacy Documentation) 237 | 238 | 对同一个名称,你也可能有一些弃用的别名, 例如 somedb_select_result and somedb_selectresult 239 | 240 | 文档只会记录最新的名字, 别名会记录在父函数的文档列表中. 241 | 242 | 为了便于参考,对于功能相同的名称不同的用户函数(如highlight_file和show_source),应分别描述说明。 243 | 244 | 描述函数别名的原型仍应包括在内 , 只要这部分代码能够被维护, 功能和名称应该尽可能保持向后兼容 245 | 246 | --- 247 | 248 | --- 249 | # **原文::** 250 | --- 251 | 252 | --- 253 | 254 | ```code 255 | ======================== 256 | PHP Coding Standards 257 | ======================== 258 | 259 | This file lists several standards that any programmer adding or changing 260 | code in PHP should follow. Since this file was added at a very late 261 | stage of the development of PHP v3.0, the code base does not (yet) fully 262 | follow it, but it's going in that general direction. Since we are now 263 | well into version 5 releases, many sections have been recoded to use 264 | these rules. 265 | 266 | Code Implementation 267 | ------------------- 268 | 269 | 0. Document your code in source files and the manual. [tm] 270 | 271 | 1. Functions that are given pointers to resources should not free them 272 | 273 | For instance, ``function int mail(char *to, char *from)`` should NOT free 274 | to and/or from. 275 | 276 | Exceptions: 277 | 278 | - The function's designated behavior is freeing that resource. E.g. efree() 279 | 280 | - The function is given a boolean argument, that controls whether or not 281 | the function may free its arguments (if true - the function must free its 282 | arguments, if false - it must not) 283 | 284 | - Low-level parser routines, that are tightly integrated with the token 285 | cache and the bison code for minimum memory copying overhead. 286 | 287 | 2. Functions that are tightly integrated with other functions within the 288 | same module, and rely on each other non-trivial behavior, should be 289 | documented as such and declared 'static'. They should be avoided if 290 | possible. 291 | 292 | 3. Use definitions and macros whenever possible, so that constants have 293 | meaningful names and can be easily manipulated. The only exceptions 294 | to this rule are 0 and 1, when used as false and true (respectively). 295 | Any other use of a numeric constant to specify different behavior 296 | or actions should be done through a #define. 297 | 298 | 4. When writing functions that deal with strings, be sure to remember 299 | that PHP holds the length property of each string, and that it 300 | shouldn't be calculated with strlen(). Write your functions in such 301 | a way so that they'll take advantage of the length property, both 302 | for efficiency and in order for them to be binary-safe. 303 | Functions that change strings and obtain their new lengths while 304 | doing so, should return that new length, so it doesn't have to be 305 | recalculated with strlen() (e.g. php_addslashes()) 306 | 307 | 5. NEVER USE strncat(). If you're absolutely sure you know what you're doing, 308 | check its man page again, and only then, consider using it, and even then, 309 | try avoiding it. 310 | 311 | 6. Use ``PHP_*`` macros in the PHP source, and ``ZEND_*`` macros in the Zend 312 | part of the source. Although the ``PHP_*`` macro's are mostly aliased to the 313 | ``ZEND_*`` macros it gives a better understanding on what kind of macro 314 | you're calling. 315 | 316 | 7. When commenting out code using a #if statement, do NOT use 0 only. Instead 317 | use "_0". For example, #if FOO_0, where FOO is your 318 | git user foo. This allows easier tracking of why code was commented out, 319 | especially in bundled libraries. 320 | 321 | 8. Do not define functions that are not available. For instance, if a 322 | library is missing a function, do not define the PHP version of the 323 | function, and do not raise a run-time error about the function not 324 | existing. End users should use function_exists() to test for the 325 | existence of a function 326 | 327 | 9. Prefer emalloc(), efree(), estrdup(), etc. to their standard C library 328 | counterparts. These functions implement an internal "safety-net" 329 | mechanism that ensures the deallocation of any unfreed memory at the 330 | end of a request. They also provide useful allocation and overflow 331 | information while running in debug mode. 332 | 333 | In almost all cases, memory returned to the engine must be allocated 334 | using emalloc(). 335 | 336 | The use of malloc() should be limited to cases where a third-party 337 | library may need to control or free the memory, or when the memory in 338 | question needs to survive between multiple requests. 339 | 340 | User Functions/Methods Naming Conventions 341 | ------------------ 342 | 343 | 1. Function names for user-level functions should be enclosed with in 344 | the PHP_FUNCTION() macro. They should be in lowercase, with words 345 | underscore delimited, with care taken to minimize the letter count. 346 | Abbreviations should not be used when they greatly decrease the 347 | readability of the function name itself:: 348 | 349 | Good: 350 | 'mcrypt_enc_self_test' 351 | 'mysql_list_fields' 352 | 353 | Ok: 354 | 'mcrypt_module_get_algo_supported_key_sizes' 355 | (could be 'mcrypt_mod_get_algo_sup_key_sizes'?) 356 | 'get_html_translation_table' 357 | (could be 'html_get_trans_table'?) 358 | 359 | Bad: 360 | 'hw_GetObjectByQueryCollObj' 361 | 'pg_setclientencoding' 362 | 'jf_n_s_i' 363 | 364 | 2. If they are part of a "parent set" of functions, that parent should 365 | be included in the user function name, and should be clearly related 366 | to the parent program or function family. This should be in the form 367 | of ``parent_*``:: 368 | 369 | A family of 'foo' functions, for example: 370 | 371 | Good: 372 | 'foo_select_bar' 373 | 'foo_insert_baz' 374 | 'foo_delete_baz' 375 | 376 | Bad: 377 | 'fooselect_bar' 378 | 'fooinsertbaz' 379 | 'delete_foo_baz' 380 | 381 | 3. Function names used by user functions should be prefixed 382 | with ``_php_``, and followed by a word or an underscore-delimited list of 383 | words, in lowercase letters, that describes the function. If applicable, 384 | they should be declared 'static'. 385 | 386 | 4. Variable names must be meaningful. One letter variable names must be 387 | avoided, except for places where the variable has no real meaning or 388 | a trivial meaning (e.g. for (i=0; i<100; i++) ...). 389 | 390 | 5. Variable names should be in lowercase. Use underscores to separate 391 | between words. 392 | 393 | 6. Method names follow the 'studlyCaps' (also referred to as 'bumpy case' 394 | or 'camel caps') naming convention, with care taken to minimize the 395 | letter count. The initial letter of the name is lowercase, and each 396 | letter that starts a new 'word' is capitalized:: 397 | 398 | Good: 399 | 'connect()' 400 | 'getData()' 401 | 'buildSomeWidget()' 402 | 403 | Bad: 404 | 'get_Data()' 405 | 'buildsomewidget' 406 | 'getI()' 407 | 408 | 7. Classes should be given descriptive names. Avoid using abbreviations where 409 | possible. Each word in the class name should start with a capital letter, 410 | without underscore delimiters (CamelCaps starting with a capital letter). 411 | The class name should be prefixed with the name of the 'parent set' (e.g. 412 | the name of the extension):: 413 | 414 | Good: 415 | 'Curl' 416 | 'FooBar' 417 | 418 | Bad: 419 | 'foobar' 420 | 'foo_bar' 421 | 422 | Internal Function Naming Conventions 423 | ---------------------- 424 | 425 | 1. Functions that are part of the external API should be named 426 | 'php_modulename_function()' to avoid symbol collision. They should be in 427 | lowercase, with words underscore delimited. Exposed API must be defined 428 | in 'php_modulename.h'. 429 | 430 | PHPAPI char *php_session_create_id(PS_CREATE_SID_ARGS); 431 | 432 | Unexposed module function should be static and should not be defined in 433 | 'php_modulename.h'. 434 | 435 | static int php_session_destroy() 436 | 437 | 2. Main module source file must be named 'modulename.c'. 438 | 439 | 3. Header file that is used by other sources must be named 'php_modulename.h'. 440 | 441 | 442 | Syntax and indentation 443 | ---------------------- 444 | 445 | 1. Never use C++ style comments (i.e. // comment). Always use C-style 446 | comments instead. PHP is written in C, and is aimed at compiling 447 | under any ANSI-C compliant compiler. Even though many compilers 448 | accept C++-style comments in C code, you have to ensure that your 449 | code would compile with other compilers as well. 450 | The only exception to this rule is code that is Win32-specific, 451 | because the Win32 port is MS-Visual C++ specific, and this compiler 452 | is known to accept C++-style comments in C code. 453 | 454 | 2. Use K&R-style. Of course, we can't and don't want to 455 | force anybody to use a style he or she is not used to, but, 456 | at the very least, when you write code that goes into the core 457 | of PHP or one of its standard modules, please maintain the K&R 458 | style. This applies to just about everything, starting with 459 | indentation and comment styles and up to function declaration 460 | syntax. Also see Indentstyle. 461 | 462 | Indentstyle: http://www.catb.org/~esr/jargon/html/I/indent-style.html 463 | 464 | 3. Be generous with whitespace and braces. Keep one empty line between the 465 | variable declaration section and the statements in a block, as well as 466 | between logical statement groups in a block. Maintain at least one empty 467 | line between two functions, preferably two. Always prefer:: 468 | 469 | if (foo) { 470 | bar; 471 | } 472 | 473 | to: 474 | 475 | if(foo)bar; 476 | 477 | 4. When indenting, use the tab character. A tab is expected to represent 478 | four spaces. It is important to maintain consistency in indenture so 479 | that definitions, comments, and control structures line up correctly. 480 | 481 | 5. Preprocessor statements (#if and such) MUST start at column one. To 482 | indent preprocessor directives you should put the # at the beginning 483 | of a line, followed by any number of whitespace. 484 | 485 | Testing 486 | ------- 487 | 488 | 1. Extensions should be well tested using *.phpt tests. Read about that 489 | in README.TESTING. 490 | 491 | Documentation and Folding Hooks 492 | ------------------------------- 493 | 494 | In order to make sure that the online documentation stays in line with 495 | the code, each user-level function should have its user-level function 496 | prototype before it along with a brief one-line description of what the 497 | function does. It would look like this:: 498 | 499 | /* {{{ proto int abs(int number) 500 | Returns the absolute value of the number */ 501 | PHP_FUNCTION(abs) 502 | { 503 | ... 504 | } 505 | /* }}} */ 506 | 507 | The {{{ symbols are the default folding symbols for the folding mode in 508 | Emacs and vim (set fdm=marker). Folding is very useful when dealing with 509 | large files because you can scroll through the file quickly and just unfold 510 | the function you wish to work on. The }}} at the end of each function marks 511 | the end of the fold, and should be on a separate line. 512 | 513 | The "proto" keyword there is just a helper for the doc/genfuncsummary script 514 | which generates a full function summary. Having this keyword in front of the 515 | function prototypes allows us to put folds elsewhere in the code without 516 | messing up the function summary. 517 | 518 | Optional arguments are written like this:: 519 | 520 | /* {{{ proto object imap_header(int stream_id, int msg_no [, int from_length [, int subject_length [, string default_host]]]) 521 | Returns a header object with the defined parameters */ 522 | 523 | And yes, please keep the prototype on a single line, even if that line 524 | is massive. 525 | 526 | New and Experimental Functions 527 | ----------------------------------- 528 | To reduce the problems normally associated with the first public 529 | implementation of a new set of functions, it has been suggested 530 | that the first implementation include a file labeled 'EXPERIMENTAL' 531 | in the function directory, and that the functions follow the 532 | standard prefixing conventions during their initial implementation. 533 | 534 | The file labelled 'EXPERIMENTAL' should include the following 535 | information:: 536 | 537 | Any authoring information (known bugs, future directions of the module). 538 | Ongoing status notes which may not be appropriate for Git comments. 539 | 540 | In general new features should go to PECL or experimental branches until 541 | there are specific reasons for directly adding it to the core distribution. 542 | 543 | Aliases & Legacy Documentation 544 | ----------------------------------- 545 | You may also have some deprecated aliases with close to duplicate 546 | names, for example, somedb_select_result and somedb_selectresult. For 547 | documentation purposes, these will only be documented by the most 548 | current name, with the aliases listed in the documentation for 549 | the parent function. For ease of reference, user-functions with 550 | completely different names, that alias to the same function (such as 551 | highlight_file and show_source), will be separately documented. The 552 | proto should still be included, describing which function is aliased. 553 | 554 | Backwards compatible functions and names should be maintained as long 555 | as the code can be reasonably be kept as part of the codebase. See 556 | /phpdoc/README for more information on documentation. 557 | ``` 558 | 559 | 560 | 561 | --------------------------------------------------------------------------------