clock_sourceとclock_eventとか

2013/11/03 Linux::time
※この文章は、Linux-3.12.0-rc7のソースコードを参照しながら書いています。
 versionによる差異があることは認識いただき、参考にされる場合は、実際に使用する版でも確認してください。


kernelに必須な機能である時間管理、その源となるリソースを管理するもの、と思う。

clock sourceと、clock eventのに種類があり、前者はハードウェア時計相当、後者は一定時間後に割り込みをもらうもの、ぽい。

関連しそうなドライバは、以下のディレクトリ配下の模様。
DIR: drivers/clocksource/
drivers/clocksource/dw_apb_timer.c
drivers/clocksource/arm_arch_timer.c
drivers/clocksource/arm_global_timer.c
drivers/clocksource/dummy_timer.c

driver:: clocksource "jiffies", "refined-jiffies"

探してみると、別のディレクトリにもデバイスとして登録しているものがありますね。
core_initcall()マクロで、core起動時に呼び出すように設定(要確認)しているので、
必ず登録される模様。ratingが1と最低値なので、他に登録されればそれが使われる、と。

FILE: kernel/time/jiffies.c

static struct clocksource clocksource_jiffies = {
        .name           = "jiffies",
        .rating         = 1, /* lowest valid rating*/
        .read           = jiffies_read,
        .mask           = 0xffffffff, /*32bits*/
        .mult           = NSEC_PER_JIFFY << JIFFIES_SHIFT, /* details above */
        .shift          = JIFFIES_SHIFT,
};


EXPORT_SYMBOL(jiffies);

static int __init init_jiffies_clocksource(void)
{
	return clocksource_register(&clocksource_jiffies);
}

core_initcall(init_jiffies_clocksource);

struct clocksource * __init __weak clocksource_default_clock(void)
{
	return &clocksource_jiffies;
}

"refined-jiffies"に関しては、Linux 3.12-rc7の段階で、x86でしか作られません。
./arch/x86/kernel/setup.c:1215:	register_refined_jiffies(CLOCK_TICK_RATE);

clocksource

ヘッダファイルに簡単な説明があるので、これと実装されているコードから推定する…。
あとは呼び出している側のコードをみて、ユースケースから考えるしかなさげ。

struct clocksource
フリーランカウンタのための、ハードウェア抽象化に使う。
基本となるハードウェアに、ほとんど状態依存なくアクセスするための手段を提供する。
システム時刻のために使われる。

FILE: include/linux/clocksource.h
struct clocksource {
	/*
	 * Hotpath data, fits in a single cache line when the
	 * clocksource itself is cacheline aligned.
	 */
	cycle_t (*read)(struct clocksource *cs); // clocksourceを引数で渡して、サイクル値を返す。
	cycle_t cycle_last; // read()メンバ呼び出しで最近読みだしたcycle値。
	cycle_t mask;    // 非64bitカウンタの引き算のための2の補数のためのbitmask値。
	u32 mult;        // cycleからナノ秒の乗数
	u32 shift;       // cycleからナノ秒の除数(2の累乗)
	u64 max_idle_ns; // clocksourceによって許容される最大アイドル時間(ナノ秒)
	u32 maxadj;      // 最大調整値?to multiってなんだ。。。(11%以下)
#ifdef CONFIG_ARCH_CLOCKSOURCE_DATA
	struct arch_clocksource_data archdata; // architecture固有のデータ
#endif

	const char *name;      // clocksourceの名前
	struct list_head list; // システムに登録するためのリストヘッド
	int rating;            // 複数登録された時の選択基準(大きいのが選ばれる)
	int (*enable)(struct clocksource *cs);    // [option] clocksourceを有効にする。
	void (*disable)(struct clocksource *cs);  // [option] clocksourceを無効にする。
	unsigned long flags;   // 特別な属性を記述するためのフラグ
	void (*suspend)(struct clocksource *cs);  // 必要ならば、サスペンド機能
	void (*resume)(struct clocksource *cs);   // 必要ならば、レジューム機能

