21

関数を使用setjmpしてマルチタスクを実装する方法はありますかlongjmp

4

5 に答える 5

13

あなたは確かにできます。それを達成する方法はいくつかあります。難しい部分は、最初に他のスタックを指すjmpbufsを取得することです。Longjmpは、setjmpによって作成されたjmpbuf引数に対してのみ定義されているため、アセンブリを使用するか、未定義の動作を利用せずにこれを行う方法はありません。ユーザーレベルのスレッドは本質的に移植性がないため、移植性は実際には移植性がないことを強く主張するものではありません。

ステップ1 さまざまなスレッドのコンテキストを格納する場所が必要なので、必要な数のスレッドのjmpbuf構造のキューを作成します。

ステップ2 これらのスレッドごとにスタックをmallocする必要があります。

ステップ3 割り当てたメモリ位置にスタックポインタを持ついくつかのjmpbufコンテキストを取得する必要があります。マシンのjmpbuf構造を調べて、スタックポインタが格納されている場所を見つけることができます。setjmpを呼び出し、その内容を変更して、スタックポインタが割り当てられたスタックの1つに含まれるようにします。スタックは通常、成長するため、スタックポインタはメモリの最も高い場所の近くに配置する必要があります。基本的なCプログラムを作成し、デバッガーを使用して逆アセンブルし、関数から戻ったときに実行される命令を見つけると、オフセットがどうあるべきかを知ることができます。たとえば、x86でのSystem Vの呼び出し規約では、%ebp(フレームポインタ)がポップされてからretが呼び出され、スタックからリターンアドレスがポップされることがわかります。したがって、関数に入ると、リターンアドレスとフレームポインタをプッシュします。プッシュするたびにスタックポインタが4バイト下に移動するため、スタックポインタを割り当てられた領域の上位アドレスである-8バイトから開始する必要があります(そこに到達するために関数を呼び出したかのように)。次に8バイトを埋めます。

もう1つできることは、スタックポインタを操作するための非常に小さな(1行の)インラインアセンブリを記述してから、setjmpを呼び出すことです。多くのシステムでは、jmpbufのポインターはセキュリティのためにマングルされているため、これは実際にはより移植性が高く、簡単に変更することはできません。

試したことはありませんが、非常に大きな配列を宣言してスタックポインターを移動することで、意図的にスタックをオーバーフローさせることで、asmを回避できる可能性があります。

ステップ4 システムを安全な状態に戻すには、スレッドを終了する必要があります。これを行わず、スレッドの1つが戻ると、割り当てられたスタックのすぐ上のアドレスがリターンアドレスとして取得され、ガベージの場所にジャンプして、セグメンテーション違反が発生する可能性があります。したがって、最初に安全な場所に戻る必要があります。これを取得するには、メインスレッドでsetjmpを呼び出し、グローバルにアクセス可能な場所にjmpbufを保存します。引数をとらず、保存されたグローバルjmpbufを使用してlongjmpを呼び出すだけの関数を定義します。その関数のアドレスを取得し、それを割り当てられたスタックにコピーして、リターンアドレス用のスペースを残します。フレームポインタは空のままにしておくことができます。これで、スレッドが戻ると、longjmpを呼び出す関数に移動し、毎回setjmpを呼び出したメインスレッドにジャンプして戻ります。

ステップ5 メインスレッドのsetjmpの直後に、次にジャンプするスレッドを決定するコードが必要です。適切なjmpbufをキューから取り出し、longjmpを呼び出してそこに移動します。そのキューにスレッドが残っていない場合、プログラムは完了です。

ステップ 6setjmpを呼び出して現在の状態をキューに戻し、次にキューから別のjmpbufにlongjmpを格納するコンテキストスイッチ関数を記述します。

結論 それが基本です。スレッドがコンテキストスイッチを呼び出し続ける限り、キューは再入力され続け、さまざまなスレッドが実行されます。スレッドが戻ると、実行するものが残っている場合はメインスレッドによって選択され、残っているものがない場合はプロセスが終了します。比較的少ないコードで、かなり基本的な協調マルチタスク設定を行うことができます。デッドスレッドのスタックを解放するクリーンアップ関数を実装するなど、おそらくやりたいことがもっとあります。シグナルを使用してプリエンプションを実装することもできますが、setjmpは浮動小数点レジスタを保存しないため、これははるかに困難です。状態またはフラグレジスタ。これは、プログラムが非同期で中断されるときに必要です。

