ホームページ  >  記事  >  バックエンド開発  >  PHP7 カーネルの変数の内部実装を分析する

PHP7 カーネルの変数の内部実装を分析する

coldplay.xixi
coldplay.xixi転載
2020-06-28 15:44:192665ブラウズ

PHP7 カーネルの変数の内部実装を分析する

PHP の変数実装の基本構造は zval で、さまざまな実装がこの構造に基づいて行われており、PHP の最も基本的な構造です。各 PHP 変数は zval に対応します。この構造と PHP 変数のメモリ管理メカニズムを見てみましょう。

zval 構造

関連学習の推奨事項: PHP エントリーからマスターまでのプログラミング

zval结构比较简单,内嵌一个union类型的zend_value保存具体变量类型的值或指针,zval中还有两个union:u1、u2:
  • u1: その意味は比較的直感的であり、変数の型は によって決まります。 u1.typeDifferentiation、もう 1 つの値 type_flags は変数のメモリ管理と gc メカニズムで使用される型マスクです。3 番目の部分で詳細に分析します。最後の 2 つ const_flagsreserved当面は気にしないでください

  • u2: この値は純粋に補助的な値です。 zval に次の値のみがある場合: valueu1 の 2 つの値については、zval 全体のサイズも 16 バイトに揃えられます。サイズは u2 の有無に関係なく 16 バイトですが、追加の 4 バイトを特別な目的に使用するとコスト効率が高くなります。たとえば、next はハッシュの競合を解決するためにハッシュ テーブルで使用され、fe_pos はforeach...

Fromzend_valuelongdouble 型を除いて、直接値を格納します。他の型はポインターであり、それぞれの構造体を指します。


# タイプ

zval.u1.typeタイプ:

スカラー型

最も単純な型は true、false、long、double、null です。このうち、true、false、null は値を持たず、型によって直接区別されますが、値は、 long と double の型は value に直接存在します 中: zend_long、double、つまりスカラー型は追加の値ポインターを必要としません。

String

PHP の文字列は zend_string:

    # で表されます。
  • #gc: 現在の値への参照数などの変数参照情報。参照カウントを使用するすべての変数タイプはこの構造になります。セクション 3.1 で詳細に分析します

  • h: ハッシュ値、配列内のインデックスを計算するときに使用されます

  • len: 文字列の長さ、これにより、値はバイナリ セキュリティを保証します。

  • val:文字列の内容、可変長の構造体、割り当て時に len の長さに応じてメモリに適用されます

実際、文字列はいくつかのカテゴリに分類できます: IS_STR_PERSISTENT (malloc によって割り当てられる)、IS_STR_INTERNED (関数名、変数値など、PHP コードで記述された一部のリテラル)、IS_STR_PERMANENT (永続値、ライフサイクル)要求より大きい)、IS_STR_CONSTANT (定数)、IS_STR_CONSTANT_UNQUALIFIED、この情報はフラグ zval.value->gc.u.flags を通じて保存され、後で使用するときに詳細に分析されます。

Array

array は、PHP の非常に強力なデータ構造です。その基礎となる実装は、通常の順序付けされた HashTable です。ここでは、その構造について簡単に説明します。1 つのセクションでは、配列の実装を個別に分析します。


#

オブジェクト/リソース


##オブジェクトの方が一般的で、リソースは TCP を指します。接続、ファイル ハンドル、その他の型。この型はより柔軟です。自由に struct を定義し、ptr を介してそれを指すことができます。この型は後ほど個別に分析するため、ここでは詳しく説明しません。


#Reference

Reference は PHP の特殊な型で、実際には別の PHP 変数を指します。これを変更すると、指す実際の zval が直接変更されます。これは、C のポインターとして単純に理解できます。PHP では、参照変数は

&

演算子を通じて生成されます。これは、前の変数に関係なく、参照変数が生成されることを意味します。型は何ですか? &まず、新しい zval が IS_REFERENCE 型で生成され、val の値は元の zval の値を指します。

