OpKit 是一个 PHP Zend 扩展,提供离线 Opcode 预编译和持久化功能。它将 PHP 脚本编译成二进制的 .phpc 文件,包含操作码、字符串、常量、函数和类元数据,以便快速加载而无需重新解析。
核心特性:
- 基于 Zend OPcache 架构
- 支持 PHP 8.2/8.3/8.4/8.5
- 使用影子内存分区(Metadata/Code/Data/Misc)进行持久化存储
- 增量编译支持
- Phar 打包支持
┌─────────────────────────────────────────────────────────────────┐
│ 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 │
└─────────────────────────────────────────────────────────────────┘
| 模块 | 文件 | 职责 |
|---|---|---|
| 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 结构复用 |
// 扩展的持久化脚本结构
typedef struct _opkit_persistent_script {
zend_persistent_script script; // Zend 基础脚本结构
HashTable constants_table; // 脚本定义的常量表
} opkit_persistent_script;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;.phpc 文件格式的完整定义详见 PHPC_FILE_FORMAT.md。
关键字段:
magic[8]: 文件标识 "PHPC"system_id[32]: PHP 系统标识,用于兼容性检查checksum: Adler-32 校验和,验证文件完整性- 四分区统计:
metadata_size,code_size,data_size,misc_size
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;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 │
│ └─────────────────┘ │
└────────────────────────────────────────────────────────────────┘
#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) // 杂项┌────────────────────────────────────────────────────────────────┐
│ 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 头 │
│ ├── 写入序列化后的脚本 │
│ └── 写入字符串池 │
│ │
└────────────────────────────────────────────────────────────────┘
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_nodebookkeeping 结构和 runtime cache,共享内存中的脚本数据本身不释放,直到调用opkit_shm_reset()或进程结束。
// C API
bool opkit_shm_reset(void);
// PHP API
bool opkit_shm_reset();opkit_shm_reset() 的行为:
- 获取进程间写锁(
opkit_shared_alloc_lock) - 将共享内存分配器的
pos重置为 0 - 释放锁
PHP 层的 opkit_shm_reset() 会先调用 opkit_reset_script() 清理当前进程已注册的符号和 loaded_scripts 链表,防止 reset 后出现悬空指针访问。调用后已加载的脚本需要重新 opkit_load()。
- PHP 编译:
zend_compile_file()解析源码生成 AST,编译为 Opcodes - 符号移动:
opkit_compile_file()将函数、类、常量移动到脚本结构 - 内存计算:
zend_accel_script_persist_calc()计算四分区内存需求 - 数据持久化:
zend_accel_script_persist()复制到连续内存,序列化指针为偏移量 - 文件写入:
opkit_compile_script_store()写入文件头 + 数据 + 字符串池
详细的序列化机制和数据格式定义,参见 PHPC_FILE_FORMAT.md。
- 文件加载:
opkit_compile_script_load()读取并验证文件头(magic、system_id、checksum) - 反序列化:
zend_file_cache_unserialize()将偏移量还原为指针,重建数据结构 - 类链接:
opkit_link_classes()解析继承关系,链接父类和接口 - 符号注册:
opkit_boot()注册函数、类、常量到 Zend Engine - 执行:
zend_execute()执行主 op_array
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、类/函数表、属性信息、常量等
#if PHP_VERSION_ID >= 80400
// PHP 8.4+ 代码
#elif PHP_VERSION_ID >= 80300
// PHP 8.3 代码
#else
// PHP 8.2 代码
#endif| 特性 | 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) |
#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));
}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()
生成的 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());{
"src": "src/",
"output": "dist/",
"phar": "app.phar",
"entry": "dist/entry.php",
"force": false,
"no-incremental": false,
"compress": "gz",
"sign": "sha256"
}| 函数 | 参数 | 返回值 | 说明 |
|---|---|---|---|
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) |
| 函数 | 文件 | 说明 |
|---|---|---|
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 |
判断指针是否在共享内存中 |
# 1. 准备构建环境
phpize
# 2. 配置
./configure --with-php-config=/path/to/php-config --enable-debug
# 3. 编译
make
# 4. 测试
NO_INTERACTION=1 make test TESTS=tests/# 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| 测试文件 | 描述 | 关键特性 |
|---|---|---|
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 |
--TEST--
Test description
--EXTENSIONS--
opkit
--FILE--
<?php
// Test code
?>
--EXPECT--
Expected output.phpc 文件包含系统 ID,确保只在与编译环境兼容的系统上运行:
// 系统 ID 包含:PHP 版本、架构、编译选项
if (!zend_string_equals(system_id, opkit_system_id)) {
// 系统不匹配,拒绝加载
}OpKit 可与 Zend OPcache 静默共存。OpKit 在编译时会临时保存并恢复 zend_compile_file,绕过 OPcache 的 persistent_compile_file 钩子,编译完成后再恢复原钩子。因此无需禁用 OPcache:
; 正确配置
zend_extension=opcache.so
zend_extension=opkit.so
phar.readonly=Off- 必须使用
zend_extension加载,不能使用extension - 需要
phar.readonly=Off才能创建 Phar 包 - 文件系统权限要求(读取 .phpc 文件)
使用 SSE2 优化的 Adler-32 算法:
#ifdef __SSE2__
// 使用 SIMD 指令加速校验和计算
__m128i read = _mm_loadu_si128((__m128i *) buf);
// ... SIMD 处理
#else
// 标量回退实现
#endif通过 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; // 跳过编译
}
}
}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 # 开发指南
- 编辑
src/opkit.stub.php:添加 PHP 函数签名 - 生成 arginfo:
make自动生成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
- 使用
- 实现函数:在
src/opkit_module.c中添加 C 实现 - 添加测试:在
tests/目录创建.phpt测试文件
# 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 # 可共存,无需禁用