Skip to content

Latest commit

 

History

History
729 lines (594 loc) · 31.9 KB

File metadata and controls

729 lines (594 loc) · 31.9 KB

OpKit 架构文档

1. 项目概述

OpKit 是一个 PHP Zend 扩展,提供离线 Opcode 预编译和持久化功能。它将 PHP 脚本编译成二进制的 .phpc 文件,包含操作码、字符串、常量、函数和类元数据,以便快速加载而无需重新解析。

核心特性

  • 基于 Zend OPcache 架构
  • 支持 PHP 8.2/8.3/8.4/8.5
  • 使用影子内存分区(Metadata/Code/Data/Misc)进行持久化存储
  • 增量编译支持
  • Phar 打包支持

2. 系统架构

2.1 整体架构图

┌─────────────────────────────────────────────────────────────────┐
│                        User Layer                               │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────────────┐  │
│  │   phpc CLI  │  │  Composer   │  │    User PHP Scripts     │  │
│  │   (bin/)    │  │   Plugin    │  │                         │  │
│  └──────┬──────┘  └──────┬──────┘  └───────────┬─────────────┘  │
└─────────┼────────────────┼─────────────────────┼────────────────┘
          │                │                     │
          ▼                ▼                     ▼
┌─────────────────────────────────────────────────────────────────┐
│                    PHP Extension API Layer                      │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  PHP Functions: opkit_compile_file(), opkit_boot()      │    │
│  │  opkit_get_info(), opkit_gen_entry_file()               │    │
│  └─────────────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────────────┘
          │
          ▼
┌─────────────────────────────────────────────────────────────────┐
│                    Core C Module Layer                          │
│  ┌──────────────┐ ┌──────────────┐ ┌─────────────────────────┐  │
│  │ opkit_module │ │ opkit_compile│ │  opkit_zend_persist*    │  │
│  │   (生命周期)  │ │  (编译/加载)  │ │    (数据持久化)          │  │
│  └──────────────┘ └──────────────┘ └─────────────────────────┘  │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │              opkit_util_funcs (工具函数)                 │    │
│  └─────────────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────────────┘
          │
          ▼
┌─────────────────────────────────────────────────────────────────┐
│                    Zend Engine Layer                            │
│         Zend Compiler / Executor / OPcache Codebase             │
└─────────────────────────────────────────────────────────────────┘

2.2 核心模块职责

模块 文件 职责
Module opkit_module.c 扩展生命周期管理 (MINIT/MSTARTUP, RINIT/RSHUTDOWN), PHP API 函数实现
Compile opkit_compile.c 编译引擎:保存/加载 .phpc 文件, 文件 I/O, 序列化/反序列化
Persist opkit_zend_persist.c 将数据复制到持久化内存(运行时)
Persist Calc opkit_zend_persist_calc.c 计算持久化结构所需的内存大小
Util opkit_util_funcs.c 工具函数:哈希表持久化、校验和计算、脚本加载
Shared Alloc opkit_shared_alloc.c 共享内存分配器:基于 mmap(MAP_SHARED|MAP_ANONYMOUS) 的进程间共享内存
Wrapper opkit_wrapper.h 兼容性宏定义,Zend OPcache 结构复用

3. 核心数据结构

3.1 持久化脚本结构

// 扩展的持久化脚本结构
typedef struct _opkit_persistent_script {
    zend_persistent_script script;           // Zend 基础脚本结构
    HashTable constants_table;               // 脚本定义的常量表
} opkit_persistent_script;

3.2 Zend 持久化脚本结构 (来自 OPcache)

typedef struct _zend_persistent_script {
    zend_script    script;                   // 编译后的脚本内容
    zend_long      compiler_halt_offset;     // __HALT_COMPILER 位置
    int            ping_auto_globals_mask;   // 使用的自动全局变量
    accel_time_t   timestamp;                // 脚本修改时间
    bool           corrupted;                // 是否损坏
    bool           is_phar;                  // 是否为 Phar 文件
    uint32_t       num_warnings;             // 编译警告数量
    uint32_t       num_early_bindings;       // 早期绑定数量
    zend_error_info **warnings;              // 编译警告
    zend_early_binding *early_bindings;      // 早期绑定信息
    void          *mem;                      // 共享内存区域
    size_t         size;                     // 使用的内存大小
    struct zend_persistent_script_dynamic_members dynamic_members;
} zend_persistent_script;

