PHP代碼實現 1

$PHP-SRC/run-test.php

  • 由於若是在同一個進程中執行, 測試就會中止,後面的測試也將沒法執行,php中有不少將腳本隔離的方法好比: system(),exec()等函數,這樣可使用主測試進程服務調度被測腳本和檢測測試結果,經過這些外部調用執行測試。 php測試使用了proc_open()函數, 這樣就能夠保證測試腳本和被測試腳本之間能隔離開。

若是隻須要簡單的進程單向進程通道 可使用 popenphp

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腳本html

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代碼中的宏定義包含 # 和 ##node

‘##’表明 鏈接符 用來把兩個語言符號(Token)組合成單個語言符號。 這裏的語言符號不必定是宏的變量。而且雙井號不能做爲第一個或最後一個元素存在。sql

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, \

「#」是一種預處理運算符,它的功能是將其後面的宏參數進行 字符串化操做shell

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)

顯而易見代碼只執行一次,經常使用用例舉例apache

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

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

若是直接編譯會形成if 後面的單句規則被破壞。編程

以及須要考慮到平臺移植,空操做也使用 do{}while(0)來進行宏定義json

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也是有返回值!】

相關文章
相關標籤/搜索