	/* private: */
#ifdef CONFIG_CLOCKSOURCE_WATCHDOG
	/* Watchdog related data, used by the framework */
	struct list_head wd_list;
	cycle_t cs_last;
	cycle_t wd_last;
#endif
	struct module *owner;  // module reference.. clocksource moduleで設定される(書きこむなという意)
} ____cacheline_aligned;
ratingのインフレを避けるため、クロックソースのratingを割り当てる方法に関して、
以下のリストが基準を与えます。
value meanings
1-99実際の利用には適さない。起動時やテスト目的にのみ使用可能。
100-199基本レベルのユーザビリティ。実際の使用のための機能であるが、望ましくない。
200-299良い。正しく、使えるクロックソース。
300-399望まれる。適度に高速かつ正確なクロックソース。
400-499完全に理想的なクロックソース。利用可能な場合、使用する必要があります。
数個見ただけですが、SoC依存タイマは350あたりを使っていた。

flagsの値

symbol value remark
CLOCK_SOURCE_IS_CONTINUOUS0x01?通常セットされるようなものじゃないと使えないぽい??
CLOCK_SOURCE_MUST_VERIFY0x02clocksource監視用には使えない(監視される側のみ)を宣言する。
CLOCK_SOURCE_WATCHDOG0x10※内部フラグと思われる
CLOCK_SOURCE_VALID_FOR_HRES0x20※内部フラグ?CONTINUOUSがセットされていれば、コレも各所でセットされる
CLOCK_SOURCE_UNSTABLE0x40clocksource_unstable()でセットされる。HRES,WATCHDOGフラグが落とされる。
CLOCK_SOURCE_SUSPEND_NONSTOP0x80※セットするのはx86固有コードになってる
CLOCK_SOURCE_RESELECT0x100watchdogでソース切り替えが発生条件を満たしてセット、kthreadで再選択後にクリアするぽい
クロックソースが複数ある場合に、相互監視して、ズレが大きくなったものを排除していく模様。
単数出会った場合は、自分自身で差分を見るので、故障検出はできない、か、WD機能が働かない。
ここでいうwatchdogは、クロックソースのカウンタ増加速度が正しいかどうかを見ているだけ。
冗長性をあげるためなのか、仮想化がらみか??

clock event

[b:CONFIG_GENERIC_CLOCKEVENTS_BUILD]が定義されているときのみ有効。
メンバにIRQNが入っている。clocksourceには入っていないので、使用目的が妄想可能ですね。

FILE: include/linux/clockchips.h
/* Clock event notification values */
enum clock_event_nofitiers
symbol val meaning
CLOCK_EVT_NOTIFY_ADD0
CLOCK_EVT_NOTIFY_BROADCAST_ON1
CLOCK_EVT_NOTIFY_BROADCAST_OFF2
CLOCK_EVT_NOTIFY_BROADCAST_FORCE3
CLOCK_EVT_NOTIFY_BROADCAST_ENTER4
CLOCK_EVT_NOTIFY_BROADCAST_EXIT5
CLOCK_EVT_NOTIFY_SUSPEND6
CLOCK_EVT_NOTIFY_RESUME7
CLOCK_EVT_NOTIFY_CPU_DYING8
CLOCK_EVT_NOTIFY_CPU_DEAD9
struct clock_event_device - clock event device descriptor
struct clock_event_device {
	void  (*event_handler)(struct clock_event_device *);  // event sourceの低レベルハンドラから呼ばれるために、フレームワークによってアサインされる。
	int   (*set_next_event)(unsigned long evt, struct clock_event_device *);  // clocksource deltaを使って次のイベントをセットする関数
	int   (*set_next_ktime)(ktime_t expires, struct clock_event_device *);    // ktime値を直接使って次のイベントをセットする関数
	ktime_t  next_event;   // oneshot mode時の次のイベントのためのローカルストレージ
	u64      max_delta_ns; // 最大の差分値(ナノ秒)
	u64      min_delta_ns; // 最小の差分値(ナノ秒)
	u32      mult;         // ナノ秒からサイクル数への乗数
	u32      shift;        // サイクル数からナノ秒の除数(2の累乗)
	enum clock_event_mode  mode; // マネージメントコードによって代入される、動作モード
	unsigned int           features; // 機能
	unsigned long          retries;  // 強制プログラムの再回数

