Skip to content

Latest commit

 

History

History
467 lines (378 loc) · 27.1 KB

7.扩展的结构 [ Extension structure ].md

File metadata and controls

467 lines (378 loc) · 27.1 KB
/*
|----------------------------------------
| Extension structure
| @weiChen translate, <farwish@live.com>
| @MIT License
*/

http://php.net/manual/zh/internals2.structure.php

组成扩展的文件 [ Files which make up an extension ]

=== (中文doc) http://php.net/manual/zh/internals2.structure.basics.php

基础结构 [ Basic constructs ]

=== http://php.net/manual/zh/internals2.structure.basics.php

C被现代定义为一门非常低层的语言。这意味着PHP理所当然没有许多特性的内置支持,
如反射,动态加载模块,边界检查,线程安全的数据管理和各种有用的数据结构,包括链表和哈希表。
同时,C是语言支持和功能的共同点。给予足够的工作,这些概念没有不可能的;Zend引擎使用它们。

许多努力被投入到使Zend API可扩展和可理解,但在经验不足的人看来是多余的和不必要的扩展上,
C强制确定的必要的声明。所有那些结构,本节详细是在Zend Engine2和2的"write once and forget"中。
这里有一些PHP5.3的ext_skel生成的 php_counter.h 和 counter.c中的摘录,显示了预生成的声明:

Note:精明的读者会注意到,真实文件中有许多声明这里没有显示。那些声明具体到各样的Zend子系统并在其它合适的地方讨论。

extern zend_module_entry counter_module_entry;
#define phpext_counter_ptr &counter_module_entry

#ifdef PHP_WIN32
#    define PHP_COUNTER_API __declspec(dllexport)
#elif defined(__GNUC__) && __GNUC__ >= 4
#    define PHP_COUNTER_API __attribute__ ((visibility("default")))
#else
#    define PHP_COUNTER_API
#endif

#ifdef ZTS
#include "TSRM.h"
#endif

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "php.h"
#include "php_ini.h"
#include "ext/standard/info.h"
#include "php_counter.h"

/* ... */

#ifdef COMPILE_DL_COUNTER
ZEND_GET_MODULE(counter)
#endif

  • 包含 counter_module_entry 的行定义了一个全局变量,一个宏指针指向它,也包含提供给扩展的 zend_module_entry。 尽管后面的讨论考虑“true”全局变量的缺点,这种用法是故意的;Zend采取预防措施来防止滥用这个变量。

  • PHP_COUNTER_API 定义用来给非PHP函数的想要开放给其它模块用的模块使用。

  • counter扩展没有定义任何东西,在最终版本的头文件中,这个宏已被移除。PHPAPI在其它地方声明, 在标准扩展中应用使实用的phpinfo()功用函数可用于其它扩展。

  • 引入 TSRM.h 可以跳过,如果PHP或扩展编译时没加 thread-safety 选项;因为在那种情况下,没有使用 TSRM。

  • 一个标准的引用列表,给出了扩展自己的 php_counter.h。configure 生成的 config.h 给出确定的扩展访问。 php.h 是整个PHP和Zend APIs的入口。php_ini.h 为运行时配置入口添加APIs。不是所有的扩展要使用它。 最后,/ext/standard/info.h 导入上述 phpinfo() 功用API。

  • COMPILE_DL_COUNTER 只由 configure 定义,如果counter启用了扩展并作为一个动态加载的模块而不是静态链接到PHP中。 ZEND_GET_MODULE 定义了一个Zend用来在运行时获取扩展的 zend_module_entry 的极小的函数。

Note:偷看了 main/php_config.h 的聪明读者在尝试构建静态启用地counter扩展会注意到,同样有一个定义的 HAVE_COUNTER 常量,源代码未检查。 有一个简单的未检测的原因:没必要。如果扩展没有启用,源文件永远不会被编译。

zend_module结构 [ The zend_module structure ]

=== http://php.net/manual/zh/internals2.structure.modstruct.php

