memory.txt

Kernel Memory Layout on ARM Linux

Russell King <rmk@arm.linux.org.uk>
November 17, 2005 (2.6.15)
This document describes the virtual memory layout which the Linux kernel uses for ARM processors.
It indicates which regions are free for platforms to use, and which are used by generic code.
このドキュメントでは、LinuxカーネルがARMプロセッサ用に使用する仮想メモリのレイアウトを記述します。
これは、プラットフォームが使用するためのフリーな領域がどこにあるか、そして、汎用コードによって使用されるのはどこか、を示します。
The ARM CPU is capable of addressing a maximum of 4GB virtual memory space,
and this must be shared between user space processes, the kernel, and hardware devices.
ARM CPUは、最大4GBの仮想メモリ空間アドレッシングを許容します。
この空間は、ユーザ空間プロセス、カーネル、ハードウェアデバイスの間で共有する必要があります。
As the ARM architecture matures, it becomes necessary to reserve certain regions of VM space for use for new facilities;
therefore this document may reserve more VM space over time.
ARMアーキテクチャは静寂するにつれて、新しい設備(機能)を使うために、仮想空間の特定の領域を予約する必要が出てきます。
したがって、このドキュメントは、時間の経過と共に、より多くの仮想空間を仮想空間を予約するでしょう。
Start End Use
ffff8000ffffffffcopy_user_page / clear_user_page で使う。
SA11xx と Xscale では、minicache mappingの設定に使う。
ffff4000ffffffffARMv6アーキ以降の cache aliasing
ffff1000ffff7fffReserved. Platformsはこの領域を使ってはいけない
ffff0000ffff0fffCPU vector page. CPUがヴェクタリロケーションをサポートしていれば、ここにマップされる(Control RegisterのV bitで設定)
fffe0000fffeffffXScale cache flush area. This is used in proc-xscale.S to flush the whole data cache. (XScale does not have TCM.)
fffe8000fffeffffDTCM mapping area. for platforms with DTCM mounted inside the CPU.
fffe0000fffe7fffITCM mapping area for platforms with ITCM mounted inside the CPU.
fff00000fffdffffFixmap mapping region. fix_to_virt()で与えられるアドレスは、ここに配置される。
ffc00000ffefffffDMA memory mapping region. the dma_alloc_xxx()で返ってくるメモリが、ここに動的にマップされる。
ff000000ffbfffffReserved for future expansion of DMA mapping region.
fee00000feffffffMapping of PCI I/O space. これは、vmalloc空間の静的マッピングです。
VMALLOC_STARTVMALLOC_END-1vmalloc() / ioremap() 空間。 vmalloc(),ioremap()によって返されるメモリは、この領域に動的に配置される。 マシン固有の静的マッピングも、iotable_init()によって、ここに配置される。
VMALLOC_STARTは、high_memory変数の値に基づいて得られる。VMALLOC_ENDは0xff000000である(ヘッダで定義)。
PAGE_OFFSEThigh_memory-1Kernel direct-mapped RAM region. これは、プラットフォームのRAM、通常は、1:1の関係にあるすべてのプラットフォームのRAMを配置します。
CONFIG_PAGE_OFFSETで設定する。一般的な32bit kernelでは 0xC0000000(kernel:user=1GB:3GB設定)となっている。
PKMAP_BASEPAGE_OFFSET-1永続的なkernelマッピング。 カーネル空間にHIGHMEMページを配置する方法の一つ。
MODULES_VADDRMODULES_END-1Kernel module space.
insmodにより挿入されるKernel modulesは、ここに動的に配置されます。
00001000TASK_SIZE-1User space mappings.
スレッド毎のマッピングは、mmap()システムコールにより、ここに配置される。
0000000000000fffCPU vector page / null pointer trap.
ヴェクタremapをサポートしていないCPUは、ヴェクタページをここに配置します。 kernel空間・user空間の双方から、NULLポインタデリファレンスもまた、この配置から捕まえられます。

Please note that mappings which collide with the above areas may result in a non-bootable kernel,
or may cause the kernel to (eventually) panic at run time.
上記領域と衝突するマッピングは、非ブートカーネルになったり、実行時に(最終的に)kernel panic要因となるかもしれないことに、ご注意ください。
Since future CPUs may impact the kernel mapping layout,
user programs must not access any memory which is not mapped inside their 0x0001000 to TASK_SIZE address range.
If they wish to access these areas,
they must set up their own mappings using open() and mmap().
将来のCPUでは、kernelマッピングレイアウトに影響があるかもしれないので、ユーザプログラムは、0x0001000からTASK_SIZEまでのアドレス範囲から外れたメモリをアクセスしてはならない。
もしそれらの領域をアクセスしたければ、open()やmmap()を使って、固有のマッピングをセットアップしなければならない。


関連ファイル

