2022-04-29

[php-src読書録]その18: opcacheのファイルキャッシュ

php-srcのコードリーディングした内容をコツコツ残すテスト。その18

今日はopcacheのファイルキャッシュ

キャッシュをファイルに保存

cache_script_in_shared_memory()zend_file_cache_script_store() が呼ばれる
https://github.com/php/php-src/blob/PHP-8.1.4/ext/opcache/ZendAccelerator.c#L1650:L1650

zend_file_cache_script_store()zend_file_cache_get_bin_file_path() が呼ばれキャッシュファイルのパスを生成する

/tmp/095a04392b125a7d278d14ce75956467/root/php-src-php-8.1.4/1.php.bin

こんな感じで opcache.file_cache にファイルの絶対パスを追加したファイルパスになる
https://github.com/php/php-src/blob/PHP-8.1.4/ext/opcache/zend_file_cache.c#L990:L990

そのファイルパスのディレクトリを再帰的に作成する
https://github.com/php/php-src/blob/PHP-8.1.4/ext/opcache/zend_file_cache.c#L992:L992

そのファイルパスで zend_file_cache_open() でファイルを開き、writev() でキャッシュを書き込んでいる
https://github.com/php/php-src/blob/PHP-8.1.4/ext/opcache/zend_file_cache.c#L998:L998
https://github.com/php/php-src/blob/PHP-8.1.4/ext/opcache/zend_file_cache.c#L1052:L1052

キャッシュを書き込む前に構造体をごにょる必要がある。なんでかというと構造体に含まれるポインタはその実行環境におけるポインタになるので、次にロードされるときには不正なポインタになってしまう。 各ポインタをメモリの先頭アドレスからの差分にして保存、利用するときはメモリの先頭アドレスにその差分を足せば正しいポインタになるので、ポインタの変換をキャッシュの保存・取得時に行っている。

ファイルキャッシュに保存する際の変換は zend_file_cache_serialize で行われる。
https://github.com/php/php-src/blob/PHP-8.1.4/ext/opcache/zend_file_cache.c#L908-L913

このあたりでファイルキャッシュに保存する構造体に値をセットしている
https://github.com/php/php-src/blob/PHP-8.1.4/ext/opcache/zend_file_cache.c#L896-L905

その後、 SERIALIZE_STRzend_file_cache_serialize_hash などでポインタ変換を行う。 SERIALIZE_STR はこんな感じで (ptr) = (void*)((char*)(ptr) - (char*)script->mem); によって先頭アドレスからの差分に変換している。

#define SERIALIZE_STR(ptr) do { \
		if (ptr) { \
			if (IS_ACCEL_INTERNED(ptr)) { \
				(ptr) = zend_file_cache_serialize_interned((zend_string*)(ptr), info); \
			} else { \
				ZEND_ASSERT(IS_UNSERIALIZED(ptr)); \
				/* script->corrupted shows if the script in SHM or not */ \
				if (EXPECTED(script->corrupted)) { \
					GC_ADD_FLAGS(ptr, IS_STR_INTERNED); \
					GC_DEL_FLAGS(ptr, IS_STR_PERMANENT); \
				} \
				(ptr) = (void*)((char*)(ptr) - (char*)script->mem); \
			} \
		} \
	} while (0)

キャッシュをファイルから取得

read() でファイルからキャッシュを読み取り
https://github.com/php/php-src/blob/PHP-8.1.4/ext/opcache/zend_file_cache.c#L1715:L1715
https://github.com/php/php-src/blob/PHP-8.1.4/ext/opcache/zend_file_cache.c#L1763:L1763

ファイルキャッシュにはポインタの差分を保存しているので、読み取った結果をそのままでは利用できない。そのため、 zend_file_cache_unserialize() でポインタを正しい位置に戻していたりする。
https://github.com/php/php-src/blob/PHP-8.1.4/ext/opcache/zend_file_cache.c#L1843:L1843
https://github.com/php/php-src/blob/PHP-8.1.4/ext/opcache/zend_file_cache.c#L1675-L1682

例えば、UNSERIALIZE_STR は以下のように定義されていて、 (ptr) = (void*)((char*)buf + (size_t)(ptr)) が変換している箇所になっている。

#define UNSERIALIZE_STR(ptr) do { \
		if (ptr) { \
			if (IS_SERIALIZED_INTERNED(ptr)) { \
				(ptr) = (void*)zend_file_cache_unserialize_interned((zend_string*)(ptr), !script->corrupted); \
			} else { \
				ZEND_ASSERT(IS_SERIALIZED(ptr)); \
				(ptr) = (void*)((char*)buf + (size_t)(ptr)); \
				/* script->corrupted shows if the script in SHM or not */ \
				if (EXPECTED(!script->corrupted)) { \
					GC_ADD_FLAGS(ptr, IS_STR_INTERNED | IS_STR_PERMANENT); \
				} else { \
					GC_ADD_FLAGS(ptr, IS_STR_INTERNED); \
					GC_DEL_FLAGS(ptr, IS_STR_PERMANENT); \
				} \
			} \
		} \
	} while (0)

bufは展開されたメモリの先頭アドレスでptrに先頭アドレスからの差分が入っている。 zend_file_cache_unserialize_hash などの関数も良い感じにポインタを変換している。

このエントリーをはてなブックマークに追加