本文目錄一覽:
如何執行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-hant/n/151214.html