FILE: arch/arm/include/asm/fixmap.h

めも

更新されていないのか、vendorの仕様か、ちょっと配置が違う気がする。
変数を書き換えれば、上の方(vmalloc前後)は任意に動くから、いじってるかもなぁ。

clock_sourceとclock_eventとか

2013/11/03Linux::timeimport
※この文章は、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と表記されています。

2013/11/01(金)jiffies更新を追いかける

Tickless kernelにより、既存の和文資料の示すところから離れだす。
仕方が無いのでソースコードを頼りに追いかけることにする。

主目的は最低限必要なタイマリソースはなにか、の確認。
scheduler、jiffiesの更新がなされれば、要件を満たすので、更新処理を遡る。

kernelはv3.10を参照した(LTSI候補だというのを見かけたはず?)

jiffies更新を追いかける

システム時刻:xtime だったはず

jiffies更新箇所
/*
 * The 64-bit jiffies value is not atomic - you MUST NOT read it
 * without sampling the sequence number in xtime_lock.
 * jiffies is defined in the linker script...
 */
void do_timer(unsigned long ticks)
{
	jiffies_64 += ticks;
	update_wall_time();
	calc_global_load(ticks);
}
コイツが大ボス. jiffiesそのものじゃないけどいいんか( system call "times"で返している)
jiffies更新は別か..

→一緒だった。物理メモリで同一アドレスにしてあるので、64の更新がjiffies更新と一致する(Little Endian)。
FILE: System.map
8xxxxxxx D jiffies
8xxxxxxx D jiffies_64

void xtime_update(unsigned long ticks) write_seqlock(&xtime_lock); do_timer(ticks); write_sequnlock(&xtime_lock);

FILE: kernel/time/tick-sched.c
/*
 * Must be called with interrupts disabled !
 */
static void tick_do_update_jiffies64(ktime_t now)
{
	unsigned long ticks = 0;
	ktime_t delta;

	/*
	 * Do a quick check without holding xtime_lock:
	 */
	delta = ktime_sub(now, last_jiffies_update);
	if (delta.tv64 < tick_period.tv64)
		return;

	/* Reevalute with xtime_lock held */
	write_seqlock(&xtime_lock);

	delta = ktime_sub(now, last_jiffies_update);
	if (delta.tv64 >= tick_period.tv64) {

		delta = ktime_sub(delta, tick_period);
		last_jiffies_update = ktime_add(last_jiffies_update,
						tick_period);

		/* Slow path for long timeouts */
		if (unlikely(delta.tv64 >= tick_period.tv64)) {
			s64 incr = ktime_to_ns(tick_period);

			ticks = ktime_divns(delta, incr);

			last_jiffies_update = ktime_add_ns(last_jiffies_update,
							   incr * ticks);
		}
		do_timer(++ticks);

		/* Keep the tick_next_period variable up to date */
		tick_next_period = ktime_add(last_jiffies_update, tick_period);
	}
	write_sequnlock(&xtime_lock);
}
tick_do_update_jiffies64()を呼び出すヒト.
 static void tick_nohz_update_jiffies(ktime_t now)
 void tick_nohz_stop_sched_tick(int inidle)
  tick_program_event()って?
 static void tick_nohz_restart(struct tick_sched *ts, ktime_t now)
 void tick_nohz_restart_sched_tick(void)
 static void tick_nohz_handler(struct clock_event_device *dev)  // The nohz low res interrupt handler
 static enum hrtimer_restart tick_sched_timer(struct hrtimer *timer) // high-res. timer持っているとき.
いずれも、ktime_get();で現時刻をとってきている(CPU寝ていても回っているカウンタを想定)


FILE: kernel/time/timekeeping.c
ktime_t ktime_get(void)
{
	unsigned int seq;
	s64 secs, nsecs;

	WARN_ON(timekeeping_suspended);

	do {
		seq = read_seqbegin(&xtime_lock);
		secs = xtime.tv_sec + wall_to_monotonic.tv_sec;
		nsecs = xtime.tv_nsec + wall_to_monotonic.tv_nsec;
		nsecs += timekeeping_get_ns();
		/* If arch requires, add in gettimeoffset() */
		nsecs += arch_gettimeoffset();

	} while (read_seqretry(&xtime_lock, seq));
	/*
	 * Use ktime_set/ktime_add_ns to create a proper ktime on
	 * 32-bit architectures without CONFIG_KTIME_SCALAR.
	 */
	return ktime_add_ns(ktime_set(secs, 0), nsecs);
}
EXPORT_SYMBOL_GPL(ktime_get);
ktime_get()は、xtimeを見ている。あれ..?

コチラを見ると、http://d.hatena.ne.jp/enakai00/20111117/1321508379
別の所でハードウェアリソースを参照していた。


