C语言项目PHP-SRC源码的函数运行过程简析

注:按照社区分类应该划分到C/CPP
完整分析本人已经在直播中讲过,包括从头到尾都说过,这里只是抽取我在直播过程中的部分内容进行编辑成文陈述,毕竟写博客太累了。

废话文学

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作为服务器

<?php

echo posix_getpid();

关于posix_getpid 在讲PHP多进程编程的时候已经讲过,不在过多的陈述,它的功能就是获取进程标识PID,底层用的是LINUX 核心API getpid() ,而核心API的开发编程在LINUX C 也是说过的知识点了,其它的编程语言如GO JAVA PYTHON NODE RUST ….全都会用这个函数达成目的。

在此脚本文件里,最核心的函数是getpidZEND_ECHO_SPEC_CV_HANDLER 函数,也就是说启动进程运行后,主要就是执行这2个C函数,当然运行期间会涉及大量的其它C函数。

C语言项目PHP-SRC源码的函数运行过程简析

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

C语言项目PHP-SRC源码的函数运行过程简析

C语言项目PHP-SRC源码的函数运行过程简析

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 +++

总结

时间原因,详细繁杂的内容已经在直播中说过,大家可以看到它的运行大概过程即可,若想详细研究你得努力下功夫,当然我的建议是你站在有经验的大佬的肩膀上会更轻松些,有时候你自己研究你需要花费大量的个人时间和成本,时间就是金钱,学会用别人的经验来丰富自己的技术实力才是君子应该走的路,闭门独自研究当然可以,但是要有代价的,知识无限,编程语言构建的技术体系无限,框架无限,交流群无限,因为它们在变化,你若想掌握根本,应该以无限为有限去掌握有限的根本,达到掌握上层所有编程语言变化无限的东西,利用有限的时间和精力去掌握上层变化无限的技术。

本作品采用《CC 协议》,转载必须注明作者和本文链接
北风之神,佬想交流技术和感情的可以Add my vx:Le-stdugy
本帖由系统于 2周前 自动加精
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 7
沐伶风

巨佬,Mark

2周前 评论

什么时候出一期Api网关服务

1周前 评论
北风之神 (楼主) 1周前

直播在哪 :joy:

1周前 评论
北风之神 (楼主) 1周前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!