C语言项目PHP-SRC源码的函数运行过程简析
废话文学
PHP是C,ASM开发而成,本质是C语言的大型项目,它的脚本内容解析由著名的开源bision re2c等库完成,这在直播时完整说过。我们这里讲的是PHP7.4.X的,当然最近的PHP8.X还是换汤不换药,只不过这个药没有多少人会学习。
高以下为基,贵以贱为本
物有本末事有终始知所先后则近道矣
掌握了适当的底层
才能更好的用好上层各种编程语言构建的任何技术体系
所有上层高级语言构建的任何技术体系都有一个源头,就像学中国的文化一样,典籍如山,百家争鸣,但它们的源头就是一部古老又晦涩的<易经>
,群经之首,大道之源,在计算机世界里,编程语言无数且在不断的变化发展,框架无数且在不断的变化发展,这些都越来越接近于无限
,但你的生命是有限的,你是学不完的,就像学功夫一样,门派繁杂,招式繁杂越来越多,所以李小龙提出以无法为有法,以无限为有限
就是利用有限的时间,有限的生命去掌握变化多端接近于无限
的事物。而无限
的事物是非常多的,且随着时间的变化不断演化变化发展,就像PHP一样,GO,CPP一样,语法推出了一大堆,它们都是在不断的堆积,而我们的生命和时间是非常有限的,且是珍贵的,我们要在有限的时间内,有限的精力下去掌握它们的根本
,而它们的根本就是底层
(喜欢技术的朋友加我Le-studyg ^_^)
底层
的特点是:在很多年以内变化少,对于上层各种高级编程构建的技术体系来说几乎可以说没有变化,并且它只需要学习一次,就可以掌握上层各种高级编程语言的根本
,就能理解上层各种高级语言构建的技术体系,就能更好的用好各种高级语言。
比如CPP的语法
int i1 = 10;
int i2{ 10 };
int i3(10);
int i4= { 10 };
int i5= (10);
int i6{ (10) };
int i7((10));
int i71(((((((((((((((10)))))))))))))));
int i72 = (((((((((((((((((10))))))))))))))));
int i73{};
int i8 = ((10));
int i9 = { (10) };
int i10{ i9 };
const int i13 = 10;
constexpr int i14 = 10;
auto i15 = 10;
auto const i18 = 10;
auto constexpr i19 = 10;
你一看,好像是新技术,新东西,其实你要是听我一句,看一下它的核心
全是一样,我这里的核心
并没有直接在本文给你展示。若有兴致可以互加好友详说。
示例脚本内容及运行环境说明
之所以拿LINUX,是因为目前互联网公司,工厂等企业几乎是用Linux作为服务器
<?phpecho posix_getpid();
关于posix_getpid
在讲PHP多进程编程的时候已经讲过,不再过多的陈述,它的功能就是获取进程标识PID,底层用的是LINUX 核心API getpid()
,而核心API的开发编程在LINUX C 也是说过的知识点了,其它的编程语言如GO JAVA PYTHON NODE RUST ….全都会用这个函数达成目的。
在此脚本文件里,最核心的函数是getpid
和 ZEND_ECHO_SPEC_CV_HANDLER
函数,也就是说启动进程
运行后,主要就是执行这2个C函数,当然运行期间会涉及大量的其它C函数。
op_array里收集的数据
它的结构,关于进程启动时,结构体的变量MemoryLayout我已经详细解释过。
struct _zend_op_array {
zend_uchar type;
zend_uchar arg_flags[3];
uint32_t fn_flags;
zend_string *function_name;
zend_class_entry *scope;
zend_function *prototype;
uint32_t num_args;
uint32_t required_num_args;
zend_arg_info *arg_info;
int cache_size;
int last_var;
uint32_t T;
uint32_t last;
zend_op *opcodes;
void ***run_time_cache__ptr;
HashTable **static_variables_ptr__ptr;
HashTable *static_variables;
zend_string **vars;//这里一般会收集PHP各种类型的变量名
//关于变量的实现过程,MemoryLayout已经在直播过程中解释过
uint32_t *refcount;
int last_live_range;
int last_try_catch;
zend_live_range *live_range;
zend_try_catch_element *try_catch_array;
zend_string *filename;
uint32_t line_start;
uint32_t line_end;
zend_string *doc_comment;
int last_literal;
zval *literals;//常量值 一般是变量值以及类名,函数名的收集
void *reserved[6];} *
*op_array->literals[0] ->value->str->val="posix_getpid" //脚本文件里的函数名//1 你得知道此函数占了多少字节?//2 你得知道此函数是内置函数,与哪个内置模块关联//3 你得知道假如是你自己写的函数,会占用多少字节?//4 你得知道程序运行时为什么需要内存?内存跟数据有什么关系?//5 你得知道内存减少意味着什么?
运行堆栈
1 execute_ex (ex=0x7ffff28130d0) 2 zend_execute (op_array=0x7ffff287d2a0, return_value=<optimized out>)3 zend_execute_scripts (type=type@entry=8, retval=0x7ffff2813020, retval@entry=0x0,file_count=file_count@entry=3)4 php_execute_script (primary_file=primary_file@entry=0x7fffffffcfc0)5 do_cli (argc=2, argv=0xe29bc0)6 main (argc=2, argv=0xe29bc0) 进程入口函数
execute_ex
zend_function *fbc = call->func;fbc->internal_function.handler(call, ret);//{void (zend_execute_data *, zval *)} 0x5dd370 <zif_posix_getpid>
type = 1 '\001',
quick_arg_flags = 1,
common = {
type = 1 '\001',
arg_flags = "\000\000",
fn_flags = 1,
function_name = 0xe2c210,
scope = 0x0,
prototype = 0x0,
num_args = 0,
required_num_args = 0,
arg_info = 0x9a2768
},
op_array = {
type = 1 '\001',
arg_flags = "\000\000",
fn_flags = 1,
function_name = 0xe2c210,
scope = 0x0,
prototype = 0x0,
num_args = 0,
required_num_args = 0,
arg_info = 0x9a2768,
cache_size = 6148976,
last_var = 0,
T = 14860464,
last = 0,
opcodes = 0x0,
run_time_cache__ptr = 0x0,
static_variables_ptr__ptr = 0x0,
static_variables = 0x0,
vars = 0x0,
refcount = 0x0,
last_live_range = 177595,
last_try_catch = -2147483648,--Type <RET> for more, q to quit, c to continue without paging--
live_range = 0x31,
try_catch_array = 0x1c600000001,
filename = 0xf6f90ef4248ccdc4,
line_start = 13,
line_end = 0,
doc_comment = 0x65675f7869736f70,
last_literal = 1768976500,//收集的posix_getpid函数名
literals = 0x81,
reserved = {0x100000001, 0xe2c2c0, 0x0, 0x0, 0x0, 0x9a2748}
},
internal_function = {
type = 1 '\001',
arg_flags = "\000\000",
fn_flags = 1,
function_name = 0xe2c210,//脚本文件里的`posix_getpid`函数名
scope = 0x0,
prototype = 0x0,
num_args = 0,
required_num_args = 0,
arg_info = 0x9a2768,
handler = 0x5dd370 <zif_posix_getpid>,//{void (zend_execute_data *, zval *)} 0x5dd370 <zif_posix_getpid>
//要调用的函数地址
module = 0xe2c0b0,//posix模块
/**
size = 168,
zend_api = 20190902,
zend_debug = 0 '\000',
zts = 0 '\000',
ini_entry = 0x0,
deps = 0x0,
name = 0x9a21ac "posix",
functions = 0xd72dc0 <posix_functions>,
module_startup_func = 0x5dcf70 <zm_startup_posix>,
module_shutdown_func = 0x0,
request_startup_func = 0x0,
request_shutdown_func = 0x0,
info_func = 0x44268a <zm_info_posix>,
version = 0x9ce568 "7.4.16",
globals_size = 4,
globals_ptr = 0xe1f0a0 <posix_globals>,
globals_ctor = 0x5dcf60 <zm_globals_ctor_posix>,
globals_dtor = 0x0,
post_deactivate_func = 0x0,
module_started = 1,
type = 1 '\001',
handle = 0x0,
module_number = 24,
build_id = 0x7f0412 "API20190902,NTS"
**/
reserved = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0}
}
callq 0x434e70 <getpid@plt>//linux api函数
实际运行调用的核心API流程
//1 调用execve加载ELF文件【它的奥秘值得你研究】execve("/usr/bin/php", ["php", "posix.php"], 0x7ffe9cfb56c0 /* 32 vars */) = 0//2 加载此文件,目前它没有找到,一般用于数学运算加密,需要intel芯片的另一套高级指令来完成,属于高级的数学运算指令,速度比C还要快,相应的技术我已经完整说过openat(AT_FDCWD, "/usr/lib64/tls/avx512_1/x86_64/libcrypt.so.1", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)//3 核心的linux api库,所有的编程语言都要用到它 这里几句话说不完openat(AT_FDCWD, "/usr/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3//4 打开你写的脚本文件,完成解析并存储相应的数据在内存中openat(AT_FDCWD, "posix.php", O_RDONLY) = 3//5 解析完后,调用zif_posix_getpid函数,再调用核心getpid函数getpid() = 14328 //进程标识//6 调用write核心api 在脚本里是echo write(1, "14328", 514328) = 5//7 进程退出状态码0+++ exited with 0 +++
总结
时间原因,详细繁杂的内容已经在直播中说过,大家可以看到它的运行大概过程即可,若想详细研究你得努力下功夫,当然我的建议是你站在有经验的大佬的肩膀上会更轻松些,有时候你自己研究你需要花费大量的个人时间和成本,时间就是金钱,学会用别人的经验来丰富自己的技术实力才是君子应该走的路,闭门独自研究当然可以,但是要有代价的,知识无限,编程语言构建的技术体系无限,框架无限,交流群无限,因为它们在变化,你若想掌握根本,应该以无限为有限
去掌握有限
的根本,达到掌握上层所有编程语言变化无限
的东西,利用有限
的时间和精力去掌握上层变化无限
的技术。