FILE: kernel/time/timekeeping.c
/**
 * update_wall_time - Uses the current clocksource to increment the wall time
 *
 * Called from the timer interrupt, must hold a write on xtime_lock.
 */
static void update_wall_time(void)
{
	struct clocksource *clock;
	cycle_t offset;
	int shift = 0, maxshift;

	/* Make sure we're fully resumed: */
	if (unlikely(timekeeping_suspended))
		return;

	clock = timekeeper.clock;        // ★★コレがSoC全域で使える、cpu idleでも時間を刻むクロックソースを握っている。

#ifdef CONFIG_ARCH_USES_GETTIMEOFFSET
	offset = timekeeper.cycle_interval;
#else
	offset = (clock->read(clock) - clock->cycle_last) & clock->mask;
#endif
	timekeeper.xtime_nsec = (s64)xtime.tv_nsec << timekeeper.shift;

	/*
	 * With NO_HZ we may have to accumulate many cycle_intervals
	 * (think "ticks") worth of time at once. To do this efficiently,
	 * we calculate the largest doubling multiple of cycle_intervals
	 * that is smaller then the offset. We then accumulate that
	 * chunk in one go, and then try to consume the next smaller
	 * doubled multiple.
	 */
	shift = ilog2(offset) - ilog2(timekeeper.cycle_interval);
	shift = max(0, shift);
	/* Bound shift to one less then what overflows tick_length */
	maxshift = (8*sizeof(tick_length) - (ilog2(tick_length)+1)) - 1;
	shift = min(shift, maxshift);
	while (offset >= timekeeper.cycle_interval) {
		offset = logarithmic_accumulation(offset, shift);
		if(offset < timekeeper.cycle_interval<<shift)
			shift--;
	}

	/* correct the clock when NTP error is too big */
	timekeeping_adjust(offset);

	/*
	 * Since in the loop above, we accumulate any amount of time
	 * in xtime_nsec over a second into xtime.tv_sec, its possible for
	 * xtime_nsec to be fairly small after the loop. Further, if we're
	 * slightly speeding the clocksource up in timekeeping_adjust(),
	 * its possible the required corrective factor to xtime_nsec could
	 * cause it to underflow.
	 *
	 * Now, we cannot simply roll the accumulated second back, since
	 * the NTP subsystem has been notified via second_overflow. So
	 * instead we push xtime_nsec forward by the amount we underflowed,
	 * and add that amount into the error.
	 *
	 * We'll correct this error next time through this function, when
	 * xtime_nsec is not as small.
	 */
	if (unlikely((s64)timekeeper.xtime_nsec < 0)) {
		s64 neg = -(s64)timekeeper.xtime_nsec;
		timekeeper.xtime_nsec = 0;
		timekeeper.ntp_error += neg << timekeeper.ntp_error_shift;
	}


	/*
	 * Store full nanoseconds into xtime after rounding it up and
	 * add the remainder to the error difference.
	 */
	xtime.tv_nsec =	((s64) timekeeper.xtime_nsec >> timekeeper.shift) + 1;
	timekeeper.xtime_nsec -= (s64) xtime.tv_nsec << timekeeper.shift;
	timekeeper.ntp_error +=	timekeeper.xtime_nsec <<
				timekeeper.ntp_error_shift;

	/*
	 * Finally, make sure that after the rounding
	 * xtime.tv_nsec isn't larger then NSEC_PER_SEC
	 */
	if (unlikely(xtime.tv_nsec >= NSEC_PER_SEC)) {
		int leap;
		xtime.tv_nsec -= NSEC_PER_SEC;
		xtime.tv_sec++;
		leap = second_overflow(xtime.tv_sec);
		xtime.tv_sec += leap;
		wall_to_monotonic.tv_sec -= leap;
		if (leap)
			clock_was_set_delayed();
	}

	timekeeping_update(false);
}

FILE: kernel/time/timekeeping.c
/**
 * timekeeper_setup_internals - Set up internals to use clocksource clock.
 *
 * @clock:		Pointer to clocksource.
 *
 * Calculates a fixed cycle/nsec interval for a given clocksource/adjustment
 * pair and interval request.
 *
 * Unless you're the timekeeping code, you should not be using this!
 */