3.3 文件缓存元信息

.phpc 文件格式的完整定义详见 PHPC_FILE_FORMAT.md

关键字段:

  • magic[8]: 文件标识 "PHPC"
  • system_id[32]: PHP 系统标识,用于兼容性检查
  • checksum: Adler-32 校验和,验证文件完整性
  • 四分区统计: metadata_size, code_size, data_size, misc_size

3.4 脚本节点(运行时)

typedef struct _opkit_script_node {
    zend_persistent_script *script;          // 持久化脚本
    void *mem_to_free;                       // 需要释放的内存块
    zend_string *loaded_path;                // 加载时的路径
    bool executed;                           // 是否已执行
    bool in_shm;                             // 内存是否来自共享内存(fork 后子进程无需重复释放)
    struct _opkit_script_node *next;
} opkit_script_node;

4. 内存管理架构

4.1 影子内存分区

OpKit 使用逻辑内存分区来优化缓存效率:

┌────────────────────────────────────────────────────────────────┐
│                     Shadow Memory Layout                        │
├────────────────────────────────────────────────────────────────┤
│  ┌─────────────────┐                                           │
│  │  Metadata Area  │ 脚本头、哈希表、类/函数元数据              │
│  │   (ADD_SIZE_MD) │ 大小: opkit_metadata_size                  │
│  ├─────────────────┤                                           │
│  │    Code Area    │ 操作码 (opcodes)、字面量                   │
│  │   (ADD_SIZE_CD) │ 大小: opkit_code_size                      │
│  ├─────────────────┤                                           │
│  │    Data Area    │ 常量、字符串、属性默认值                   │
│  │   (ADD_SIZE_DT) │ 大小: opkit_data_size                      │
│  ├─────────────────┤                                           │
│  │    Misc Area    │ 对齐、缓冲区、临时数据                     │
│  │   (ADD_SIZE_MS) │ 大小: opkit_misc_size                      │
│  └─────────────────┘                                           │
└────────────────────────────────────────────────────────────────┘

4.2 内存分区宏定义

#define ADD_SIZE_MD(s) opkit_metadata_size += ZEND_ALIGNED_SIZE(s)  // 元数据
#define ADD_SIZE_CD(s) opkit_code_size += ZEND_ALIGNED_SIZE(s)      // 代码
#define ADD_SIZE_DT(s) opkit_data_size += ZEND_ALIGNED_SIZE(s)      // 数据
#define ADD_SIZE_MS(s) opkit_misc_size += ZEND_ALIGNED_SIZE(s)      // 杂项

4.3 内存分配流程

┌────────────────────────────────────────────────────────────────┐
│                    Memory Allocation Flow                       │
├────────────────────────────────────────────────────────────────┤
│                                                                 │
│  1. 计算阶段 (zend_accel_script_persist_calc)                  │
│     ├── 遍历脚本结构                                            │
│     ├── 计算各部分所需内存大小                                  │
│     └── 累加到 metadata/code/data/misc_size                     │
│                          │                                      │
│                          ▼                                      │
│  2. 分配阶段 (emalloc / opkit_shared_alloc)                     │
│     ├── 总大小 = MD + CD + DT + MS                              │
│     └── ZCG(mem) = emalloc(total_size)                          │
│          或 opkit_shared_alloc(total_size) (shm 开启时)         │
│                          │                                      │
│                          ▼                                      │
│  3. 持久化阶段 (zend_accel_script_persist)                      │
│     ├── 复制脚本结构到 ZCG(mem)                                 │
│     ├── 递归复制所有子结构                                      │
│     └── 使用 xlat_table 避免重复复制                            │
│                          │                                      │
│                          ▼                                      │
│  4. 文件存储阶段 (opkit_compile_script_store)                   │
│     ├── 写入 metainfo 头                                        │
│     ├── 写入序列化后的脚本                                      │
│     └── 写入字符串池                                            │
│                                                                 │
└────────────────────────────────────────────────────────────────┘

