一直以来,笔者只知道重启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函数。
/* 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
#include
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
#include
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;
}
}
}
* 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_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 */
};