深入理解 Swoole 的底层加载原理

首发原文链接:深入理解 Swoole 的底层加载原理

PHP 扩展加载

我们从 php-src/sapi/cli/php_cli.c:1159 文件的入口函数 int main(int argc, char *argv[]) 开始分析。
大家可以先看下面这张图,描述了整个关键函数的加载、调用流程。从模块的初始化,到最后资源的释放回收整个流程,都体现在其中了。「如果图片看不清,可以在文章末尾点击 阅读原文 查看原图」

Swoole 入口文件

我们先看下 Swoole 源码中对入口文件的定义,接下来我们再看在 PHP 中对扩展定义的结构体。

// swoole-src/php_swoole.h:40
extern zend_module_entry swoole_module_entry; // Swoole扩展的模块入口
#define phpext_swoole_ptr &swoole_module_entry 

PHP_MINIT_FUNCTION(swoole); // 在PHP启动时被调用,用于初始化模块的全局状态。
PHP_MSHUTDOWN_FUNCTION(swoole); // 在PHP关闭时被调用,用于清理和释放模块的全局资源。
PHP_RINIT_FUNCTION(swoole); // 每个PHP请求开始时被调用,用于初始化每个请求的相关资源。
PHP_RSHUTDOWN_FUNCTION(swoole); // 在每个PHP请求结束时被调用,用于释放每个请求的相关资源。
PHP_MINFO_FUNCTION(swoole); // 定义了模块信息的获取函数。这个函数用于返回关于模块的信息,例如版本号、配置选项等。

// 定义了Swoole扩展的全局变量。这些全局变量可以在整个扩展中访问,用于存储一些配置选项和状态信息。
// clang-format off
ZEND_BEGIN_MODULE_GLOBALS(swoole)
    zend_bool display_errors;
    zend_bool cli;
    zend_bool use_shortname;
    zend_bool enable_coroutine;
    zend_bool enable_preemptive_scheduler;
    zend_bool enable_library;
    zend_bool enable_fiber_mock;
    long socket_buffer_size;
    int req_status;
ZEND_END_MODULE_GLOBALS(swoole)
// clang-format on

extern ZEND_DECLARE_MODULE_GLOBALS(swoole);

在 PHP 的 Zend 虚拟解析引擎中,对 PHP 扩展的入口文件,进行了统一的定义。基于 PHP 开发的扩展都得实现这一标准,因此 Swoole 也不例外。

// php-src/Zend/zend_modules.h:71
struct _zend_module_entry {
    ....

    //  PHP 引擎加载模块时需要执行的函数
    zend_result (*module_startup_func)(INIT_FUNC_ARGS);
    // PHP 引擎关闭模块时需要执行的函数
    zend_result (*module_shutdown_func)(SHUTDOWN_FUNC_ARGS);
    // 每个请求开始时需要执行的函数
    zend_result (*request_startup_func)(INIT_FUNC_ARGS);
    // 每个请求结束时需要执行的函数
    zend_result (*request_shutdown_func)(SHUTDOWN_FUNC_ARGS);
    // 执行 phpinfo 函数时需要显示的模块信息
    void (*info_func)(ZEND_MODULE_INFO_FUNC_ARGS);

    ....
};

我们通过这张图来看下对应关系。

下面我们对逐个函数进行分析。

PHP_MINIT_FUNCTION

如上述所说,这个函数主要是定义了大量的全局常量、及一些子模块的初始化工作。

// Swoole-src/ext-src/php_swoole.c:369
PHP_MINIT_FUNCTION(swoole) {
    // 定义常量
    ZEND_INIT_MODULE_GLOBALS(swoole, php_swoole_init_globals, nullptr);
    REGISTER_INI_ENTRIES();

    // clang-format off
    // MUST be on the same line for the inspection tool to recognize correctly
    SW_REGISTER_STRING_CONSTANT("SWOOLE_VERSION", SWOOLE_VERSION);
    
    ...

    // 事件循环等子模块的初始化
    /** <Sort by dependency> **/
    php_swoole_event_minit(module_number);
    // base
    php_swoole_atomic_minit(module_number);

    ...

    SwooleG.fatal_error = fatal_error;
    // 设置 Swoole socket 默认缓存区大小
    Socket::default_buffer_size = SWOOLE_G(socket_buffer_size);
    SwooleG.dns_cache_refresh_time = 60;
    
    // 初始化 Zend 引擎字符串资源分配
    zend::known_strings_init();

    return SUCCESS;
}

