事前感受性:プロセッサを持ち去る方法

この記事は、マルチタスクOSでコンテキストを切り替えるための基本的なメカニズムを説明した以前の記事がなければ意味がありません。



ここでは、協調マルチタスクが敵対的な先制攻撃にどのように変わるかを説明します。



この変換の本質は簡単です。 マシンにはタイマーがあり、タイマーは割り込みを生成し、割り込みはスレッドコードを中断し、プロセッサをマルチタスクメカニズムの手に渡します。 前の記事で説明したように、彼はすでにプロセッサーを新しいスレッドに非常に協力的に切り替えています



しかし、いつものように、微妙な違いがあります。 intelのコードを参照してください。



プロセッサの「ウィーニング」自体は、通常はタイマーによる通常のハードウェア割り込みのフレームワーク内で行われ、実際には同じ割り込みですが、プロセッサからの特別な命令によって引き起こされる「ソフトウェア」割り込みの一部として行われます。 コンテキストを切り替えるこの方法は、(たとえば、同期プリミティブの一部として)スレッドを明示的に停止し、タイマー割り込みが到着するまで待機したくない場合に必要です。



まず、プロセッサを劣悪なスレッドから取り出す前に、割り込み自体を処理する必要があります。 プロセッサ/割り込みコントローラは、割り込みが開始されたことを「認識」しており、別のスレッドに切り替える前にサービスを完了したと言うことで「再測定」する必要があります。 そうしないと、割り込みコントローラーの状態が非常に奇妙になり、システムがインテリジェントに機能しなくなります。 また、他のスレッドが実行の数秒前に動作する場合、割り込みハンドラ自体は満足しません。



したがって、最初に実際の割り込みを処理します。 次に、ネストされていないことを確認します。別の割り込みのサービスを中断せず、終了する必要があります。



そして今、まだ割り込みサービス関数の内部にいるが、割り込み自体が処理され、割り込みコントローラーにこれについて適切に通知されたとき(そしてもちろん、割り込み自体がグローバルに無効になっているとき)、ソフトirq要求があるかどうかを確認できます-彼に仕えます。



ここで、変数irq_nestの上位ビットには、ソフトウェア割り込みの処理を禁止するフラグと、ソフトウェア割り込み要求なしのフラグがあることを理解する必要があります。 つまり、ゼロに等しい場合、同時に、ハードウェア割り込み要求のネストはゼロに等しく、ソフトウェア割り込みの禁止はなく、ソフトウェア割り込み要求があります。



if(irq_nest) return; // Now for soft IRQs irq_nest = SOFT_IRQ_DISABLED|SOFT_IRQ_NOT_PENDING; hal_softirq_dispatcher(ts); ENABLE_SOFT_IRQ();
      
      







当然、ソフトウェア割り込みを処理する間、ソフトウェア割り込み自体は禁止されています。 サービスの終了時に、それらを再度解決します。 しかし-前の記事を参照してください-現在のスレッドがプロセッサから削除された場合、ソフトウェア割り込みを再度有効にします-さもないと、切り替えたスレッドは元に戻りません。 実際、このためには、ソフトウェア割り込みを実行する必要があります。 私はあなたを混乱させないことを望みます。



スレッドの初期化中に、カーネルはソフトウェア割り込みハンドラーを登録します



  hal_set_softirq_handler( SOFT_IRQ_THREADS, (void *)phantom_scheduler_soft_interrupt, 0 );
      
      







このハンドラーは、チェックを除き、phantom_thread_switch()を呼び出すことになります。つまり、単に次のスレッドに切り替えます。



2つのポイントが残っています。 1つ目は、プロセッサを明示的に「放棄」する方法です。 たとえば、すでにロックされているミューテックスを取得しようとすると、スレッドを停止する必要があります。



これを行うために、ソフトウェア割り込み要求を設定し、ハードウェア割り込み(ただし、使用頻度の低い方が望ましい)を要求します。



 void phantom_scheduler_request_soft_irq() { hal_request_softirq(SOFT_IRQ_THREADS); __asm __volatile("int $15"); }
      
      







前述のように、これにより、ソフトウェア割り込みのコンテキストからphantom_thread_switch関数が呼び出されます。



2番目:そして、誰が割り当てられたプロセッサタイムスロットの最後に現在実行中のスレッドを完了するためにソフトウェア割り込みを要求しますか?



これには次のようなリクエストがあります。



 void phantom_scheduler_schedule_soft_irq() { hal_request_softirq(SOFT_IRQ_THREADS); }
      
      







いつ実行されますか。 タイマー割り込みの内部では、特別な関数が呼び出されます:



 // Called from timer interrupt 100 times per sec. void phantom_scheduler_time_interrupt(void) { if(GET_CURRENT_THREAD()->priority & THREAD_PRIO_MOD_REALTIME) return; // Realtime thread will run until it blocks or reschedule requested if( (GET_CURRENT_THREAD()->ticks_left--) <= 0 ) phantom_scheduler_request_reschedule(); }
      
      







ご覧のとおり、スレッド変数ticks_leftをデクリメントし、ゼロにカウントされると、スレッドの切り替えを要求します。



シェダーは、実行するスレッドを選択するときに変数ticks_left自体を設定します。この変数には、スレッドが動作する間隔(10ミリ秒)を規定します(以前に停止したくない場合)。



スケジューラの動作時間は、固定(プロセッサにスレッドを設定する頻度で優先順位を設定)するか、優先順位を考慮する(スレッドの間隔を長くする)ことができます。



これには、phantom_scheduler_request_reschedule()を、誰がプロセッサに乗るべきかを判断するときだと考える人が呼び出すことができることを追加する必要があります。



