zend_fcall_info
。我们先来看看zend_fcall_info
的定义:
typedef struct _zend_fcall_info {
size_t size;
zval function_name;
zval *retval;
zval *params;
zend_object *object;
zend_bool no_separation;
uint32_t param_count;
} zend_fcall_info;
size
是结构体zend_fcall_info
的大小,通过sizeof(fci)
计算得到。
function_name
是函数的名字,用来查找函数是否存在于EG(function_table)
中。EG(function_table)
里面包含了所有的函数。
retval
是用来存放函数返回值的。
params
用来存放我们需要传递给函数的参数,它是一个zval
数组。
object
当这个函数是属于某个类的时候会用到,指向这个类。
no_separation
表示zend_call_function
内部要不要释放我们的参数引用计数(一般都是传1,表示我们自己控制参数的引用计数,而zend_call_function
只管使用即可)。
param_count
是传递给函数的参数个数。
OK,我们通过调试来具体看看。
脚本如下:
<?php
function task()
{
echo "success\n";
}
Study\Coroutine::create('task');
开始调试:
~/codeDir/cppCode/study # cgdb php
GNU gdb (GDB) 8.2
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-alpine-linux-musl".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from php...(no debugging symbols found)...done.
(gdb)
我们在zim_study_coroutine_util_create
出打断点:
(gdb) b zim_study_coroutine_util_create
Breakpoint 1 at 0x7ffff78d62e0: file /root/codeDir/cppCode/study/study_coroutine_util.cc, line 10.
(gdb)
然后执行:
(gdb) r test.php
Starting program: /usr/local/bin/php test.php
Breakpoint 1, zim_study_coroutine_util_create (execute_data=0x7ffff761d090, return_value=0x7fffffffb0b0) at /root/codeDir/cppCode/study/study_coroutine_util.cc:10
(gdb)
9│ PHP_METHOD(study_coroutine_util, create)
10├>{
11│ zend_fcall_info fci = empty_fcall_info;
12│ zend_fcall_info_cache fcc = empty_fcall_info_cache;
13│ zval result;
14│
15│ ZEND_PARSE_PARAMETERS_START(1, 1)
16│ Z_PARAM_FUNC(fci, fcc)
17│ ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
18│
19│ fci.retval = &result;
20│ if (zend_call_function(&fci, &fcc) != SUCCESS) {
21│ return;
22│ }
23│
24│ *return_value = result;
25│ }
我们执行到19
行之前:
(gdb) u 19
zim_study_coroutine_util_create (execute_data=0x7ffff761d090, return_value=0x7fffffffb0b0) at /root/codeDir/cppCode/study/study_coroutine_util.cc:19
(gdb)
9│ PHP_METHOD(study_coroutine_util, create)
10│ {
11│ zend_fcall_info fci = empty_fcall_info;
12│ zend_fcall_info_cache fcc = empty_fcall_info_cache;
13│ zval result;
14│
15│ ZEND_PARSE_PARAMETERS_START(1, 1)
16│ Z_PARAM_FUNC(fci, fcc)
17│ ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
18│
19├───> fci.retval = &result;
20│ if (zend_call_function(&fci, &fcc) != SUCCESS) {
21│ return;
22│ }
23│
24│ *return_value = result;
25│ }
此时,我们来看看fci
里面的信息:
(gdb) p fci
$2 = {size = 56, function_name = {value = {lval = 93825009556768, dval = 4.6355713942725711e-310, counted = 0x5555565d9d20, str = 0x5555565d9d20, arr = 0x5555565d9d20, obj = 0x5555565d9d20, res = 0x555556
5d9d20, ref = 0x5555565d9d20, ast = 0x5555565d9d20, zv = 0x5555565d9d20, ptr = 0x5555565d9d20, ce = 0x5555565d9d20, func = 0x5555565d9d20, ww = {w1 = 1448975648, w2 = 21845}}, u1 = {v = {type = 6 '\006',
type_flags = 0 '\000', u = {call_info = 0, extra = 0}}, type_info = 6}, u2 = {next = 0, cache_slot = 0, opline_num = 0, lineno = 0, num_args = 0, fe_pos = 0, fe_iter_idx = 0, access_flags = 0, property_gu
ard = 0, constant_flags = 0, extra = 0}}, retval = 0x0, params = 0x0, object = 0x0, no_separation = 1 '\001', param_count = 0}
(gdb)
fci.size
的值是56,正好是sizeof(zend_fcall_info)
的大小:
8 + 16 + 8 + 8 + 8 + 8(本来是zend_bool + uint32_t是5字节的,但是因为内存对齐所以占用8字节)
我们再来看看function_name
,它是一个zval
类型的结构体。因为function_name.u1.v.type
的值是6,所以这个zval
里面指向一个zend_string
。所以我们需要取fci.function_name.value.str
:
(gdb) p *fci.function_name.value.str
$5 = {gc = {refcount = 1, u = {type_info = 454}}, h = 9223372043240494936, len = 4, val = "t"}
(gdb)
我们发现,这个zend_string
里面包含的字符串长度为4
,所以我们如下查看字符串内容:
(gdb) p *fci.function_name.value.str.val@4
$7 = "task"
(gdb)
OK,这就是我们的传递给我们接口的函数名字。
retval
的话,没必要讲。
我们再来看看我们传递给task
函数的参数fci.params
:
(gdb) p fci.params
$8 = (zval *) 0x0
(gdb)
因为我们没有给task
函数传递任何参数。
很显然,fci.param_count
的值为0:
(gdb) p fci.param_count
$9 = 0
(gdb)
我们再来看看fci.object
的值:
(gdb) p fci.object
$10 = (zend_object *) 0x0
(gdb)
因为我们这个函数不在任何类里面定义,所以很显然object
是0。
OK,那如果我要给这个task
函数传递参数我该怎么去做呢?比如如下脚本:
<?php
function task($a, $b)
{
echo $a . PHP_EOL;
echo $b . PHP_EOL;
}
Study\Coroutine::create('task', 'a', 'b');
我们需要修改一下PHP_METHOD(study_coroutine_util, create)
这个接口:
PHP_METHOD(study_coroutine_util, create)
{
zend_fcall_info fci = empty_fcall_info;
zend_fcall_info_cache fcc = empty_fcall_info_cache;
zval result;
ZEND_PARSE_PARAMETERS_START(1, -1)
Z_PARAM_FUNC(fci, fcc)
Z_PARAM_VARIADIC('*', fci.params, fci.param_count)
ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
fci.retval = &result;
if (zend_call_function(&fci, &fcc) != SUCCESS) {
return;
}
*return_value = result;
}
我们重新编译扩展:
make clean && make && make install
然后执行我们的脚本:
~/codeDir/cppCode/study # php test.php
a
b
~/codeDir/cppCode/study #
其中,ZEND_PARSE_PARAMETERS_START(1, -1)
的-1
代表没有限制传递给Study\Coroutine::create
接口函数的最大参数个数限制,也就是可变参数。
Z_PARAM_VARIADIC
这个宏是用来解析可变参数的,'*'
对于Z_PARAM_VARIADIC
实际上并没有用到。*
表示可变参数可传或者不传递。与之对应的是'+'
,表示可变参数至少传递一个。
OK,我们来进行调试:
~/codeDir/cppCode/study # cgdb php
GNU gdb (GDB) 8.2
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-alpine-linux-musl".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from php...(no debugging symbols found)...done.
(gdb)
我们在zim_study_coroutine_util_create
出打断点:
(gdb) b zim_study_coroutine_util_create
Breakpoint 1 at 0x7ffff78d62e0: file /root/codeDir/cppCode/study/study_coroutine_util.cc, line 10.
(gdb)
然后执行:
(gdb) r test.php
Starting program: /usr/local/bin/php test.php
Breakpoint 1, zim_study_coroutine_util_create (execute_data=0x7ffff761d090, return_value=0x7fffffffb0b0) at /root/codeDir/cppCode/study/study_coroutine_util.cc:10
(gdb)
9│ PHP_METHOD(study_coroutine_util, create)
10├>{
11│ zend_fcall_info fci = empty_fcall_info;
12│ zend_fcall_info_cache fcc = empty_fcall_info_cache;
13│ zval result;
14│
15│ ZEND_PARSE_PARAMETERS_START(1, -1)
16│ Z_PARAM_FUNC(fci, fcc)
17│ Z_PARAM_VARIADIC('*', fci.params, fci.param_count)
18│ ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
19│
20│ fci.retval = &result;
21│ if (zend_call_function(&fci, &fcc) != SUCCESS) {
22│ return;
23│ }
24│
25│ *return_value = result;
26│ }
我们执行到20
行之前:
(gdb) u 20
zim_study_coroutine_util_create (execute_data=0x7ffff761d090, return_value=0x7fffffffb0b0) at /root/codeDir/cppCode/study/study_coroutine_util.cc:20
(gdb)
此时,我们来查看一下fci.param_count
:
(gdb) p fci.param_count
$1 = 2
(gdb)
因为我们传递了2
个可变参数,所以这里是2
。
我们再来看看fci.params
:
(gdb) p *fci.params
$3 = {value = {lval = 140737353162016, dval = 6.9533491283979037e-310, counted = 0x7ffff7f11d20, str = 0x7ffff7f11d20, arr = 0x7ffff7f11d20, obj = 0x7ffff7f11d20, res = 0x7ffff7f11d20, ref = 0x7ffff7f11d2
0, ast = 0x7ffff7f11d20, zv = 0x7ffff7f11d20, ptr = 0x7ffff7f11d20, ce = 0x7ffff7f11d20, func = 0x7ffff7f11d20, ww = {w1 = 4159773984, w2 = 32767}}, u1 = {v = {type = 6 '\006', type_flags = 0 '\000', u =
{call_info = 0, extra = 0}}, type_info = 6}, u2 = {next = 0, cache_slot = 0, opline_num = 0, lineno = 0, num_args = 0, fe_pos = 0, fe_iter_idx = 0, access_flags = 0, property_guard = 0, constant_flags = 0
, extra = 0}}
(gdb)
我们打印第一个参数:
(gdb) p *fci.params[0].value.str.val@1
$21 = "a"
(gdb)
再打印第二个参数:
(gdb) p *fci.params[1].value.str.val@1
$22 = "b"
(gdb)
那我如果给创建协程的接口传递一个匿名函数会这么样呢?PHP
脚本如下:
<?php
$c = 'c';
$d = 'd';
Study\Coroutine::create(function ($a, $b) use ($c, $d) {
echo $a . PHP_EOL;
echo $b . PHP_EOL;
echo $c . PHP_EOL;
echo $d . PHP_EOL;
}, 'a', 'b');
执行结果如下:
~/codeDir/cppCode/study # php test.php
a
b
c
d
~/codeDir/cppCode/study #
我们来调试一下:
~/codeDir/cppCode/study # cgdb php
GNU gdb (GDB) 8.2
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-alpine-linux-musl".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from php...(no debugging symbols found)...done.
(gdb)
我们在zim_study_coroutine_util_create
出打断点:
(gdb) b zim_study_coroutine_util_create
Breakpoint 1 at 0x7ffff78d62e0: file /root/codeDir/cppCode/study/study_coroutine_util.cc, line 10.
(gdb)
然后执行:
(gdb) r test.php
Starting program: /usr/local/bin/php test.php
Breakpoint 1, zim_study_coroutine_util_create (execute_data=0x7ffff761d090, return_value=0x7fffffffb0b0) at /root/codeDir/cppCode/study/study_coroutine_util.cc:10
(gdb)
9│ PHP_METHOD(study_coroutine_util, create)
10├>{
11│ zend_fcall_info fci = empty_fcall_info;
12│ zend_fcall_info_cache fcc = empty_fcall_info_cache;
13│ zval result;
14│
15│ ZEND_PARSE_PARAMETERS_START(1, -1)
16│ Z_PARAM_FUNC(fci, fcc)
17│ Z_PARAM_VARIADIC('*', fci.params, fci.param_count)
18│ ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
19│
20│ fci.retval = &result;
21│ if (zend_call_function(&fci, &fcc) != SUCCESS) {
22│ return;
23│ }
24│
25│ *return_value = result;
26│ }
我们执行到20
行之前:
(gdb) u 20
zim_study_coroutine_util_create (execute_data=0x7ffff761d090, return_value=0x7fffffffb0b0) at /root/codeDir/cppCode/study/study_coroutine_util.cc:20
(gdb)
此时,我们来查看一下fci.function_name
:
(gdb) p fci.function_name
$1 = {value = {lval = 140737344071552, dval = 6.9533486792693069e-310, counted = 0x7ffff7666780, str = 0x7ffff7666780, arr = 0x7ffff7666780, obj = 0x7ffff7666780, res = 0x7ffff7666780, ref = 0x7ffff766678
0, ast = 0x7ffff7666780, zv = 0x7ffff7666780, ptr = 0x7ffff7666780, ce = 0x7ffff7666780, func = 0x7ffff7666780, ww = {w1 = 4150683520, w2 = 32767}}, u1 = {v = {type = 8 '\b', type_flags = 1 '\001', u = {c
all_info = 0, extra = 0}}, type_info = 264}, u2 = {next = 0, cache_slot = 0, opline_num = 0, lineno = 0, num_args = 0, fe_pos = 0, fe_iter_idx = 0, access_flags = 0, property_guard = 0, constant_flags = 0
, extra = 0}}
(gdb)
因为fci.function_name.u1.v.type
的值为8,所以它是一个对象,因为我们传递了一个闭包进去。我们通过打印如下值来进行确认:
(gdb) p /t fcc.function_handler.op_array.fn_flags
$4 = 1000000100000000000000000000
(gdb)
我们看到了第21
位为1
,所以这是一个闭包。
OK,分析完毕。
下一篇:协程创建(五)
----------伟大的分割线-----------
PHP饭米粒(phpfamily) 由一群靠谱的人建立,愿为PHPer带来一些值得细细品味的精神食粮!
饭米粒只发原创或授权发表的文章,不转载网上的文章
所发的文章,均可找到原作者进行沟通。
也希望各位多多打赏(算作稿费给文章作者),更希望大家多多投搞。
投稿请联系:
shenzhe163@gmail.com
本文由 codinghuang 授权 饭米粒 发布,转载请注明本来源信息