sched: Do not account irq time to current task

今日はタイトルにもある [sched: Do not account irq time to current task](http://git.kernel.org/cgit/linux/kernel/git/stable/linux-stable.git/commit/?id=a3fe22ee824895aafdc1b788e19c081a2e6dd9da) というカーネルパッチについて。そもそもはAndroidで[先に取り込まれた](https://android.googlesource.com/kernel/common/+/aa483808516ca5cacfa0e5849691f64fec25828e%5E%5E!/)みたいですが。少なくとも[Linux-2.6.37では入っている様子](http://ftp.dei.uc.pt/pub/linux/kernel/v2.6/ChangeLog-2.6.37)。

何故このパッチに気づいたかというと、使用しているマシンのタスクスケジュール関連と思われるOSハングの原因を調査しているとき、新しいカーネルでランキュー構造体 `struct rq` に見慣れないメンバ変数が追加されてるな、と思ったことが契機。そのメンバ変数が `clock_task` です (タスクスケジューラのランキューとかの説明は割愛します。時間があったらまた書くつもりですが、最近は巷にCFSの説明も溢れてるので今更なんか書くことあるかなという感じ。)。

```
@@ -491,6 +491,7 @@ struct rq {
 	struct mm_struct *prev_mm;
 
 	u64 clock;
+	u64 clock_task;
 
 	atomic_t nr_iowait;
```

簡単に言うと、「今までのタスクスケジューラだと割り込み入ったときにもタスクのタイムスライスを消費してたよね。でも基本的に割り込みってそのタスクに関係ないんだからタイムスライス削っちゃだめだよね。」っていうパッチ。そのために、タスクのタイムスライスを計算するためのメンバ変数 `clock_task` が追加されてます。カーネルが新しくなるにつれて、このパッチに対する修正もちょろちょろ入ってるみたいですが、根本的な処理は変わってません。

ここからはCentOS 7.0のソースを参考にします(CentOS 7.0はLinux-3.10ベースなので、このパッチは入っている。少なくともCentOS 6.7には入ってないことを確認済み。)。
ランキューのクロック `rq->clock` の加算は `update_rq_clock()` でやってまして、ランキューへのタスク追加・削除、タイマ割り込み契機などで呼び出されては逐一更新されています。そして、`update_rq_clock_task()` が `rq->clock_task` の更新箇所のメインです。

```
<linux-3.10.0-123.el7/kernel/sched/core.c>

void update_rq_clock(struct rq *rq)
{
	s64 delta;

	if (rq->skip_clock_update > 0)
		return;

	delta = sched_clock_cpu(cpu_of(rq)) - rq->clock;
	rq->clock += delta;
	update_rq_clock_task(rq, delta);
}
```

```
<linux-3.10.0-123.el7/kernel/sched/core.c>

static void update_rq_clock_task(struct rq *rq, s64 delta)
{
/*
 * In theory, the compile should just see 0 here, and optimize out the call
 * to sched_rt_avg_update. But I don't trust it...
 */
#if defined(CONFIG_IRQ_TIME_ACCOUNTING) || defined(CONFIG_PARAVIRT_TIME_ACCOUNTING)
	s64 steal = 0, irq_delta = 0;
#endif
#ifdef CONFIG_IRQ_TIME_ACCOUNTING
	irq_delta = irq_time_read(cpu_of(rq)) - rq->prev_irq_time;

	/*
	 * Since irq_time is only updated on {soft,}irq_exit, we might run into
	 * this case when a previous update_rq_clock() happened inside a
	 * {soft,}irq region.
	 *
	 * When this happens, we stop ->clock_task and only update the
	 * prev_irq_time stamp to account for the part that fit, so that a next
	 * update will consume the rest. This ensures ->clock_task is
	 * monotonic.
	 *
	 * It does however cause some slight miss-attribution of {soft,}irq
	 * time, a more accurate solution would be to update the irq_time using
	 * the current rq->clock timestamp, except that would require using
	 * atomic ops.
	 */
	if (irq_delta > delta)
		irq_delta = delta;

	rq->prev_irq_time += irq_delta;
	delta -= irq_delta;
#endif
#ifdef CONFIG_PARAVIRT_TIME_ACCOUNTING
	if (static_key_false((&paravirt_steal_rq_enabled))) {
		u64 st;

		steal = paravirt_steal_clock(cpu_of(rq));
		steal -= rq->prev_steal_time_rq;

		if (unlikely(steal > delta))
			steal = delta;

		st = steal_ticks(steal);
		steal = st * TICK_NSEC;

		rq->prev_steal_time_rq += steal;

		delta -= steal;
	}
#endif

	rq->clock_task += delta;

#if defined(CONFIG_IRQ_TIME_ACCOUNTING) || defined(CONFIG_PARAVIRT_TIME_ACCOUNTING)
	if ((irq_delta + steal) && sched_feat(NONTASK_POWER))
		sched_rt_avg_update(rq, irq_delta + steal);
#endif
}
```

IRQ時間を考慮してタスク消費時間を調整するのは `CONFIG_IRQ_TIME_ACCOUNTING` で囲まれているところです。コンフィグで切り替えられるようにしてるんですね。`CONFIG_PARAVIRT_TIME_ACCOUNTING` の方はひとまずほっておくとしましょう。

CPU毎に用意しているIRQアカウンティング時間から前のIRQ時間を差し引いた値を、更に引数で与えられた `update_rq_clock()` で計算済みのdeltaから差し引くことでタスクが本当に消費したdeltaを計算しており、最後に `rq->clock_task` に足しこんでます。なるほど。

Kconfigには説明もありますが、`CONFIG_NO_HZ_FULL` とは排他なんですね。まぁ、割り込みをなるべく少なくしようとする `CONFIG_NO_HZ_FULL` があれば、IRQ時間なんて微々たるものですからね（何もなければ１秒に１回の割り込みになるはず）。割り込みが多発しそうな Android からこのパッチが投稿されたのは、なんとなく理解できます。

```
<linux-3.10.0-123.el7/init/Kconfig>

config IRQ_TIME_ACCOUNTING
        bool "Fine granularity task level IRQ time accounting"
        depends on HAVE_IRQ_TIME_ACCOUNTING && !NO_HZ_FULL
        help
          Select this option to enable fine granularity task irq time
          accounting. This is done by reading a timestamp on each
          transitions between softirq and hardirq state, so there can be a
          small performance impact.

          If in doubt, say N here.
```

今日はここまで。カーネルは背景を理解した時が一番面白いと思います。
こんなにちゃんと書いてるパッチは最近でこそ多いが、昔は殆ど無い（よくわからない英語だったりする・・・）ので非常にありがたい。