今天在詳細介紹下php_module_startup 。
從php module startup 的字面就知道是PHP初始化。
各個SAPI啟動,會調用php_module_startup這個函數,例如之前提到的fast-cig.
php_module_startup(sapi_module_struct *sf, zend_module_entry *additional_modules, uint num_additional_modules)
這個函數的啟動需要傳入
sapi_module_struct
zend_module_entry *additional_modules
uint num_additional_modules
sapi_module_struct 就是每個執行模式內部定義結構。 如果各位有能力寫執行模式,也需要按照這個規則,寫_sapi_module_struct。規則如下:
2014-09-10 08:53:34的屏幕截圖
2014-09-10 08:54:06的屏幕截圖
上面2張圖分別是cli 和fast-cgi 的sapi module struct。
zend_module_entry 這個struct 寫過模塊的朋友就更加熟悉了。直接看代碼:
上面2張圖分別是cli 和fast-cgi 的sapi module struct。
zend_module_entry 這個struct 寫過模塊的朋友就更加熟悉了。直接看代碼:
/* {{{ kerphp_module_entry
*/
zend_module_entry kerphp_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
STANDARD_MODULE_HEADER,
#endif
"kerphp",
kerphp_functions,
PHP_MINIT(kerphp),
PHP_MSHUTDOWN(kerphp),
PHP_RINIT(kerphp),
PHP_RSHUTDOWN(kerphp),
PHP_MINFO(kerphp),
#if ZEND_MODULE_API_NO >= 20010901
"0.1",
#endif
STANDARD_MODULE_PROPERTIES
};
/* }}} */
#ifdef COMPILE_DL_KERPHP
ZEND_GET_MODULE(kerphp)
#endif
順便做下宣傳,kerphp 這個是我在11年寫的PHP框架。簡單解讀下這個module_entry 。
一句土話概括:這個框架(或extension)名字叫kerphp,裡面有kerphp_functions定義的一組方法,同時有PHP_MINIT、PHP_MSHUTDOWN、PHP_RINIT、PHP_RSHUTDOWN這幾個生命周期,和PHP_MINFO這個愛在phpinfo裡炫耀 support enable的家伙。
接下sapi 就初始化 並且置空請求 —– 強烈建議千萬別理解成清空。
SAPI_API void sapi_initialize_empty_request(TSRMLS_D)
{
SG(server_context) = NULL;
SG(request_info).request_method = NULL;
SG(request_info).auth_digest = SG(request_info).auth_user = SG(request_info).auth_password = NULL;
SG(request_info).content_type_dup = NULL;
}
繼續 激活 sapi_activate
SAPI_API void sapi_activate(TSRMLS_D)
{
zend_llist_init(&SG(sapi_headers).headers, sizeof(sapi_header_struct), (void (*)(void *)) sapi_free_header, 0);
SG(sapi_headers).send_default_content_type = 1;
/*
SG(sapi_headers).http_response_code = 200;
*/
SG(sapi_headers).http_status_line = NULL;
SG(sapi_headers).mimetype = NULL;
SG(headers_sent) = 0;
SG(callback_run) = 0;
SG(callback_func) = NULL;
SG(read_post_bytes) = 0;
SG(request_info).request_body = NULL;
SG(request_info).current_user = NULL;
SG(request_info).current_user_length = 0;
SG(request_info).no_headers = 0;
SG(request_info).post_entry = NULL;
SG(request_info).proto_num = 1000; /* Default to HTTP 1.0 */
SG(global_request_time) = 0;
SG(post_read) = 0;
/* It's possible to override this general case in the activate() callback, if necessary. */
if (SG(request_info).request_method && !strcmp(SG(request_info).request_method, "HEAD")) {
SG(request_info).headers_only = 1;
} else {
SG(request_info).headers_only = 0;
}
SG(rfc1867_uploaded_files) = NULL;
/* Handle request method */
if (SG(server_context)) {
if (PG(enable_post_data_reading)
&& SG(request_info).content_type
&& SG(request_info).request_method
&& !strcmp(SG(request_info).request_method, "POST")) {
/* HTTP POST may contain form data to be processed into variables
* depending on given content type */
sapi_read_post_data(TSRMLS_C);
} else {
SG(request_info).content_type_dup = NULL;
}
/* Cookies */
SG(request_info).cookie_data = sapi_module.read_cookies(TSRMLS_C);
if (sapi_module.activate) {
sapi_module.activate(TSRMLS_C);
}
}
if (sapi_module.input_filter_init) {
sapi_module.input_filter_init(TSRMLS_C);
}
}
看這個的結構體 要配合 下面的一起看
typedef struct _sapi_globals_struct {
void *server_context;
sapi_request_info request_info;
sapi_headers_struct sapi_headers;
int64_t read_post_bytes;
unsigned char post_read;
unsigned char headers_sent;
struct stat global_stat;
char *default_mimetype;
char *default_charset;
HashTable *rfc1867_uploaded_files;
long post_max_size;
int options;
zend_bool sapi_started;
double global_request_time;
HashTable known_post_content_types;
zval *callback_func;
zend_fcall_info_cache fci_cache;
zend_bool callback_run;} sapi_globals_struct;
SG(sapi_headers) 就是 sapi_globals_struct 裡的 sapi_headers_struct sapi_headers;
以此類推。
提供下 sapi_activate(TSRMLS_D) 這個過程,每次http requrest請求 都會執行。
這個函數裡面還有一個重要的地方需要解讀,
&& !strcmp(SG(request_info).request_method, "POST")) {
sapi_read_post_data(TSRMLS_C);
就是如果http請求是post方法的話 那麼回去執行 單獨的獲取post數據。
哈哈,那麼你是不是覺得post從理論源頭回比get慢一些呢?
好像有點,當然可以胡率不計。
總之這個函數內會進行http 的數據構造 如請求方法、get/post數據獲取、cookie、header等等。
接著回到 php_module_startup 。
開始系列的注冊常量
REGISTER_MAIN_STRINGL_CONSTANT/REGISTER_MAIN_LONG_CONSTANT
例如:
REGISTER_MAIN_STRINGL_CONSTANT("PHP_VERSION", PHP_VERSION, sizeof(PHP_VERSION)-1, CONST_PERSISTENT | CONST_CS);
REGISTER_MAIN_LONG_CONSTANT("PHP_MAJOR_VERSION", PHP_MAJOR_VERSION, CONST_PERSISTENT | CONST_CS);
這個就是為什麼你能輸出PHP內置常量的原因
<?php
echo PHP_MAJOR_VERSION;?>
接著是我認為在中國面試很無聊的一個問題:
說說PHP在何時讀取php.ini/或何時載入php.ini?
這個其實是在小小面試桌前很無趣的一道題目,考官估計也半知半解抄來的。
/main/php_ini.c
int php_init_config(TSRMLS_D)
這時候開始進行php.ini 的讀取
讀取過程
1 定義php_ini_file_name 名稱
2 定義 php_ini_search_path 搜索路徑
3 通過 zend_parse_ini_file 解析ini文件
載入擴展的2個宏定義
#define PHP_EXTENSION_TOKEN "extension"#define ZEND_EXTENSION_TOKEN "zend_extension"
這就是為什麼php.ini 裡面要這麼寫的加載擴展原因
(回頭在詳細php.ini的解析過程)
接著 通過 php_register_extensions_bc 注冊cig-fcgi。
static int php_register_extensions_bc(zend_module_entry *ptr, int count TSRMLS_DC){
while (count--) {
if (zend_register_internal_module(ptr++ TSRMLS_CC) == NULL) {
return FAILURE;
}
}
return SUCCESS;}
大家可以用如下代碼測試
FILE *fhd;static int php_register_extensions_bc(zend_module_entry *ptr, int count TSRMLS_DC){
fhd=fopen("/log/php_register_extensions_bc.txt","a");
while (count--) {
fwrite(ptr->name,strlen(ptr->name),1,fhd);
if (zend_register_internal_module(ptr++ TSRMLS_CC) == NULL) {
return FAILURE;
}
} fclose(fhd);
return SUCCESS;}
唠叨下:
源碼閱讀我建議用fwrite寫文件的方式來做跟蹤。
接著注冊ini裡定義的php 擴展
php_ini_register_extensions(TSRMLS_C);
void php_ini_register_extensions(TSRMLS_D){
zend_llist_apply(&extension_lists.engine, php_load_zend_extension_cb TSRMLS_CC);
zend_llist_apply(&extension_lists.functions, php_load_php_extension_cb TSRMLS_CC);
zend_llist_destroy(&extension_lists.engine);
zend_llist_destroy(&extension_lists.functions);}
主要通過 , php_load_php_extension_cb 回掉函數挨個加載
static void php_load_php_extension_cb(void *arg TSRMLS_DC){#ifdef HAVE_LIBDL
php_load_extension(*((char **) arg), MODULE_PERSISTENT, 0 TSRMLS_CC);#endif}
接著根據php.ini 啟動禁用的配置
php_disable_functions(TSRMLS_C);
php_disable_classes(TSRMLS_C);