前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >手把手撸PHP扩展 0x08: 协程创建(四)

手把手撸PHP扩展 0x08: 协程创建(四)

作者头像
桶哥
发布2019-08-01 10:05:20
7760
发布2019-08-01 10:05:20
举报
文章被收录于专栏:PHP饭米粒PHP饭米粒

这篇文章我们介绍下zend_fcall_info。我们先来看看zend_fcall_info的定义:

代码语言:javascript
复制
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,我们通过调试来具体看看。

脚本如下:

代码语言:javascript
复制
<?php

function task()
{
	echo "success\n";
}

Study\Coroutine::create('task');

开始调试:

代码语言:javascript
复制
~/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出打断点:

代码语言:javascript
复制
(gdb) b zim_study_coroutine_util_create
Breakpoint 1 at 0x7ffff78d62e0: file /root/codeDir/cppCode/study/study_coroutine_util.cc, line 10.
(gdb)

然后执行:

代码语言:javascript
复制
(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)
代码语言:javascript
复制
 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行之前:

代码语言:javascript
复制
(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)
代码语言:javascript
复制
 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里面的信息:

代码语言:javascript
复制
(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)的大小:

代码语言:javascript
复制
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

代码语言:javascript
复制
(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,所以我们如下查看字符串内容:

代码语言:javascript
复制
(gdb) p *fci.function_name.value.str.val@4
$7 = "task"
(gdb)

OK,这就是我们的传递给我们接口的函数名字。

retval的话,没必要讲。

我们再来看看我们传递给task函数的参数fci.params

代码语言:javascript
复制
(gdb) p fci.params
$8 = (zval *) 0x0
(gdb)

因为我们没有给task函数传递任何参数。

很显然,fci.param_count的值为0:

代码语言:javascript
复制
(gdb) p fci.param_count
$9 = 0
(gdb)

我们再来看看fci.object的值:

代码语言:javascript
复制
(gdb) p fci.object
$10 = (zend_object *) 0x0
(gdb)

因为我们这个函数不在任何类里面定义,所以很显然object是0。

OK,那如果我要给这个task函数传递参数我该怎么去做呢?比如如下脚本:

代码语言:javascript
复制
<?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)这个接口:

代码语言:javascript
复制
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;
}

我们重新编译扩展:

代码语言:javascript
复制
make clean && make && make install

然后执行我们的脚本:

代码语言:javascript
复制
~/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,我们来进行调试:

代码语言:javascript
复制
~/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出打断点:

代码语言:javascript
复制
(gdb) b zim_study_coroutine_util_create
Breakpoint 1 at 0x7ffff78d62e0: file /root/codeDir/cppCode/study/study_coroutine_util.cc, line 10.
(gdb)

然后执行:

代码语言:javascript
复制
(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)
代码语言:javascript
复制
 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行之前:

代码语言:javascript
复制
(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

代码语言:javascript
复制
(gdb) p fci.param_count
$1 = 2
(gdb)

因为我们传递了2个可变参数,所以这里是2

我们再来看看fci.params

代码语言:javascript
复制
(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)

我们打印第一个参数:

代码语言:javascript
复制
(gdb) p *fci.params[0].value.str.val@1
$21 = "a"
(gdb)

再打印第二个参数:

代码语言:javascript
复制
(gdb) p *fci.params[1].value.str.val@1
$22 = "b"
(gdb)

那我如果给创建协程的接口传递一个匿名函数会这么样呢?PHP脚本如下:

代码语言:javascript
复制
<?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');

执行结果如下:

代码语言:javascript
复制
~/codeDir/cppCode/study # php test.php
a
b
c
d
~/codeDir/cppCode/study # 

我们来调试一下:

代码语言:javascript
复制
~/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出打断点:

代码语言:javascript
复制
(gdb) b zim_study_coroutine_util_create
Breakpoint 1 at 0x7ffff78d62e0: file /root/codeDir/cppCode/study/study_coroutine_util.cc, line 10.
(gdb)

然后执行:

代码语言:javascript
复制
(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)
代码语言:javascript
复制
 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行之前:

代码语言:javascript
复制
(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

代码语言:javascript
复制
(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,所以它是一个对象,因为我们传递了一个闭包进去。我们通过打印如下值来进行确认:

代码语言:javascript
复制
(gdb) p /t fcc.function_handler.op_array.fn_flags
$4 = 1000000100000000000000000000
(gdb)

我们看到了第21位为1,所以这是一个闭包。

OK,分析完毕。

下一篇:协程创建(五)

----------伟大的分割线-----------

PHP饭米粒(phpfamily) 由一群靠谱的人建立,愿为PHPer带来一些值得细细品味的精神食粮!

饭米粒只发原创或授权发表的文章,不转载网上的文章

所发的文章,均可找到原作者进行沟通。

也希望各位多多打赏(算作稿费给文章作者),更希望大家多多投搞。

投稿请联系:

shenzhe163@gmail.com

本文由 codinghuang 授权 饭米粒 发布,转载请注明本来源信息

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-07-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 PHP饭米粒 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 这篇文章我们介绍下zend_fcall_info。我们先来看看zend_fcall_info的定义:
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档