一. 背景
我们最近有一项业务,购买百度云资源以及部署基础服务,流程是这样的根据不同用户的需求,生成用户需要购买资源的配置
调用百度云接口,购买资源,并且查询百度云实例购买的eip等信息
在我们的一台中控机上执行脚本,建立中控机和百度云购买实例之间的信任关系
信任关系建立后,可以启动部署脚本,将部署资源发送到百度云实例上
实际上,可以预先在百度云上打一个镜像,在购买实例的时候,指定镜像id,在某些时候,就可以不用传输基础服务资源到购买的实例了;但是,由于我们部署的每一个基础服务的配置不一样,还是需要一台中控机来发布服务
在发布服务的时候,需要建立信任关系,我们整个流程是通过php实现,面临的问题是两个php执行shell脚本问题,我们选择php的exec方法
建立信任关系涉及到服务之间的交互,我们选择expect来做这个交互,实际上,php也有实现交互的扩展,我们这次没有选择,主要是考虑到未来可能的php版本升级问题。
在使用中我们发现了一些问题,主要是expect的使用问题,这个在后面一篇博客上介绍一下,这篇文章,主要是关心php的exec实现,php的exec和我之前研究的php和Go语言之间的交互存在相关的地方,实际上也是跨语言的交互问题,那么php内置的exec是怎么实现调起服务的呢?
二. php的exec实现
exec是php标准方法,具体实现,在ext/standard/exec.c:250做了定义,我们通过gdb调试,打一个断点,来看看调用栈; 首先我们写了一个php脚本代码很简单,让php的源码执行一个ls -lecho exec("ls -l");
在gdb中执行
1
2
3
4
5
6
7
8
9
10
11
12(gdb) bt
#0 php_exec (type=0, cmd=0x101a5a8d8 "ls -l", array=0x0, return_value=0x101a16090) at ext/standard/exec.c:97
#1 0x00000001003c2574 in php_exec_ex (execute_data=0x101a160a0, return_value=0x101a16090, mode=0) at ext/standard/exec.c:231
#2 0x00000001003c241f in zif_exec (execute_data=0x101a160a0, return_value=0x101a16090) at ext/standard/exec.c:250
#3 0x00000001005d5538 in ZEND_DO_ICALL_SPEC_HANDLER (execute_data=0x101a16030) at Zend/zend_vm_execute.h:586
#4 0x0000000100579404 in execute_ex (ex=0x101a16030) at Zend/zend_vm_execute.h:417
#5 0x000000010057955a in zend_execute (op_array=0x101a73300, return_value=0x0) at Zend/zend_vm_execute.h:458
#6 0x0000000100515832 in zend_execute_scripts (type=8, retval=0x0, file_count=3) at Zend/zend.c:1445
#7 0x000000010046c4a1 in php_execute_script (primary_file=0x7ffeefbff188) at main/main.c:2516
#8 0x000000010060531d in do_cli (argc=2, argv=0x7ffeefbff898) at sapi/cli/php_cli.c:977
#9 0x00000001006042b1 in main (argc=2, argv=0x7ffeefbff898) at sapi/cli/php_cli.c:1347
(gdb)
简述一下php的处理流程先解析出exec方法的输入参数,通过zend_parse_parameters()通用方法解析。
1
2
3
4
5
6
7
8
9if (mode) {
if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|z/", &cmd, &cmd_len, &ret_code) == FAILURE) {
RETURN_FALSE;
}
} else {
if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|z/z/", &cmd, &cmd_len, &ret_array, &ret_code) == FAILURE) {
RETURN_FALSE;
}
}执行php_exec方法
这里遇到一件非常有意思的事情,gdb在调试的时候,我想进VCWD_POPEN预定义的方法看一下,被IDE的代码跳转给带偏了, popen方法在unix下面是系统方法,在windows下面是要应用程序自己实现的,结果IDE一直带我进了window下的方法实现中,导致我深陷其中 ^_^
```c#define VCWD_POPEN(command, type) popen(command, type)
#ifdef PHP_WIN32
fp = VCWD_POPEN(cmd, “rb”);
#else
fp = VCWD_POPEN(cmd, “r”);
#endif
1
2
3
4
5
6
7
8对于函数是否能进入,执行一下`nm`,一般就知道了
```bash
[email protected]:~/Debug/php-7.0.29/bin$nm ./php | grep "popen"
0000000100a7ca70 s _arginfo_popen
U _popen
000000010055e6a0 T _virtual_popen
00000001003c71d0 t _zif_popenunix的popen函数执行 popen函数执行中会创建一个双向管道,然后fork一个进程,执行shell命令,之后通过pclose关闭
```bash
POPEN(3) BSD Library Functions Manual POPEN(3)
NAME
pclose, popen – process I/O
LIBRARY
Standard C Library (libc, -lc)
SYNOPSIS
#include
1
2
3
4
5- **从`pipe`中读取流信息**
```bash
stream = php_stream_fopen_from_pipe(fp, "rb");
三. 结论
相比与我们之前通过动态链接库的方式实现跨语言的调用,php的exec在实现的时候,需要创建进程和创建管道,性能的开销会大很多,当然,使用exec的优势也很明显,开发效率会高很多,一行代码解决了跨语言问题…