例として、現在のスレッドが、優先度の高い(特にリアルタイム)スレッドがロックされている同期プリミティブをロック解除した場合に適切です。



それ自体では、この呼び出しはフラグを設定するだけです。実際の切り替えは、上で説明したように、割り込みサービスの終了時にのみ発生します。



完全を期すために、スレッドの説明の構造(phantom_thread構造)を詳細に検討してください。



cpuフィールドには、スレッドの停止時にプロセッサの状態が保存されるこのアーキテクチャ固有のフィールドが含まれています。 cpu_id-スレッドが最後に実行されたか、現在動作しているプロセッサ番号。 tidは単なるスレッドIDです。 所有者は、ファントムオブジェクト環境によって使用され、アプリケーションレベルでスレッドを記述するオブジェクトをここにバインドします。 スレッドがUnix互換サブシステムを提供する場合、pidはスレッドが属するUnixプロセス番号を保存します。 この名前はデバッグのみを目的としています。



  /** NB! Exactly first! Accessed from asm. */ cpu_state_save_t cpu; //! on which CPU this thread is dispatched now int cpu_id; int tid; //! phantom thread ref, etc void * owner; //! if this thread runs Unix simulation process - here is it pid_t pid; const char * name;
      
      







ctty-スレッドのstdinバッファー。グラフィックスサブシステムとの通信に使用されます。 stack / kstack-スタックセグメントの仮想アドレスと物理アドレス、それぞれユーザーモードとカーネルモード用。 start_funcとstart_func_argは、スレッドの関数( "main")へのエントリポイントであり、この関数の引数です。



  wtty_t * ctty; void * stack; physaddr_t stack_pa; size_t stack_size; void * kstack; physaddr_t kstack_pa; size_t kstack_size; void * kstack_top; // What to load to ESP void * start_func_arg; void (*start_func)(void *);
      
      







sleep_flags-何らかの理由で眠りに落ちる兆候。 ゼロでない場合、スレッドは開始できません(ミューテックス、タイマー、誕生、死亡などを待機しています)。 thread_flags-スレッドのさまざまな兆候:スレッドはPhantom仮想マシンにサービスを提供し、スレッドは同期プリミティブをタイムアウトにしたなど。



waitcond / mutex / sem-スレッドはこのプリミティブでスリープし、リリースを待機します。 ownmutex-このスレッドはこのミューテックスをロックします。それが死んだ場合は、解放する必要があります。 (セマフォの場合、残念ながらすべては明らかではありません。)



sleep_event-同期プリミティブがタイムアウトでロックされている場合に使用-カーネルタイマーサブシステムは、タイマー要求のステータスをここに保存します。



チェーン-複数のスレッドが1つの同期プリミティブを待機している場合、スレッドをキューに入れるときに使用されます。



kill_chain-scaffoldのキュー。 特別なシステムスレッドが、他のスレッドの死後の初期化解除(メモリの解放、ミューテックスのロック解除など)に従事しており、これが番です。



  u_int32_t thread_flags; // THREAD_FLAG_xxx /** if this field is zero, thread is ok to run. */ u_int32_t sleep_flags; //THREAD_SLEEP_xxx hal_cond_t * waitcond; hal_mutex_t * waitmutex; hal_mutex_t * ownmutex; hal_sem_t * waitsem; queue_chain_t chain; // used by mutex/cond code to chain waiting threads queue_chain_t kill_chain; // used kill code to chain threads to kill //* Used to wake with timer, see hal_sleep_msec timedcall_t sleep_event;
      
      







snap_lock-スレッドはスナップショットを実行できない状態です。



preemption_disabled-スレッドをプロセッサから削除できません。 実際、特にSMP環境では、このことにはほとんど意味がありません。



death_handler-スレッドが停止すると呼び出されます。 atexit。



trap_handlerは、ユーザーモードで呼び出されるシグナルに類似しています。スレッドがプロセッサの実行につながった場合、関数が呼び出されます。



  int snap_lock; // nonzero = can't begin a snapshot int preemption_disabled; //! void (*handler)( phantom_thread_t * ) void * death_handler; // func to call if thread is killed //! Func to call on trap (a la unix signals), returns nonzero if can't handle int (*trap_handler)( int sig_no, struct trap_state *ts );
      
      







残りはシェダーの機械です。 ここではすべてが簡単です:



priorityには、スレッドの優先度が含まれます(クラスとともに-リアルタイム、通常、アイドル)



ticks_left-スレッドがプロセッサ上で動作する「ティック」数(10ミリ秒間隔)



runq_chain-スレッドの実行準備ができているが実行されていない場合、スレッドは実行キューにあります。



sw_unlock-プロセッサからスレッドを削除した後にロック解除されるスピンロックへのポインタが含まれ、同期プリミティブの実装に使用されます。



  u_int32_t priority; /** * How many (100HZ) ticks this thread can be on CPU before resched. * NB! Signed, so that underrun is not a problem. **/ int32_t ticks_left; /** Used by runq only. Is not 0 if on runq. */ queue_chain_t runq_chain; /** Will be unlocked just after this thread is switched off CPU */ hal_spinlock_t *sw_unlock;
      
      







世界の写真に追加すべき最後の弓:システムには常にスレッド(プロセッサの数に応じて複数のスレッド)があり、シェダーがスレッドの価値のある作業を見つけられなかった場合にプロセッサに置かれます。



このスレッドは何もしません-プロセッサ命令を実行し、割り込みを受信するまでプロセッサを停止し、自身の実行時間をカウントします。 1秒あたりの実行時間により、プロセッサの使用率を取得でき、プロセッサを停止すると電気と熱を節約できます。



ふう おそらく今日-それだけです。 ここに続きます



All Articles