永続的なOS:何もブロックされません

これは質問記事です。 ここで説明することについて完璧な答えはありません。 何らかの種類がありますが、どれほど成功するかは明らかではありません。



この記事は、Phantom OSの概念上の問題の1つ、または永続的で「揮発性の」コンポーネントがある他のシステムに関するものです。



問題の本質を理解するには、以前の記事の1つ、つまり永続RAMについて読む必要があります



問題の簡単な説明:Phantom OSのアプリケーションは永続的(再起動時に再起動しない)が、カーネルは再起動しない(再起動時に再起動し、起動間で変更できる)ため、そのようなシステムではシステムコールをブロックできません。 通常の方法で。



実際、アプリケーションプログラムがカーネルを呼び出し、この状態でスナップショットを作成する場合、そのようなスナップショットを復元する方法は完全に不明です。 カーネルはスナップショットで撮影されず、アプリケーションプログラムのメモリのみが記録および復元されます。 彼女がコアのどこにいたかは明らかではありません。 実際のエントリポイントがコアの正しい場所を示しているかどうかは不明です。 カーネルがアプリケーションレベルのどのオブジェクトに触れて作成したかは明らかではありません。



この状態でスナップショットを実行する方法は個別に理解できません-カーネルは、オブジェクトをディスクに書き込むときにオブジェクトに触れますか?



最初に、カーネルとオブジェクト環境の間でデータにアクセスするためのインターフェースについて説明します。



オブジェクト環境には、組み込みクラスの形式でカーネルへのインターフェースがあります-Javaのネイティブに類似しています。 これらのクラスは、メソッドに対応するC関数としてカーネルに実装されます。 そのような機能はブロックできません-できるだけ早く戻る必要があり、実行中は-スナップショットは不可能です。 これは、window.paintLine()やstring.concat()などの単純なメソッドには十分ですが、それ以上のものはありません。



一般的な例( ソース ):



static int si_string_8_substring(struct pvm_object me, struct data_area_4_thread *tc ) { DEBUG_INFO; ASSERT_STRING(me); struct data_area_4_string *meda = pvm_object_da( me, string ); int n_param = POP_ISTACK; CHECK_PARAM_COUNT(n_param, 2); int parmlen = POP_INT(); int index = POP_INT(); if( index < 0 || index >= meda->length ) SYSCALL_THROW_STRING( "string.substring index is out of bounds" ); int len = meda->length - index; if( parmlen < len ) len = parmlen; if( len < 0 ) SYSCALL_THROW_STRING( "string.substring length is negative" ); SYSCALL_RETURN(pvm_create_string_object_binary( (char *)meda->data + index, len )); }
      
      







通常のオブジェクトのコンテンツがリンクのみである場合、内部クラスオブジェクトには、通常の命令では仮想マシンにアクセスできないが、この例ではC-struct data_area_4_stringで記述されたメソッド内ではアクセス可能な任意のデータ構造が含まれます。



明らかに、そのようなメソッドは、カーネルデータ構造にアクセスできれば、カーネルデータ構造で機能します。 しかし、その逆は真実ではありません。カーネル内に自分自身へのリンクを残すことはできません。 むしろ、彼らはできるが、いくつかの「しかし」で。



それらの2つがあります。



まず、ガベージコレクターは、オブジェクトの世界からのリンクがなくなっても、一部のオブジェクトがカーネルからアクセス可能であり、それを「収集」しないことを知る必要があります。



第二に、スナップショットを形成するキー操作が進行中は、コアがそのようなオブジェクトのデータに触れないことが必要です。 これは些細なことであり、スナップショット形成スレッドを除くすべてのスレッドをグローバルに停止することによってのみ実現できるようです。 神に感謝しますが、これは数ミリ秒だけ必要です。



ガベージコレクタに関しては、これはこの方法で実装されます。 オブジェクト環境のルートオブジェクトには、他のオブジェクトの中でも、いわゆる再起動リスト(オブジェクトの単純なリスト)が含まれています。 任意の内部クラスオブジェクトを追加できます。



 void pvm_add_object_to_restart_list( pvm_object_t o ); void pvm_remove_object_from_restart_list( pvm_object_t o );
      
      







そのようなリスト(およびカーネルが動作するオブジェクトはすべてリストに含まれている必要があります)に含まれていることは、2つのタスクに役立ちます。 まず、「カーネルに代わって」オブジェクトへの参照があることを保証します-このリンクは、他のすべてのオブジェクトがそれを忘れても、GCがオブジェクトを殺すのを防ぎます。



第二に、次の問題が解決されています。 オブジェクトを「デバイス」として作成し、それを操作すると、スナップショットになり、その後リセットによってシステムが再起動されたとします。 カーネルを再起動すると、どういうわけか問題を見つけて、そのようなオブジェクトとカーネルとの接続を復元するか、すべてを伝える必要があります-それを復活させることはできません。 (たとえば、対応するデバイスがコンピューターから削除された場合。)



これを行うには、再起動後のカーネルが、オブジェクト環境が再起動されるまで、再起動リストを脇に置き、新しい空のリストを作成し、古い再起動リスト内のすべてのオブジェクトをバイパスします。 それぞれについて、再起動関数が呼び出されます。再起動関数は、オブジェクトとカーネルの接続を復元するか、オブジェクトが死んだことを通知する必要があります。 関数の最後に、オブジェクトは古い再起動リストからスローされます。 再起動機能がオブジェクトをカーネルに再接続すると、新しい再起動リストに追加されます。 そうでない場合、「カーネルに代わって」オブジェクトへの参照が消えます。 それが最後のものである場合、誰もそれを必要としないため、オブジェクトは削除されます。



root.hを参照)



