signal の話
或いは
Zend Signals とは何か
2016/12/11
第七回闇PHP勉強会
do_aki
updated 2016-12-13
@do_aki
@do_aki
http://do-aki.net/
signal とは
• UNIX系OS に古くからあるプロセス間通信の
一つ
• プロセスがシグナル(実態は整数) を受信す
ると、あらかじめ設定した処理(シグナルハ
ンドラ)が実行される(乱暴に言えば割り込
みが発生する)
• シグナルハンドラが設定されてないシグナル
は、無視されるかあるいは既定の動きをする
(その多くはプログラムの終了)
php においては
• pcntl 拡張を利用することで signal 処
理を管理することが可能
• pcntl_signal(signo, callback)
pcntl_signal の利用例<?php
declare(ticks=1);
$signal_callback_function = function ($signo) {
echo "receive signal: {$signo}¥n";
exit;
};
pcntl_signal(SIGINT, $signal_callback_function);
while(1) {
sleep(1);
}
// 通常の動きだけでは永久ループ
// Ctrl + C 押下で、 "receive signal: 2" を出力して終了
pcntl による signal 処理
これは単純に
“シグナルを受信したら
callback が実行される”
というものではない
pcntl_signal が行うこと
• signal_table に シグナル番号とコール
バック関数を登録
• 該当シグナル番号に対するシグナルハン
ドラとして pcntl_signal_handler を
(実行環境に対して) 登録
• 実際にコールバック関数を呼ぶのは
pcntl_signal_dispatch
signal handling
pcntl_signal_handler
signo
pcntl_signal_dispatch
SIGHUP funcA
SIGINT funcB
SIGALRM funcC
……
signal table
lookup
&
call
signo
pending signal queue
[receive signal]
[tick or vm_interrupt]
シグナルハンドラの制約
• どんなタイミングであっても呼ばれる可能性
がある
– 当然 php のスタックフレームは無視
• 呼び出し可能なシステムコールが限られる
– man signal 7 を参照
最小限の処理(受信したことの記録) だけ
行い、あとから適切なタイミングでコール
バックを実行(dispatch)している
なので
tick による dispatch
• declare(ticks = N); // (N >= 1)
– N個の tick 可能命令ごとに ZEND_TICKS opcode
が発行される
– 詳しくは以前書いた記事を参照
http://d.hatena.ne.jp/do_aki/20151204/14491
97226
• ticks=1 ならば、だいたい php スクリプト1行
ごとに発行されてる
– 1行ごとに pcntl_signal_dispatch が呼ばれるイ
メージ
• ファイル単位で、コンパイル時に影響
– ticks 指定忘れるとシグナルコールバックされない
vm_interrupt による dispatch
• pcntl_async_signals(true); // 7.1~
– declare(ticks=1) が不要になる
– pcntl_signal_handler が
EG(vm_interrupt) = 1 するようになる
• EG(vm_interrupt) が 1 の場合、 PHP VM は、
zend_interrupt_function (関数ポインタ) を呼ぶ
– Opcode ひとつ処理するたびにチェックしている
– pcntl の MINIT で
zend_interrupt_function = pcntl_interrupt_function
してて、このなかで pcntl_signal_dispatch を呼ぶ
– ZEND_TICKS Opcode で毎回呼ばれるよりも低コスト
– (async_signals しなくても pcntl 以外が vm_interrupt
をセットすれば dispatch される気がする)
dispatch by vm_interrupt
pcntl_signal_handler
pcntl_signal_dispatch
[receive signal]
EG(vm_in
terrupt)
PHP VM
zend_interrupt_function =
pcntl_interrupt_function
[each opcode]
set1
EG(vm_interrupt)
• 元は Safe timeout handling により導入
された仕組み
• EG(vm_interrupt) かつ EG(timed_out)
ならば (zend_interrupt_function ではなく)
zend_timeout(0) を呼ぶ(おそらくこちらが本命)
• EG(timed_out) は php スクリプトの実行
時間が max_execution_time を超えたとき
に設定される
max_execution_time の実装
• setitimer (ITIMER_PROF)を利用 (!WIN32)
– プログラムの実行時間が指定時間経過すると
SIGPROF が飛ぶ
– php に kill –PROF すると終了する
• zend engine でシグナルハンドラ を設定し
てる
– 7.0まで: signalシステムコールを利用
(直接 zend_timeout が呼ばれてた)
– 7.1から: zend_signalを利用 (ZEND_SIGNALS)
(zend_timeout_handler)
この中で EG(timed_out) = 1
Safe timeout handling
zend_timeout_
handler
PHP VM
zend_timeout
set1
zend_timeout
~7.0 (or !ZEND_SIGNALS)
7.1~ (with ZEND_SIGNALS)
EG(vm_in
terrupt)
EG(timed
_out)
ZEND_SIGNALS
• ZendEngineやSAPI,拡張 と 実行環境 の間に作ら
れた signal 処理の中間層
– 環境依存な signal 処理を画一的に扱うための仕組み
– 7.1 から デフォルトで有効
• php スクリプトにおける pcntl のようなものを、
C言語で(コアや拡張向けに) 提供した感じ
– zend_signal によって実行環境に登録されるシグナル
ハンドラは zend_signal_handler_defer に統一さ
れる (シグナルハンドラとして指定した関数はここか
ら間接的に呼ばれる)
Deferred Signals
• ZEND_SIGNAL_BLOCK_INTERRUPTIONS (=
HANDLE_BLOCK_INTERRUPTIONS) により、
シグナルハンドラの実行を延期することが可
能
– 現状使ってるのは opcache のみ
– 解放は ZEND_SIGNAL_UNBLOCK_INTERRUPTIONS
– シグナルハンドラの実行タイミングを制御するこ
とで、共有メモリ操作時の不安定な動きを解消し
ている(らしい)
– 以前はメモリ操作周りでも利用されていたが、
7.0 でのメモリマネージャ刷新によって不要に
なった?
ZEND SIGNALS
zend_signal_handler_defer
1 handler1
2 hanlder2
3 hanlder3
……
SIGG(handlers)
zend signal queue
signo
signal blocked
(HANDLE_BLOCK_INTERRUPTIONS)
signal unblocked
HANDLE_UNBLOCK_INTERRUPTIONS
signo
callzend_signal_handler
まとめ
– pcntl signal
– EG(vm_interrupt)
– Safe timeout handling
– ZEND_SIGNALS
• について話しました
• 7.1 で php における signal 処理はだいぶ大き
く変容します
– おそらく ふつーに php スクリプト書いている人に
は気づかれないような変化。
けど、これにより安定化とパフォーマンス向上が図
られているのです
参照
• Zend Signal Handling
– https://wiki.php.net/rfc/zendsignals
– https://github.com/php/php-
src/commit/939875133a2c389d621a9999a8ede3ddbc9b6637
• Asynchronous Signal Handling (without TICKs)
– https://wiki.php.net/rfc/async_signals
– https://github.com/php/php-
src/commit/c03ccfe78d6b13cab9546efb616a42a8f3e8a4e0
• Safe execution timeout handling
– https://www.mail-archive.com/internals@lists.php.net/msg76907.html
– https://github.com/php/php-src/pull/1876
• Enable Zend Signals by Default
– https://www.mail-archive.com/internals@lists.php.net/msg86428.html