于 2013-03-21T18:03:41.707 に答える
8

ルールを少し曲げているかもしれませんが、GNUpthはこれを行います。可能ですが、学術的な概念実証の演習として以外は、自分で試してはいけません。真剣に、リモートで移植可能な方法で実行したい場合は、pth実装を使用してください。 p番目のスレッド作成コード。

(基本的に、シグナルハンドラーを使用して、OSをだまして新しいスタックを作成し、longjmpをそこから出して、スタックを維持します。明らかに機能しますが、地獄のように大ざっぱです。)

本番コードでは、OSがmakecontext / swapcontextをサポートしている場合は、代わりにそれらを使用してください。CreateFiber / SwitchToFiberをサポートしている場合は、代わりにそれらを使用してください。また、コルーチンの最も説得力のある使用法の1つ、つまり、外部コードによって呼び出されたイベントハンドラーから解放することによって制御を反転することは、呼び出し元のモジュールが再入可能である必要があるため安全ではないという残念な真実に注意してください。それを証明しないでください。これが、.NETでファイバーがまだサポートされていない理由です...

于 2010-08-09T05:41:32.577 に答える
4

これは、ユーザースペースコンテキストスイッチングとして知られているものの形式です。

可能ですが、特にsetjmpとlongjmpのデフォルトの実装を使用する場合は、エラーが発生しやすくなります。これらの関数の問題の1つは、多くのオペレーティングシステムで、コンテキスト全体ではなく、64ビットレジスタのサブセットのみが保存されることです。これは、たとえばシステムライブラリを扱う場合など、多くの場合十分ではありません(ここでの私の経験は、amd64 / windowsのカスタム実装であり、考慮されるすべてのものがかなり安定して機能しました)。

とはいえ、複雑な外部コードベースやイベントハンドラーを操作しようとしておらず、何をしているのかを知っている場合、および(特に)現在のコンテキストをより多く保存する独自のバージョンをアセンブラーで作成する場合( '32ビットウィンドウまたはLinuxを使用している場合、これは必要ない場合があります。BSDの一部のバージョンを使用している場合は、ほぼ間違いなくそうだと思います)、逆アセンブル出力に注意を払ってデバッグすると、次のことを達成できる可能性があります。あなたが欲しい。

于 2011-11-05T17:08:13.870 に答える
2

私は研究のためにこのようなことをしました。 https://github.com/Kraego/STM32L476_MiniOS/blob/main/Usercode/Concurrency/scheduler.c

コンテキスト/スレッドの切り替えは、setjmp/longjmpによって行われます。難しい部分は、割り当てられたスタックを正しくすることでした(allocateStack()を参照)。これはプラットフォームによって異なります。

これは、これがどのように機能するかを示すためのものであり、本番環境では使用しません。

于 2021-03-13T23:21:43.953 に答える
1

Sean Ogdenがすでに述べたように、longjmp()はスタックを上に移動することしかできず、異なるスタック間をジャンプできないため、マルチタスクには適していません。それはダメです。

user414736で述べたように、getcontext / makecontext / swapcontext関数を使用できますが、これらの関数の問題は、それらが完全にユーザースペースにないことです。コンテキスト切り替えの一部として信号マスクを切り替えるため、実際にはsigprocmask()システムコールを呼び出します。これにより、swapcontext()はlongjmp()よりもはるかに遅くなり、遅いコルーチンは必要ない可能性があります。

私の知る限り、この問題に対するPOSIX標準の解決策はないので、利用可能なさまざまなソースから独自の解決策をまとめました。libtaskから抽出されたコンテキスト操作関数は次の場所にあり
ます。https
://github.com/dosemu2/dosemu2/tree/devel/src/base/lib/mcontext関数は次のとおりです。getmcontext()、setmcontext()、makemcontext( )およびswapmcontext()。これらは、同様の名前を持つ標準関数と同様のセマンティクスを持っていますが、setmcontext()によってジャンプされたときにgetmcontext()が(0ではなく)1を返すという点でsetjmp()セマンティクスも模倣しています。

さらに、コルーチンライブラリであるlibpclのポートを使用できます
。https
://github.com/dosemu2/dosemu2/tree/devel/src/base/lib/libpcl これにより、高速協調を実装できます。ユーザースペースのスレッド化。Linux、i386およびx86_64アーチで動作します。

于 2016-01-24T00:34:31.037 に答える