4.4 SHM 共享内存管理

OpKit 支持通过 mmap(MAP_SHARED | MAP_ANONYMOUS) 分配进程间共享内存,用于缓存已加载的脚本数据,实现多进程(如 PHP-FPM Worker、fork 子进程)之间的零拷贝共享。

共享内存数据结构

typedef struct _opkit_shared_segment {
    void   *p;      // mmap 映射的共享内存基地址
    size_t  size;   // 总容量(由 opkit.shm_size INI 设置)
    size_t  pos;    // 当前分配偏移量
    size_t  end;    // 结束位置(等于 size)
} opkit_shared_segment;

共享内存分配器接口

函数 文件 说明
opkit_shared_alloc_startup(size_t) opkit_shared_alloc.c MINIT 阶段初始化共享内存段
opkit_shared_alloc_shutdown() opkit_shared_alloc.c MSHUTDOWN 阶段释放共享内存
opkit_shared_alloc(size_t) opkit_shared_alloc.c 从共享内存分配指定大小的块(类似 bump allocator)
opkit_shared_alloc_get_free_memory() opkit_shared_alloc.c 返回共享内存剩余可用空间
opkit_accel_in_shm(void*) opkit_shared_alloc.c 判断指针是否位于共享内存范围内
opkit_shm_reset() opkit_shared_alloc.c 重置共享内存分配器(带进程间锁),清空所有缓存
opkit_shared_alloc_lock/unlock() opkit_shared_alloc.c 基于 fcntl(F_WRLCK) 的进程间文件锁

共享内存分配流程

┌─────────────────────────────────────────────────────────────────┐
│                     SHM Allocation Flow                         │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  1. 初始化阶段 (MINIT)                                           │
│     ├── 检查 opkit.shm_size INI 配置                            │
│     ├── mmap(NULL, size, PROT_READ|PROT_WRITE,                  │
│     │          MAP_SHARED|MAP_ANONYMOUS, -1, 0)                 │
│     └── 创建进程间锁文件 (memfd / O_TMPFILE / mkstemp)          │
│                          │                                       │
│                          ▼                                       │
│  2. 加载阶段 (opkit_load)                                        │
│     ├── 计算脚本持久化所需内存                                   │
│     ├── opkit_shared_alloc(total_size)                          │
│     │   └── 若空间不足,回退到 emalloc (堆内存)                 │
│     ├── zend_accel_script_persist(script, for_shm=1)            │
│     └── opkit_keep_memory(script, mem, path)                    │
│                          │                                       │
│                          ▼                                       │
│  3. 运行时阶段 (opkit_boot)                                      │
│     ├── 注册函数/类/常量到 Zend Engine                          │
│     ├── 子进程通过 fork() 继承共享内存映射                      │
│     └── 子进程可直接访问父进程加载的脚本数据                    │
│                          │                                       │
│                          ▼                                       │
│  4. 清理阶段 (RSHUTDOWN / opkit_shm_reset)                       │
│     ├── opkit_reset_script()                                    │
│     │   ├── 注销符号表项                                        │
│     │   └── 释放堆分配的 runtime cache                          │
│     └── 若 in_shm == true:不调用 efree(由 SHM 统一管理)     │
│         若 in_shm == false:调用 efree(mem_to_free)            │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

进程间共享机制

由于共享内存通过 MAP_SHARED 映射,fork() 创建的子进程会继承相同的物理内存映射。因此:

  • 父进程调用 opkit_load() 将脚本数据写入共享内存后,子进程无需再次加载即可直接访问。
  • 符号注册opkit_boot())仍然需要在每个进程中独立执行,因为 Zend Engine 的函数表/类表是进程私有的。
  • 内存释放:RSHUTDOWN 时仅释放堆分配的 opkit_script_node bookkeeping 结构和 runtime cache,共享内存中的脚本数据本身不释放,直到调用 opkit_shm_reset() 或进程结束。