static void timekeeper_setup_internals(struct clocksource *clock)
{
	cycle_t interval;
	u64 tmp, ntpinterval;

	timekeeper.clock = clock;
	clock->cycle_last = clock->read(clock);
以下略
この関数でセットしている。張り替えもできる模様。
static int change_clocksource(void *data)
FILE: kernel/time/timekeeping.c
/*
 * timekeeping_init - Initializes the clocksource and common timekeeping values
 */
void __init timekeeping_init(void)
{
	struct clocksource *clock;
	unsigned long flags;
	struct timespec now, boot;

	read_persistent_clock(&now);
	read_boot_clock(&boot);

	write_seqlock_irqsave(&xtime_lock, flags);

	ntp_init();

	clock = clocksource_default_clock();
	if (clock->enable)
		clock->enable(clock);
	timekeeper_setup_internals(clock);
...
おうふ...
clocksource_default_clock()のデフォルトは jiffiesを返すだけの論理的なクロックソースだ。
kernel/time/jiffies.c

ドライバの初期化処理で、タイマリソースを追加して、張り替える処理が走っていると想像。
/**
 * timekeeping_notify - Install a new clock source
 * @clock:		pointer to the clock source
 *
 * This function is called from clocksource.c after a new, better clock
 * source has been registered. The caller holds the clocksource_mutex.
 */
void timekeeping_notify(struct clocksource *clock)
{
	if (timekeeper.clock == clock)
		return;
	stop_machine(change_clocksource, clock, NULL);
	tick_clock_notify();
}
これか. inline関数になってる。SMPとSTOP_MACHINEが定義されていなければ関数呼び出しに置換される。
FILE: include/linux/stop_machine.h
change_clocksource( clock );


FILE: kernel/time/clocksource.c
#ifndef CONFIG_ARCH_USES_GETTIMEOFFSET

/**
 * clocksource_select - Select the best clocksource available
 *
 * Private function. Must hold clocksource_mutex when called.
 *
 * Select the clocksource with the best rating, or the clocksource,
 * which is selected by userspace override.
 */
static void clocksource_select(void)
コレ呼び出しているところが多数ある。。
clocksource_listの先頭がbest ratingになるらしい?
→追加するときに大きい物順になるようにコーディングしてある。
static void clocksource_enqueue(struct clocksource *cs)


カウントオーバーするまでに、起き上がる保証は?

なんとなく、登録情報で上限が定まるから、ここからタイマーを張っているような気がする。
タイマを貼ってしまったらTicklessじゃないような気がしないでもないよなぁ。

FILE: include/linux/clocksource.h
/*
 * Don't call __clocksource_register_scale directly, use
 * clocksource_register_hz/khz
 */
extern int
__clocksource_register_scale(struct clocksource *cs, u32 scale, u32 freq);
extern void
__clocksource_updatefreq_scale(struct clocksource *cs, u32 scale, u32 freq);

static inline int clocksource_register_hz(struct clocksource *cs, u32 hz)
{
	return __clocksource_register_scale(cs, 1, hz);
}
FILE: kernel/time/clocksource.c
/**
 * __clocksource_register_scale - Used to install new clocksources
 * @t:		clocksource to be registered
 * @scale:	Scale factor multiplied against freq to get clocksource hz
 * @freq:	clocksource frequency (cycles per second) divided by scale
 *
 * Returns -EBUSY if registration fails, zero otherwise.
 *
 * This *SHOULD NOT* be called directly! Please use the
 * clocksource_register_hz() or clocksource_register_khz helper functions.
 */
int __clocksource_register_scale(struct clocksource *cs, u32 scale, u32 freq)
{

	/* Initialize mult/shift and max_idle_ns */
	__clocksource_updatefreq_scale(cs, scale, freq);   // ★★★ここでcs->max_idle_nsに最長時間を設定してくれる。

	/* Add clocksource to the clcoksource list */
	mutex_lock(&clocksource_mutex);
	clocksource_enqueue(cs);
	clocksource_enqueue_watchdog(cs);
	clocksource_select();
	mutex_unlock(&clocksource_mutex);
	return 0;
}
EXPORT_SYMBOL_GPL(__clocksource_register_scale);
この最長時間を返す関数が以下にある。(timerクロックリソースを保持するオブジェクト(ファイルスコープ))
FILE: kernel/time/timekeeping.c
u64 timekeeping_max_deferment(void)
これを使っていて、スケジューラ絡みのソースは以下。
FILE: kernel/time/tick-sched.c
/**
 * tick_nohz_stop_sched_tick - stop the idle tick from the idle task
 *
 * When the next event is more than a tick into the future, stop the idle tick
 * Called either from the idle loop or from irq_exit() when an idle period was
 * just interrupted by an interrupt which did not cause a reschedule.
 */
void tick_nohz_stop_sched_tick(int inidle)
{

		if (ts->nohz_mode == NOHZ_MODE_HIGHRES) {
			hrtimer_start(&ts->sched_timer, expires,
				      HRTIMER_MODE_ABS_PINNED);
			/* Check, if the timer was already in the past */
			if (hrtimer_active(&ts->sched_timer))
				goto out;
		} else if (!tick_program_event(expires, 0))
				goto out;
tickを止める前に、タイマを貼ってますなぁ。ハイレゾが有効ならそれ。違えば普通の。

ハイレゾタイマが存在している場合。
FILE: kernel/hrtimer.c
/**
 * hrtimer_start_range_ns - (re)start an hrtimer on the current CPU
 * @timer:	the timer to be added
 * @tim:	expiry time
 * @delta_ns:	"slack" range for the timer
 * @mode:	expiry mode: absolute (HRTIMER_ABS) or relative (HRTIMER_REL)
 *
 * Returns:
 *  0 on success
 *  1 when the timer was active
 */
int hrtimer_start_range_ns(struct hrtimer *timer, ktime_t tim,
		unsigned long delta_ns, const enum hrtimer_mode mode)
{
	return __hrtimer_start_range_ns(timer, tim, delta_ns, mode, 1);
}
EXPORT_SYMBOL_GPL(hrtimer_start_range_ns);
"nohz_mode = NOHZ_MODE_HIGHRES"を設定しているのは、以下。
/**
 * tick_setup_sched_timer - setup the tick emulation timer
 */
void tick_setup_sched_timer(void)
{
	struct tick_sched *ts = &__get_cpu_var(tick_cpu_sched);
	ktime_t now = ktime_get();

	/*
	 * Emulate tick processing via per-CPU hrtimers:
	 */
	hrtimer_init(&ts->sched_timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS);
	ts->sched_timer.function = tick_sched_timer;

	/* Get the next period (per cpu) */
	hrtimer_set_expires(&ts->sched_timer, tick_init_jiffy_update());

	for (;;) {
		hrtimer_forward(&ts->sched_timer, now, tick_period);
		hrtimer_start_expires(&ts->sched_timer,
				      HRTIMER_MODE_ABS_PINNED);
		/* Check, if the timer was already in the past */
		if (hrtimer_active(&ts->sched_timer))
			break;
		now = ktime_get();
	}

#ifdef CONFIG_NO_HZ
	if (tick_nohz_enabled) {
		ts->nohz_mode = NOHZ_MODE_HIGHRES;
		printk(KERN_INFO "Switched to NOHz mode on CPU #%d\n", smp_processor_id());
	}
#endif
}
#endif /* HIGH_RES_TIMERS */

普通の。
/**
 * tick_program_event
 */
int tick_program_event(ktime_t expires, int force)
{
	struct clock_event_device *dev = __this_cpu_read(tick_cpu_device.evtdev);

	return tick_dev_program_event(dev, expires, force);
}

タイマリソースの張替えが自由にできてしまうので、
AMPで同一カウンタを参照するような実装をしたければ、
抜けないようにしておくのが良い。stop requestでnack返せばいいかなぁ?(要確認)





ハイレゾタイマがあれば、それを使ってしまうぽい?
FILE: kernel/timer.c
/*
 * This function runs timers and the timer-tq in bottom half context.
 */
static void run_timer_softirq(struct softirq_action *h)
{
	struct tvec_base *base = __this_cpu_read(tvec_bases);

	hrtimer_run_pending();

	if (time_after_eq(jiffies, base->timer_jiffies))
		__run_timers(base);
}
void __init init_timers(void)
{
	int err = timer_cpu_notify(&timers_nb, (unsigned long)CPU_UP_PREPARE,
				(void *)(long)smp_processor_id());

	init_timer_stats();

	BUG_ON(err != NOTIFY_OK);
	register_cpu_notifier(&timers_nb);
	open_softirq(TIMER_SOFTIRQ, run_timer_softirq);
}

FILE: kernel/timer.c
/*
 * Called from timer softirq every jiffy, expire hrtimers:
 *
 * For HRT its the fall back code to run the softirq in the timer
 * softirq context in case the hrtimer initialization failed or has
 * not been done yet.
 */
void hrtimer_run_pending(void)
{
	if (hrtimer_hres_active())   //★★ ->hres_activeを返す。
		return;

	/*
	 * This _is_ ugly: We have to check in the softirq context,
	 * whether we can switch to highres and / or nohz mode. The
	 * clocksource switch happens in the timer interrupt with
	 * xtime_lock held. Notification from there only sets the
	 * check bit in the tick_oneshot code, otherwise we might
	 * deadlock vs. xtime_lock.
	 */
	if (tick_check_oneshot_change(!hrtimer_is_hres_enabled())) // hrtimer_is_hres_enabled()は真になりやすい(hrtimer_hres_enabledを返す)
		hrtimer_switch_to_hres();
}
hrtimer_hres_active()が
/*
 * Switch to high resolution mode
 */
static int hrtimer_switch_to_hres(void)
{
	int i, cpu = smp_processor_id();
	struct hrtimer_cpu_base *base = &per_cpu(hrtimer_bases, cpu);
	unsigned long flags;

	if (base->hres_active)
		return 1;

	local_irq_save(flags);

	if (tick_init_highres()) {
		local_irq_restore(flags);
		printk(KERN_WARNING "Could not switch to high resolution "
				    "mode on CPU %d\n", cpu);
		return 0;
	}
	base->hres_active = 1;
	for (i = 0; i < HRTIMER_MAX_CLOCK_BASES; i++)
		base->clock_base[i].resolution = KTIME_HIGH_RES;

	tick_setup_sched_timer();
	/* "Retrigger" the interrupt to get things going */
	retrigger_next_event(NULL);
	local_irq_restore(flags);
	return 1;
}
clocksourceとclockeventとhrtimerと。
clockeventがhrtimerの代わりになれる?
clocksourceは、永続的に回るカウンタで、NO_HZに必要、か。
起き上がるのに別途タイマが必要になってくる。
(compare "以上"で割込みが出せるなら、一発でいける、か?)
hrtimerとしては登録しない

参考
 http://www.spinics.net/lists/linux-rt-users/msg06427.html


clocksource
 カレンダ時計みたいなもん。NO_HZで起き上がった時にデルタを見るために使う。
登録したratioの最大値のものを採用する(名前を指定して動的にもとれる)
デフォルトがjiffiesになっているので、昔ながらの定周期割込みでも運用できるぽい。

clockevent
 hrtimerにも使われる(どこでそうしているか未確認)
 属性をしっかりと設定して、PERIODIC,ONESHOTがアレば良さそう。
 インタフェースと使われ方を照会して、インプリする。

 ドキュメントはあまりない
(2013/03のコミットで、timerまわりの設計思想はthomasが書いているけど、ドライバの実装の話はナシ)



これ、ticklessだと?無効ですよ..
FILE: arm/kernel/time.c
#ifndef CONFIG_GENERIC_CLOCKEVENTS
/*
 * Kernel system timer support.
 */
void timer_tick(void)
{
	profile_tick(CPU_PROFILING);
	do_leds();
	xtime_update(1);
#ifndef CONFIG_SMP
	update_process_times(user_mode(get_irq_regs()));
#endif
}
#endif


別件

ARMのbootメモは、以下も詳しい。なぞる感じになるなぁ
http://amitshah.bizhat.com/arm/arm_linux_boot-1.html

2013/10/27(日)LDD2_Chap13

本コンテンツは更新中です。
書籍を置いてきたので、和文(本)を読めば進みが早いかもしれません。
が、単語の役され方次第で混乱するので原文も参照する必要があるでしょう。
内容理解という点では、進みは同じかもしれません。。

前置き

ざっくりとここを流し読み。基本、ドライバ開発者向けの資料なので、何もなければkernel空間での話だと思う。
DMAを触るためには、kernelのメモリ管理を知らなければならない、ということですね。
メモリ枯渇も目にすることが多いけれど、意味がわからないことがあるので、
ちょっとお勉強…。このレベルでもguruには程遠いというので、精進が足りなさすぎる…のね。

http://www.xml.com/ldd/chapter/book/ch13.html

アドレスの種類

name 説明
User virtual addresses,仮想アドレスユーザ空間のプログラムから見える一般的なアドレスです。 ユーザアドレスは、32bitか64bitの長さで、ハードウェアアーキテクチャと 各プロセス毎の仮想アドレス空間に基づきます。
Physical addresses, 物理アドレスこのアドレスは、プロセッサとシステムのメモリとの間で使用される。 物理アドレスは32bitまたは64bitの量である。 32ビットシステムでも、いくつかの状況では、64ビットの物理アドレスを使用することができます。
Bus addresses, バスアドレスこのアドレスは周辺バスとメモリの間で使用される。 多くの場合、それらは、プロセッサによって使用される物理アドレスと同じであるが、それは必ずしもそうではない。 バスアドレスは、もちろん、非常にアーキテクチャに依存している。
Kernel logical addresses, カーネル論理アドレスこれらはカーネルの通常のアドレス空間を構成しています。 これらのアドレスは、ほとんどまたはメインメモリのすべてのマップします。 そして、これらは物理アドレスであるかのように頻繁に扱われます。 ほとんどのアーキテクチャでは、(カーネル)論理アドレスとそれに関連付けられた 物理アドレスは、一定のオフセットだけ異なる。 (カーネル)論理アドレスは、ハードウェアのネイティブポインタサイズを使用するので、 重度に使用する*1 32ビットシステム上の物理メモリのすべてに対処できない可能性があります。 論理アドレスは通常型unsigned long型またはvoid*の変数に格納されています。 kmallocのから返されたメモリは、(カーネル)論理アドレスを持っています。
Kernel virtual addresses, カーネル仮想アドレスこれらは、必ずしも物理アドレスに直接マッピングされていないという点で、(カーネル)論理アドレスとは異なる。 すべての論理アドレスは、カーネル仮想アドレスである; vmalloc()で割り当てられたメモリは、仮想アドレスも持ちます。ただし物理アドレスに直接マッピングされていません。 kmap関数も、仮想アドレスを返します。 仮想アドレスは、通常はポインタ変数に格納されています。

論理アドレスを使用している場合、マクロ __pa()(<asm/page.h>で定義されている)は、それに関連する物理アドレスを返します。
物理アドレスは、__va()マクロで(カーネル)論理アドレスにマッピングできます。ただしlow-memoryページに対してのみです。
異なるカーネル関数は、異なるアドレスの型を要求します。
必要なアドレスの種類が明示的となるように定義された別のCの型があったならば良いだろうが、我々はそのような運を持っていません。
どちらのアドレス型をどこで使うのかを明確にします。

High and Low Memory

論理アドレスと、カーネル仮想アドレスとの差は、大量のメモリが装備されている32bitシステム上で強調される。
32bitだと、4GBのメモリを表現することができる。
最近までは、32bitシステムのLinuxは、それよりも大幅に少ないメモリに限定されていました。
しかし、仮想アドレス空間を設定することで解決することができます。(超意訳)
そのシステムでは、論理アドレスを設定することができ、より多くのメモリを扱うことができませんでした。
それが必要なので、すべてのメモリをカーネルアドレスに直接マップしました。

最近の開発では、メモリの制限を排除しており、32bitシステムでは、今のシステムメモリである4GBを優に超えるメモリで作業できます。
(もちろん、プロセッサ自身がより多くのメモリをアドレスできると仮定する)
しなしながら、多くのメモリを直接論理アドレスにマッピングする方法についての制限は残っている。

メモリの最下部(上限1〜2GB、kernel config.とハードウェアに依存します)だけは、論理アドレスを持っています。残り(high-memory)は違います。
high-memoryは、64bit物理アドレスを必要とすることができます。
そして、カーネルは、それを操作するための明示的な仮想アドレスのマッピングを設定する必要があります。
したがって、多くのカーネル関数は、メモリ不足に制限される。
high memoryは、ユーザ空間プロセスページのために予約される傾向にある。

"high memory"という語は、PCの世界で別の意味を持っているため、誰かを混乱させることがあります。
ここでは、語彙を以下のように定義します。
Type describe
Low memoryカーネル空間に存在する、論理アドレスメモリ。 よく遭遇するほぼすべてのシステムで、すべてのメモリはlow memoryです。
On almost every system you will likely encounter, all memory is low memory.
High memoryカーネル空間に論理アドレスが存在しないメモリ。 32bitでアドレッシングできるよりも多い物理メモリを搭載したシステム。
と、いいつつも、実際には memory mapped I/Oが存在するので、
自由に使える空間は32bit全域では無いわけですが。。
時間も無いのでキーワードだけ。

The Memory Map and struct page

high memoryをサポートするため、struct pageが存在する。
参照カウントとwait queueを有しており、low-memoryにmapされていればそのアドレスを持つ。
low-memoryにmapされていない状態もある。

ruct page *virt_to_page(void *kaddr);
カーネル論理アドレスを引数に渡す。vmalloc()やhigh memoryのアドレスでは、機能しない。
void *page_address(struct page *page);
page構造体のポインタを渡して、このページが指すカーネルアドレスを返す。
high-memoryの場合、mapされている場合に返してくる。
#include <linux/highmem.h>
void *kmap(struct page *page);
void kunmap(struct page *page);
low-memoryでも、high-memoryでも適切に機能し、カーネル仮想アドレスを返す。
high-memoryの場合、適切な(システムに依存した定められた)空間にmapしてから返してくれる。
限られた空間なので、kunmap()の呼び出しは必須である。

Page Tables

kernelのメモリ管理にかかわる構造体。<asm/page.h>にて定義されている。
(ARMで云うなら、4Kpageまで分解するのにテーブルルックアップを3回必要とするので、そのソフトウェア版と見ればよい、か?(消化中))
略語 name description
?struct mm_struct
PGDpgd_tPage Directory.
PMDpmd_tPage mid-level Directory.
PTEpte_tPage Table.
?struct page



*1 : heavily equipped 32-bit systems.

補足事項?

"BSS"は、歴史的な遺物です。
古いアセンブリ演算子から来ていて、意味は"Block Started by Symbol"です。

[ARM] compressed kernel

2013/10/20linux::ARMimport

zImageの展開処理

調査対象は、【kernel 3.0.39 "Sneaky Weasel"】です。

例として、u-bootから起動する場合を考えます。
ここで上げるのは、とあるボードへの移植時に調査した値です。
ボード依存部の値は、デタラメに書いている可能性があるので、注意してください。


NOR bootの場合は、ROM上のコード実行が可能ですが、昨今の方式*1ではbootloaderがDRAM上に展開してから実行することが多いでしょう。

u-bootが読み込むファイル、uImageは、zImageにuImageヘッダがついたもの。
図にしてみると以下のイメージ。(AAでスミマセン)
+------------------------+
| u-boot header(64oct.)  |
+------------------------+
| zImage decompress code |
| .text                  |
| (with compressed data )|
| .got                   |
| .got.plt               |
| .bss                   |
+------------------------+
このデータの読み込みは、u-bootによって行われるため、任意のアドレスに展開されます。


ldscriptを見ると、ざっくりとこんな感じ。
  _text = .;
  .text : {}
  _etext = .;
  _got_start = .;
  .got			: { *(.got) }
  _got_end = .;
  .got.plt		: { *(.got.plt) }
  _edata = .;

  __bss_start = .;
  .bss			: { *(.bss) }
  _end = .;

*1 : NAND/SDから2nd boot programを読み出して、本当のローダを呼び出す。

事前準備

実際には、コードを読みながら参照していった結果です。

FILE: arch/arm/boot/compressed/Makefile
# Supply ZRELADDR to the decompressor via a linker symbol.
ifneq ($(CONFIG_AUTO_ZRELADDR),y)
LDFLAGS_vmlinux += --defsym zreladdr=$(ZRELADDR)
ZRELADDRは、以下で定義されます。
FILE: arch/arm/boot/Makefile
ZRELADDR    := $(zreladdr-y)
右辺は、SoC依存部にて定義されています。
FILE: arch/arm/mach-*/Makefile.boot
zreladdr-y	:= 0x40008000
params_phys-y	:= 0x40000100
initrd_phys-y	:= 0x40800000


zImage先頭から

エントリーポイントはここ。

bootloaderから飛んでくるところ

アドレスは任意。

FILE: ./boot/compressed/head.S
start:
 ※cache有効, TLBはVA-PA一対一対応, 

restart:
		.align	2
		.type	LC0, #object
LC0:
		.word	LC0					@ r1
		.word	__bss_start			@ r2
		.word	_end				@ r3
		.word	_edata				@ r6
		.word	input_data_end - 4	@ r10 (inflated size location)
		.word	_got_start			@ r11
		.word	_got_end			@ ip(r12)

		.word	.L_user_stack_end	@ sp

		.size	LC0, . - LC0

		/*
		 * We might be running at a different address.  We need
		 * to fix up various pointers.
		 */
		sub	r0, r0, r1		@ calculate the delta offset
		add	r6, r6, r0		@ _edata
		add	r10, r10, r0	@ inflated kernel size location
LC0(DRAMの位置)から、"LC0"の配置した位置(ldscriptでは先頭0、コード配置後のLC0の位置)を差っ引いて、r0に保持する。
圧縮カーネルの、展開後のサイズは、build systemによって、圧縮カーネルの後ろにLEで付与される。したがって、r10へloadする値は、input_data_endから4引いてある。(32bit決め打ちだね)
spも、relocate後のアドレスをセット。r10にsp+64kのアドレスをセット(malloc用に64k確保)(CONFIG_ZBOOT_ROM未定義の場合。定義時はコード本体がROMに入るから、_edataの後ろに配置する)

このイメージがロードされる位置がどこでも動けるようになっている。

展開後のkernel位置と、この展開コード、edata類の空間が重なっているかをチェックする。重複している場合、reset〜edataまでを、後ろからコピーする(memmove相当)。その後、コピーした後のrestartへjumpする。(これで重複しない位置で再度relocateする)

※ユーザモードで入ってきた場合、SVCで特権を得るようになってます。
ヴェクタ位置へのロードを期待しているようですが、必ずしもそうではないから、ちょっと不味そうな気がするネ。

relocate後

decompress処理のソフトと、圧縮イメージとを再配置して、kernel imageを展開できる状態になって、wont_overwriteへたどり着く。前述のように、最初のバイナリがロードされる位置によって、ここへ直接来ることもあれば、relocateすることもありますね。
wont_overwrite:
/*
 * If delta is zero, we are running at the address we were linked at.
 *   r0  = delta
 *   r2  = BSS start
 *   r3  = BSS end
 *   r4  = kernel execution address
 *   r7  = architecture ID
 *   r8  = atags pointer
 *   r11 = GOT start
 *   r12 = GOT end
 *   sp  = stack pointer
 */
・GOT書き換え(オフセット分加算して、飛び先がrelocate先のアドレスになるように)
・BSSをゼロクリア


本来のImage(vmlinux)へ

kernelの展開先アドレスを第一引数、spからsp+64kを第二〜三引数に渡して、第四引数にアーキテクチャIDをのせてcall。
void decompress_kernel(unsigned long output_start, unsigned long free_mem_ptr_p, unsigned long free_mem_ptr_end_p, int arch_id)
展開が終わったら、cache clean、cache-offしてから、圧縮前のkernel先頭へ飛ぶ。