一直以来,笔者只知道重启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 */
- }
- 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函数。
reboot是一个系统调用,man手册说明如下:
- /* 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);
如果不考虑影响其它服务、进程的话,直接在代码调用reboot即可完成系统重启功能。
三、内核空间在了解用户空间的执行过程后,再看看内核空间是如何实现的。
前节提到reboot是一个系统调用。其定义位于kernel/reboot.c文件,如下:
- 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);
函数最后调用machine_restart。接着看一下machine_restart实现(位于arch/x86/kernel/reboot.c):
- 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。
最终,重启实现的函数为native_machine_emergency_restar(位于arch/x86/kernel/reboot.c):
- 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;
- }
- }
- }
|