我的内核学习笔记13:x86平台linux系统重启流程跟踪
一直以来,笔者只知道重启Linux系统性使用reboot,但对其过程却无所知,涉及到哪些知识点也无概念。本文就跟踪一下重启的流程,平台为Intel x86,Linux内核版本为3.17。行文中“重启”与“复位”等价。
一、初识
在Linux命令行下输入reboot,终端出现如下信息:
* Stopping web server apache2 * Stop in my script and clean net.rules .... * Asking all remaining proc[ OK ]to terminate... [ OK ] * All processes ended within 1 seconds... rpcbind: rpcbind terminating on signal. Restart with "rpcbind -w" [ OK ]ctivating swap... [ OK ] * Unmounting local filesystems... * Will now restart [ 847.054796] reboot: Restarting system
信息格式错乱,但不影响分析。系统首先做的是停止apache2,然后执行用户自定义脚本。再结束进程、卸载文件系统。最后提示“reboot: Restarting system”,便完成使命,系统重启。
从上面信息第二行看到执行了笔者自己写的脚本,它的作用是用于清除70-persistent-net.rules文件,主要解决当时的一个棘手问题,离今二年有余,由是怀念。
二、用户空间
本节抽取uClinux和busybox源码中关于重启部分函数代码,以便了解用户空间重启的过程。两者的重启代码具备一定代表。
先看一下uClinux重启代码片段:
int main(int argc, char *argv[]) { kill(1, SIGTSTP); sync(); signal(SIGTERM,SIG_IGN); setpgrp(); kill(-1, SIGTERM); kill(-1, SIGHUP); sleep(1); kill(-1, SIGKILL); sync(); sleep(1); #if __GNU_LIBRARY__ > 5 reboot(0x01234567); #else reboot(0xfee1dead, 672274793, 0x01234567); #endif exit(0); /* Shrug */ }
再看一下busybox重启代码:
extern int bb_shutdown_system(unsigned long magic) { int pri = LOG_KERN|LOG_NOTICE|LOG_FACMASK; const char *message; /* Don't kill ourself */ signal(SIGTERM,SIG_IGN); signal(SIGHUP,SIG_IGN); setpgrp(); /* Allow Ctrl-Alt-Del to reboot system. */ #ifndef RB_ENABLE_CAD #define RB_ENABLE_CAD 0x89abcdef #endif reboot(RB_ENABLE_CAD); openlog(bb_applet_name, 0, pri); message = "\nThe system is going down NOW !!"; syslog(pri, "%s", message); printf(bb_shutdown_format, message); sync(); /* Send signals to every process _except_ pid 1 */ message = "Sending SIGTERM to all processes."; syslog(pri, "%s", message); printf(bb_shutdown_format, message); kill(-1, SIGTERM); sleep(1); sync(); message = "Sending SIGKILL to all processes."; syslog(pri, "%s", message); printf(bb_shutdown_format, message); kill(-1, SIGKILL); sleep(1); sync(); reboot(magic); return 0; /* Shrug */ }
两者处理过程类似,首先调用kill发送信号,最后调用reboot函数。
/* For libc4 and libc5 the library call and the system call are identical, and since kernel version 2.1.30 there are symbolic names LINUX_REBOOT_* for the constants and a fourth argument to the call: */ #include <unistd.h> #include <linux/reboot.h> int reboot(int magic, int magic2, int cmd, void *arg); /* Under glibc some of the constants involved have gotten symbolic names RB_*, and the library call is a 1-argument wrapper around the 3-argument system call: */ #include <unistd.h> #include <sys/reboot.h> int reboot(int cmd);
三、内核空间
SYSCALL_DEFINE4(reboot, int, magic1, int, magic2, unsigned int, cmd, void __user *, arg) { mutex_lock(&reboot_mutex); switch (cmd) { case LINUX_REBOOT_CMD_RESTART: kernel_restart(NULL); break; case LINUX_REBOOT_CMD_HALT: kernel_halt(); do_exit(0); panic("cannot halt"); case LINUX_REBOOT_CMD_POWER_OFF: kernel_power_off(); do_exit(0); break; default: ret = -EINVAL; break; } mutex_unlock(&reboot_mutex); return ret; }
reboot进行LINUX_REBOOT_CMD_RESTART分支,调用的函数为kernel_restart。在终端看到的字符串“Restarting system”就是在里面打印的。它的实现如下(同样位于kernel/reboot.c文件):
void kernel_restart(char *cmd) { kernel_restart_prepare(cmd); migrate_to_reboot_cpu(); syscore_shutdown(); if (!cmd) pr_emerg("Restarting system\n"); else pr_emerg("Restarting system with command '%s'\n", cmd); kmsg_dump(KMSG_DUMP_RESTART); machine_restart(cmd); } EXPORT_SYMBOL_GPL(kernel_restart);
void machine_restart(char *cmd) { machine_ops.restart(cmd); }
该函数调用了machine_ops结构体的函数指针,看一下machine_ops结构体定义(位于arch/x86/kernel/reboot.c):
struct machine_ops machine_ops = { .power_off = native_machine_power_off, .shutdown = native_machine_shutdown, .emergency_restart = native_machine_emergency_restart, .restart = native_machine_restart, .halt = native_machine_halt, #ifdef CONFIG_KEXEC .crash_shutdown = native_machine_crash_shutdown, #endif };
真正重启的restart实际上是native_machine_restart函数:
static void native_machine_restart(char *__unused) { pr_notice("machine restart\n"); if (!reboot_force) machine_shutdown(); __machine_emergency_restart(0); }
继续看调用的函数__machine_emergency_restart:
static void __machine_emergency_restart(int emergency) { reboot_emergency = emergency; machine_ops.emergency_restart(); }
而machine_ops.emergency_restart函数实际为native_machine_emergency_restart。
static void native_machine_emergency_restart(void) { for (;;) { /* Could also try the reset bit in the Hammer NB */ switch (reboot_type) { // 重启标志:reboot_type。 case BOOT_ACPI: acpi_reboot(); reboot_type = BOOT_KBD; // BOOT_ACPI不成功再到BOOT_KBD break; case BOOT_KBD: mach_reboot_fixups(); /* For board specific fixups */ for (i = 0; i < 10; i++) { kb_wait(); udelay(50); outb(0xfe, 0x64); /* Pulse reset low */ outb(0x0e, 0xcf9); /* for byatrail e3800 SOC by Late Lee*/ udelay(50); } if (attempt == 0 && orig_reboot_type == BOOT_ACPI) { attempt = 1; reboot_type = BOOT_ACPI; } else { reboot_type = BOOT_EFI; // BOOT_KBD不成功再到BOOT_EFI } break; case BOOT_EFI: efi_reboot(reboot_mode, NULL); reboot_type = BOOT_BIOS; // BOOT_EFI不成功再到BOOT_BIOS break; case BOOT_BIOS: machine_real_restart(MRR_BIOS); /* We're probably dead after this, but... */ reboot_type = BOOT_CF9_SAFE;// BOOT_BIOS不成功再到BOOT_CF9_SAFE break; case BOOT_CF9_FORCE: port_cf9_safe = true; /* Fall through */ case BOOT_CF9_SAFE: if (port_cf9_safe) { u8 reboot_code = reboot_mode == REBOOT_WARM ? 0x06 : 0x0E; u8 cf9 = inb(0xcf9) & ~reboot_code; outb(cf9|2, 0xcf9); /* Request hard reset */ udelay(50); /* Actually do the reset */ outb(cf9|reboot_code, 0xcf9); udelay(50); } reboot_type = BOOT_TRIPLE; // BOOT_CF9_SAFE不成功再到BOOT_TRIPLE break; case BOOT_TRIPLE: load_idt(&no_idt); __asm__ __volatile__("int3"); /* We're probably dead after this, but... */ reboot_type = BOOT_KBD; break; } } }
BOOT_ACPI->BOOT_KBD->BOOT_ACPI->BOOT_EFI->BOOT_BIOS->BOOT_CF9_SAFE->BOOT_TRIPLE…
* Will now restart [ 716.203870] reboot: Restarting system [ 716.208485] reboot: reboot_type: 97(a) [ 716.212675] reboot: 11111reboot_type: 97(a) [ 716.217350] acpi_reboot() acpi is disable... [ 716.222121] reboot: 11111reboot_type: 107(k) [ 716.226900] reboot: native_machine_emergency_restart() in KBD reboot... [ 718.637262] reboot: 11111reboot_type: 97(a) [ 718.641936] acpi_reboot() acpi is disable... [ 718.646707] reboot: 11111reboot_type: 107(k) [ 718.651484] reboot: native_machine_emergency_restart() in KBD reboot... [ 721.061846] reboot: 11111reboot_type: 101(e) [ 721.066616] reboot: 11111reboot_type: 98(b)
四、u-boot环境reset
U_BOOT_CMD( reset, 1, 0, do_reset, "Perform RESET of the CPU", "" );
do_reset函数实现在arch/x86/cpu/cpu.c文件:
int do_reset(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) { printf("resetting ...\n"); /* wait 50 ms */ udelay(50000); disable_interrupts(); reset_cpu(0); /*NOTREACHED*/ return 0; } __weak void reset_cpu(ulong addr) { /* Do a hard reset through the chipset's reset control register */ outb(SYS_RST | RST_CPU, IO_PORT_RESET); for (;;) cpu_hlt(); } void x86_full_reset(void) { outb(FULL_RST | SYS_RST | RST_CPU, IO_PORT_RESET); }
上面列出2个复位的函数:reset_cpu、x86_full_reset。它们只有细微区别,但都是往IO_PORT_RESET这个端口上写数值。接着看看这些宏定义是什么。它们的定义位于文件arch/x86/include/asm/processor.h:
/* * This register is documented in (for example) the Intel Atom Processor E3800 * Product Family Datasheet in "PCU - Power Management Controller (PMC)". * * RST_CNT: Reset Control Register (RST_CNT) Offset cf9. * * The naming follows Intel's naming. */ #define IO_PORT_RESET 0xcf9 enum { SYS_RST = 1 << 1, /* 0 for soft reset, 1 for hard reset */ RST_CPU = 1 << 2, /* initiate reset */ FULL_RST = 1 << 3, /* full power cycle */ };
五、所遇问题及解决
六、小结
2、内核源码官网:
3、内核源码查询:
如果本文对阁下有帮助,可赞助笔者以输出更多好文章。
支付宝[email protected] 或 微信fly_camel_fly 均可。感谢!
本文固定链接: /embedded-linux/kernel-note-13-x86-linux-reboot-source-htm.html