PHP_MSHUTDOWN_FUNCTION

这个函数的主要是做清扫的作用,清除运行过程中的变量、及关闭相应的资源,避免资源泄露等。

// Swoole-src/ext-src/php_swoole.c:774
PHP_MSHUTDOWN_FUNCTION(swoole) {
    // 调用 Zend 引擎释放字符串资源
    zend::known_strings_dtor();

    // 释放 Swoole 运行过程中资源
    php_swoole_runtime_mshutdown();
    // 释放 Swoole Websocket 服务资源
    php_swoole_websocket_server_mshutdown();
#ifdef SW_USE_PGSQL
    php_swoole_pgsql_mshutdown();
#endif

#ifdef SW_USE_ORACLE
    php_swoole_oracle_mshutdown();
#endif

#ifdef SW_USE_SQLITE
    php_swoole_sqlite_mshutdown();
#endif

    // 执行 Swoole 的清理工作
    swoole_clean();

    return SUCCESS;
}

PHP_RINIT_FUNCTION

每个请求开始需要做的一些初始化工作,主要是针对 Http Server 实现的。

// Swoole-src/ext-src/php_swoole.c:982
PHP_RINIT_FUNCTION(swoole) {
    if (!SWOOLE_G(cli)) {
        return SUCCESS;
    }

    // 开始请求的初始化工作
    SWOOLE_G(req_status) = PHP_SWOOLE_RINIT_BEGIN;
    // 将 Swoole 更改为运行状态
    SwooleG.running = 1;

    php_swoole_register_shutdown_function("swoole_internal_call_user_shutdown_begin");

    ...

    // 初始化 Swoole 的 Http Server 等模块
    php_swoole_http_server_rinit();
    php_swoole_coroutine_rinit();
    php_swoole_runtime_rinit();
#ifdef SW_USE_ORACLE
    php_swoole_oracle_rinit();
#endif

    // 结束请求的初始化工作
    SWOOLE_G(req_status) = PHP_SWOOLE_RINIT_END;

    return SUCCESS;
}

PHP_RSHUTDOWN_FUNCTION

针对请求资源进行释放,并及时关闭相应的连接资源。

// Swoole-src/ext-src/php_swoole.c:1018
PHP_RSHUTDOWN_FUNCTION(swoole) {
    if (!SWOOLE_G(cli)) {
        return SUCCESS;
    }

    // 开始请求的清理工作
    SWOOLE_G(req_status) = PHP_SWOOLE_RSHUTDOWN_BEGIN;

    rshutdown_callbacks.execute();

    // 释放 Swoole 事件循环资源等
    swoole_event_free();

    php_swoole_server_rshutdown();
    php_swoole_async_coro_rshutdown();
    php_swoole_redis_server_rshutdown();
    php_swoole_coroutine_rshutdown();
    php_swoole_coroutine_scheduler_rshutdown();
    php_swoole_runtime_rshutdown();

    // 清理 Swoole 进程相关的资源
    php_swoole_process_clean();

    // 更改 Swoole 的运行状态
    SwooleG.running = 0;
    // 结束请求的清理工作
    SWOOLE_G(req_status) = PHP_SWOOLE_RSHUTDOWN_END;

    ...

    return SUCCESS;
}

PHP_MINFO_FUNCTION

这个最好理解了,就是打印这个扩展的信息,例如:Swoole 的作者、版本等信息。

PHP_MINFO_FUNCTION(swoole) {
    char buf[64];
    php_info_print_table_start();
    php_info_print_table_header(2, "Swoole", "enabled");
    php_info_print_table_row(2, "Author", "Swoole Team <team@swoole.com>");
    php_info_print_table_row(2, "Version", SWOOLE_VERSION);
    
    ...

    php_info_print_table_end();

    DISPLAY_INI_ENTRIES();
}

总结

首先,理解 Swoole 扩展的加载原理,最重要的是要搞懂最开始提到的 PHP 扩展加载全流程。我就是在这个全流程的分析上,花了大量的时间。经常分析到一半,发现逻辑不对,然后就反复的分析其中的关联关系。

其次,对于之前只写 PHP 业务代码,没有接触过 PHP 源代码的人来说,简直就是看天书。因此,如果有志于学习源码的朋友,一定到有足够的耐心,反复研读、琢磨。

最后,Swoole 作为 PHP 在异步通信框架领域的一个重要的扩展,还是值得好好学习的。

posted @ 2024-01-15 22:09  Yxh_blogs  阅读(49)  评论(0编辑  收藏  举报