構造は非常に単純です。公開部分

zend_refcounted_h

を除けば、val は 1 つだけです。例を見てみましょう。特定の構造関係を確認するには:

最終結果は以下のようになります:



注: 参照は & を通じてのみ生成でき、代入によって渡すことはできません。 :



#$b = &$a

現時点では、

$a$b の型は参照ですが、$ c = $ b は、$b$c に直接代入しませんが、$b が実際に指す zval を $c に代入します。 #$c を参照にしたい場合は、次のようにする必要があります:

## これは、PHP の 参照には 1 つのレイヤー しか含めることができず、

には別の参照

を指す参照が 1 つも存在しない、つまり、 C言語におけるポインタ の概念。 メモリ管理

次に、変数の割り当てと破棄を分析します。

在分析变量内存管理之前我们先自己想一下可能的实现方案,最简单的处理方式:定义变量时alloc一个zval及对应的value结构(ref/arr/str/res...),赋值、函数传参时硬拷贝一个副本,这样各变量最终的值完全都是独立的,不会出现多个变量同时共用一个value的情况,在执行完以后直接将各变量及value结构free掉。

这种方式是可行的,而且内存管理也很简单,但是,硬拷贝带来的一个问题是效率低,比如我们定义了一个变量然后赋值给另外一个变量,可能后面都只是只读操作,假如硬拷贝的话就会有多余的一份数据,这个问题的解决方案是:引用计数+写时复制。PHP变量的管理正是基于这两点实现的。

引用计数

引用计数是指在value中增加一个字段refcount记录指向当前value的数量,变量复制、函数传参时并不直接硬拷贝一份value数据,而是将refcount++,变量销毁时将refcount--,等到refcount减为0时表示已经没有变量引用这个value,将它销毁即可。

引用计数的信息位于给具体value结构的gc中:

从上面的zend_value结构可以看出并不是所有的数据类型都会用到引用计数,longdouble直接都是硬拷贝,只有value是指针的那几种类型才可能会用到引用计数。

下面再看一个例子:

$a = "hi~";$b = $a;

猜测一下变量$a/$b的引用情况。

这个不跟上面的例子一样吗?字符串"hi~"$a/$b两个引用,所以zend_string1(refcount=2)。但是这是错的,gdb调试发现上面例子zend_string的引用计数为0。这是为什么呢?

$a,$b -> zend_string_1(refcount=0,val="hi~")

事实上并不是所有的PHP变量都会用到引用计数,标量:true/false/double/long/null是硬拷贝自然不需要这种机制,但是除了这几个还有两个特殊的类型也不会用到:interned string(内部字符串,就是上面提到的字符串flag:IS_STR_INTERNED)、immutable array,它们的type是IS_STRINGIS_ARRAY,与普通string、array类型相同,那怎么区分一个value是否支持引用计数呢?还记得zval.u1中那个类型掩码type_flag吗?正是通过这个字段标识的,这个字段除了标识value是否支持引用计数外还有其它几个标识位,按位分割,注意:type_flagzval.value->gc.u.flag不是一个值。

支持引用计数的value类型其zval.u1.type_flag包含(注意是&,不是等于)IS_TYPE_REFCOUNTED

#define IS_TYPE_REFCOUNTED          (1<<2)

下面具体列下哪些类型会有这个标识:

|     type       | refcounted |
+----------------+------------+
|simple types    |            |
|string          |      Y     |
|interned string |            |
|array           |      Y     |
|immutable array |            |
|object          |      Y     |
|resource        |      Y     |
|reference       |      Y     |

simple types很显然用不到,不再解释,string、array、object、resource、reference有引用计数机制也很容易理解,下面具体解释下另外两个特殊的类型:

  • interned string:内部字符串,这是种什么类型?我们在PHP中写的所有字符都可以认为是这种类型,比如function name、class name、variable name、静态字符串等等,我们这样定义:$a = "hi~;"后面的字符串内容是唯一不变的,这些字符串等同于C语言中定义在静态变量区的字符串:char *a = "hi~";,这些字符串的生命周期为request期间,request完成后会统一销毁释放,自然也就无需在运行期间通过引用计数管理内存。

  • immutable array:只有在用opcache的时候才会用到这种类型,不清楚具体实现,暂时忽略。