SHM 重置与安全性

// C API
bool opkit_shm_reset(void);

// PHP API
bool opkit_shm_reset();

opkit_shm_reset() 的行为:

  1. 获取进程间写锁(opkit_shared_alloc_lock
  2. 将共享内存分配器的 pos 重置为 0
  3. 释放锁

PHP 层的 opkit_shm_reset() 会先调用 opkit_reset_script() 清理当前进程已注册的符号和 loaded_scripts 链表,防止 reset 后出现悬空指针访问。调用后已加载的脚本需要重新 opkit_load()


5. 编译与加载流程

5.1 编译时流程

  1. PHP 编译: zend_compile_file() 解析源码生成 AST,编译为 Opcodes
  2. 符号移动: opkit_compile_file() 将函数、类、常量移动到脚本结构
  3. 内存计算: zend_accel_script_persist_calc() 计算四分区内存需求
  4. 数据持久化: zend_accel_script_persist() 复制到连续内存,序列化指针为偏移量
  5. 文件写入: opkit_compile_script_store() 写入文件头 + 数据 + 字符串池

详细的序列化机制和数据格式定义,参见 PHPC_FILE_FORMAT.md

5.2 运行时加载流程

  1. 文件加载: opkit_compile_script_load() 读取并验证文件头(magic、system_id、checksum)
  2. 反序列化: zend_file_cache_unserialize() 将偏移量还原为指针,重建数据结构
  3. 类链接: opkit_link_classes() 解析继承关系,链接父类和接口
  4. 符号注册: opkit_boot() 注册函数、类、常量到 Zend Engine
  5. 执行: zend_execute() 执行主 op_array

6. 序列化机制

OpKit 使用指针偏移量序列化技术将内存结构持久化到文件:

// 序列化:指针 → 偏移量
#define SERIALIZE_PTR(ptr) do { \
    if (ptr) { \
        ptr = (void*)((char*)(ptr) - (char*)script->mem); \
    } \
} while (0)

// 反序列化:偏移量 → 指针
#define UNSERIALIZE_PTR(ptr) do { \
    if (ptr) { \
        ptr = (void*)((char*)buf + (size_t)(ptr)); \
    } \
} while (0)

序列化策略(详见 PHPC_FILE_FORMAT.md):

  • 普通指针:转换为相对于 script->mem 的偏移量
  • Interned 字符串:存储到独立字符串池,偏移量低 1 位标记
  • 环形引用:使用 xlat 表避免重复序列化
  • 需要处理的主要结构:opcodes、literals、类/函数表、属性信息、常量等

7. PHP 版本兼容性

7.1 版本检测宏

#if PHP_VERSION_ID >= 80400
    // PHP 8.4+ 代码
#elif PHP_VERSION_ID >= 80300
    // PHP 8.3 代码
#else
    // PHP 8.2 代码
#endif

7.2 PHP 8.4/8.5 主要兼容性修改

特性 PHP 8.2/8.3 PHP 8.4+
doc_comment in zend_property_info 存在 已移除
doc_comment in zend_op_array / zend_class_constant 存在 存在
doc_comment in zend_class_entry ce->info.user.doc_comment ce->doc_comment (移出 union)
Property Hooks 不支持 支持 (2 种钩子: get, set)
prop_info 不存在于 zend_op_array 存在
Runtime Cache 堆分配时需要手动清理 堆分配时需要手动清理
FCC 常量 (PHP 8.5) 不支持 支持 ZEND_AST_CALLABLE_CONVERT
ZEND_DECLARE_ATTRIBUTED_CONST 不支持 支持 (PHP 8.5)

7.3 Property Hooks 支持