PHP扩展的主要源文件含几个给C程序员的新的结构体。最重要的是,开始一个新扩展时首先接触的是 zend_module 结构体。 这个结构体包含大量的信息来告诉Zend引擎有关扩展的依赖,版本,回调,和其它关键数据。这个结构体随着时间的推移已突变; 这节将聚焦在从 PHP5.2 开始出现的结构体上,并识别少量在 PHP5.3 中改变的部分。

counter.c 中的 zend_module 声明看起来像在任何代码写入前。例子文件由 ext_skel --extname=counter,包括移除一些过时的结构:

Example #1 zend_module declaration in the counter extension

/* {{{ counter_module_entry
 */
zend_module_entry counter_module_entry = {
  STANDARD_MODULE_HEADER,
  "counter",
  counter_functions,
  PHP_MINIT(counter),
  PHP_MSHUTDOWN(counter),
  PHP_RINIT(counter),        /* Replace with NULL if there's nothing to do at request start */
  PHP_RSHUTDOWN(counter),    /* Replace with NULL if there's nothing to do at request end */
  PHP_MINFO(counter),
  "0.1", /* Replace with version number for your extension */
  STANDARD_MODULE_PROPERTIES
};
/* }}} */

乍一看,这有点令人畏惧,但大部分非常容易理解。这是 PHP5.3 zend_modules.h 中的 zend_module 定义:

Example #2 zend_module definition in PHP5.3

struct _zend_module_entry {
  unsigned short size;
  unsigned int zend_api;
  unsigned char zend_debug;
  unsigned char zts;
  const struct _zend_ini_entry *ini_entry;
  const struct _zend_module_dep *deps;
  const char *name;
  const struct _zend_function_entry *functions;
  int (*module_startup_func)(INIT_FUNC_ARGS);
  int (*module_shutdown_func)(SHUTDOWN_FUNC_ARGS);
  int (*request_startup_func)(INIT_FUNC_ARGS);
  int (*request_shutdown_func)(SHUTDOWN_FUNC_ARGS);
  void (*info_func)(ZEND_MODULE_INFO_FUNC_ARGS);
  const char *version;
  size_t globals_size;
#ifdef ZTS
  ts_rsrc_id* globals_id_ptr;
#else
  void* globals_ptr;
#endif
  void (*globals_ctor)(void *global TSRMLS_DC);
  void (*globals_dtor)(void *global TSRMLS_DC);
  int (*post_deactivate_func)(void);
  int module_started;
  unsigned char type;
  void *handle;
  int module_number;
};

这些中的许多字段永远不会被一个扩展开发者接触到。有一些标准宏自动把它们设置成合适的值。 宏 STANDARD_MODULE_HEADER 填充所有的值直到 deps 字段。另外,宏 STANDARD_MODULE_HEADER_EX 会把 deps 字段设置为空来给开发者使用。 开发者永远为 name 到 version 的一切富有责任。然后,宏 STANDARD_MODULE_PROPERTIES 会填充其余的结构, 或者宏 STANDARD_MODULE_PROPERTIES_EX 可以用来把扩展的全局变量和使用后的函数字段设为非填充的。许多新式的扩展会使用模块的全局变量。

Note:如果开发者完全手工填充它们,不借助任何快捷的宏,表格给出了每个字段该有的值。这并不推荐。许多字段的“正确的值”可能会改变。
只要有可能就使用宏。

 -----------------------------------------------------------------------------------------------------------------------------
