PHP代码实现 1

$PHP-SRC/run-test.php

  • 因为如果在同一个进程中执行, 测试就会停止,后面的测试也将无法执行,php中有很多将脚本隔离的方法比如: system(),exec()等函数,这样可以使用主测试进程服务调度被测脚本和检测测试结果,通过这些外部调用执行测试。 php测试使用了proc_open()函数, 这样就可以保证测试脚本和被测试脚本之间能隔离开。

如果只需要简单的进程单向进程通道 可以使用 popen

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
function foo() {
echo date('Y-m-d H:i:s')."\n";
echo shell_exec('php -r \'sleep(1); echo date("Y-m-d H:i:s")." by shell_exec:blocking\n";\''); //阻塞
$pipe1 = popen('php -r \'sleep(2); echo date("Y-m-d H:i:s")." by popen1:non-blocking\n";\'', 'r'); //非阻塞(管道)
$pipe2 = popen('php -r \'sleep(1); echo date("Y-m-d H:i:s")." by popen2:non-blocking\n";\'', 'r'); //非阻塞(管道)
echo date('Y-m-d H:i:s')."\n";
register_shutdown_function(function() use ($pipe1, $pipe2) { //事件驱动(脚本结束事件),异步回调
echo stream_get_contents($pipe1); //输出子进程返回的数据
echo stream_get_contents($pipe2); //输出子进程返回的数据
pclose($pipe1);
pclose($pipe2);
});
}
foo();

但是如果需要双方向的功能更多的进程控制 可以使用proc_open()打开新的子进程非阻塞的执行PHP脚本

1
2
3
4
5
6
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
32
33
34
35
36
37
38
39
function foo() {
$proc = proc_open(
//task.php 内容为 $arr = unserialize(stream_get_contents(STDIN)); $arr['time'] = date('Y-m-d H:i:s', $arr['time']); echo serialize($arr);
'/png/php/5.4.45/bin/php /home/eechen/task.php',
array(
0 => array('pipe','r'), //stdin (用fwrite写入数据给管道)
1 => array('pipe','w'), //stdout(用stream_get_contents获取管道输出)
2 => array('pipe','w'), //stderr(用stream_get_contents获取管道输出)
//2 => array('file','/tmp/err.txt','a') //stderr(写入到文件)
),
$pipes, //管道(stdin/stdout/stderr)
'/tmp', //当前PHP进程的工作目录
array('foo' => 'bar') //php.ini 配置 variables_order = "EGPCS" 其中E表示$_ENV,否则$_ENV输出为空 要执行脚本所需要的环境变量
);
//var_dump($pipes); //resource of type (stream)
if(is_resource($proc)) {
//stdin
$stdin = serialize(array('time' => time()));
fwrite($pipes[0], $stdin); //把参数传给脚本task.php
fclose($pipes[0]); //fclose关闭管道后proc_close才能退出子进程,否则会发生死锁
register_shutdown_function(function() use($pipes, $proc) { //事件驱动(脚本结束事件),异步回调
//stdout
$stdout = stream_get_contents($pipes[1]);
fclose($pipes[1]);
//stderr
$stderr = stream_get_contents($pipes[2]);
fclose($pipes[2]);
//exit code (返回进程的终止状态码,如果发生错则返回-1)
$status = proc_close($proc);
$data = array(
'stdout' => $stdout,
'stderr' => $stderr,
'status' => $status,
);
var_export($data); //echo json_encode($data);
});
}
}
foo();

宏定义 C

PHP代码中的宏定义包含 # 和 ##

‘##’代表 连接符 用来把两个语言符号(Token)组合成单个语言符号。 这里的语言符号不一定是宏的变量。并且双井号不能作为第一个或最后一个元素存在。

1
2
3
4
5
6