#if PHP_VERSION_ID >= 80400
#define ZEND_PROPERTY_HOOK_COUNT 2  // get, set
#define ZEND_PROPERTY_HOOK_STRUCT_SIZE (sizeof(zend_function*) * ZEND_PROPERTY_HOOK_COUNT)
#endif

序列化注意事项

// 正确做法:先保存原始指针,再序列化
zend_function **hooks = prop->hooks;  // 先保存
SERIALIZE_PTR(prop->hooks);            // 这会修改 prop->hooks 为偏移量
for (uint32_t i = 0; i < ZEND_PROPERTY_HOOK_COUNT; i++) {
    if (hooks[i]) {                    // 使用保存的指针
        SERIALIZE_PTR(hooks[i]);
    }
}

持久化关键

if (copy->hooks[i]) {
    zend_op_array *hook = (zend_op_array *)copy->hooks[i];
    hook = zend_shared_memdup_put(hook, sizeof(zend_op_array));
    hook->prop_info = copy;  // 关键:在 persist 前设置
    zend_persist_op_array_ex(hook, ZCG(current_persistent_script));
}

8. CLI 工具 (phpc)

8.1 功能模块

bin/phpc
├── 参数解析 (getopt)
│   └── 支持 CLI 参数和配置文件 (opkit.json)
│
├── 编译模式 (-s -o)
│   ├── 递归目录扫描
│   ├── 增量编译 (mtime + System ID)
│   └── 调用 opkit_compile_file()
│
├── 入口生成 (-e)
│   └── 调用 opkit_gen_entry_file()
│
├── Phar 打包 (-p)
│   ├── 压缩支持 (gz, bz2)
│   ├── 签名支持 (sha1, sha256, sha512, openssl)
│   └── 自动入口生成
│
├── 静态分析 (-a, analyze)
│   ├── 符号冲突检测
│   └── 内存使用统计
│
└── 信息查看 (-i, info)
    └── 调用 opkit_get_info()

8.2 入口文件生成

生成的 entry.php 示例:

<?php

if (!extension_loaded('opkit')) {
    if (!@dl('opkit.so')) {
        trigger_error('OpKit extension not loaded', E_USER_ERROR);
    }
}

opkit_load_multi([
    __DIR__ . '/main.phpc',
    __DIR__ . '/lib/utils.phpc',
]);

exit(opkit_boot());

8.3 配置文件格式 (opkit.json)

{
    "src": "src/",
    "output": "dist/",
    "phar": "app.phar",
    "entry": "dist/entry.php",
    "force": false,
    "no-incremental": false,
    "compress": "gz",
    "sign": "sha256"
}

9. 关键 API 函数

9.1 PHP 扩展函数

函数 参数 返回值 说明
opkit_compile_file() $output_dir, $source_file, $base_path = null bool 编译 PHP 文件为 .phpc
opkit_compile_dir() $output_dir, $dir bool 递归编译目录下所有 .php 文件
opkit_boot() $entry, $args int 注册符号并执行入口函数
opkit_load() $filename bool 加载 .phpc 文件(不执行)
opkit_load_multi() $filenames void 批量加载 .phpc 文件
opkit_get_info() $filename ?array 获取 .phpc 文件信息
opkit_gen_entry_file() $output_path bool 生成入口文件
opkit_is_loaded() $filename bool 检查 .phpc 文件是否已加载
opkit_shm_reset() bool 重置共享内存分配器,清空所有缓存脚本
opkit_shm_stat() ?array 获取共享内存统计信息(shm_size、used、free)

9.2 内部 C 函数

函数 文件 说明
opkit_compile_file() opkit_compile.c 主编译函数
opkit_compile_script_store() opkit_compile.c 存储脚本到文件
opkit_compile_script_load() opkit_compile.c 从文件加载脚本
zend_accel_script_persist() opkit_zend_persist.c 持久化脚本到内存
zend_accel_script_persist_calc() opkit_zend_persist_calc.c 计算内存需求
zend_accel_load_script() opkit_util_funcs.c 加载脚本到运行时
opkit_shared_alloc_startup() opkit_shared_alloc.c 初始化共享内存段
opkit_shared_alloc() opkit_shared_alloc.c 从共享内存分配块
opkit_shm_reset() opkit_shared_alloc.c 重置共享内存分配器
opkit_accel_in_shm() opkit_shared_alloc.c 判断指针是否在共享内存中