signal の話 或いは Zend Signals とは何か

  • 1.
    signal の話 或いは Zend Signalsとは何か 2016/12/11 第七回闇PHP勉強会 do_aki updated 2016-12-13
  • 2.
  • 3.
    signal とは • UNIX系OSに古くからあるプロセス間通信の 一つ • プロセスがシグナル(実態は整数) を受信す ると、あらかじめ設定した処理(シグナルハ ンドラ)が実行される(乱暴に言えば割り込 みが発生する) • シグナルハンドラが設定されてないシグナル は、無視されるかあるいは既定の動きをする (その多くはプログラムの終了)
  • 4.
    php においては • pcntl拡張を利用することで signal 処 理を管理することが可能 • pcntl_signal(signo, callback)
  • 5.
    pcntl_signal の利用例<?php declare(ticks=1); $signal_callback_function =function ($signo) { echo "receive signal: {$signo}¥n"; exit; }; pcntl_signal(SIGINT, $signal_callback_function); while(1) { sleep(1); } // 通常の動きだけでは永久ループ // Ctrl + C 押下で、 "receive signal: 2" を出力して終了
  • 6.
    pcntl による signal処理 これは単純に “シグナルを受信したら callback が実行される” というものではない
  • 7.
    pcntl_signal が行うこと • signal_tableに シグナル番号とコール バック関数を登録 • 該当シグナル番号に対するシグナルハン ドラとして pcntl_signal_handler を (実行環境に対して) 登録 • 実際にコールバック関数を呼ぶのは pcntl_signal_dispatch
  • 8.
    signal handling pcntl_signal_handler signo pcntl_signal_dispatch SIGHUP funcA SIGINTfuncB SIGALRM funcC …… signal table lookup & call signo pending signal queue [receive signal] [tick or vm_interrupt]
  • 9.
    シグナルハンドラの制約 • どんなタイミングであっても呼ばれる可能性 がある – 当然php のスタックフレームは無視 • 呼び出し可能なシステムコールが限られる – man signal 7 を参照 最小限の処理(受信したことの記録) だけ 行い、あとから適切なタイミングでコール バックを実行(dispatch)している なので
  • 10.
    tick による dispatch •declare(ticks = N); // (N >= 1) – N個の tick 可能命令ごとに ZEND_TICKS opcode が発行される – 詳しくは以前書いた記事を参照 http://d.hatena.ne.jp/do_aki/20151204/14491 97226 • ticks=1 ならば、だいたい php スクリプト1行 ごとに発行されてる – 1行ごとに pcntl_signal_dispatch が呼ばれるイ メージ • ファイル単位で、コンパイル時に影響 – ticks 指定忘れるとシグナルコールバックされない
  • 11.
    vm_interrupt による dispatch •pcntl_async_signals(true); // 7.1~ – declare(ticks=1) が不要になる – pcntl_signal_handler が EG(vm_interrupt) = 1 するようになる • EG(vm_interrupt) が 1 の場合、 PHP VM は、 zend_interrupt_function (関数ポインタ) を呼ぶ – Opcode ひとつ処理するたびにチェックしている – pcntl の MINIT で zend_interrupt_function = pcntl_interrupt_function してて、このなかで pcntl_signal_dispatch を呼ぶ – ZEND_TICKS Opcode で毎回呼ばれるよりも低コスト – (async_signals しなくても pcntl 以外が vm_interrupt をセットすれば dispatch される気がする)
  • 12.
    dispatch by vm_interrupt pcntl_signal_handler pcntl_signal_dispatch [receivesignal] EG(vm_in terrupt) PHP VM zend_interrupt_function = pcntl_interrupt_function [each opcode] set1
  • 13.
    EG(vm_interrupt) • 元は Safetimeout handling により導入 された仕組み • EG(vm_interrupt) かつ EG(timed_out) ならば (zend_interrupt_function ではなく) zend_timeout(0) を呼ぶ(おそらくこちらが本命) • EG(timed_out) は php スクリプトの実行 時間が max_execution_time を超えたとき に設定される
  • 14.
    max_execution_time の実装 • setitimer(ITIMER_PROF)を利用 (!WIN32) – プログラムの実行時間が指定時間経過すると SIGPROF が飛ぶ – php に kill –PROF すると終了する • zend engine でシグナルハンドラ を設定し てる – 7.0まで: signalシステムコールを利用 (直接 zend_timeout が呼ばれてた) – 7.1から: zend_signalを利用 (ZEND_SIGNALS) (zend_timeout_handler) この中で EG(timed_out) = 1
  • 15.
    Safe timeout handling zend_timeout_ handler PHPVM zend_timeout set1 zend_timeout ~7.0 (or !ZEND_SIGNALS) 7.1~ (with ZEND_SIGNALS) EG(vm_in terrupt) EG(timed _out)
  • 16.
    ZEND_SIGNALS • ZendEngineやSAPI,拡張 と実行環境 の間に作ら れた signal 処理の中間層 – 環境依存な signal 処理を画一的に扱うための仕組み – 7.1 から デフォルトで有効 • php スクリプトにおける pcntl のようなものを、 C言語で(コアや拡張向けに) 提供した感じ – zend_signal によって実行環境に登録されるシグナル ハンドラは zend_signal_handler_defer に統一さ れる (シグナルハンドラとして指定した関数はここか ら間接的に呼ばれる)
  • 17.
    Deferred Signals • ZEND_SIGNAL_BLOCK_INTERRUPTIONS(= HANDLE_BLOCK_INTERRUPTIONS) により、 シグナルハンドラの実行を延期することが可 能 – 現状使ってるのは opcache のみ – 解放は ZEND_SIGNAL_UNBLOCK_INTERRUPTIONS – シグナルハンドラの実行タイミングを制御するこ とで、共有メモリ操作時の不安定な動きを解消し ている(らしい) – 以前はメモリ操作周りでも利用されていたが、 7.0 でのメモリマネージャ刷新によって不要に なった?
  • 18.
    ZEND SIGNALS zend_signal_handler_defer 1 handler1 2hanlder2 3 hanlder3 …… SIGG(handlers) zend signal queue signo signal blocked (HANDLE_BLOCK_INTERRUPTIONS) signal unblocked HANDLE_UNBLOCK_INTERRUPTIONS signo callzend_signal_handler
  • 19.
    まとめ – pcntl signal –EG(vm_interrupt) – Safe timeout handling – ZEND_SIGNALS • について話しました • 7.1 で php における signal 処理はだいぶ大き く変容します – おそらく ふつーに php スクリプト書いている人に は気づかれないような変化。 けど、これにより安定化とパフォーマンス向上が図 られているのです
  • 20.
    参照 • Zend SignalHandling – https://wiki.php.net/rfc/zendsignals – https://github.com/php/php- src/commit/939875133a2c389d621a9999a8ede3ddbc9b6637 • Asynchronous Signal Handling (without TICKs) – https://wiki.php.net/rfc/async_signals – https://github.com/php/php- src/commit/c03ccfe78d6b13cab9546efb616a42a8f3e8a4e0 • Safe execution timeout handling – https://www.mail-archive.com/internals@lists.php.net/msg76907.html – https://github.com/php/php-src/pull/1876 • Enable Zend Signals by Default – https://www.mail-archive.com/internals@lists.php.net/msg86428.html

Editor's Notes

  • #9 process が signal を受け取ると、 pcntl_signal_handler が受け取る。 signal handler は実行できることが限られるため、任意の処理を行えないようにしている。 tick あるいは pcntl_signal_dispatch php 関数が呼ばれると、 pcntl_signal_dispatch C関数が呼ばれ、 pending signal queue から一つずつ 受信した signal 番号を取り出し、signal table に登録されている関数を callback する。 7.1からは、環境が siginfo に対応している場合には siginfo (シグナルの呼び出し元pidやuid 等の詳細情報)も保持する。、 これを array に変換したものが pcntl_signal で設定した callback 関数の第2引数として渡ってくる (まだドキュメントにはない)