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

相关推荐
BingoGo2 天前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php
JaguarJack2 天前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php·服务端
BingoGo3 天前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php
JaguarJack3 天前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php·服务端
JaguarJack4 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
后端·php·服务端
BingoGo4 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
php
JaguarJack5 天前
告别 Laravel 缓慢的 Blade!Livewire Blaze 来了,为你的 Laravel 性能提速
后端·php·laravel
郑州光合科技余经理6 天前
代码展示:PHP搭建海外版外卖系统源码解析
java·开发语言·前端·后端·系统架构·uni-app·php
QQ5110082856 天前
python+springboot+django/flask的校园资料分享系统
spring boot·python·django·flask·node.js·php
WeiXin_DZbishe6 天前
基于django在线音乐数据采集的设计与实现-计算机毕设 附源码 22647
javascript·spring boot·mysql·django·node.js·php·html5