10. 构建系统

10.1 构建流程

# 1. 准备构建环境
phpize

# 2. 配置
./configure --with-php-config=/path/to/php-config --enable-debug

# 3. 编译
make

# 4. 测试
NO_INTERACTION=1 make test TESTS=tests/

10.2 多版本 PHP 支持

# PHP 8.2
php-src/php-8.2.30/scripts/phpize && \
./configure --with-php-config=php-src/php-8.2.30/scripts/php-config && \
make

# PHP 8.3
php-src/php-8.3.30/scripts/phpize && \
./configure --with-php-config=php-src/php-8.3.30/scripts/php-config && \
make

# PHP 8.4
php-src/php-8.4.19/scripts/phpize && \
./configure --with-php-config=php-src/php-8.4.19/scripts/php-config && \
make

# PHP 8.5
php-src/php-8.5.4/scripts/phpize && \
./configure --with-php-config=php-src/php-8.5.4/scripts/php-config && \
make

11. 测试架构

11.1 测试文件组织

测试文件 描述 关键特性
01_basic.phpt 基础编译测试 简单函数和输出
02_directory.phpt 目录递归编译 多文件编译
03_relative_path.phpt 相对路径测试 命名空间类支持
03_phar_relative_path.phpt Phar 相对路径 Phar + 命名空间
04_phar.phpt 基础 Phar 加载 Phar 归档
05_triple_des.phpt TripleDES 扩展 需要 openssl
06_phpc_tool.phpt phpc CLI 工具 命令行编译
07_constants.phpt 常量测试 类和常量
08_phpc_config.phpt 配置文件 opkit.json
09_phpc_incremental.phpt 增量编译 mtime 检查
10_eval_test.phpt eval 测试 运行时编译
11_main_args.phpt 入口参数 opkit_boot 传参
12_load_multi.phpt 批量加载 opkit_load_multi
13_phpc_phar_readonly.phpt Phar 只读错误 错误处理
14_stubs_support.phpt Stub 生成 --stubs
15_phpc_analyze.phpt 静态分析 phpc analyze
16_phpc_phar_adv.phpt 高级 Phar 压缩与签名
17_boot_exception.phpt 异常处理 opkit_boot 错误
18_property_hooks.phpt 属性钩子 PHP 8.4+
19_class_properties.phpt 类属性 类型属性支持
20_constants_comprehensive.phpt 全面常量测试 namespace/define/类常量
21_properties_comprehensive.phpt 全面属性测试 类型/可见性/readonly
22_constants_properties_integration.phpt 集成测试 继承/抽象类/常量默认值
23_is_loaded.phpt 加载检测 opkit_is_loaded
24_php85_fcc_const.phpt PHP 8.5 FCC 常量 第一类可调用对象
25_property_type_info.phpt 属性类型元数据 opkit_get_info 类型提取
26_shared_memory.phpt 共享内存加载 opkit.shm_size INI
27_fork_shm.phpt Fork 共享内存验证 需要 pcntl
28_shm_memory.phpt SHM 内存占用对比 堆 vs SHM
29_shm_reset_fork.phpt SHM reset + fork opkit_shm_reset + pcntl
30_shm_hit_rate.phpt SHM 命中率/reset opkit_shm_stat + reset
31_compile_file_basepath.phpt base_path 编译 目录结构保留
32_compile_file_no_basepath.phpt 无 base_path 编译 向后兼容
33_enum_basic.phpt Enum 支持 backed/unbacked enum

11.2 测试格式

--TEST--
Test description
--EXTENSIONS--
opkit
--FILE--
<?php
// Test code
?>
--EXPECT--
Expected output

12. 安全与限制