|                                模块结构体字段的值                                                                           |
|-----------------------------------------------------------------------------------------------------------------------------|
| 字段                      值                              描述                                                              |
|-----------------------------------------------------------------------------------------------------------------------------|
| size ①②③                sizeof(zend_module_entry)     结构体以字节为单位的大小。                                            |
|-----------------------------------------------------------------------------------------------------------------------------|
| zend_api ①②③            ZEND_MODULE_API_NO            这个模块编译时的Zend API版本。                                        |
|-----------------------------------------------------------------------------------------------------------------------------|
| zend_debug ①②③          ZEND_DEBUG                    标示模块是否在开启debug的情况下编译的标志                             |
|-----------------------------------------------------------------------------------------------------------------------------|
| zts ①②③                 USING_ZTS                     标示模块是否在ZTS启用的情况下编译的标志。                             |
|-----------------------------------------------------------------------------------------------------------------------------|
| ini_entry ①③            NULL                          这个指针由Zend在内部使用来保持模块声明的 INI 记录的本地引用。         |
|-----------------------------------------------------------------------------------------------------------------------------|
| deps ③                  NULL                          指向模块的依赖关系列表的指针。                                        |
|-----------------------------------------------------------------------------------------------------------------------------|
| name                    "mymodule"                    模块名称。这是简称,例如"spl"或"standard"。                           |
|-----------------------------------------------------------------------------------------------------------------------------|
| functions               mymodule_functions            指向模块函数表的指针,Zend用来把模块中的函数公开给用户层。            |
|-----------------------------------------------------------------------------------------------------------------------------|
| module_startup_func     PHP_MINIT(mymodule)           Zend在模块加载进PHP的一个特殊实例时第一次调用的回调函数。             |
|-----------------------------------------------------------------------------------------------------------------------------|
| module_shutdown_func    PHP_MSHUTDOWN(mymodule)       Zend在模块从PHP的一个特殊实例中卸载时调用的回调函数,通常在最后关机时.|
|-----------------------------------------------------------------------------------------------------------------------------|
| request_startup_func    PHP_RINT(mymodule)            Zend在每个请求之初调用的回调函数。由于调用它每个请求都有消耗,        |
|                                                       这个应该尽可能短或为NULL。                                            |
|-----------------------------------------------------------------------------------------------------------------------------|
| request_shutdown_func   PHP_RSHUTDOWN(mymodule)       Zend在每个请求最后调用的回调函数。由于调用它每个请求都有消耗,        |
|                                                       这个应该尽可能短或为NULL。                                            |
|-----------------------------------------------------------------------------------------------------------------------------|
| info_func               PHP_MINFO(mymodule)           Zend在phpinfo()函数调用时调用的回调函数。                             |
|-----------------------------------------------------------------------------------------------------------------------------|
| version                 NO_VERSION_YET                模块的版本,由开发者指定。推荐的版本号预期格式是version_compare()     |
|                                                       (如 "1.0.5-dev"),或者一个CVS或者SVN版本号(如 "$Rev:322138 $")。      |
|-----------------------------------------------------------------------------------------------------------------------------|
| globals_size ①④⑤⑥       sizeof(zend_mymodule_globals) 含有模块全局变量的数据结构的大小,如果有。                            |
|-----------------------------------------------------------------------------------------------------------------------------|
| globals_id_ptr ①④⑤      &mymodule_globals_id     |    这两个只有一个可以存在,取决于 USING_ZTS 常亮是否为 TRUE。            |
|--------------------------------------------------|    前者是一个针对模块全局变量的TSRM的分配表的索引,                      |
| globals_ptr ①④⑤⑥⑧       &mymodule_globals        |    后面的是指向全局变量的指针。                                          |
|-----------------------------------------------------------------------------------------------------------------------------|
| globals_ctor ④⑤⑥        PHP_GINIT(mymodule)           函数在任何 module_startup_func 之前调用来初始化模块的全局变量。       |
|-----------------------------------------------------------------------------------------------------------------------------|
| globals_dtor ④⑤⑥        PHP_GSHUTDOWN(mymodule)       函数在任何 module_shutdown_func 之后调用来释放模块的全局变量。        |
|-----------------------------------------------------------------------------------------------------------------------------|
| post_deactivate_func ④  ZEND_MODULE_POST_ZEND_DEACTIVATE_N(mymodule)  函数由Zend在请求结束后调用。难得使用。                |
|-----------------------------------------------------------------------------------------------------------------------------|
| module_started ①⑨④      0                        |                                                                          |
|--------------------------------------------------|    这些字段用于Zend内部跟踪信息。                                        |
| type ①⑨④                0                        |                                                                          |
|--------------------------------------------------|                                                                          |
| handle ①⑨④              NULL                     |                                                                          |
|--------------------------------------------------|                                                                          |
| module_number ①⑨④       0                        |                                                                          |
| ----------------------------------------------------------------------------------------------------------------------------|
| ① 这个字段不用于模块开发者使用。                                                                                            |
| ② 这个字段由 STANDARD_MODULE_HEADER_EX 填充。                                                                               |
| ③ 这个字段由 STANDARD_MODULE_HEADER 填充。                                                                                  |
| ④ 这个字段由 STANDARD_MODULE_PROPERTIES 填充。                                                                              |
| ⑤ 这个字段由 NO_MODULE_GLOBALS 填充。                                                                                       |
| ⑥ 这个字段由 PHP_MODULE_GLOBALS 填充。                                                                                      |
| ⑦ 这个字段只在 USING_ZTS 为 TRUE 时存在。                                                                                   |
| ⑧ 这个字段只在 USING_ZTS 为 FALSE 时存在。                                                                                  |
| ⑨ 这个字段由 STANDARD_MODULE_PROPERTIES_EX 填充。                                                                           |
 -----------------------------------------------------------------------------------------------------------------------------

