setcontext は、コンテキスト制御に使用されるCライブラリ関数群(他の関数はgetcontext、makecontext、swapcontext )の 1 つです。この関数群により、イテレータ、ファイバー、コルーチンなどの高度な制御フローパターンをC 言語で実装できます。これらはsetjmp/longjmpの高度なバージョンと見なすことができます。後者はスタックを一回だけ非ローカルにジャンプすることしかできませんが、 setcontext ではそれぞれ独自のスタックを持つ複数の協調的な制御スレッドを作成できます。setcontextsetcontext
setcontextPOSIX .1-2001 およびSingle Unix Specificationバージョン 2で規定されていますが、すべてのUnix ライクなオペレーティングシステムがこれらを提供しているわけではありません。POSIX .1-2004ではこれらの関数は廃止され、[ 1 ] POSIX .1-2008では含まれておらず、代替としてPOSIX スレッドが示されています。
関数と関連する型は、ucontext.hシステムヘッダーファイルで定義されています。これにはucontext_t、4つの関数すべてが操作する型が含まれます。
typedef struct { ucontext_t * uc_link ; sigset_t uc_sigmask ; stack_t uc_stack ; mcontext_t uc_mcontext ; ... } ucontext_t ;uc_linkは、現在のコンテキストが終了したときに再開されるコンテキストを指しますmakecontext(コンテキストが(セカンダリ コンテキスト)で作成された場合)。は、コンテキストでブロックされたシグナルuc_sigmaskのセットを格納するために使用され、 はコンテキストで使用されるスタックです。 は、すべてのレジスタとCPUフラグ、命令ポインタ、スタック ポインタを含む実行状態を格納します。は不透明な型です。uc_stackuc_mcontextmcontext_t
機能は以下のとおりです。
intsetcontext(constucontext_t*ucp)ucp。実行は、コンテキストがに格納された時点から再開されますucp。setcontextは戻りません。intgetcontext(ucontext_t*ucp)ucp。この関数は、次の 2 つの場合に を返します。最初の呼び出し後、またはスレッドが または を介して のコンテキストに切り替えたときucp。setcontextこのswapcontext関数は、ケースを区別するための戻り値getcontextを提供しません(戻り値はエラーを通知するためだけに使用されます)。そのため、プログラマは明示的なフラグ変数を使用する必要があります。このフラグ変数はレジスタ変数であってはならず、定数の伝播やその他のコンパイラの最適化を避けるためにvolatile として宣言する必要があります。voidmakecontext(ucontext_t*ucp,void(*func)(),intargc,...)makecontext別の制御スレッドを設定します。メンバは適切なサイズのスタックを指す必要があります。定数がよく使用されます。 がまたはを使用して にジャンプすると、が指す関数のエントリ ポイントから、指定された引数で実行が開始されます。 が終了すると、制御は に戻ります。ucpgetcontextucp.uc_stackSIGSTKSZucpsetcontextswapcontextfuncargcfuncucp.uc_linkintswapcontext(ucontext_t*oucp,ucontext_t*ucp)ucp、現在の実行状態を に保存しますoucp。以下の例は、イテレータの使用例を示していますsetcontext。
#include <stdio.h> #include <stdlib.h> #include <ucontext.h> #include <signal.h>/* 3 つのコンテキスト: * (1) main_context1 : ループが戻る main 内のポイント。* (2) main_context2 : コンテキストを切り替えることで、ループからの制御が流れる main 内のポイント。* (3) loop_context : コンテキストを切り替えることで、メインからの制御が流れる loop 内のポイント。 */ ucontext_t main_context1 , main_context2 , loop_context ;/* イテレータの戻り値。 */ volatile int i_from_iterator ;/* これはイテレータ関数です。swapcontext の最初の呼び出しでこの関数に入り、0 から 9 までループします。各値は i_from_iterator に保存され、swapcontext を使用してメイン ループに戻ります。メイン ループは値を出力し、swapcontext を呼び出して関数に戻ります。ループの終わりに達すると、関数は終了し、実行はmain_context1 が指すコンテキストに切り替わります。 */ void loop ( ucontext_t * loop_context , ucontext_t * other_context , int * i_from_iterator ) { int i ; for ( i = 0 ; i < 10 ; ++ i ) { /* ループ カウンタをイテレータの戻り位置に書き込みます。 */ * i_from_iterator = i ; /* ループ コンテキスト (コードのこの時点) を ''loop_context'' に保存し、 * other_context に切り替えます。 */ swapcontext ( loop_context , other_context ); } /* 関数は暗黙の * ''setcontext(&loop_context->uc_link);'' で呼び出し元のコンテキストにフォールスルーします */ } int main ( void ) { /* イテレータ関数のスタック。 */ char iterator_stack [ SIGSTKSZ ];/* イテレータが完了したことを示すフラグ。 */ volatile int iterator_finished ;getcontext ( & loop_context ); /* イテレータコンテキストを初期化します。uc_link は main_context1 を指し、 イテレータが終了したときに戻るポイントです。 */ loop_context . uc_link = & main_context1 ; loop_context . uc_stack . ss_sp = iterator_stack ; loop_context . uc_stack . ss_size = sizeof ( iterator_stack );/* loop_context を埋めて、swapcontext がループを開始するようにします。 * (void (*)(void)) 型キャストはコンパイラの警告を回避するためのものですが、 * 関数の動作には関係ありません。 */ makecontext ( & loop_context , ( void ( * )( void )) loop , 3 , & loop_context , & main_context2 , & i_from_iterator ); /* 完了フラグをクリアします。 */ iterator_finished = 0 ;/* 現在のコンテキストを main_context1 に保存します。ループが終了すると、 * 制御フローはこの点に戻ります。 */ getcontext ( & main_context1 ); if ( ! iterator_finished ) { /* iterator_finished を設定することで、前回の getcontext が uc_link を介して * 戻されたときに、上記の if 条件が偽となり、 * イテレータが再起動されないようにします。 */ iterator_finished = 1 ; while ( 1 ) { /* この点を main_context2 に保存し、イテレータに切り替えます。 * 最初の呼び出しでループが開始されます。以降の呼び出しでは 、 * ループ内の swapcontext に切り替わります。 */ swapcontext ( & main_context2 , & loop_context ); printf ( "%d \n " , i_from_iterator ); } } return 0 ; }注: この例は正しくありませんが、[ 1 ]場合によっては意図どおりに動作するかもしれません。この関数はmakecontext型の追加パラメータを必要としますintが、この例ではポインタを渡しています。そのため、この例は 64 ビット マシン (特にLP64アーキテクチャ、ここで) で失敗する可能性があります。この問題は 64 ビット値を分割して再構築することで回避できますが、パフォーマンスが低下します。sizeof(void*)>sizeof(int)
int型とポインタ型のサイズが同じアーキテクチャ(例:x86-32、両方の型が32ビット)では、argcの後にポインタをmakecontext()の引数として渡すことで対応できる場合があります。ただし、この方法は移植性が保証されておらず、標準規格では未定義であり、ポインタがint型より大きいアーキテクチャでは動作しません。それでも、バージョン2.8以降、glibcは、一部の64ビットアーキテクチャ(例:x86-64)でこれを可能にするために、いくつかの変更を加えています。
get および set コンテキストの場合、より小さなコンテキストが便利な場合があります。
#include <stdio.h> #include <ucontext.h> #include <unistd.h>int main ( int argc , const char * argv []){ ucontext_t context ; getcontext ( & context ); puts ( "Hello world" ); sleep ( 1 ); setcontext ( & context ); return 0 ; }コンテキストがプログラムカウンタを保持しているため、これは無限ループを引き起こします。