php扩展开发相关总结
1、线程安全宏定义在tsrm/tsrm.h文件中有如下定义
#define tsrmls_fetch() void ***tsrm_ls = (void ***) ts_resource_ex(0, null)#define tsrmls_fetch_from_ctx(ctx) void ***tsrm_ls = (void ***) ctx#define tsrmls_set_ctx(ctx) ctx = (void ***) tsrm_ls#define tsrmg(id, type, element) (((type) (*((void ***) tsrm_ls))[tsrm_unshuffle_rsrc_id(id)])->element)#define tsrmls_d void ***tsrm_ls#define tsrmls_dc , tsrmls_d#define tsrmls_c tsrm_ls#define tsrmls_cc , tsrmls_c
在ext/xsl/php_xsl.h有这么一段话
/* in every utility function you add that needs to use variables. in php_xsl_globals, call tsrm_fetch(); after declaring other. variables used by that function, or better yet, pass in tsrmls_cc after the last function argument and declare your utility function with tsrmls_dc after the last declared argument. always refer to the globals in your function as xsl_g(variable). you are. encouraged to rename these macros something shorter, see examples in any other php module directory.*/
1.在方法定义时加上tsrmls_d(如果方法没有参数用这个)或者tsrmls_dc(有1个以上的参数)
2.在方法调用时用tsrmls_c(如果方法没有参数用这个)或者tsrmls_cc(有1个以上的参数)
应该可以这样理解
第一个后缀字母d表示定义,即d=define,第一个后缀字母c表示调用,即c=call,而第二个后缀字母c是不是表示逗号呢? c=comma (逗号)
tsrmls_d就是定义了,所以是 void ***tsrm_lstsrmls_dc是带逗号的定义,所以是 , void ***tsrm_lstsrmls_c是调用,即tsrm_lstsrmls_cc是调用并带逗号,即 ,tsrm_ls
所以一个是形参、一个是实参
可以这样使用
int php_myext_action(int action_id, char *message tsrmls_dc);php_myext_action(42, the meaning of life tsrmls_cc);
一般推荐使用tsrm_ls指针定义的方式来保证线程安全
tsrmls_fetch调用需要一定的处理时间。这在单次迭代中并不明显,但是随着你的线程数增多,随着你调用tsrmls_fetch()的点的增多,你的扩展就会显现出这个瓶颈。因此,请谨慎的使用它。 注意:为了和c++编译器兼容,请确保将tsrmls_fetch()和所有变量定义放在给定块作用域的顶部(任何其他语句之前)。因为tsrmls_fetch()宏自身有多种不同的解析方式,因此最好将它作为变量定义的最后一行
2、php的生命周期php的最多的两种运行模式是web模式、cli模式,无论哪种模式,php工作原理都是一样的,作为一种sapi运行。
1、当我们在终端敲入php这个命令的时候,它使用的是cli。
它就像一个web服务器一样来支持php完成这个请求,请求完成后再重新把控制权交给终端。
2、当使用apache作为宿主时,当一个请求到来时,php会来支持完成这个请求
php_minit_function 初始化module时运行 php_mshutdown_function 当module被卸载时运行 php_rinit_function 当一个request请求初始化时运行 php_rshutdown_function 当一个request请求结束时运行 php_minfo_function 这个是设置phpinfo中这个模块的信息 php_ginit_function 初始化全局变量时 php_gshutdown_function 释放全局变量时
比如php_ginit_function比如php_ginit_functionphp_ginit_function(test){ /** 初始化全局变量 */}//对应的c代码void zm_globals_ctor_test (zend_test_globals *test_globals tsrmls_dc){ /** 初始化全局变量 */}//在线程退出时,需要将之前自己申请的资源释放时,可以使用 php_gshutdown_function来注册析构函数。php_gshutdown_function(test){ /** 清除全局变量 */}//对应的c代码void zm_globals_dtor_test (zend_test_globals *test_globals tsrmls_dc){ /** 清除全局变量 */}
这里有一段代码,可以测试一下int minit_time;php_minit_function(test){ minit_time = time(null); return success;}php_mshutdown_function(test){ file *fp=fopen(mshutdown.txt,a+); fprintf(fp,%ld\n,time(null)); fclose(fp); return success;}int rinit_time;php_rinit_function(test){ rinit_time = time(null); return success;}php_rshutdown_function(test){ file *fp=fopen(rshutdown.txt,a+); fprintf(fp,%ld\n,time(null)); fclose(fp); return success;}php_minfo_function(test){ php_info_print_table_start(); php_info_print_table_header(2, module info, enabled); php_info_print_table_end(); /* remove comments if you have entries in php.ini display_ini_entries(); */}php_function(test){ php_printf(%d,time_of_minit); php_printf(%d,time_of_rinit); return;}
3、段错误调试linux下的c程序常常会因为内存访问错误等原因造成segment fault(段错误)此时如果系统core dump功能是打开的,那么将会有内存映像转储到硬盘上来,之后可以用gdb对core文件进行分析,还原系统发生段错误时刻的堆栈情况。这对于我们发现程序bug很有帮助。
使用ulimit -a可以查看系统core文件的大小限制;使用ulimit -c [kbytes]可以设置系统允许生成的core文件大小。
ulimit -c 0 不产生core文件
ulimit -c 100 设置core文件最大为100k
ulimit -c unlimited 不限制core文件大小
步骤:
1、当发生段错误时,我们查看ulimit -a (core file size (blocks, -c) 0)并没有文件,
2、设置 :ulimit -c unlimited 不限制core文件大小
3、运行程序 ,发生段错误时会自动记录在core中 (php -f workwitharray.php)
4、ls -al core.* 在那个文件下(-rw------- 1 leconte leconte 139264 01-06 22:3 1 core.2065)
5、使用gdb 运行程序和段错误记录的文件。(gdb ./test core.2065)
6、会提哪行有错。
很多系统默认的core文件大小都是0,我们可以通过在shell的启动脚本/etc/bashrc或者~/.bashrc等地方来加入 ulimit -c 命令来指定core文件大小,从而确保core文件能够生成。
除此之外,还可以在/proc/sys/kernel/core_pattern里设置core文件的文件名模板,详情请看core的官方man手册。
4、常见的变量操作宏cg -> complier global 编译时信息,包括函数表等(zend_globals_macros.h:32)eg -> executor global 执行时信息(zend_globals_macros.h:43)pg -> php core global 主要存储php.ini中的信息sg -> sapi global sapi信息
1、sg 针对sapi信息 在main/sapi.h文件中typedef struct _sapi_globals_struct { void *server_context; sapi_request_info request_info; sapi_headers_struct sapi_headers; int read_post_bytes; 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的定义
begin_extern_c()#ifdef zts# define sg(v) tsrmg(sapi_globals_id, sapi_globals_struct *, v)sapi_api extern int sapi_globals_id;#else# define sg(v) (sapi_globals.v)extern sapi_api sapi_globals_struct sapi_globals;#endifsapi_api void sapi_startup(sapi_module_struct *sf);sapi_api void sapi_shutdown(void);sapi_api void sapi_activate(tsrmls_d);sapi_api void sapi_deactivate(tsrmls_d);sapi_api void sapi_initialize_empty_request(tsrmls_d);end_extern_c()
成员都在sapi_globals_struct这里了
那么我么可以这样调用
sg(default_mimetype)sg(request_info).request_uri
可以感受一下这么一段代码
static int sapi_cgi_send_headers(sapi_headers_struct *sapi_headers tsrmls_dc){ char buf[sapi_cgi_max_header_length]; sapi_header_struct *h; zend_llist_position pos; long rfc2616_headers = 0; if(sg(request_info).no_headers == 1) { return sapi_header_sent_successfully; } if (sg(sapi_headers).http_response_code != 200) { int len; len = sprintf(buf, status: %d\r\n, sg(sapi_headers).http_response_code); phpwrite_h(buf, len); } if (sg(sapi_headers).send_default_content_type) { char *hd; hd = sapi_get_default_content_type(tsrmls_c); phpwrite_h(content-type:, sizeof(content-type: )-1); phpwrite_h(hd, strlen(hd)); phpwrite_h(\r\n, 2); efree(hd); } h = zend_llist_get_first_ex(&sapi_headers->headers, &pos); while (h) { phpwrite_h(h->header, h->header_len); phpwrite_h(\r\n, 2); h = zend_llist_get_next_ex(&sapi_headers->headers, &pos); } phpwrite_h(\r\n, 2); return sapi_header_sent_successfully;}
2、eg executor globalseg获取的是struct _zend_execution_globals结构体中的数据
struct _zend_execution_globals { ... hashtable symbol_table; /* 全局作用域,如果没有进入函数内部,全局=活动 */ hashtable *active_symbol_table; /* 活动作用域,当前作用域 */ ...}
通常,使用eg(symbol_table)获取的是全局作用域中的符号表,使用eg(active_symbol_table)获取的是当前作用域下的符号表
例如 来定义$foo = 'bar'
zval *fooval;make_std_zval(fooval);zval_string(fooval, bar, 1);zend_set_symbol(eg(active_symbol_table), foo, fooval);
或者从符号表中查找$foo
zval **fooval;if(zend_hash_find(&eg(symbol_table), foo, sizeof(foo), (void **)&fooval) == success) { return_stringl(z_strval_pp(fooval), z_strlen_pp(fooval));} else { return_false;}
上面的代码中,eg(active_symbol_table) == &eg(symbol_table)
3、cg() 用来访问核心全局变量。(zend/zend_globals_macros.h)
4、pg() php全局变量。我们知道php.ini会映射一个或者多个php全局结构。(main/php_globals.h)
5、fg() 文件全局变量。大多数文件i/o或相关的全局变量的数据流都塞进标准扩展出口结构。(ext/standard/file.h)
5、获取变量的类型和值#define z_type(zval) (zval).type#define z_type_p(zval_p) z_type(*zval_p)#define z_type_pp(zval_pp) z_type(**zval_pp)
比如获取一个变量的类型
void describe_zval(zval *foo){ if ( z_type_p(foo) == is_null ) { php_printf(这个变量的数据类型是: null); } else { php_printf(这个变量的数据类型不是null,这种数据类型对应的数字是: %d, z_type_p(foo)); }}
有这么几种类型
#define is_null 0#define is_long 1#define is_double 2#define is_bool 3#define is_array 4#define is_object 5#define is_string 6#define is_resource 7#define is_constant 8#define is_constant_array 9#define is_callable 10
php_printf()函数是内核对printf()函数的一层封装,我们可以像使用printf()函数那样使用它,以一个p结尾的宏的参数大多是*zval型变量。 此外获取变量类型的宏还有两个,分别是z_type和z_type_pp,前者的参数是zval型,而后者的参数则是**zval
比如gettype函数的实现
//开始定义php语言中的函数gettypephp_function(gettype){ //arg间接指向调用gettype函数时所传递的参数。是一个zval**结构 //所以我们要对他使用__pp后缀的宏。 zval **arg; //这个if的操作主要是让arg指向参数~ if (zend_parse_parameters(zend_num_args() tsrmls_cc, z, &arg) == failure) { return; } //调用z_type_pp宏来获取arg指向zval的类型。 //然后是一个switch结构,retval_string宏代表这gettype函数返回的字符串类型的值 switch (z_type_pp(arg)) { case is_null: retval_string(null, 1); break; case is_bool: retval_string(boolean, 1); break; case is_long: retval_string(integer, 1); break; case is_double: retval_string(double, 1); break; case is_string: retval_string(string, 1); break; case is_array: retval_string(array, 1); break; case is_object: retval_string(object, 1); break; case is_resource: { char *type_name; type_name = zend_rsrc_list_get_rsrc_type(z_lval_pp(arg) tsrmls_cc); if (type_name) { retval_string(resource, 1); break; } } default: retval_string(unknown type, 1); }}
获取变量的值,有这么多宏来获取
long
boolean
double
string value
string length
z_lval( )
z_bval( )
z_dval( )
z_strval( )
z_strlen( )
z_lval_p( )
z_bval_p( )
z_dval_p( )
z_strval_p( )
z_strlen_p( )
z_lval_pp( )
z_bval_pp( )
z_dval_pp( )
z_strval_pp( )
z_strlen_pp( )
hashtable
object
object properties
object class entry
resource value
z_arrval( )
z_obj( )
z_objprop( )
z_objce( )
z_resval( )
z_arrval_p( )
z_obj_p( )
z_objprop_p( )
z_objce_p( )
z_resval_p( )
z_arrval_pp( )
z_obj_pp( )
z_objprop_pp( )
z_objce_pp( )
z_resval_pp( )
rot13函数的实现
php_function(rot13){ zval **arg; char *ch, cap; int i; if (zend_num_args( ) != 1 || zend_get_parameters_ex(1, &arg) == failure) { wrong_param_count; } *return_value = **arg; zval_copy_ctor(return_value); convert_to_string(return_value); for(i=0, ch=return_value->value.str.val; ivalue.str.len; i++, ch++) { cap = *ch & 32; *ch &= ~cap; *ch = ((*ch>='a') && (*ch php_module_startup() -> php_startup_auto_globals() -> 保存变量到symbol_table符号表php_cgi_startup()在 fpm/fpm/fpm_main.c中定义php_module_startup() 在main/main.c中定义php_startup_auto_globals() 在main/php_variables.h中定义zend_hash_update(&eg(symbol_table), _get, sizeof(_get) + 1, &vars, sizeof(zval *), null);/* 读取$_server变量 */static php_function(print_server_vars) { zval **val; if (zend_hash_find(&eg(symbol_table), _server, sizeof(_server), (void **)&val) == success) { return_zval(*val, 1, 0); }else{ return_false; }}/* 读取$_server[$name] */zend_begin_arg_info(print_server_var_arginfo, 0) zend_arg_info(0, name)zend_end_arg_info()static php_function(print_server_var) { char *name; int name_len; zval **val; hashtable *ht_vars = null; hashposition pos; zval **ret_val; if (zend_parse_parameters(zend_num_args() tsrmls_cc, |s!, &name, &name_len) == failure) { return_null(); } if (zend_hash_find(&eg(symbol_table), _server, sizeof(_server), (void **)&val) == success) { ht_vars = z_arrval_pp(val); //此处需传入大于name长度+1的值,因为字符串值后面需要'\0' if (zend_hash_find(ht_vars, name, name_len+1, (void **)&ret_val) == success) { return_string(z_strval_pp(ret_val), 0); }else{ return_null(); } }else{ return_null(); }}
8、包装第三方库配置(config.m4)
search_path=/usr/local /usr #lib搜索的目录search_for=/include/curl/curl.h #lib头文件的路径if test -r $php_libs/$search_for; then libs_dir=$php_libselse # search default path list ac_msg_checking([for libs files in default path]) for i in $search_path ; do if test -r $i/$search_for; then libs_dir=$i #搜索到的lib的路径 ac_msg_result(found in $i) fi donefi/*验证lib是否存在*/if test -z $libs_dir; then ac_msg_result([not found]) ac_msg_error([please reinstall the libs distribution])fi/*编译的时候添加lib的include目录, -i/usr/include*/php_add_include($libs_dir/include) libname=curl #lib名称 libsymbol=curl_version #lib的一个函数,用来php_check_library验证lib/*验证lib*/php_check_library($libname,$libsymbol, [ php_add_library_with_path($libname, $libs_dir/$php_libdir, libs_shared_libadd) #编译的时候链接lib, -llibcurl ac_define(have_libslib,1,[ ])],[ ac_msg_error([wrong libs lib version or lib not found])],[ -l$libs_dir/$php_libdir -lm]) php_subst(libs_shared_libadd)
9、用于返回的宏//这些宏都定义在zend/zend_api.h文件里#define retval_resource(l) zval_resource(return_value, l)#define retval_bool(b) zval_bool(return_value, b)#define retval_null() zval_null(return_value)#define retval_long(l) zval_long(return_value, l)#define retval_double(d) zval_double(return_value, d)#define retval_string(s, duplicate) zval_string(return_value, s, duplicate)#define retval_stringl(s, l, duplicate) zval_stringl(return_value, s, l, duplicate)#define retval_empty_string() zval_empty_string(return_value)#define retval_zval(zv, copy, dtor) zval_zval(return_value, zv, copy, dtor)#define retval_false zval_bool(return_value, 0)#define retval_true zval_bool(return_value, 1)#define return_resource(l) { retval_resource(l); return; }#define return_bool(b) { retval_bool(b); return; }#define return_null() { retval_null(); return;}#define return_long(l) { retval_long(l); return; }#define return_double(d) { retval_double(d); return; }#define return_string(s, duplicate) { retval_string(s, duplicate); return; }#define return_stringl(s, l, duplicate) { retval_stringl(s, l, duplicate); return; }#define return_empty_string() { retval_empty_string(); return; }#define return_zval(zv, copy, dtor) { retval_zval(zv, copy, dtor); return; }#define return_false { retval_false; return; }#define return_true { retval_true; return; }
其实,除了这些标量类型,还有很多php语言中的复合类型我们需要在函数中返回,如数组和对象,我们可以通过retval_zval与return_zval来操作它们
10、hashtable的遍历函数//基于long key的操作函数zval *v3;make_std_zval(v3);zval_string(v3, value3, 1);zend_hash_index_update(names, 0, &v3, sizeof(zval *), null);//按数字索引键更新hashtable元素的值zval **v4;zend_hash_index_find(names, 1, &v4); //按数字索引获取hashtable元素的值php_printf(v4 : );phpwrite(z_strval_pp(v4), z_strlen_pp(v4));php_printf(\n);ulong idx;idx = zend_hash_index_exists(names, 10);//按数字索引查找hashtable,如果找到返回 1, 反之则返回 0zend_hash_index_del(names, 2); //按数字索引删除hashtable元素//hashtable的遍历函数zend_hash_internal_pointer_reset(names); //初始化hash指针zend_hash_internal_pointer_reset_ex(names, &pos);//初始化hash指针,并付值给poszend_hash_get_current_data(names, (void**) &val); //获取当前hash存储值,data should be cast to void**, ie: (void**) &datazend_hash_get_current_data_ex(names, (void**) &val, &pos) == success; //获取当前hash存储值zend_hash_get_current_key(names, &key, &klen, &index, 0) == hash_key_is_longzend_hash_get_current_key_ex(names, &key, &klen, &index, 0, &pos) == hash_key_is_long; //读取hashtable当前的key,返回值会有两种 hash_key_is_long | hash_key_is_string ,分别对应array(value),array(key=>value)两种hashtablezend_hash_move_forward(names); zend_hash_move_forward_ex(names, &pos); //hash指针移至下一位//hashtable长度php_printf(%*carray(%d) {\n, depth * 2, ' ', zend_hash_num_elements(z_arrval_p(zv))
一个简单的函数
function hello_array_strings($arr) { if (!is_array($arr)) return null; printf(the array passed contains %d elements , count($arr)); foreach($arr as $data) { if (is_string($data)) echo $data ; }}
php内核实现
php_function(hello_array_strings){ zval *arr, **data; hashtable *arr_hash; hashposition pointer; int array_count; if (zend_parse_parameters(zend_num_args() tsrmls_cc, a, &arr) == failure) { return_null(); } arr_hash = z_arrval_p(arr); array_count = zend_hash_num_elements(arr_hash); php_printf(the array passed contains %d elements , array_count); for(zend_hash_internal_pointer_reset_ex(arr_hash, &pointer); zend_hash_get_current_data_ex(arr_hash, (void**) &data, &pointer) == success; zend_hash_move_forward_ex(arr_hash, &pointer)) { if (z_type_pp(data) == is_string) { phpwrite(z_strval_pp(data), z_strlen_pp(data)); php_printf( ); } } return_true;}
11、输入输出int php_printf(const char *format, ...);int php_write(void *buf, uint size tsrmls_dc);int phpwrite(void *buf, uint size);void php_html_puts(const char *buf, uint size tsrmls_dc)
示例
php_printf(hello %s\n, name);php_printf(hello );phpwrite(name, name_len);php_printf(\n);
注意,用的是php_printf()而不是更熟悉的printf()。使用这个函数是有重要的理由的。
首先,它允许字符串通过php的缓冲机制的处理,该机制除了可以缓冲数据,还可执行额外的处理,比如gzip压缩。
其次,虽然stdout是极佳的输出目标,使用cli或cgi时,多数sapi期望通过特定的pipe或socket传来输出。
所以,试图只是通过printf()写入stdout可能导致数据丢失、次序颠倒或者被破坏,因为它绕过了预处理。
最后,函数通过返回true将控制权还给调用程序。你可以没有显式地返回值(默认是null)而是让控制到达你的函数的结尾,但这是坏习惯。函数如果不传回任何有意义的结果,应该返回true以说明:“完成任务,一切正常”。
php字符串实际可能包含null值,所以,输出含有null的二进制安全的字符串以及后跟null的多个字符的方法是,使用上面的代码块替换php_printf()指令:这段代码使用php_printf()处理确信没有null的字符串,但使用另外的宏-phpwrite-处理用户提供的字符串。这个宏接受zend_parse_parameters()提供的长度(name_len)参数以便可以打印name的完整内容,不论它是否含有null。
当需要调试或者是查看变量当前的值的时候,可以使用下列函数
zend_api int zend_print_zval(zval *expr, int indent);zend_api void zend_print_zval_r(zval *expr, int indent tsrmls_dc);
这里的expr是需要打印的zval变量,indent是打印的时候的缩进量。这两个函数不同之处在于, 前者打印出zval的平面表示,并且打印出那些无法很好显示的复杂类型的文本描述。后者则会递归打印zval, 输出结果与php中的print_r函数相同。
格式化函数在php扩展开发中,应该避免直接使用sprintf函数,取而代之的是使用main/spprintf.h 中定义的spprintf和vspprintf函数。
在main/spprintf.h中,总共定义了两个函数api
phpapi int spprintf( char **pbuf, size_t max_len, const char *format, ...);phpapi int vspprintf(char **pbuf, size_t max_len, const char *format, va_list ap);
字符串连接/* zend_operators.h */zend_api int add_char_to_string(zval *result, const zval *op1, const zval *op2);zend_api int add_string_to_string(zval *result, const zval *op1, const zval *op2);zend_api int concat_function(zval *result, zval *op1, zval *op2 tsrmls_dc);
如果需要将str2连接到str1之后,则可以将result设置为str1。
大小写转换zend_api void zend_str_tolower(char *str, unsigned int length);zend_api char *zend_str_tolower_copy(char *dest, const char *source, unsigned int length);zend_api char *zend_str_tolower_dup(const char *source, unsigned int length);
注意的是,在zend中并没有提供转换为大写的函数,在php标准扩展中可以找到该函数。
参考文章
http://php.find-info.ru/php/016/ch23lev1sec1.html
http://docstore.mik.ua/orelly/webprog/php/ch14_10.htm
http://wiki.jikexueyuan.com/project/extending-embedding-php/2.4.html
http://www.php-internals.com/book/?p=chapt02/02-03-02-opcode
http://blog.csdn.net/phpkernel/article/details/5721134
https://github.com/chenpingzhao/php-ext-trie-filter/blob/master/trie_filter.c
http://aicode.cc/tags/php%e6%89%a9%e5%b1%95/
http://www.phpboy.net/2013-12/28-php-stream-wrapper.html
http://thiniki.sinaapp.com/?p=163
http://top.jobbole.com/26400/
https://github.com/walu/phpbook/blob/master/9.1.md
http://devzone.zend.com/318/extension-writing-part-ii-parameters-arrays-and-zvals-continued/