Linux Device Driver 輪講 7. 時の流れ ACE suzuk
table of contents 時間の経過 現在時刻を知る 実行を遅らせる カーネルタイマ タスクレット 作業待ち列
はじめに ドライバ作成のために必要な実装 デバイス制御,メモリ管理,ハードウェアアクセス タイミング 時間経過を計測する,時刻を比較する 現在時刻を知る 特定の時間だけ操作を遅らせる 非同期関数が特定の時間が経過した後に処理するようにスケジュールする この章では、タイミングをどう扱うのかについて説明 する
1.時間の経過 カーネルはタイマ割り込みを使い時間の流れを管理する タイミングハードウェアで一定間隔で生成される 内部カーネルカウンタ プラットフォームでは毎秒100または1000の割り込みが実行される Linux2.6では250を使う 内部カーネルカウンタ タイマ割り込みにより1加算される システム起動時に0に初期化される カウンタは64ビットの変数「jiffies_64」
1.1 jiffiesカウンタを使う <linux/jiffies.h>でカウンタとユーティリティ関数が宣言されている カウンタ値比較関数 時の表現変換関数 timespec_to_jiffies(struct timespec *value) ユーザ空間では実際のクロック周波数はほぼ隠されている
1.2 プロセッサ固有のレジスタ クロックカウンタ(クロック周期のカウンタ) 高解像度の時間管理タスクを実現する信頼できる唯一の手段 カウンタレジスタに記録されている X86ではTSCが該当する <asm/msr.h>(マシン固有レジスタ)のrdsc関数で取得 <asm/timex.h>のget_cycles関数でも取得できる
2. 現在時刻を知る カーネルコードはjiffies値を調べて現在時刻取得できる Jiffies値は起動してからの経過時間 短い時間経過を厳密に計る場合プロセッサ固有のレジスタが役に立つ 実時刻をjiffiesに変換するカーネル関数 <linux/time.h>のmktime関数 カーネル空間で絶対的なタイムスタンプ取得関数 <linux/time.h>のdo_gettimeofday関数
3. 実行を遅らせる 1クロックティックから十分に長い遅延 システムクロックを使った実装 非常に短い遅延 ソフトウェアループ実装
3.1 長い遅延 ビジーwait while(time_before(jiffies, j1)){ cpu_relax(); } それほどの精度なしにクロックティックの整数倍の時間遅延する場合 Jiffyカウンタを監視するループ この種の実装は可能であれば絶対に避けるべき ビジーループはシステムの性能を著しく低下させる while(time_before(jiffies, j1)){ cpu_relax(); }
3.1 長い遅延 プロセッサの譲渡 while(time_before(jiffies, j1)){ schedule(); } 必要ないときにCPUを明示的に開放する <linux/sched.h>のschedule関数を呼び出す scheduleでプロセスがプロセッサを解放すると,実行をすぐに取り戻せる保障がない scheduleを呼び出すのはドライバのニーズに対する安全な解決策ではない while(time_before(jiffies, j1)){ schedule(); }
3.1 長い遅延 タイムアウト 遅延を実装する最良の手法はカーネルに遅延処理を依頼する <linux/wait.h>のwait_event_timeout関数 指定したタイムアウトが満了した後にリターンする <linux/sched.h>のschedule_timeout関数 イベント待ち列が必要ない
3.1 短い遅延 ハードウェアの遅延を扱う場合(数十マイクロ秒以下の精度) <linux/delay.h>のndelay, udelay, mdelay関数を使う ブート時に計算されたプロセッサ速度に基づくソフトウェアループで実装されている(busy wait!!)
3 遅延まとめ 要求よりも長く待っても良い場合は 非常に短い時間待つ場合は schedule_timeout, msleep, ssleep 3 遅延まとめ 要求よりも長く待っても良い場合は schedule_timeout, msleep, ssleep 非常に短い時間待つ場合は ndelay, udelay, mdelay
4.カーネルタイマとは スケジュールに使うカーネルのデータ構造体 ユーザの指定時間に指定した引き数を使い,ユーザ定義関数をカーネルに実行させる 実際は,ソフトウェア割り込みの結果 タイマ関数(ユーザ定義関数)は5.4.2で説明したすべての点でアトミックを保障する必要がある <linux/timer.h>, kernel/timer.c
4. カーネルタイマの用途 クロックティックを利用し,未来の特定の時刻にタイマハンドラの実行をスケジュールする 後で起こるアクションをスケジュールする必要があり,その時が来るまでカレントプロセスをロックしない手法 フロッピーディスクのモーターをオフにする ハードウェア割り込みできない時に,状態を一定の 間隔でチェックすることでデバイスをポーリングする (自分自身を再スケジューリングできる) 例:連射パッドの制御
タイマ関数のルール ユーザ空間へはアクセスできない 割り込みで起動するため,currentポインタ(カレントプロセス)は使えない 割り込みコンテキスト実行を確かめるにはin_interrup()呼び出しが使える in_atomic()もあるよ! スリープ関数を呼び出せない sleep,schedule(), wait_event(),スリープする可能性のある関数 Kmalloc()はスリープするかもしれないからダメ
4.1 タイマのAPI <linux/timer.h>のタイマAPI達 void init_timer(struct timer_list *timer); void add_timver(…); 新しいタイマーをスケジュールする void del_timer(…); int mod_timer(…); タイマの終了時間を変更する int del_timer_sync(…); リターンしたと時、タイマがどのCPUでも実行されないことを保証する int timer_pendint(…); 現在タイマがスケジュールされているか否かをチェック
4.2 カーネルタイマの実装 実装は興味深い!ぜひ見てみよう!! 機能要件,前提条件 タイマの管理は可能な限り軽い処理であること アクティブなタイマ数が増えても対応できる設計 長い遅延のタイマはごく稀で,ほとんどのタイマは長くても数秒または数分以内に満了する タイマハンドらはタイマを登録したのと同じCPUで実行される カーネル開発者たちの解決策 そうだ,CPU毎にデータ構造体を扱おう!
5. タスクレット(tasklet) タスクレットとは? タスクレットAPI スケジュールすることで,後でカーネルが選択した時間に実行できる カーネルタイマのように特定時間に実行できない タスクレットAPI void tasklet_schedule(struct tasklet_struct *t);など ただのタスクのリンクリスト
6. 作業待ち列(workqueue) タスクレットの違い スリープ関数が使える! スケジュールしたCPUと別CPUで実行できる カーネルコードは作業待ち列関数の実行を,時間指定して遅延させることができる つまり,アトミックである必要がない
6.作業待ち列の動作 <linux/workqueue.h> Struct workqueu_struct構造体 各作業待ち列には1つ以上の専用プロセス(カーネルスレッド)がある 専用プロセスが登録された関数を実行する つまり,ドライバ自身の作業待ち列を作成できる
6.1 共有待ち列 カーネルが提供する共有待ち列 他の誰かと共有した待ち列を使う タスクを待ち列に登録するだけであれば効率的 struct work_structを扱う