以及php腳本解釋執行的機制,php執行腳本命令

本文目錄一覽:

如何執行PHP腳本

php文件如何打開:

確保電腦上安裝有php的運行環境如:wampserver, phpstudy等等

安裝好後,把php文件複製進對應的目錄(一般是www目錄下);

在瀏覽器上輸入localhost/加你的php文件路徑(localhost在這裡就代表www目錄,你只需要把後面的路徑補齊即可),按下enter,php腳本將被自動解析運行

PHP命令執行PHP腳本,結束之前,內存會回收嗎

再詳細說下問題:

unix下,用php命令來執行php腳本,在php結束之前,內存有沒有機會被回收?新的GC演算法有沒有機會被調用?

出現這個問題,是因為線上有個 離線數據導入腳本,需要把幾千萬行數據篩選入庫,發現,在執行過程中,到達一定程度,就會拋出 內存使用超過最大值。

1    Fatal error: Allowed memory size of 293601280 bytes exhausted    

那第一想到的就是程序是不是有什麼bug,造成內存超出,看了半天沒有發現問題,於是,突然出現了這個疑問。 那要解決這個疑問,最好的辦法就去翻源碼吧。

在之前我這麼說:

都知道,PHP5.3有了新的垃圾回收機制:GC,它不是重點,不談它的原理。

經過翻閱PHP源碼,發現,調用這個的時機是在 main/main.c ::php_request_shutdown這個函數中,

12    /* 7. Shutdown scanner/executor/compiler and restore ini entries */        zend_deactivate(TSRMLS_C);    

php_request_shutdown,通過名字就能看出,它是在php請求結束的時候執行的,在這裡會執行 gc_collect_cycles 來清理內存。

其實這句話是沒錯,但它只針對於SAPI介面(之前我就錯在這個地方。),在用PHP命令執行php腳本的時候,是不會執行這個php_request_shutdown的。

那回到最初的問題,過程中到底有沒有執行GC呢?

為了更直觀有效的知道答案,我選擇了最BT,最暴力的方法,攔截gc_collect_cycles,輸出error_log到文件,只要執行了,

那肯定會輸出log來。

重新編譯PHP後,果不其然,符合官方的說法,只要buckets滿超過默認值1000,就會啟動GC來清理沒用的內存,防止內存泄露。

那問 「什麼時間 觸發的GC呢?」,答 「buckets超過1000的時候啊」,這不屁話嘛,要的是真真正正的執行流程,so。。不斷的debug,

不斷的grep,不斷的step,不斷的C+T,終於搞清楚了。下面就來根據官方的說法詳細談談,PHP到底是怎麼觸發的。

有一點要注意,PHP的命令入口 和 sapi介面的入口 是不同的,我就載在這個地方,以為都公用一個。

測試代碼以官方文檔為例:

1234567891011121314    ?phpclass Foo{    public $var = ‘3.1415962654’;} for ( $i = 0; $i = 1000000; $i++ ){    $a = new Foo;    $a-self = $a;} echo memory_get_peak_usage(), “\n”;?    

這樣的代碼,在PHP5.3之前,肯定會造成大量的 內存泄露,不過,誰在開發時又能開發出這麼變態的代碼來?除非這個人很變態。^.*

那PHP的命令入口是什麼?流程又是什麼?

主要函數流程如下:

入口main函數(sapi/cli/php_cli.c) ==》php_execute_script(main/main.c)==zend_execute_scripts(Zend/zend.c)==execute(Zend/zend_vm_execute.h)

調用GC的地方在execute里。

簡單描述下這個過程,

main 是入口,它的作用是根據我們傳遞的參數做不同的設置,最後會把我們的php腳本作為一個zend_file_handle指針傳遞給

php_execute_script函數,zend_file_handle其實就是把FILE*做了一下封裝,保存了一些其他的文件信息。

php_execute_script會做一些文件檢查工作,把php腳本加到 哈希表included_files中。