实际情况中的结构填充

要和所有这些字段打交道,知道以什么目的使用哪个是令人混淆的。这是"counter"例子扩展中的zend_module定义在修改后的最终形式。

Example #3 Counter扩展模块定义
```
/* {{{ counter_module_entry
*/
zend_module_entry counter_module_entry = {
  STANDARD_MODULE_HEADER,
  "counter",
  counter_functions,
  PHP_MINIT(counter),
  PHP_MSHUTDOWN(counter),
  PHP_RINIT(counter),
  PHP_RSHUTDOWN(counter),
  PHP_MINFO(counter),
  NO_VERSION_YET,
  PHP_MODULE_GLOBALS(counter),
  PHP_GINIT(counter),
  PHP_GSHUTDOWN(counter),
  NULL,
  STANDARD_MODULE_PROPERTIES_EX
};
/* }}} */
```

* STANDARD_MODULE_HEADER 在模块没有定义任何依赖时使用。
* "counter"是模块名,用来定义模块传递给Zend的许多回调函数。"counter"在启动和关闭的时候使用模块,全局变量,和请求函数,
  并提供信息给 phpinfo(), 所以所有七个回调已被定义。
* 假定有一个很早就在文件内的类型是 zend_function_entry* 名字叫 counter_functions 的变量包含模块定义,
  列出了模块输出到用户空间的函数。
* NO_VERSION_YET 是一个告诉Zend 这个模块没有版本的特别好的方式。在一个真实的模块中,可能更正确的是把"1.0"放到里面。
* "counter" 使用每个模块的全局变量,所以使用了 PHP_MODULE_GLOBALS。
* 这个模块没有后关闭功能,所以使用了NULL。
* 当这个模块使用了全局变量,STANDARD_MODULE_PROPERTIES_EX 用来释放结构体。

5.2 与 5.3 之间的变化

没有任何东西。PHP5.2 和 PHP5.3 zend_modulede 的唯一区别是极少的常量关键字。  

扩展的全局变量 [ Extension globals ]

=== http://php.net/manual/zh/internals2.structure.globals.php

PHP中的全局变量介绍

在像C这样的语言中,一个全局变量可以在任何函数中访问不需要任何额外声明。这些传统的全局变量有一些缺点:  
* 阻拦任何特殊选项传递给编译器,一个全局变量可以被程序的任何地方的任何一块代码访问和改变,不管那块代码是否应该做这件事。
* 一个典型的全局变量不是线程安全的。
* 全局变量的名称和变量本身一样是全局变量。

PHP扩展的全局变量更适合称为"扩展状态",因为大部分模块必须记住它们在函数间调用的关系。  
"counter"扩展是满足这个需要的完美例子:  

基础接口调用一个有持续存在的值的counter。对Zend和PHP陌生的程序员可能要在 counter.c 中做些事来存储这个值:

