ユーザランドでは、kexec-toolsを用いる。仕組みがわかっていれば、
システムコールを自前で叩いて準備も出来そう。
実際の処理はアーキテクチャ依存部にて行われる。
ここではARMについて調査をすすめる。
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のイメージを物理ページへ転送して起動準備。
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);
物理的に連続するページを確保して、転送先アドレスとの重複チェックを行う。
該当する場合は改めて取得を試みる。最後に未使用分は開放する。
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)
{
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要求により発動する。
/*
* 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のみ稼働状態(ほかは停止へ遷移させる)
void kernel_restart_prepare(char *cmd)
{
blocking_notifier_call_chain(&reboot_notifier_list, SYS_RESTART, cmd);
system_state = SYSTEM_RESTART;
usermodehelper_disable();
device_shutdown();
}
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枚用意しておくなどしたほうが良い気はする。
以上より、以下のことが解った。
kexecに失敗するようならば、以下の観点が必要。
cpu0以外が生きている状態で切り替えようとしていないか。
通常のpower on resetと同じ状態にするのが望ましい。
これはペリフェラルに関しても同じことが言える。power managementを有効化して、
ドライバはpower save有効と指示されれば、ペリフェラルを極力寝かせる。
状態としても、ほぼPOR直後と同じ状態にしておくことが望ましい。
ソフト的な状態保持は影響を受けないので、
制御レジスタ・外部デバイスの設定値が効いてくるだろう。
もしくは、初期化処理で、既に初期化された状態でも正常に
動作するような実装であれば良い。