php_execute_scripts 會執行 zend_compile_file函數來解釋我們寫的PHP代碼,最後執行execute。

應該都知道 Zend把腳本解析完會生成op代碼保存到 哈希表:active_op_array中,execute會逐個執行每個op,

op基本上都對應一個ZEND_ASSIGN_*_HANDLER這樣的一個宏,它就保存在active_op_array-opline-handlers中。

在進入到 execute之後:

首先初始化execute_data,它保存了很多重要信息,上下文信息,然後調用 ZEND_VM_SET_OPCODE宏,

把execute_data-opline的指針指向active_op_array-opline-handlers。

之後,execute會執行一個while循環,逐條執行opline:

123456789101112131415161718192021222324            while (1) {        int ret;#ifdef ZEND_WIN32                if (EG(timed_out)) {                        zend_timeout(0);                }#endif                 if ((ret = EX(opline)-handler(execute_data TSRMLS_CC))  0) {                        switch (ret) {                                case 1:                                        EG(in_execution) = original_in_execution;                                        return;                                case 2:                                        op_array = EG(active_op_array);                                        goto zend_vm_enter;                                case 3:                                        execute_data = EG(current_execute_data);                                default:                                        break;                        }                }         }    

每個handlers都會執行一個宏:ZEND_VM_NEXT_OPCODE(),它意思就是跳到下一個Opline,這樣就能逐條執行了。

最後跟蹤 上面的PHP代碼會執行 ZEND_ASSIGN_SPEC_CV_VAR_HANDLER這個宏,它是幹嘛的?他就是 變數賦值

下面代碼執行的操作:

1234    class A{ }$a=new A();    

這裡就會執行 這個宏。

在這個宏里有段代碼:

12345678910111213141516171819202122232425262728293031        static int ZEND_FASTCALL  ZEND_ASSIGN_SPEC_CV_VAR_HANDLER(ZEND_OPCODE_HANDLER_ARGS){        zend_op *opline = EX(opline);        zend_free_op free_op2;        zval *value = _get_zval_ptr_var(opline-op2, EX(Ts), free_op2 TSRMLS_CC);        zval **variable_ptr_ptr = _get_zval_ptr_ptr_cv(opline-op1, EX(Ts), BP_VAR_W TSRMLS_CC);         if (IS_CV == IS_VAR  !variable_ptr_ptr) {                if (zend_assign_to_string_offset(EX_T(opline-op1.u.var), value, IS_VAR TSRMLS_CC)) {                        if (!RETURN_VALUE_UNUSED(opline-result)) {                                EX_T(opline-result.u.var).var.ptr_ptr = EX_T(opline-result.u.var).var.ptr;                                ALLOC_ZVAL(EX_T(opline-result.u.var).var.ptr);                                INIT_PZVAL(EX_T(opline-result.u.var).var.ptr);                                ZVAL_STRINGL(EX_T(opline-result.u.var).var.ptr, Z_STRVAL_P(EX_T(opline-op1.u.var).str_offset.str)+EX_T(opline-op1.u.var).str_offset.offset, 1, 1);                        }                } else if (!RETURN_VALUE_UNUSED(opline-result)) {                        AI_SET_PTR(EX_T(opline-result.u.var).var, EG(uninitialized_zval_ptr));                        PZVAL_LOCK(EG(uninitialized_zval_ptr));                }        } else {                value = zend_assign_to_variable(variable_ptr_ptr, value, 0 TSRMLS_CC);                if (!RETURN_VALUE_UNUSED(opline-result)) {                        AI_SET_PTR(EX_T(opline-result.u.var).var, value);                        PZVAL_LOCK(value);                }        }         /* zend_assign_to_variable() always takes care of op2, never free it! */        if (free_op2.var) {zval_ptr_dtor(free_op2.var);};         ZEND_VM_NEXT_OPCODE();}    

free_op2.var保存的是 new A的對象.

free_op2.var這個是哪兒來的呢?

在整個execute期間,維持一個 execute_data結構,裡面有個 Ts指針

1    union _temp_variable *Ts;    

它用來保存一些臨時的變數信息,比如 new A(),這個會保存到Ts鏈表裡,

opline-op2.u.var這個裡面保存了此臨時變數所在的位置,然後Ts+這個值是一個zval*指針,它就保存了new A產生的對象.

在代碼中

1    if (free_op2.var) {zval_ptr_dtor(free_op2.var);};    

zval_ptr_dtor會根據free_op2.var的值執行到 Zend/zend_execute_API.c::_zval_ptr_dtor函數中,

1234567891011121314151617181920212223242526    ZEND_API void _zval_ptr_dtor(zval **zval_ptr ZEND_FILE_LINE_DC) /* {{{ */{        zval *zv = *zval_ptr; #if DEBUG_ZEND=2        printf(“Reducing refcount for %x (%x): %d-%d\n”, *zval_ptr, zval_ptr, Z_REFCOUNT_PP(zval_ptr), Z_REFCOUNT_PP(zval_ptr) – 1);#endif        Z_DELREF_P(zv);        if (Z_REFCOUNT_P(zv) == 0) {                TSRMLS_FETCH();                 if (zv != EG(uninitialized_zval)) {                        GC_REMOVE_ZVAL_FROM_BUFFER(zv);                        zval_dtor(zv);                        efree_rel(zv);                }        } else {                TSRMLS_FETCH();                 if (Z_REFCOUNT_P(zv) == 1) {                        Z_UNSET_ISREF_P(zv);                }                 GC_ZVAL_CHECK_POSSIBLE_ROOT(zv);        }}    

GC_ZVAL_CHECK_POSSIBLE_ROOT(zv);

它就是最終GC演算法執行的地方.

gc_collect_cycles就在這個宏中執行了..

所以..

回到上面的問題,

php無論在SAPI介面或命令端,都會執行 GC演算法來進行垃圾內存回收.

PHP腳本程序主要是由哪幾部分組成?

運行PHP腳本程序,必須藉助PHP預處理器、WEB伺服器和WEB瀏覽器,必要時還需藉助資料庫伺服器。

1.WEB瀏覽器

也叫網頁瀏覽器,簡稱瀏覽器。常見的瀏覽器有微軟的Internet Explorer(IE)瀏覽器、Mozilla的Firefox瀏覽器等。

2.PHP預處理器

PHP Preprocessor的功能是將PHP程序中的代碼解釋為文本信息,這些文本信息中可以包含HTML代碼。

3.WEB伺服器

WEB Server也稱為WWW(World Wide Web)伺服器,功能是解析HTTP。當WEB伺服器接收到瀏覽器的一個HTTP動態請求時,Web伺服器會調用與請求對應的程序,程序經PHP預處理器解釋執行後,WEB伺服器向瀏覽器返回HTTP響應,該響應通常是一個HTML頁面。瀏覽器收到該HTTP響應後,將執行結果顯示在瀏覽器上。

常見的WEB伺服器有微軟的Internet Information Server(IIS)伺服器、IBM的WebSphere伺服器、開源的Apache伺服器等。IIS中文全稱是互聯網信息伺服器。

WEB瀏覽器請求的是靜態頁面,只需要WEB伺服器響應該請求;如果瀏覽器請求的是動態頁面(php代碼),此時WEB伺服器會委託PHP預處理器將動態頁面解釋為HTML靜態頁面,再有WEB伺服器返回給瀏覽器顯示。

4.資料庫伺服器

Database Server是一套為應用程序提供數據管理的服務軟體,服務包含數據的添加、修改、刪除、事務管理、索引服務、高速緩存、查詢優化、安全及存儲控制等。

常見的資料庫伺服器有甲骨文的Oracle、微軟的SQL Server、IBM的DB2、SAP的Sybase以及SUN公司的MySQL等。

什麼是php預處理器,說明解釋執行與編譯執行

PHP(外文名:PHP: Hypertext Preprocessor,中文名:「超文本預處理器」)是一種通用開源腳本語言。語法吸收了C語言、Java和Perl的特點,利於學習,使用廣泛,主要適用於Web開發領域。PHP 獨特的語法混合了C、Java、Perl以及PHP自創的語法。它可以比CGI或者Perl更快速地執行動態網頁。用PHP做出的動態頁面與其他的編程語言相比,PHP是將程序嵌入到HTML(標準通用標記語言下的一個應用)文檔中去執行,執行效率比完全生成HTML標記的CGI要高許多;PHP還可以執行編譯後代碼,編譯可以達到加密和優化代碼運行,使代碼運行更快。

原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/151214.html

(0)
打賞 微信掃一掃 微信掃一掃 支付寶掃一掃 支付寶掃一掃
小藍的頭像小藍
上一篇 2024-11-10 01:13
下一篇 2024-11-11 13:40

相關推薦

  • PHP和Python哪個好找工作?

    PHP和Python都是非常流行的編程語言,它們被廣泛應用於不同領域的開發中。但是,在考慮擇業方向的時候,很多人都會有一個問題:PHP和Python哪個好找工作?這篇文章將從多個方…

    編程 2025-04-29
  • Python腳本控制其他軟體

    Python作為一種簡單易學、功能強大的腳本語言,具有廣泛的應用領域,在自動化測試、Web開發、數據挖掘等領域都得到了廣泛的應用。其中,Python腳本控制其他軟體也是Python…

    編程 2025-04-29
  • Shell腳本與Python腳本的區別

    本文將從多個方面對Shell腳本與Python腳本的區別做詳細的闡述。 一、語法差異 Shell腳本和Python腳本的語法存在明顯差異。 Shell腳本是一種基於字元命令行的語言…

    編程 2025-04-29
  • PHP怎麼接幣

    想要在自己的網站或應用中接受比特幣等加密貨幣的支付,就需要對該加密貨幣擁有一定的了解,並使用對應的API進行開發。本文將從多個方面詳細闡述如何使用PHP接受加密貨幣的支付。 一、環…

    編程 2025-04-29
  • Python命令大全及說明

    Python是一種高級編程語言,由Guido van Rossum於1989年底發明。它具有良好的語法結構和面向對象的編程思想,具有簡潔、易讀、易學的特點,是初學者以及專業開發人員…

    編程 2025-04-29
  • Git config命令用法介紹:用正確的郵箱保障開發工作

    本文將詳細介紹如何使用git config命令配置Git的全局和本地用戶信息,特別是如何正確使用用戶郵箱,保障Git操作的正常進行。 一、git config命令介紹 Git中的每…

    編程 2025-04-29
  • Python SSH 遠程執行命令

    Python SSH 遠程執行命令是指在一個伺服器上執行遠程另一個伺服器上命令。如果你需要在本地機器上執行命令,或者在遠程機器上執行本地命令,你都可以使用 SSH。在 Python…

    編程 2025-04-29
  • Python自動化運維腳本

    Python自動化運維腳本是使用Python編寫的代碼,可以幫助管理員自動化執行繁瑣、重複的操作任務。通過Python自動化運維腳本,管理員可以在更短的時間內完成工作,提高工作效率…

    編程 2025-04-28
  • 使用Python圖書館搶座腳本的完整步驟

    本文將從多個方面詳細介紹如何使用Python編寫圖書館的座位搶佔腳本,並幫助您快速了解如何自動搶佔圖書館的座位,並實現您的學習計劃。 一、開發環境搭建 首先,我們需要安裝Pytho…

    編程 2025-04-28
  • 使用PHP foreach遍歷有相同屬性的值

    本篇文章將介紹如何使用PHP foreach遍歷具有相同屬性的值,並給出相應的代碼示例。 一、基礎概念 在講解如何使用PHP foreach遍歷有相同屬性的值之前,我們需要先了解幾…

    編程 2025-04-28

發表回復

登錄後才能評論