	void    (*broadcast)(const struct cpumask *mask); // broadcastイベント関数(?)
	void    (*set_mode)(enum clock_event_mode mode, struct clock_event_device *); // 動作モード設定関数
	void    (*suspend)(struct clock_event_device *);
	void    (*resume)(struct clock_event_device *);
	unsigned long  min_delta_ticks; // リコンフィギュレーションのために保存されたtickの最小デルタ値
	unsigned long  max_delta_ticks; // リコンフィギュレーションのために保存されたtickの最大デルタ値

	const char  *name;  // clock event名へのポインタ
	int   rating;       // clockeventの選択基準値
	int   irq;          // IRQ number (only for non CPU local devices)
	const struct cpumask  *cpumask; // このデバイスが動作するCPUについて示す値
	struct list_head      list;     // マネージメントコードのためのリストヘッド
	struct module         *owner;   // module参照
} ____cacheline_aligned;


clock eventのユースケース(?)

どうやら、clock_sourceにしても、clock_eventにしても、登録時(*_register_device()呼び出し)にシステムで使えるかどうかのチェックが入っている模様。

FILE: kernel/time/clockevents.c
void clockevents_register_device(struct clock_event_device *dev)
{
	unsigned long flags;

	BUG_ON(dev->mode != CLOCK_EVT_MODE_UNUSED);
	if (!dev->cpumask) {
		WARN_ON(num_possible_cpus() > 1);
		dev->cpumask = cpumask_of(smp_processor_id());
	}

	raw_spin_lock_irqsave(&clockevents_lock, flags);

	list_add(&dev->list, &clockevent_devices);
	tick_check_new_device(dev);       // ★★★ここ。この先で使えると思ったCPUが奪っていく。
	clockevents_notify_released();

	raw_spin_unlock_irqrestore(&clockevents_lock, flags);
}

FILE: kernel/time/tick-common.c
void tick_check_new_device(struct clock_event_device *newdev)
{
	struct clock_event_device *curdev;
	struct tick_device *td;
	int cpu;

	cpu = smp_processor_id();
	if (!cpumask_test_cpu(cpu, newdev->cpumask))
		goto out_bc;

	td = &per_cpu(tick_cpu_device, cpu);
	curdev = td->evtdev;

	/* cpu local device ? */
	if (!tick_check_percpu(curdev, newdev, cpu))
		goto out_bc;

	/* Preference decision */
	if (!tick_check_preferred(curdev, newdev))
		goto out_bc;

	if (!try_module_get(newdev->owner))
		return;

	/*
	 * Replace the eventually existing device by the new
	 * device. If the current device is the broadcast device, do
	 * not give it back to the clockevents layer !
	 */
	if (tick_is_broadcast_device(curdev)) {
		clockevents_shutdown(curdev);
		curdev = NULL;
	}
	clockevents_exchange_device(curdev, newdev);
	tick_setup_device(td, newdev, cpu, cpumask_of(cpu));
	if (newdev->features & CLOCK_EVT_FEAT_ONESHOT)
		tick_oneshot_notify();
	return;

out_bc:
	/*
	 * Can the new device be used as a broadcast device ?
	 */
	tick_install_broadcast_device(newdev);
}

clock_eventの操作に関しては、ファイルスコープで閉じられている模様。
アクセサは関数で定義され、第一引数にオブジェクトのポインタが渡される(C++実装みたいなもん?)。
FILE: kernel/time/clockevents.c
/**
 * clockevents_set_mode - set the operating mode of a clock event device
 * @dev:	device to modify
 * @mode:	new mode
 *
 * Must be called with interrupts disabled !
 */
void clockevents_set_mode(struct clock_event_device *dev,
				 enum clock_event_mode mode)
{
	if (dev->mode != mode) {
		dev->set_mode(mode, dev);
		dev->mode = mode;

		/*
		 * A nsec2cyc multiplicator of 0 is invalid and we'd crash
		 * on it, so fix it up and emit a warning:
		 */
		if (mode == CLOCK_EVT_MODE_ONESHOT) {
			if (unlikely(!dev->mult)) {
				dev->mult = 1;
				WARN_ON(1);
			}
		}
	}
}
このモード、以下のヘッダで定義されている。コメントと使用している部位で期待している動作を抽出してみる。
(明文化されていないので、ざっくりとした判断。間違っていたら指摘していただきたい。)
  • >set_mode()呼び出しで、ナニを期待されているのか、という視点で書いています。