#define PHP_FUNCTION ZEND_FUNCTION
#define ZEND_FUNCTION(name) ZEND_NAMED_FUNCTION(ZEND_FN(name))
#define ZEND_FN(name) zif_##name
#define ZEND_NAMED_FUNCTION(name) void name(INTERNAL_FUNCTION_PARAMETERS)
#define INTERNAL_FUNCTION_PARAMETERS int ht, zval *return_value, zval **return_value_ptr, \

“#”是一种预处理运算符,它的功能是将其后面的宏参数进行 字符串化操作

PHP中代码的do-while宏

1
2
3
4
5
#define ALLOC_ZVAL(z)                                   \
do { \
(z) = (zval*)emalloc(sizeof(zval_gc_info)); \
GC_ZVAL_INIT(z); \
} while (0)

显而易见代码只执行一次,常用用例举例

1
2
3
4
5
6
#define TEST(a, b)  a++;b++;

if (expr)
TEST(a, b);
else
do_else();

如果直接编译会造成if 后面的单句规则被破坏。

以及需要考虑到平台移植,空操作也使用 do{}while(0)来进行宏定义

PHP运行时的全局参数 PHP CORE GLOBALS

首先会根据ZTS 或者NTS (线程安全) 来有一些变化

1
2
3
4
5
6
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
struct _php_core_globals {
zend_bool magic_quotes_gpc; // 是否对输入的GET/POST/Cookie数据使用自动字符串转义。
zend_bool magic_quotes_runtime; //是否对运行时从外部资源产生的数据使用自动字符串转义
zend_bool magic_quotes_sybase; // 是否采用Sybase形式的自动字符串转义

zend_bool safe_mode; // 是否启用安全模式

zend_bool allow_call_time_pass_reference; //是否强迫在函数调用时按引用传递参数 引用传值 值复制传值
zend_bool implicit_flush; //是否要求PHP输出层在每个输出块之后自动刷新数据

long output_buffering; //输出缓冲区大小(字节)

char *safe_mode_include_dir; //在安全模式下,该组目录和其子目录下的文件被包含时,将跳过UID/GID检查。
zend_bool safe_mode_gid; //在安全模式下,默认在访问文件时会做UID比较检查
zend_bool sql_safe_mode;
zend_bool enable_dl; //是否允许使用dl()函数。dl()函数仅在将PHP作为apache模块安装时才有效。

char *output_handler; // 将所有脚本的输出重定向到一个输出处理函数。

char *unserialize_callback_func; // 如果解序列化处理器需要实例化一个未定义的类,这里指定的回调函数将以该未定义类的名字作为参数被unserialize()调用,
long serialize_precision; //将浮点型和双精度型数据序列化存储时的精度(有效位数)。

char *safe_mode_exec_dir; //在安全模式下,只有该目录下的可执行程序才允许被执行系统程序的函数执行。

long memory_limit; //一个脚本所能够申请到的最大内存字节数(可以使用K和M作为单位)。
long max_input_time; // 每个脚本解析输入数据(POST, GET, upload)的最大允许时间(秒)。

zend_bool track_errors; //是否在变量$php_errormsg中保存最近一个错误或警告消息。
zend_bool display_errors; //是否将错误信息作为输出的一部分显示。
zend_bool display_startup_errors; //是否显示PHP启动时的错误。
zend_bool log_errors; // 是否在日志文件里记录错误,具体在哪里记录取决于error_log指令
long log_errors_max_len; //设置错误日志中附加的与错误信息相关联的错误源的最大长度。
zend_bool ignore_repeated_errors; // 记录错误日志时是否忽略重复的错误信息。
zend_bool ignore_repeated_source; //是否在忽略重复的错误信息时忽略重复的错误源。
zend_bool report_memleaks; //是否报告内存泄漏。
char *error_log; //将错误日志记录到哪个文件中。

char *doc_root; //PHP的”根目录”。
char *user_dir; //告诉php在使用 /~username 打开脚本时到哪个目录下去找
char *include_path; //指定一组目录用于require(), include(), fopen_with_path()函数寻找文件。
char *open_basedir; // 将PHP允许操作的所有文件(包括文件自身)都限制在此组目录列表下。
char *extension_dir; //存放扩展库(模块)的目录,也就是PHP用来寻找动态扩展模块的目录。

char *upload_tmp_dir; // 文件上传时存放文件的临时目录
long upload_max_filesize; // 允许上传的文件的最大尺寸。

char *error_append_string; // 用于错误信息后输出的字符串
char *error_prepend_string; //用于错误信息前输出的字符串

char *auto_prepend_file; //指定在主文件之前自动解析的文件名。
char *auto_append_file; //指定在主文件之后自动解析的文件名。

arg_separators arg_separator; //PHP所产生的URL中用来分隔参数的分隔符。

char *variables_order; // PHP注册 Environment, GET, POST, Cookie, Server 变量的顺序。

HashTable rfc1867_protected_variables; // RFC1867保护的变量名,在main/rfc1867.c文件中有用到此变量

short connection_status; // 连接状态,有三个状态,正常,中断,超时
short ignore_user_abort; // 是否即使在用户中止请求后也坚持完成整个请求。

unsigned char header_is_being_sent; // 是否头信息正在发送

zend_llist tick_functions; // 仅在main目录下的php_ticks.c文件中有用到,此处定义的函数在register_tick_function等函数中有用到。

zval *http_globals[6]; // 存放GET、POST、SERVER等信息

zend_bool expose_php; // 是否展示php的信息

zend_bool register_globals; // 是否将 E, G, P, C, S 变量注册为全局变量。
zend_bool register_long_arrays; // 是否启用旧式的长式数组(HTTP_*_VARS)。
zend_bool register_argc_argv; // 是否声明$argv和$argc全局变量(包含用GET方法的信息)。
zend_bool auto_globals_jit; // 是否仅在使用到$_SERVER和$_ENV变量时才创建(而不是在脚本一启动时就自动创建)。

zend_bool y2k_compliance; //是否强制打开2000年适应(可能在非Y2K适应的浏览器中导致问题)。

char *docref_root; // 如果打开了html_errors指令,PHP将会在出错信息上显示超连接,
char *docref_ext; //指定文件的扩展名(必须含有’.')。

zend_bool html_errors; //是否在出错信息中使用HTML标记。
zend_bool xmlrpc_errors;

long xmlrpc_error_number;

zend_bool activated_auto_globals[8];

zend_bool modules_activated; // 是否已经激活模块
zend_bool file_uploads; //是否允许HTTP文件上传。
zend_bool during_request_startup; //是否在请求初始化过程中
zend_bool allow_url_fopen; //是否允许打开远程文件
zend_bool always_populate_raw_post_data; //是否总是生成$HTTP_RAW_POST_DATA变量(原始POST数据)。
zend_bool report_zend_debug; // 是否打开zend debug,仅在main/main.c文件中有使用。

int last_error_type; // 最后的错误类型
char *last_error_message; // 最后的错误信息
char *last_error_file; // 最后的错误文件
int last_error_lineno; // 最后的错误行

char *disable_functions; //该指令接受一个用逗号分隔的函数名列表,以禁用特定的函数。
char *disable_classes; //该指令接受一个用逗号分隔的类名列表,以禁用特定的类。
zend_bool allow_url_include; //是否允许include/require远程文件。
zend_bool exit_on_timeout; // 超时则退出
#ifdef PHP_WIN32
zend_bool com_initialized;
#endif
long max_input_nesting_level; //最大的嵌套层数
zend_bool in_user_include; //是否在用户包含空间

char *user_ini_filename; // 用户的ini文件名
long user_ini_cache_ttl; // ini缓存过期限制

char *request_order; // 优先级比variables_order高,在request变量生成时用到,个人觉得是历史遗留问题

zend_bool mail_x_header; // 仅在ext/standard/mail.c文件中使用,
char *mail_log;

zend_bool in_error_log;
};