Example #1 存储基础counter接口值的错误方式
```
/* ... */
static long basic_counter_value;

/* ... */

PHP_FUNCTION(counter_get)
{
  RETURN_LONG(basic_counter_value);
}
```

表面上这似乎是一个可行的解决方案,而且事实上,在一个简单的测试例子中它将正确发挥作用。然而,  

有许多情况是不止一个PHP的副本运行在相同的线程里,这意味着超过一个的counter模块实例。
这些多线程突然共用相同的counter值,这显然是不可取的。
另一个问题是当考虑到另一个扩展可能总有一天碰巧会有一个同名的全局变量,
并由于C作用域规则,这有潜在可能性导致编译失败或更坏的运行错误。更精细的事是必要的,
所以存在Zend对每个模块全局变量线程安全的支持。

声明全局变量

不管一个模块只使用一个全局变量或许多的,它们必须定义在结构体中,并且那个结构体必须被声明。  
有一些协助以避免模块间名称冲突的宏:  
ZEND_BEGIN_MODULE_GLOBALS(),ZEND_END_MODULE_GLOBALS(),和ZEND_DECLARE_MODULE_GLOBALS()。  
这三个都作为模块短名称的一个参数,其中在counter模块中是一个简单的"计数器"。  
这是 php_counter.h 全局变量的结构定义:  

Example #2 counter模块的全局变量
```
ZEND_BEGIN_MODULE_GLOBALS(counter)
  long        basic_counter_value;
ZEND_END_MODULE_GLOBALS(counter)
```

这是 counter.c 中的定义:  

Example #3 counter模块的全局变量结构体声明
```
ZEND_DECLARE_MODULE_GLOBALS(counter)
```

访问模块全局变量

正如上面讨论的,每个模块的全局变量在C结构体中声明,名字被Zend宏遮盖。  
作为结果,访问结构体成员的理想方式是使用深层次的宏。  
因此,大部分不是所有的扩展有声明的全局变量像这样在它们的头文件中:  

Example #4 每个模块的存取宏
```
#ifdef ZTS
#define COUNTER_G(v) TSRMG(counter_globals_id, zend_counter_globals *, v)
#else
#define COUNTER_G(v) (counter_globals.v)
#endif
```

Note:这可能已经被Zend API普及至它自己的宏里,但是截至PHP5.3(和在写这篇文章时的PHP6),这并没有发生。  
全局访问器构造由 ext_skel 写入头部中,因此一般由扩展作者单独留下,除非它们希望改变访问器宏的名称。  

Note:COUNTER_G 是由 ext_skel 给出的宏的名称,但那个名称是非必要的,可以仅仅简单叫做 FOO。  

在counter扩展中的任何代码访问一个全局变量必须把它包裹在COUNTER_G宏中。  

警告:访问全局变量的任何函数必须要么由Zend的宏声明,有 TSRMLS_DC 最为它最后的参数,或在访问全局变量前调用 TSRMLS_FETCH 宏。  
更多信息参见TSRM文档。  

