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

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

PHP 扩展加载

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

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);

    ....
};

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

Image

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

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 在异步通信框架领域的一个重要的扩展,还是值得好好学习的。

Image

本作品采用《CC 协议》,转载必须注明作者和本文链接
Image
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

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