I/O スケジューラの実装における各種制限
1. io スケジューラ内部でプロセススケジューラを呼び出してはならない制限
[結論]
__make_request() 内部において spin_lock_irq() を呼び出している為
[解説]
spin_lock_irq() の処理は次のようになっている。
1 void __lockfunc _spin_lock_irq(spinlock_t *lock)
2 {
3 local_irq_disable();
4 preempt_disable();
5 spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
6 LOCK_CONTENDED(lock, _raw_spin_trylock, _raw_spin_lock);
7 }
3 行目の local_irq_disable() の実行によって、現在使用中の cpu の割り込みを禁止する。内部では cpu の割り込み禁止命令を実行する。x86 では cli アセンブラコードを実行している。
preempt_disable() [include/linux/preempt.h] では inc_preempt_count() を実行し、カレントタスクの thread_info オブジェクトの preempt_count メンバの値をインクリメントしている。
thread_info オブジェクトの preempt_count メンバは 0 の時に、プリエンプト可能である事を表している。この値が 0 より大きい状態でプロセススケジューラを呼び出すと、システムがバグる (可能性がある)。この場合、GNU/Linux は全力でエラーメッセージやら、各種ダンプを吐き出す。
io スケジューラが cpu を使っている場合にプロセススケジューラが呼び出せないのは I/O ブロックレイヤによって、ブロックデバイスが持つリクエストキューを対象として spin_lock_irq() が呼ばれている為である。GNU/Linux では、io スケジューラ処理はスレッドセーフでなければならないらしい。これは io スケジューラ側がどうこうできる問題ではない。
[解決策]
・io スケジューラ内部でプロセススイッチを必要とする処理を実行しない。
・プロセススイッチを必要とする処理を代替するスレッドを別途用意して、そいつと通信するようにする。
2. io スケジューラ内部 (*1) でメモリアロケーションを行ってはならない制限
[結論]
ページフレームの回収が行われる可能性がある為
[解説]
kmalloc() によってスラブキャッシュからメモリを取得しようとした時、スラブが不足しているとページの取得を行うために __alloc_pages() を呼び出す。
しかし __alloc_pages() 内部において、ページフレームの獲得が出来ないと、ページアロケーションはページフレームの回収を行うために try_to_free_pages() を実行しようとする。
io スケジューラがメモリアロケーション時に、ページフレーム回収処理の待ち受けを許可した場合、ページフレームの回収を行うとする。ブロックデバイスのキューのロックは既に I/O ブロックレイヤの処理によって取得されているので、デッドロックに陥る。
[解決策]
・メモリアロケーションを行わない
・アロケーション時に、ページフレーム回収の待ち受けを許可しない (空きページフレームの取得に失敗した場合には、メモリの取得を諦める) 。
(*1) elevator_init メソッドを除く
- Category(s)
- 学習経過
- The URL to Trackback this entry is:
- http://dev.ariel-networks.com/Members/ohyama/i-o-30b930b130e530fc30e9306e5b9f88c5306b304a3051308b54047a2e52369650/tbping