所以有这些事在脑子里,这是我们 counter_get() 的新版本:  
(  http://php.net/manual/zh/internals2.counter.function.counter-get.php )  

Example #5 存储基础counter接口值的正确方式
```
/* php_counter.h */
ZEND_BEGIN_MODULE_GLOBALS(counter)
  long        basic_counter_value;
ZEND_END_MODULE_GLOBALS(counter)

#ifdef ZTS
#define COUNTER_G(v) TSRMG(counter_globals_id, zend_counter_globals *, v)
#else
#define COUNTER_G(v) (counter_globals.v)
#endif

/* counter.c */
ZEND_DECLARE_MODULE_GLOBALS(counter)

/* ... */

PHP_FUNCTION(counter_get)
{
  RETURN_LONG(COUNTER_G(basic_counter_value));
}
```

这是一个正确的实现。然而,它不是完整的一个。一个扩展的生命周期这节将解释为什么。  

一个扩展的生命周期 [ Life cycle of an extension ]

=== http://php.net/manual/zh/internals2.structure.lifecycle.php

一个PHP扩展在其生命周期经过几个阶段。所有这些阶段都是开发者执行各种初始化,终止,或信息功能的机会。
Zend API 允许钩入PHP扩展存在的五个独立阶段,除去被PHP函数调用的。

加载,卸载,和请求

当Zend引擎运行时,它从客户端处理一个或更多请求。在传统的CGI实现中,这相当于一个进程的一次执行。  
然而,许多其它的实现,最显著的是Apache模块,可以映射许多请求到一个PHP进程上。  
一个PHP扩展可能因此在它的生命期中看到许多请求。  

概述

* 在 Zend API 中,当相关的PHP进程启动时,一个模块只有一次被加载进内存中。每个模块加载时,
  接收在 zend_module 结构体中指定的 "模块初始化" 函数的调用。
* 无论何时相关的PHP进程开始从客户端处理请求 - 即无论何时PHP解释器被告知开始工作 - 每个模块接收
  在 zend_module 结构体中指定的 "请求初始化" 函数的调用。
* 无论何时相关的PHP进程处理完一个请求,每个模块接收在 zend_module 结构体中指定的 "请求终止" 函数的调用。
* 当相关的PHP进程以有序的方式关闭时,一个给定的模块从内存中卸载。这个模块在这个时候接收在 zend_module 结构体中指定的
  "模块终止" 函数的调用。

做什么,什么时候做

有许多任务可能在任何这四点情况下执行。这个表格详情说明了许多共同的初始化和终止任务。  
 -----------------------------------------------------------------------------
|                         做什么,什么时候做                                  |
|-----------------------------------------------------------------------------|
| 模块初始化/终止                           请求初始化/终止                   |
|-----------------------------------------------------------------------------|
| 1.分配/释放 和 初始化模块全局变量         分配/释放 和 初始化具体请求变量   |
|-----------------------------------------------------------------------------|
| 2.注册/注销 类条目                                                          |
|-----------------------------------------------------------------------------|
| 3.注册/注销 INI条目                                                         |
|-----------------------------------------------------------------------------|
| 4.注册常量                                                                  |
 -----------------------------------------------------------------------------

phpinfo()回调

除了全局变量初始化和某些很少使用的回调,还有一部分模块的生命周期要检查:调用 phpinfo()。  
用户看到的调用输出,无论是文本或HTML或其它的,由产生调用时每个加载进PHP解释器的独立的扩展产生。  

为了提供格式中立的输出,头文件 "ext/standard/info.h" 提供了一个产生标准化显示元素的函数数组。  
具体来说,几个创建已存在相似表格的函数:  

php_info_print_table_start()  
  在phpinfo()输出中打开一个表格。没有参数。  

php_info_print_table_now()  
  在phpinfo()输出中打印一个表格行。没有参数,列的数量,加上相同数量的针对每列内容的文本 char* 参数。  

php_info_print_table_end()  
  关闭之前用php_info_print_table_start()打开的表格。没有参数。  

使用这四个函数,可以为扩展的几乎任何结合的特点产生状态信息。这是counter扩展中的回调信息:  

Example #1 counter的 PHP_MINFO 函数  
```
/* {{{ PHP_MINFO(counter) */
PHP_MINFO_FUNCTION(counter)
{
  char        buf[10];

  php_info_print_table_start();
  php_info_print_table_row(2, "counter support", "enabled");
  snprintf(buf, sizeof(buf), "%ld", COUNTER_G(basic_counter_value));
  php_info_print_table_row(2, "Basic counter value", buf);
  php_info_print_table_end();
}
/* }}} */
```

测试一个扩展 [ Testing an extension ]

=== http://php.net/manual/zh/internals2.structure.tests.php

良好编程的一个公认必要条件是测试。PHP提供了一种用于回归测试的测试单元,以一个叫run-test.php的脚本的形式。

特定模块的测试放在模块目录的 test/ 子目录内,有一个 .phpt 扩展。PHPT 格式的细节见:http://qa.php.net