Code前端首页关于Code前端联系我们

PHP7扩展持久化zend_array实现及共享使用原理

terry 2年前 (2023-09-25) 阅读数 48 #后端开发

项目必须在PHP7扩展中维护一个全局持久化zend_array,可以在多个请求之间共享。

这里是关于实现和原则的简要说明。

首先定义一个全局的zend_array*

zend_array *ormosia_domain_cache 在初始化♿actendation oc = ❀NULL;吃并初始化一个 zend_array:

  1. ormosia_domain_cache = (zend_array*)pemalloc(sizeof(*ormosia_domain_cache), 1);
  2. zend_hash_init(ormosia_domain_cache, 0, NULL, permanent_zval_dtor, 1);

首先zend_array♿♿,自己的内存创建pe 持久内存 相当于 malloc- val 代替emalloc,请求过期后不会发出。在

之后,调用zend_hash_init来初始化数组。注意value dtor回调函数不是zval_ptr_dtor,而是我自己实现的persistant_zval_dtor♿

  • 函数。

    还有最后一个参数perspective=1,所以zend_array在内部分配内存的时候也使用pemalloc来分配持久内存,比如哈希收集器。

    由于需要持久化,除了zend_array之外,zend_array中存储的zval也必须包含在内存中♿ stedzend_string,值任何类型的永久 zval。

    这里说一下为什么我们需要自定义value dtor函数而不是使用zend API自带的zval_ptr_dtor。以下是实现的摘录:

    1. #define zval_ptr_dtor(zval_ptr) _zval_ptr_ dtor ((zval_ptr) ZEND_FILE_LINE_CC)
    2. ZEND_API void _zval_ptr_dtor_wrapper(❿)ptr_dtor_zval i_zval_ptr_ dtor(zval_ptr ZEND_FILE_LINE_CC);
    3. }
    4. 静态zend_always_inline void i_zval_ptr_dtor(zval *zval_ptr ZEND_FILE_LINE_DC)
    5. {
    6. if (Z_REFCOUNTED_P(Z_REFCOUNTED_P) {zval_ptrF) {zval_ptrF) ptr)) {
    7. _zval_dtor_func(Z_COUNTED_P(zval_ptr) ZEND_FILE_LINE_RELAY_CC);
    8. } else {
    9. GC_ZVAL_CHECK_POSSIBLE_ROOT(zval_ptr);♿♿❝♿♓}} ZEND_API 无效 ZEND_FASTCALL _zval_d tor_func(zend_refcounted *p ZEND_FILE_LINE_DC) I {
    10. 开关(gc_type (p)) {
    11. case is_string:
    12. case_constant: {
    13. zend_string *str = (zend_string *) p; send_string_free (str);
    14. 暂停;
    15. }
    16. case is_array: {
    17. zend_array *arr = (zend_array *) p;
    18. zend_array_destroy (arr);
    19. 暂停;
    20. }

    重点关注最后一个实现函数,该函数是在zend_array为0时调用的。对于字符串类型来说,

  • 实际上是由
  • ❓d_st的内部实现定义的。是 zend_string 一个常量内存:
    1. static zend_always_inline void zend_string_free(zend_string *s)
    2.     
    3. } }
    4. }

    可以看出的gc场zend_string

    保存标签IS_STR_PERSISTANT,其中
  • string❓zend由最后一个参数控制,因此可以通过pe正确处理根据内存类型免费。相应地释放。问题出在数组类型表 *ht)
  • {
  • ...
  •                                               ' ki a ki '' s s   ‐ ‐‐ ‐‐‐‐ ———————————— ———————————————————————————————————————— ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~直到 efree(HT_GET_DATA_ADDR(ht));
    1.               案例 IS_REFERENCE:{
    2. zend_reference *ref = (zend_reference*)p;
    3. efree_size (ref, size (zend_reference));
    4.      中断; zval_dtor_ptr不能直接用于持久化zend_array的值析构函数。

      因为在我的业务场景中zend_array保存的值只有两种类型:字符串和数组,而嵌套数组也是保存的字符串或数组的类型,所以只涵盖:†               case IS_ARRAY :

    5.               zend_hash_destroy(zval_ptr -> value.arr);默认值:
    6.                             中断; GC_ZVAL_CHECK_POSSIBLE_ROOT(zval_ptr);
    7. }
    8. }
    9. }

    此函数基本上将参考编号减少zval_dtorzval_dtor 首先。如果将其减少到 0,则资源将被释放。如果是字符串,则直接对应的api,如果是数组,则另外调用。 API 的名称为 zend_hash_destroy,其内部区分了释放所需的内存类型:

    1. ZEND_API void ZEND_FASTCALL zend_hash_destroy ...♽♽(HashTable) } else if ( EXPECTED (! (ht->u .flags & HASH_FLAG_INITIALIZED))) {
    2.                     return; _参展商);
    3. }

    zend_string 原理上类似。持久化的zend_array将被标记来控制pefree的释放行为。

    zend_hash_destroy只有dtor销毁容器中的所有key和value,然后释放hash桶内存。它不会释放 zend_array 结构本身的内存,所以我调用 pefree 来释放它自己。

    那么代码另一部分提到的“循环引用回收”是什么意思呢?我为什么评论?

    所谓的“循环引用”指的是这样一个例子:

    我有一个zval1zend_array❿并且我有N = 1❓❓❓引用。 ext,输入key=”myself”,值为zval1本身,保存zendhash更新到zval1。按照规则,我给这个值加上1个参考号。 ,所以值为zend_array,所以zval1的引用数将为2。

    在某些时候,我们不想再访问 zval1 引用,因此我们将其解锁。结果,剩下 1 个号码,对 zend_hash_destroy 的调用失败。这个zval1永远没有机会彻底死亡。出来了。

    原因是zval1保存了zval1,导致循环引用,GC垃圾回收无法生效。

    上面的 C 操作对应 PHP 中的这段代码:

  • 版权声明

    本文仅代表作者观点,不代表Code前端网立场。
    本文系作者Code前端网发表,如需转载,请注明页面地址。

    发表评论:

    ◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

    热门