良いが、十分ではない。 それにもかかわらず、おそらく、何らかの方法でオブジェクト環境からread()を作成し、結果を待ちたいのです。 命令内の仮想マシンのスレッドからの直接呼び出しをブロックせずに。



実装には3つのオプションを検討しました。



  1. 中間停止:ブロッキングシステムコールは、呼び出しの開始と読み取りのペアで構成されます。 それらの間では、命令の境界で、仮想マシンがブロックされます。 スナップショットと再起動が発生すると、マシンは2番目の命令から再起動し、明示的な拒否を受け取ります。
  2. コールバック:長い操作の最後に、オブジェクト環境はカーネルからコールバックを受け取ります。
  3. 操作の疑似終了:ブロッキングコールは、ブロッキングコールとまったく同じように機能します。カーネルを離れ、任意の長い時間待機します。 しかし、その前に、呼び出しは完了したふりをします-nullを返すようにスタックにnull参照をプッシュします;実装に書き込まれ、完了したら、このnullを削除して実際の結果に置き換えます




これで最後のメソッドが実装されました。 残りの2つは、使用するのが非常に不便であることがわかりました。



それも不完全です-もちろん、カーネルを再起動するときにリクエストを再起動し、障害を発行しない方が良いでしょう。 原則として、これも実行可能です。



これがすべて重要である理由を詳しく説明します。



仮想マシン(インタープリター)はループで実行され、命令の後に命令を実行します。 カーネルが再起動すると、仮想マシンが再起動します-カーネルは.internal.threadタイプのすべてのオブジェクトを実行し、それらを起動します。 カーネルの起動時の状態。



この状態は何ですか? これは、スナップショットの作成時の状態です。 明らかに、この瞬間は、慣例により、この時点からインタープリター関数のエントリーポイントまでのlongjmpがシステムに致命的な傷を与えないようなものでなければなりません。



再起動後に制御が飛ぶ場所



したがって、インタープリターの文字列(またはJITコード、それは重要ではありません)をブロックする場合、状態が整数である必要があります-常に命令の境界にあり、オブジェクト環境にとって重要なのは永続メモリにあります。



(完全なコード: ブロッキング呼び出しを実装する関数



これのために何が行われていますか。



まず、仮想マシンの命令が実行されたふりをします。 つまり、スタックからパラメーターを読み取り、スタックに「誤った」戻りコードnullを配置します。 スナップショットに落ちてから殺された場合、これは仮想マシンの保存された状態での命令の操作の結果になります。



  int n_param = POP_ISTACK; CHECK_PARAM_COUNT(n_param, 2); //  exception,   2  int nmethod = POP_INT(); pvm_object_t arg = POP_ARG; // push zero to obj stack pvm_ostack_push( tc->_ostack, pvm_create_null_object() );
      
      







そうすれば、私たちはやりたいことをほとんど自由に行うことができます。 ほぼ。



実際には、スナップショットを生成するために、仮想メモリサブシステムのコードは仮想マシンのすべてのスレッドを停止し、停止したことを確認します。 彼が今それをやるか、または後でやるなら-停止したように見えることを彼に伝えます-仮想マシンのコードの実行を本当に停止しました。 はい-このすべての前に、インタープリターに、キャッシュされたすべての変数がそれらが存在するオブジェクト(save_fast_acc)に戻されるように指示します。 同時に、一般に、仮想マシンを停止する(シャットダウン)要求があるかどうかを確認します。要求がある場合は、それを実行します。



  pvm_exec_save_fast_acc(tc); // Before snap if(phantom_virtual_machine_stop_request) hal_exit_kernel_thread(); hal_mutex_lock( &interlock_mutex ); phantom_virtual_machine_threads_stopped++; phantom_virtual_machine_threads_blocked++; hal_cond_broadcast( &phantom_snap_wait_4_vm_enter ); hal_mutex_unlock( &interlock_mutex );
      
      







リクエスト自体を実行しましょう。 最後に、変数を解放します(引数へのリンク)。



  // now do syscall - can block pvm_object_t ret = syscall_worker( this, tc, nmethod, arg ); ref_dec_o( arg );
      
      







スナップショットサブシステムに、ビジネスを終了し、インタープリターに戻りたいことを知らせましょう。 彼女が反対する場合(スナップショットがあります)、彼女が私たちを起こすまで眠ります。



  hal_mutex_lock( &interlock_mutex ); if(phantom_virtual_machine_snap_request) hal_cond_wait( &phantom_vm_wait_4_snap, &interlock_mutex ); phantom_virtual_machine_threads_stopped--; phantom_virtual_machine_threads_blocked--; hal_cond_broadcast( &phantom_snap_wait_4_vm_leave ); hal_mutex_unlock( &interlock_mutex );
      
      







すべてが完了したら、仮想マシンスタックから偽の戻り値を削除し、実際の値を書き込みます。



  // pop zero from obj stack pvm_ostack_pop( tc->_ostack ); // push ret val to obj stack pvm_ostack_push( tc->_ostack, ret );
      
      







一般に、この実装は機能します。 しかし、微妙な間違いがあります。 スナップショット生成システムは、スナップショットの形成前にすべてのスレッドがスリープ状態になったことだけでなく、すべてのスレッドが起動したこともチェックします。 一部のスレッドが永久にブロックされると、スナップショットが永久に停止することは簡単にわかります(「起動」しないため)。



この問題を真っ先に解決しようとしても(ブロックされたスレッドの数をカウントし、停止/実行中のカウントを考慮するため)、成功には至りませんでした。オブジェクトの状態の整合性に違反します。



解決策にはまだ見ぬ落とし穴があるかもしれません。



これで、今のところ、私は許可されたスピーチを止めて、お茶を飲みに行きます。 :)



All Articles