写时复制

上一小节介绍了引用计数,多个变量可能指向同一个value,然后通过refcount统计引用数,这时候如果其中一个变量试图更改value的内容则会重新拷贝一份value修改,同时断开旧的指向,写时复制的机制在计算机系统中有非常广的应用,它只有在必要的时候(写)才会发生硬拷贝,可以很好的提高效率,下面从示例看下:

$a = array(1,2);$b = &$a;$c = $a;//发生分离$b[] = 3;


最终的结果:

不是所有类型都可以copy的,比如对象、资源,实时上只有string、array两种支持,与引用计数相同,也是通过zval.u1.type_flag标识value是否可复制的:

#define IS_TYPE_COLLECTABLE         (1<<3)
|     type       |  copyable  |
+----------------+------------+
|simple types    |            |
|string          |      Y     |
|interned string |            |
|array           |      Y     |
|immutable array |            |
|object          |            |
|resource        |            |
|reference       |            |

copyable的意思是当value发生duplication时是否需要copy,这个具体有两种情形下会发生:

  • a.从literal变量区复制到局部变量区,比如:$a = [];实际会有两个数组,而$a = "hi~";//interned string则只有一个string

  • b.局部变量区分离时(写时复制):如改变变量内容时引用计数大于1则需要分离,$a = [];$b = $a; $b[] = 1;这里会分离,类型是array所以可以复制,如果是对象:$a = new user;$b = $a;$a->name = "dd";这种情况是不会复制object的,$a、$b指向的对象还是同一个</pre></span></p> <p style="color:rgb(51,51,51);clear:both;min-height:1em;font-family:'Helvetica Neue', Helvetica, 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;font-size:16px;">具体literal、局部变量区变量的初始化、赋值后面编译、执行两篇文章会具体分析,这里知道变量有个<code>copyable的属性就行了。

    变量回收

    PHP变量的回收主要有两种:主动销毁、自动销毁。主动销毁指的就是unset,而自动销毁就是PHP的自动管理机制,在return时减掉局部变量的refcount,即使没有显式的return,PHP也会自动给加上这个操作。

    垃圾回收

    PHP变量的回收是根据refcount实现的,当unset、return时会将变量的引用计数减掉,如果refcount减到0则直接释放value,这是变量的简单gc过程,但是实际过程中出现gc无法回收导致内存泄漏的bug,先看下一个例子:

    $a = [1];$a[] = &amp;$a;unset($a);

    unset($a)之前引用关系:

    unset($a)之后:

    可以看到,unset($a)之后由于数组中有子元素指向$a,所以refcount > 0,无法通过简单的gc机制回收,这种变量就是垃圾,垃圾回收器要处理的就是这种情况,目前垃圾只会出现在array、object两种类型中,所以只会针对这两种情况作特殊处理:当销毁一个变量时,如果发现减掉refcount后仍然大于0,且类型是IS_ARRAY、IS_OBJECT则将此value放入gc可能垃圾双向链表中,等这个链表达到一定数量后启动检查程序将所有变量检查一遍,如果确定是垃圾则销毁释放。

    标识变量是否需要回收也是通过u1.type_flag区分的:

    #define IS_TYPE_COLLECTABLE
    |     type       | collectable |
    +----------------+-------------+
    |simple types    |             |
    |string          |             |
    |interned string |             |
    |array           |      Y      |
    |immutable array |             |
    |object          |      Y      |
    |resource        |             |
    |reference       |             |

    具体的垃圾回收过程这里不再介绍。

    以上がPHP7 カーネルの変数の内部実装を分析するの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はcsdn.netで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。