12.1 系统 ID 验证

.phpc 文件包含系统 ID,确保只在与编译环境兼容的系统上运行:

// 系统 ID 包含:PHP 版本、架构、编译选项
if (!zend_string_equals(system_id, opkit_system_id)) {
    // 系统不匹配,拒绝加载
}

12.2 OPcache 共存

OpKit 可与 Zend OPcache 静默共存。OpKit 在编译时会临时保存并恢复 zend_compile_file,绕过 OPcache 的 persistent_compile_file 钩子,编译完成后再恢复原钩子。因此无需禁用 OPcache:

; 正确配置
zend_extension=opcache.so
zend_extension=opkit.so
phar.readonly=Off

12.3 加载限制

  • 必须使用 zend_extension 加载,不能使用 extension
  • 需要 phar.readonly=Off 才能创建 Phar 包
  • 文件系统权限要求(读取 .phpc 文件)

13. 性能优化

13.1 校验和计算

使用 SSE2 优化的 Adler-32 算法:

#ifdef __SSE2__
// 使用 SIMD 指令加速校验和计算
__m128i read = _mm_loadu_si128((__m128i *) buf);
// ... SIMD 处理
#else
// 标量回退实现
#endif

13.2 增量编译

通过 mtime 和 System ID 检查避免不必要的重新编译:

if ($incremental && !$force && file_exists($target_file)) {
    if (filemtime($src_file) <= filemtime($target_file)) {
        $info = opkit_get_info($target_file);
        if ($info && $info['system_id_match']) {
            $should_compile = false;  // 跳过编译
        }
    }
}

14. 文件组织

opkit/
├── src/                          # C 源代码
│   ├── php_opkit.h              # 主头文件
│   ├── opkit_module.c/h         # 扩展生命周期和 API
│   ├── opkit_compile.c/h        # 编译和文件 I/O
│   ├── opkit_zend_persist.c     # 数据持久化
│   ├── opkit_zend_persist_calc.c # 内存计算
│   ├── opkit_util_funcs.c/h     # 工具函数
│   ├── opkit_wrapper.h          # 兼容性包装器
│   ├── opkit_arginfo.h          # PHP 函数参数信息
│   └── opkit.stub.php           # PHP API 定义
├── bin/                          # CLI 工具
│   └── phpc                     # PHP 编译脚本
├── composer/                     # Composer 插件
│   └── src/Plugin.php
├── tests/                        # 测试文件
│   ├── 01_basic.phpt
│   ├── 02_directory.phpt
│   └── ...
├── docs/                         # 文档
│   ├── ARCHITECTURE.md          # 架构文档(本文件)
│   ├── PHPC_FILE_FORMAT.md      # .phpc 文件格式规范
│   ├── COMPILATION_PROCESS.md   # 编译流程详解
│   └── ZEND_COMPILE_OPTIONS.md  # Zend 编译选项参考
├── config.m4                     # Autotools 配置
└── AGENTS.md                     # 开发指南

15. 扩展开发指南

15.1 添加新 API 函数的步骤

  1. 编辑 src/opkit.stub.php:添加 PHP 函数签名
  2. 生成 arginfomake 自动生成 src/opkit_arginfo.h
    • 使用 build/gen_stub.php(来自 PHP 源码 php-src/php-X.X.X/build/gen_stub.php
    • 命令:php build/gen_stub.php src/opkit.stub.php
  3. 实现函数:在 src/opkit_module.c 中添加 C 实现
  4. 添加测试:在 tests/ 目录创建 .phpt 测试文件

15.2 调试技巧

# GDB 调试
gdb --args php-src/php-8.2.30/sapi/cli/php -d zend_extension=./modules/opkit.so test.php

# 检查内存泄漏
php-src/php-8.2.30/sapi/cli/php -d memory_limit=256M test.php

# 验证 OPcache 是否加载(OpKit 支持与其共存)
php-src/php-8.2.30/sapi/cli/php -m | grep -i opcache  # 可共存,无需禁用

16. 参考资料