Document

Linux Device Driver 輪講
7. 時の流れ
ACE suzuk
table of contents
1.
2.
3.
4.
5.
6.
時間の経過
現在時刻を知る
実行を遅らせる
カーネルタイマ
タスクレット
作業待ち列
はじめに
 ドライバ作成のために必要な実装
 デバイス制御,メモリ管理,ハードウェアアクセス
 タイミング




時間経過を計測する,時刻を比較する
現在時刻を知る
特定の時間だけ操作を遅らせる
非同期関数が特定の時間が経過した後に処理する
ようにスケジュールする
この章では、タイミングをどう扱うのかについて説明
する
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取得で大抵間に合う
 短い時間経過を厳密に計る場合プロセッサ固有のレ
ジスタが役に立つ
 実時刻をjiffiesに変換するカーネル関数
 <linux/time.h>のmktime関数
 カーネル空間で絶対的なタイムスタンプ取得関数
 <linux/time.h>のdo_gettimeofday関数
3. 実行を遅らせる
 1クロックティックから十分に長い遅延
 システムクロックを使った実装
 非常に短い遅延
 ソフトウェアループ実装
3.1 長い遅延 ビジーwait
 それほどの精度なしにクロックティックの整数
倍の時間遅延する場合
 Jiffyカウンタを監視するループ
while(time_before(jiffies, j1)){
cpu_relax();
}
 この種の実装は可能であれば絶対に避けるべき
 ビジーループはシステムの性能を著しく低下させる
3.1 長い遅延 プロセッサの譲渡
 必要ないときにCPUを明示的に開放する
 <linux/sched.h>のschedule関数を呼び出す
while(time_before(jiffies, j1)){
schedule();
}
 scheduleでプロセスがプロセッサを解放すると,
実行をすぐに取り戻せる保障がない
 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
 非常に短い時間待つ場合は
 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
 void tasklet_schedule(struct tasklet_struct *t);
など
ただのタスクのリンクリスト
6. 作業待ち列(workqueue)
 タスクレットの違い
 スリープ関数が使える!
 スケジュールしたCPUと別CPUで実行できる
 デフォルトでは同一CPUで実行する
 カーネルコードは作業待ち列関数の実行を,時間
指定して遅延させることができる
 つまり,アトミックである必要がない
6.作業待ち列の動作
 <linux/workqueue.h>
 Struct workqueu_struct構造体
 各作業待ち列には1つ以上の専用プロセス(カー
ネルスレッド)がある
 専用プロセスが登録された関数を実行する
 つまり,ドライバ自身の作業待ち列を作成できる
6.1 共有待ち列
 カーネルが提供する共有待ち列
 他の誰かと共有した待ち列を使う
 タスクを待ち列に登録するだけであれば効率的
 struct work_structを扱う