再入可能とは、関数またはサブルーチンの実行が完了する前に中断して再開できるプログラミング概念です。つまり、関数は、前回の実行が完了する前に再度呼び出すことができます。再入可能コードは、同じ関数の複数のインスタンスが同時に、または立て続けに呼び出される場合に、安全で予測可能になるように設計されています。コンピューター プログラムまたはサブルーチンが再入可能と呼ばれるのは、複数の呼び出しが複数のプロセッサ上で安全に同時に実行できる場合、またはシングル プロセッサ システムで実行を中断して新しい実行を安全に開始できる(「再入可能」) 場合です。中断は、ジャンプや呼び出しなどの内部アクション、または割り込みやシグナルなどの外部アクションによって発生する可能性があります。再帰では、新しい呼び出しは内部呼び出しによってのみ発生します。
この定義は、複数のプロセスが同時にアクティブになり、制御フローが割り込みによって中断され、割り込みサービス ルーチン(ISR) または「ハンドラー」サブルーチンに転送される可能性があるマルチプログラミング環境に由来しています。割り込みがトリガーされたときに実行されていた可能性のあるハンドラーによって使用されるサブルーチンは、再入可能である必要があります。同様に、共有データにアクセスする 2 つのプロセッサによって共有されるコードも再入可能である必要があります。多くの場合、オペレーティング システムカーネルを介してアクセス可能なサブルーチンは再入可能ではありません。したがって、割り込みサービス ルーチンは実行できるアクションが制限されています。たとえば、通常はファイル システムへのアクセスが制限され、メモリの割り当てさえ制限されることがあります。
再入可能性は、マルチスレッド環境におけるスレッドセーフティには必要でも十分でもありません。言い換えれば、再入可能なサブルーチンはスレッドセーフである可能性がありますが[1]、保証されていません[要出典]。逆に、スレッドセーフなコードは再入可能である必要はありません (例については以下を参照)。
再入可能プログラムに使用される他の用語には、「共有可能コード」があります。[2]再入可能サブルーチンは、参考資料で「シグナルセーフ」であるとマークされることがあります。 [3]再入可能プログラムは多くの場合、 [a]「純粋なプロシージャ」 です。
背景
再入可能性は、関数が複数回呼び出されても、一度だけ呼び出された場合とまったく同じ出力が生成される、べき等性とは異なります。一般的に、関数は何らかの入力データに基づいて出力データを生成します (ただし、一般的にはどちらもオプションです)。共有データは、いつでもどの関数からでもアクセスできます。データがどの関数によっても変更可能 (かつ、その変更を追跡しない) な場合、データを共有するものにとって、そのデータが以前のどの時点と同じであるかは保証されません。
データには、プログラム内のどこでデータが使用されるかを表すスコープと呼ばれる特性があります。データ スコープは、グローバル(関数のスコープ外にあり、範囲は不定) またはローカル(関数が呼び出されるたびに作成され、終了時に破棄される) のいずれかです。
ローカル データは、再入するルーチンでもそうでないルーチンでも共有されません。したがって、再入には影響しません。グローバル データは関数の外部で定義され、グローバル変数(すべての関数間で共有されるデータ) または静的変数(同じ関数のすべての呼び出しで共有されるデータ) の形式で、複数の関数からアクセスできます。オブジェクト指向プログラミングでは、グローバル データはクラスのスコープ内で定義され、プライベートにして、そのクラスの関数だけがアクセスできるようにすることができます。また、クラス変数がクラス インスタンスにバインドされるインスタンス変数の概念もあります。これらの理由から、オブジェクト指向プログラミングでは、この区別は通常、クラス外部でアクセス可能なデータ (パブリック) と、クラス インスタンスに依存しないデータ (静的) に対してのみ使用されます。
再入可能性はスレッドセーフとは異なりますが、密接に関連しています。関数はスレッドセーフであっても再入可能ではない場合があります。たとえば、関数全体をミューテックスでラップすることはできますが(これにより、マルチスレッド環境での問題を回避できます)、その関数が割り込みサービス ルーチンで使用されると、最初の実行がミューテックスを解放するまで待機して、関数が機能しなくなる可能性があります。混乱を避けるための鍵は、再入可能とは1 つのスレッドのみが実行されることを指すということです。これは、マルチタスク オペレーティング システムが存在しなかった時代からの概念です。
再入のルール
- 再入可能コードは、同期なしでは静的またはグローバルな非定数データを保持できません。
- 再入可能関数はグローバル データで作業できます。たとえば、再入可能割り込みサービス ルーチンは、グローバルであるだけでなく揮発性でもあるハードウェア ステータスの一部 (シリアル ポート読み取りバッファーなど) を取得して作業できます。ただし、静的変数とグローバル データの一般的な使用は推奨されません。同期されているコード セクションを除いて、これらの変数ではアトミックな 読み取り、変更、書き込み命令のみを使用する必要があります (このような命令の実行中に割り込みや信号が来ることがないようにする必要があります)。C では、読み取りまたは書き込みであってもアトミックであることが保証されず、複数の読み取りまたは書き込みに分割される可能性があることに注意してください。[4] C 標準と SUSv3では
sig_atomic_tこの目的のために提供されていますが、単純な読み取りと書き込みのみが保証されており、増分や減分は保証されていません。[5]より複雑なアトミック操作は、を提供するC11で使用できますstdatomic.h。 - 再入可能コードは同期なしでは自分自身を変更できません。
- オペレーティング システムによっては、プロセスがコードを変更することを許可する場合があります。これにはさまざまな理由 (たとえば、グラフィックスを高速にブリットするなど) がありますが、通常は再入の問題を回避するために同期が必要です。
ただし、独自のメモリ内に存在する場合は、それ自体を変更する可能性があります。つまり、新しい呼び出しごとに、元のコードのコピーが作成される異なる物理マシン コードの場所を使用する場合、その特定の呼び出し (スレッド) の実行中にそれ自体を変更しても、他の呼び出しには影響しません。
- 再入可能コードは、同期なしでは再入不可能なコンピュータ プログラムまたはルーチンを呼び出すことはできません。
- 通常、ユーザー、オブジェクト、またはプロセスの優先順位が複数レベルであったり、マルチプロセスであったりすると、再入可能コードの制御が複雑になります。再入可能になるように設計されたルーチン内で行われるアクセスや副作用を追跡することが重要です。
オペレーティング システムのリソースまたは非ローカル データに対して操作するサブルーチンの再入可能性は、それぞれの操作のアトミック性に依存します。たとえば、サブルーチンが 32 ビット マシン上の 64 ビット グローバル変数を変更する場合、操作は 2 つの 32 ビット操作に分割される可能性があります。そのため、サブルーチンが実行中に中断され、割り込みハンドラーから再度呼び出されると、グローバル変数は 32 ビットのみ更新された状態になる可能性があります。プログラミング言語は、ジャンプや呼び出しなどの内部アクションによって引き起こされる中断に対してアトミック性の保証を提供する場合があります。その場合、fのような式の関数では(global:=1) + (f())、部分式の評価順序はプログラミング言語では任意である可能性がありますが、グローバル変数は 1 に設定されているか、以前の値に設定されているかのいずれかになりますが、一部のみが更新された中間状態になることはありません。 (後者はCでは式にシーケンス ポイントがないため発生する可能性があります。) オペレーティング システムは、部分的な影響を持たないシグナルによって割り込まれたシステム コールなど、シグナルに対してアトミック性の保証を提供することがあります。プロセッサ ハードウェアは、部分的な影響を持たない割り込まれたプロセッサ命令など、
割り込みに対してアトミック性の保証を提供することがあります。
例
再入可能性を説明するために、この記事では、2 つのポインターを受け取ってその値を転置するCユーティリティ関数swap()と、swap 関数も呼び出す割り込み処理ルーチンを
例として使用します。
再入可能でもスレッドセーフでもない
これは、再入可能またはスレッドセーフではない swap 関数の例です。変数tmpは関数の同時インスタンス間で同期なしでグローバルに共有されるため、1 つのインスタンスが別のインスタンスが依存するデータに干渉する可能性があります。そのため、割り込みサービス ルーチンでは使用すべきではありませんisr()。
一時ファイル;
void swap ( int * x , int * y ) { tmp = * x ; * x = * y ; /* ハードウェア割り込みにより、ここで isr() が呼び出される可能性があります。 */ * y = tmp ; }
void isr () { int x = 1 、y = 2 ; swap ( & x 、& y ); }
スレッドセーフだが再入可能ではない
前の例の関数は、 をスレッドローカルswap()にすることでスレッドセーフにすることができます。それでも再入可能ではないため、がすでに実行中のスレッドと同じコンテキストで呼び出されると、引き続き問題が発生します。
tmp isr()swap()
_Thread_local int tmp ;
void swap ( int * x , int * y ) { tmp = * x ; * x = * y ; /* ハードウェア割り込みにより、ここで isr() が呼び出される可能性があります。 */ * y = tmp ; }
void isr () { int x = 1 、y = 2 ; swap ( & x 、& y ); }
再入可能でスレッドセーフ
グローバルではなくスタックにswap()割り当て、共有されていない変数のみをパラメータとして呼び出される実装[b]は、スレッドセーフかつ再入可能です。スタックはスレッドに対してローカルであり、ローカル データのみで動作する関数は常に期待どおりの結果を生成するため、スレッドセーフです。共有データへのアクセスはないため、データ競合は発生しません。
tmp
void swap ( int * x , int * y ) { int tmp ; tmp = * x ; * x = * y ; * y = tmp ; /* ハードウェア割り込みにより、ここで isr() が呼び出される可能性があります。 */ }
void isr () { int x = 1 、y = 2 ; swap ( & x 、& y ); }
再入可能割り込みハンドラ
再入可能割り込みハンドラは、割り込みハンドラ内で割り込みを早期に再有効化する割り込みハンドラです。これにより、割り込みレイテンシが短縮される可能性があります。[6]一般に、割り込みサービスルーチンをプログラミングする際は、割り込みハンドラ内でできるだけ早く割り込みを再有効化することが推奨されます。この方法は、割り込みの損失を回避するのに役立ちます。[7]
その他の例
次のコードでは、関数はどちらもf再g入可能ではありません。
整数v = 1 ;
int f () { v += 2 ; v を返す; }
int g () { return f () + 2 ; }
上記では、 はf()非定数グローバル変数 に依存していますv。したがって、f()が実行中に を変更する ISR によって割り込まれた場合v、 に再入するとf()の誤った値が返されますv。 の値v、したがって の戻り値は確実に予測できません。これらは、 の実行中にf割り込みが変更されたかどうかによって変わります。したがって、は再入可能ではありません。 も再入可能ではありません。 は を呼び出すためですが、 も再入可能ではありません。
vffgf
わずかに変更されたこれらのバージョンは再入可能です:
int f ( int i ) {戻り値i + 2 ; }
int g ( int i ) {戻り値f ( i ) + 2 ; }
次の関数はスレッドセーフですが、必ずしも再入可能ではありません。
int関数(){ mutex_lock ();
// ...
// 関数本体// ...
mutex_unlock ();
}
上記では、function()問題なく異なるスレッドから呼び出すことができます。ただし、関数が再入可能割り込みハンドラで使用され、関数内で 2 番目の割り込みが発生した場合、2 番目のルーチンは永久にハングします。割り込みサービスによって他の割り込みが無効になる可能性があるため、システム全体に悪影響が出る可能性があります。
注記
- ^ 自己変更をシリアル化するプログラムは再入可能になる可能性があり、適切なシリアル化を行わずにグローバル データを更新する純粋なプロシージャは再入可能にならない可能性があります。
- ^ isr() が 1 つまたは 2 つのグローバル変数をパラメータとして swap() を呼び出す場合、swap() は再入可能ではありません。
参照
参考文献
- ^ ケリスク2010、657ページ。
- ^ ラルストン2000、1514-1515頁。
- ^ 「pthread_cond_init()--条件変数の初期化」。IBM Knowledge Center 。2019年10月5日閲覧。
- ^ Preshing, Jeff (2013-06-18). 「アトミック操作と非アトミック操作」。Preshing on Programming。2014-12-03のオリジナルからアーカイブ。2018-04-24に取得。
- ^ ケリスク2010、428ページ。
- ^ スロスら2004年、342頁。
- ^ Regehr, John (2006). 「リアルタイムおよび組み込みソフトウェアにおける割り込みの安全で構造化された使用」(PDF) .リアルタイムおよび組み込みシステムのハンドブック. CRC Press . 2007-08-24 にオリジナルからアーカイブ(PDF) – ユタ大学コンピューティング学部の著者の Web サイト経由。
引用文献
- Kerrisk, Michael (2010)。Linuxプログラミング インターフェイス。No Starch Press。
- Ralston, Anthony 編 (2000)。「リエントラント プログラム」。コンピュータ サイエンス百科事典(第 4 版)。Nature Publishing Group。
- Sloss, Andrew N.; Symes, Dominic; Wright, Chris ; Rayfield, John (2004) 。ARMシステム開発者ガイド。Morgan Kaufmann Publishers。ISBN 9780080490496。
さらに読む
- Chen, Raymond (2004-06-29)。「スレッドセーフと再入可能性の違い」。The Old New Thing。Microsoft Developer Network。2018-04-24にオリジナルからアーカイブ。2018-04-24に取得。
- Ganssle, Jack (2001-03-15). 「再入性入門」. Embedded.com . 2013-01-21 にオリジナルからアーカイブ。2018-04-24に取得。
- IBM (2018)。「プログラミングの一般概念」(PDF)。AIXバージョン 7.2 マニュアル。pp. 636–641。2018年 4 月 24 日閲覧。
- Jha, Dipak (2005-01-20). 「より安全なシグナル処理のために再入可能関数を使用する」IBM DeveloperWorks。 2014-07-07 にオリジナルからアーカイブ。2018-04-24に取得。
