linux kernelドライバでタイマー割り込みを実現するのにhrtimerを使ったのでメモする
はじめに
hrtimerはhigh resolusion timerの略。
従来のtimer_list
より高解像度なタイマーを提供する。
もとのドライバソフトウェアでは、timer_list
を用いたブロックデバイスを使用していた。
timer_list
では割り込みできる最小単位がmili secであったため、
nano secで割り込み可能なhrtimer
を使用するコードに変更した。
変更する過程で調べたことを下記にまとめる。
16666666nano secで割り込むようにして、printkで割り込んだ時刻を記録したが
誤差が発生することがわかった。
誤差が発生する要因の1つに仮想マシン上で実行していることが挙げられる。
実機でもそのうち評価できたらいいな。
環境
- virtualbox上
- CentOS 7.9.2009
- 3.10.0-1160.15.2.el7.x86_64
使用できるか確認する
下記で調べられる。
後で追記する。
cat /proc/timer_list
.resolution: 1 nsecs
event_handler: hrtimer_interrupt
初期化
調べたことを記録するに当たり、要素だけを抜き出したコードもどきを下記に示す。
(計測に使ったコードそのままではないという意味)
ドライバの初期化関数で、wait_q
およびhrtimer
の初期化を行う。
hrtimer_init
の引数は下記の通り。
void hrtimer_init(struct hrtimer *timer, clockid_t which_clock, enum hrtimer_mode mode);
- struct hrtimer : タイマー
- clockid_t which_clock:
CLOCK_MONOTONIC
かCLOCK_REALTIME
、CLOCK_BOOTTIME
、CLOCK_TAI
を選べる - enum hrtimer_mode mode:
HRTIMER_MODE_ABS
かHRTIMER_MODE_REL
を選べる
hrtimerのメンバであるfunctionにタイマーが期限切れになったときに発火する関数を与える。
hrtimer_start ()で、タイマーの計測を開始する。
int hrtimer_start(struct hrtimer *timer, ktime_t tim, const enum hrtimer_mode mode)
ktime_t には、ktime_get()で現在時刻を取得し、そこにnanosecだけ進めた時刻をktime_add_ns()で計算して設定する。
ktime_t で設定した時刻に到達すると、functionに与えた関数が発火する。
static struct mymod_device{ struct hrtimer timer; wait_queue_head_t waitq; }mymod_dev; int __init mymod_init(void){ init_waitqueue_head(&vsync_dev.waitq); hrtimer_init(&mymod_dev.timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS); mymod_dev.timer.function = &mymod_interrupt; long nanosec = 1000; hrtimer_start(&mymod_dev.timer, ktime_add_ns(ktime_get(), nanosec), HRTIMER_MODE_ABS); }
割り込み
初期化関数内でタイマーが期限切れになったときにmymod_interrupt()を発火するように設定した。
mymod_interrupt()を下記に示す。
wake_up_interruptible_all()でwaitqで待っている関数を発火する(後述)。
hrtimer_forward()で次にtimer.functionに与えられている関数(mymod_interrupt())を発火する時刻を設定する。
u64 hrtimer_forward(struct hrtimer *timer, ktime_t now, ktime_t interval)
ktime_t now に現在時刻を、ktime_t intervalに待機する時間を指定する。
ここで、発火してから現在時刻を取得する処理時間分の遅延が誤差になっている?
return HRTIMER_RESTART; で再度タイマーをスタートしている。
1回だけ実行したい場合は、 return HRTIMER_NORESTART;とする。
以上により、wait_event_interruptible()で待機している関数を、intervalごとに起床する。
enum hrtimer_restart mymod_interrupt(struct hrtimer *timer){ wake_up_interruptible_all(&mymod_dev.waitq); long nanosec = 16666666; ktime_t currtime, interval; currtime = ktime_get(); interval = ktime_set(0,nanosec); interval = ktime_set(0,nanosec); hrtimer_forward(timer, currtime , interval); return HRTIMER_RESTART; }
割り込み待ち
mymod_interrupt()で割り込むまで、待機する関数の説明をする。
wait_event_interruptible()で待機する。
mymod_interrupt()内のwake_up_interruptible_all()で起床する。
起床すると、copy_to_user()でユーザランドのデバイスファイルへへ書き込む。
ssize_t mymod_read(struct posix_clock *pc, uint rdflags, char __user *buf, size_t count){ wait_event_interruptible(mymod_dev.waitq, true); copy_to_user((void *)buf, "hello ", len); return (len); }
計測
上記ではhelloを送るように書いているが、実際はgetnstimeofday()で時刻を取得して送っている)。 カーネルレイヤの結果と、ユーザレイヤの結果をそれぞれ示す。
printkで出力した、割り込み時刻と前回からの差を下記に示す。
差の期待値は016666usecであったが、最大約1.5msecの誤差がある。
一方、ほぼ設定したタイマーどおりに動作しているパターンも存在する。
[ 143.477075] dddd 1617174009.033413, 033413 [ 143.493679] dddd 1617174009.050083, 016670 [ 143.510381] dddd 1617174009.066785, 016702 [ 143.527836] dddd 1617174009.084235, 017450 [ 143.543590] dddd 1617174009.099994, 015759 [ 143.560254] dddd 1617174009.116658, 016664 [ 143.576927] dddd 1617174009.133331, 016673 [ 143.593592] dddd 1617174009.149996, 016665 [ 143.611618] dddd 1617174009.168155, 018159 [ 143.626930] dddd 1617174009.183335, 015180 [ 143.643595] dddd 1617174009.200000, 016665 [ 143.660191] dddd 1617174009.216595, 016595 [ 143.678135] dddd 1617174009.234729, 018134 [ 143.693585] dddd 1617174009.249989, 015260 [ 143.710252] dddd 1617174009.266657, 016668 [ 143.726976] dddd 1617174009.283381, 016724 [ 143.743596] dddd 1617174009.300001, 016620
ユーザランドで出力した値を下記に示す。
kernelから受信した時刻と、前回からの差を出力している。
差の期待値は016666usecであったが、最大約1.5msecの誤差がある。
sec 1617174009, usec 033413, 7:0:9.33 ,sa 033413 sec 1617174009, usec 050083, 7:0:9.50 ,sa 016670 sec 1617174009, usec 066785, 7:0:9.66 ,sa 016702 sec 1617174009, usec 084235, 7:0:9.84 ,sa 017450 sec 1617174009, usec 099994, 7:0:9.99 ,sa 015759 sec 1617174009, usec 116658, 7:0:9.116 ,sa 016664 sec 1617174009, usec 133331, 7:0:9.133 ,sa 016673 sec 1617174009, usec 149996, 7:0:9.149 ,sa 016665 sec 1617174009, usec 168155, 7:0:9.168 ,sa 018159 sec 1617174009, usec 183335, 7:0:9.183 ,sa 015180 sec 1617174009, usec 200000, 7:0:9.200 ,sa 016665 sec 1617174009, usec 216595, 7:0:9.216 ,sa 016595 sec 1617174009, usec 234729, 7:0:9.234 ,sa 018134 sec 1617174009, usec 249989, 7:0:9.249 ,sa 015260 sec 1617174009, usec 266657, 7:0:9.266 ,sa 016668 sec 1617174009, usec 283381, 7:0:9.283 ,sa 016724 sec 1617174009, usec 300001, 7:0:9.300 ,sa 016620
おわりに
hrtimerを導入してnano sec単位で割り込みタイマーを仕掛けられるようにした。
しかし、誤差が大きく、安定してnano secで割り込みできない。
今後は実機で試して精度を確認する。
また、hrtimer_initのclockid_t やhrtimer_mode を他の値に変えて精度がどのように変わるか調査する。