PHP 生命周期

PHPINIT 调用模块前的初始化:

初始化若干全局变量 大都设置为NULL

初始化若干常量 PHP_Version等

初始化zend Engine 和 核心组件

  • 包括内存管理初始化、 全局使用的函数指针初始化(如前面所说的zend_printf等),对PHP源文件进行词法分析、语法分析、 中间代码执行的函数指针的赋值,初始化若干HashTable(比如函数表,常量表等等),为ini文件解析做准备, 为PHP源文件解析做准备,注册内置函数(如strlen、define等),注册标准常量(如E_ALL、TRUE、NULL等)、注册GLOBALS全局变量等。

解析PHPini到PHP core globals

全局操作函数初始化

初始化静态构建的模块和共享模块MINIT 模块初始化 扩展模块的函数初始化 注册常量 定义模块的使用 Web Server启动或者脚本开始执行

  • 通过php_register_internal_extensions_func函数用来注册静态构建的模块,也就是默认加载的模块, 我们可以将其认为内置模块

RINIT 模块激活阶段 开始使用模块

解析

RSHUTDOWN 停用模块

MSHUTDOWN WebServer关闭或者脚本停止使用

关于扩展重用

因为PHP的实现是和zend引擎实现紧耦合,所以PHP的扩展并不具有很好的移植性,比如移植到JVM虚拟机,移植到HHVM等,如果实现解耦合,那么扩展的重用将会容易。

