kexecの実装を調べる(ARM編)
2014/03/26
kexecを追う
ユーザランドでは、kexec-toolsを用いる。仕組みがわかっていれば、システムコールを自前で叩いて準備も出来そう。
実際の処理はアーキテクチャ依存部にて行われる。
ここではARMについて調査をすすめる。
FILE: kernel/kexec.c
SYSCALL_DEFINE4(kexec_load, unsigned long, entry, unsigned long, nr_segments, struct kexec_segment __user *, segments, unsigned long, flags) { ... result = kimage_normal_alloc(&image, entry, nr_segments, segments); ... result = machine_kexec_prepare(image); ... for (i = 0; i < nr_segments; i++) { result = kimage_load_segment(image, &image->segment[i]); ... kimage_terminate(image);kernel imageやinitrdのイメージを物理ページへ転送して起動準備。
load image用メモリ確保(kernel/kexec.c)
static int kimage_normal_alloc(struct kimage **rimage, unsigned long entry, unsigned long nr_segments, struct kexec_segment __user *segments) { ... image->control_code_page = kimage_alloc_control_pages(image, get_order(KEXEC_CONTROL_PAGE_SIZE));image->control_code_page には、連続した4096page=16MBの物理連続空間が割り当てられる。
以下の順序で関数が呼ばれて取得できる。この領域はreboot用につかう。
struct page *kimage_alloc_control_pages(struct kimage *image, unsigned int order) { struct page *pages = NULL; switch (image->type) { case KEXEC_TYPE_DEFAULT: pages = kimage_alloc_normal_control_pages(image, order); break;DEFAULTとCRASH時の2種類あり。今はDEFAULTを追う。
static struct page *kimage_alloc_normal_control_pages(struct kimage *image, unsigned int order) { pages = kimage_alloc_pages(GFP_KERNEL, order); ... if ((epfn >= (KEXEC_CONTROL_MEMORY_LIMIT >> PAGE_SHIFT)) || kimage_is_destination_range(image, addr, eaddr)) { list_add(&pages->lru, &extra_pages); pages = NULL; } } while (!pages); ... kimage_free_page_list(&extra_pages);物理的に連続するページを確保して、転送先アドレスとの重複チェックを行う。
該当する場合は改めて取得を試みる。最後に未使用分は開放する。
範囲チェック(FILE: arch/arm/kernel/machine_kexec.c)
int machine_kexec_prepare(struct kimage *image) ... memblock_is_region_memory(current_segment->mem,current_segment->memsz)現在のkernelが把握している memory空間の有効範囲が指示されているかをチェックしているようだ。
ロード
static int kimage_load_segment(struct kimage *image, struct kexec_segment *segment) { ... case KEXEC_TYPE_DEFAULT: result = kimage_load_normal_segment(image, segment);
static int kimage_load_normal_segment(struct kimage *image, struct kexec_segment *segment) {
FILE:kernel/reboot.c
SYSCALL_DEFINE4(reboot, int, magic1, int, magic2, unsigned int, cmd, void __user *, arg) { #ifdef CONFIG_KEXEC case LINUX_REBOOT_CMD_KEXEC: ret = kernel_kexec();kexecシステムコールで保持したイメージデータは、
rebootシステムコールのKEXEC要求により発動する。
FILE: kernel/kexec.c
/* * Move into place and start executing a preloaded standalone * executable. If nothing was preloaded return an error. */ int kernel_kexec(void) { { kexec_in_progress = true; kernel_restart_prepare(NULL); migrate_to_reboot_cpu(); printk(KERN_EMERG "Starting new kernel\n"); machine_shutdown(); } machine_kexec(kexec_image);ドライバ・ユーザヘルパ?の終了をしつつ(kernel_restart_prepare(NULL))、
CPU0のみ稼働状態(ほかは停止へ遷移させる)
FILE: kernel/reboot.c
void kernel_restart_prepare(char *cmd) { blocking_notifier_call_chain(&reboot_notifier_list, SYS_RESTART, cmd); system_state = SYSTEM_RESTART; usermodehelper_disable(); device_shutdown(); }
FILE: arch/arm/kernel/machine_kexec.c
void machine_kexec(struct kimage *image) { ... unsigned long reboot_entry = (unsigned long)relocate_new_kernel; ... /* copy our kernel relocation code to the control code page */ reboot_entry = fncpy(reboot_code_buffer, reboot_entry, relocate_new_kernel_size); ★imageで渡されたロードイメージをページ単位で保存し、 そのPFNをLookUpTableとして管理している。 そのポインタを受け取って、指定された物理アドレスへイメージを 転送するアセンブラコード本体を、コレでコピーする。 コードはentry pointへのjumpも含まれている。 ... printk(KERN_INFO "Bye!\n"); if (kexec_reinit) kexec_reinit(); soft_restart(reboot_entry_phys); }
FILE: arch/arm/kernel/relocate_kernel.S
ENTRY(relocate_new_kernel) ... /* Jump to relocated kernel */ mov lr,r1 mov r0,#0 ldr r1,kexec_mach_type ldr r2,kexec_boot_atags ARM( mov pc, lr ) THUMB( bx lr ) .align .globl kexec_start_address kexec_start_address: .long 0x0 .globl kexec_indirection_page kexec_indirection_page: .long 0x0 .globl kexec_mach_type kexec_mach_type: .long 0x0 /* phy addr of the atags for the new kernel */ .globl kexec_boot_atags kexec_boot_atags: .long 0x0 ENDPROC(relocate_new_kernel)
FILE: arch/arm/kernel/process.c
void soft_restart(unsigned long addr) { u64 *stack = soft_restart_stack + ARRAY_SIZE(soft_restart_stack); /* Disable interrupts first */ ... /* Disable the L2 if we're the last man standing. */ ... /* Change to the new stack and continue with the reset. */ call_with_stack(__soft_restart, (void *)addr, (void *)stack);FILE: arch/arm//lib/call_with_stack.S
ENTRY(call_with_stack) str sp, [r2, #-4]! str lr, [r2, #-4]! mov sp, r2 mov r2, r0 mov r0, r1 adr lr, BSYM(1f) mov pc, r2 1: ldr lr, [sp] ldr sp, [sp, #4] mov pc, lr ENDPROC(call_with_stack)ARM-EABI仕様にしたがい、r0にLUTへのポインタ(PFN/dest-adr/end-mark/next-LUTの入った先頭ページ)、r1に関数アドレス、r2にスタック(static変数)が渡されてくる。
ここで使われるスタック領域が気がかりであり、旧kernelのstatic変数領域なのだが、
ロードイメージがその領域を侵食しない保証がないのではなかろうか。
control_iamgeの後半から使うとか、予め1枚用意しておくなどしたほうが良い気はする。
まとめ
以上より、以下のことが解った。- 新しいイメージの保存先は、転送先アドレスにぶつからないように作られている。
- 渡されたイメージは、任意の物理アドレスにコピーされ、entryへのjumpも行われる。\→kernel imageに限らず任意の領域に任意のデータをおけそう。
- 最終的にcpu0/割り込み全部禁止状態で、entryへ飛ぶ
- reboot前の処理として、なにか作りこみたい場合は、以下の関数ポインタへ適切な関数へのアドレスをセットする。\設定方法は、mach-*のあたりで、直接代入するしかなさそうだ(mach-kirkwoodのみ該当@3.14-rc8)。
void (*kexec_reinit)(void);
cpu0以外が生きている状態で切り替えようとしていないか。
通常のpower on resetと同じ状態にするのが望ましい。
これはペリフェラルに関しても同じことが言える。power managementを有効化して、
ドライバはpower save有効と指示されれば、ペリフェラルを極力寝かせる。
状態としても、ほぼPOR直後と同じ状態にしておくことが望ましい。
ソフト的な状態保持は影響を受けないので、
制御レジスタ・外部デバイスの設定値が効いてくるだろう。
もしくは、初期化処理で、既に初期化された状態でも正常に
動作するような実装であれば良い。