PHP內核探索:如何執行PHP腳本

前面介紹了PHP的生命週期,PHP的SAPI,SAPI處於PHP整個架構較上層,而真正腳本的執行主要由Zend引擎來完成, 這一小節我們介紹PHP腳本的執行。

目前編程語言可以分爲兩大類:

  • 第一類是像C/C++, .NET, Java之類的編譯型語言, 它們的共性是:運行之前必須對源代碼進行編譯,然後運行編譯後的目標文件。
  • 第二類比如PHP, Javascript, Ruby, Python這些解釋型語言, 他們都無需經過編譯即可“運行”。

雖然可以理解爲直接運行,但它們並不是真的直接就被能被機器理解, 機器只能理解機器語言,那這些語言是怎麼被執行的呢, 一般這些語言都需要一個解釋器, 由解釋器來執行這些源碼, 實際上這些語言還是會經過編譯環節,只不過它們一般會在運行的時候實時進行編譯。爲了效率,並不是所有語言在每次執行的時候都會重新編譯一遍, 比如PHP的各種opcode緩存擴展(如APC, xcache, eAccelerator等),比如Python會將編譯的中間文件保存成pyc/pyo文件, 避免每次運行重新進行編譯所帶來的性能損失。

PHP的腳本的執行也需要一個解釋器, 比如命令行下的php程序,或者apache的mod_php模塊等等。 前面提到了PHP的SAPI接口, 下面就以PHP命令行程序爲例解釋PHP腳本是怎麼被執行的。 例如如下的這段PHP腳本:

1 <?php
2 $str = "Hello, nowamagic!\n";
3 echo $str;
4 ?>

假設上面的代碼保存在名爲hello.php的文件中, 用PHP命令行程序執行這個腳本:

1 $ php ./hello.php

這段代碼的輸出顯然是Hello, nowamagic!, 那麼在執行腳本的時候PHP/Zend都做了些什麼呢? 這些語句是怎麼樣讓php輸出這段話的呢? 下面將一步一步的進行介紹。

程序的執行

  1. 如上例中, 傳遞給php程序需要執行的文件, php程序完成基本的準備工作後啓動PHP及Zend引擎, 加載註冊的擴展模塊。
  2. 初始化完成後讀取腳本文件,Zend引擎對腳本文件進行詞法分析,語法分析。然後編譯成opcode執行。 如過安裝了apc之類的opcode緩存, 編譯環節可能會被跳過而直接從緩存中讀取opcode執行。

PHP在讀取到腳本文件後首先對代碼進行詞法分析,PHP的詞法分析器是通過lex生成的, 詞法規則文件在$PHP_SRC/Zend/zend_language_scanner.l, 這一階段lex會會將源代碼按照詞法規則切分一個一個的標記(token)。PHP中提供了一個函數token_get_all(), 該函數接收一個字符串參數, 返回一個按照詞法規則切分好的數組。 例如將上面的php代碼作爲參數傳遞給這個函數:

1 <?php
2 $code =<<<PHP_CODE
3 <?php
4 $str = "Hello, nowamagic\n";
5 echo $str;
6 PHP_CODE;
7   
8 var_dump(token_get_all($code));
9 ?>

運行上面的腳本你將會看到一如下的輸出:

01 array (
02   0 =>
03   array (
04     0 => 368,       // 腳本開始標記
05     1 => '<?php     // 匹配到的字符串
06 ',
07     2 => 1,
08   ),
09   1 =>
10   array (
11     0 => 371,
12     1 => ' ',
13     2 => 2,
14   ),
15   2 => '=',
16   3 =>
17   array (
18     0 => 371,
19     1 => ' ',
20     2 => 2,
21   ),
22   4 =>
23   array (
24     0 => 315,
25     1 => '"Hello, nowamagic
26 "',
27     2 => 2,
28   ),
29   5 => ';',
30   6 =>
31   array (
32     0 => 371,
33     1 => '
34 ',
35     2 => 3,
36   ),
37   7 =>
38   array (
39     0 => 316,
40     1 => 'echo',
41     2 => 4,
42   ),
43   8 =>
44   array (
45     0 => 371,
46     1 => ' ',
47     2 => 4,
48   ),
49   9 => ';',

這也是Zend引擎詞法分析做的事情,將代碼切分爲一個個的標記,然後使用語法分析器(PHP使用bison生成語法分析器, 規則見$PHP_SRC/Zend/zend_language_parser。y), bison根據規則進行相應的處理, 如果代碼找不到匹配的規則,也就是語法錯誤時Zend引擎會停止,並輸出錯誤信息。 比如缺少括號,或者不符合語法規則的情況都會在這個環節檢查。 在匹配到相應的語法規則後,Zend引擎還會進行編譯, 將代碼編譯爲opcode, 完成後,Zend引擎會執行這些opcode, 在執行opcode的過程中還有可能會繼續重複進行編譯-執行, 例如執行eval,include/require等語句, 因爲這些語句還會包含或者執行其他文件或者字符串中的腳本。

例如上例中的echo語句會編譯爲一條ZEND_ECHO指令, 執行過程中,該指令由C函數zend_print_variable(zval* z)執行,將傳遞進來的字符串打印出來。 爲了方便理解, 本例中省去了一些細節,例如opcode指令和處理函數之間的映射關係等。 後面的章節將會詳細介紹。

如果想直接查看生成的Opcode,可以使用php的vld擴展查看。擴展下載地址: http://pecl.php.net/package/vld。Win下需要自己編譯生成dll文件。

有關PHP腳本編譯執行的細節,請閱讀後面有關詞法分析,語法分析及opcode編譯相關內容。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章