PHP的运行模式

FastCGI、CGI、SAPI模块形式、嵌入式

SAPI:Server Application Program Interface

CGI:“通用网关接口”(Common Gateway Interface)

FastCGI:long-live 常驻内存型升级版高性能CGI 防止CGI的fork-and-execute模式。

PHP脚本运行

目前编程语言可以分为两大类:

  • 第一类是像C/C++, .NET, Java之类的编译型语言, 它们的共性是: 运行之前必须对源代码进行编译,然后运行编译后的目标文件。

  • 第二类比如:PHP, Javascript, Ruby, Python这些解释型语言, 他们都无需经过编译即可”运行”,虽然可以理解为直接运行,

PHP完成基本准备工作

启动PHP以及Zend引擎

加载注册扩展模块

Zend引擎进行

lex生成的 词法分析:将代码转换为一个个标记token

  • 很多编程语言都使用lex/yacc或他们的变体(flex/bison)来作为语言的词法语法分析生成器

语法分析 bison生成 语法分析器

转换为opcode

opcode结构体

1
2
3
4
5
6
7
8
9
struct _zend_op { // 这里是struct实现
opcode_handler_t handler; // 执行该opcode时调用的处理函数
znode result;
znode op1;
znode op2;
ulong extended_value; // 脚本执行时候需要的其他信息
uint lineno;
zend_uchar opcode; // opcode代码
};

函数是在编译器遇到echo语句的时候进行编译的函数:

1
2
3
4
5
6
7
8
9
10
void zend_do_echo(const znode *arg TSRMLS_DC) {
zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC);

opline->opcode = ZEND_ECHO;

opline->op1 = *arg;

SET_UNUSED(opline->op2);

}

编译器遇到print语句的时候进行编译的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void zend_do_print(znode *result,const znode *arg TSRMLS_DC) {
zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC);

opline->result.op_type = IS_TMP_VAR;

opline->result.u.var = get_temporary_variable(CG(active_op_array));

opline->opcode = ZEND_PRINT;

opline->op1 = *arg;

SET_UNUSED(opline->op2);

*result = opline->result;
}

【这里可以看到print和echo的根本区别 print有返回值 echo并没有 两者都是语法结构,返回null也是有返回值!】

posted @ 2018-09-10 23:27  guoguoqingzhe  阅读(273)  评论(0编辑  收藏  举报