状態としては、単語のとおりでしょう。

FILE: include/linux/clockchips.h
Clock event mode commands
enum clock_event_mode
symbol value meaning
CLOCK_EVT_MODE_UNUSED0device登録時にこの値である必要がある。 clockevents_notify()でCLOCK_EVT_NOTIFY_CPU_DEADを受けた時もこれになる。
CLOCK_EVT_MODE_SHUTDOWN1動作を停止させる。スケジューリング予定も全てキャンセル
CLOCK_EVT_MODE_PERIODIC2周期タイマの初期化(周期は"HZ"で定義されているもの)と開始
CLOCK_EVT_MODE_ONESHOT3周期タイマ動作を止めて、タイマの設定を待つ。
CLOCK_EVT_MODE_RESUME4suspendからの復帰で呼ばれる。suspend前は、shutdownを呼ばれるけれど、改めてモード設定からやってくれる@

hrtimerとの関係

このへんで呼ばれてます。
FILE: kernel/hrtimer.c
void hrtimer_interrupt(struct clock_event_device *dev)
...
	/* Reprogramming necessary ? */
	if (expires_next.tv64 == KTIME_MAX ||
	    !tick_program_event(expires_next, 0)) {
		cpu_base->hang_detected = 0;
		return;
	}
...
	tick_program_event(expires_next, 1);
void hrtimer_peek_ahead_timers(void)
 static void __hrtimer_peek_ahead_timers(void)
 {
  td = &__get_cpu_var(tick_cpu_device);
  if (td && td->evtdev)
   hrtimer_interrupt(td->evtdev);
ざっくりこんな感じで呼ばれるので、デバイス自体はregisterしたときに割りつけられてそう。
足りなければ動かない、か?(エミュレーションデバイスが居るかも)


変数はヘッダで実体化されてる?あんまり美しくないような気がするけど、マクロも追いかけたほうがイイかな(´・ω・`)

FILE: include/linux/hrtimer.h
DECLARE_PER_CPU(struct tick_device, tick_cpu_device);

参考にしてみたドライバ:

FILE: drivers/clocksource/arm_global_timer.c

ARM-Cortex-Aシリーズで使われるSCU(Snoop Control Unit)に搭載されているglobal timerを使うもの。
中を見てみると、clocksourceとclockeventと、2つのドライバを登録している。
ハードウェアは64bit counter一発なので、経済的(?)。

コンパレータを搭載していて、カウンタの値と比較して、その値以上でマッチ条件を満たす。
r2p0より前のハードウェアマクロ(Hardware IP macro)であれば、マッチ条件が等号のみであり、
本ドライバの対象外となる。(設定した瞬間に時間が過ぎてしまうと、一周待つことになるからだろうネ(想像))


昔のマイコンで使われているタイマってのは、オートリロードがあれば御の字なアップダウンカウンタ持っているようなものだと思う。
コンペアマッチで停止するかリロードしてしまうやつね。

それはclockeventには使えても、このglobal timernのように、同時にclocksourceとしては使えない。
止まること無く実時間の進みに比例して?、単調な増減するカウンタが必要。ビット数は32〜64bitあれば良い。
bit幅が大きい方が、一発で時間設定ができるので、電力的に有利。

安物のタイマであれば、source/eventそれぞれにタイマを用意すれば対応できそうかな。
CPU事にevent timerが必要となってくるわけですが。
そこで比較するためのレジスタがCPU事に割付いている*1のが、global timerなのですね。。。
カウンタ自体はSoC内唯一の64bit counterで、どのCPUからも共通の時間を見ることができる、という設計かな。NTPや外部の時刻情報に対してリアルタイムクロックを補正していく機構は存在しているけれど、これをCPU間でどうシェアしているのか、かなぁ。補正自体は特定のCPUがやることになってるみたい。

※ソースコード引用していないのは、ほぼ想像とチラ見した程度の記憶なので、信憑性は無いという事にしてください。

*1 : ARM TRMではbankedと表記されています。

OK キャンセル 確認 その他