sys_reboot-restart模式linux源码解读3103

kernel_restart()

linux 6.6

sys_reboot函数调用栈:

作用: 重启。这在中断上下文中调用是不安全的

参数:

  • cmd:指向缓冲区的指针,其中包含命令行参数,可以为NULL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void kernel_restart(char *cmd) {
// 调用准备函数,处理一些重启前的准备工作
kernel_restart_prepare(cmd);
// 调用系统重启前的额外准备工作
do_kernel_restart_prepare();
// 将当前进程迁移到重启所需的CPU上,确保重启操作是在正确的CPU上执行
migrate_to_reboot_cpu();
// 执行系统关闭的钩子函数(例如,关闭文件系统等)
// 这个机制主要是跟reboot有关,可以让其他子系统注册一些回调函数,在系统reboot的时候执行
syscore_shutdown();

// 判断是否有提供命令行参数
if (!cmd)
pr_emerg("Restarting system\n");
else
pr_emerg("Restarting system with command '%s'\n", cmd);

// 打印内核日志的内容,通常包括系统状态和错误信息
kmsg_dump(KMSG_DUMP_SHUTDOWN);
// 执行与机器硬件相关的重启操作,这通常是硬件相关的操作,可能涉及重启操作系统、重启硬件等
machine_restart(cmd);
}
// 通过EXPORT_SYMBOL_GPL将kernel_restart函数导出为符号,使得其他模块可以调用它。
EXPORT_SYMBOL_GPL(kernel_restart);

kernel_restart_prepare()

linux 6.6

作用: 处理一些重启前的准备工作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void kernel_restart_prepare(char *cmd) {
// 调用阻塞的通知链,通知所有注册的重启通知处理函数,传递重启相关信息
// 通知链是一种机制,允许多个函数注册为回调函数,在特定事件发生时被调用
// reboot_notifier_list是一个通知链,所有希望在系统重启时执行某些操作的函数都应该注册到这个链表。通过传递SYS_RESTART作为事件代码(注册此通知链的回调函数通常属于内核子系统或驱动程序,例如卸载文件系统、关闭网络接口等)
blocking_notifier_call_chain(&reboot_notifier_list, SYS_RESTART, cmd);

// 设置系统状态为重启中,表明系统正处于重启流程中
system_state = SYSTEM_RESTART;

// 禁用用户态帮助程序,这可能会阻止再重启期间重启新的用户态进程
// 用户态帮助程序通常负责在内核和用户空间之间传递任务或启动进程。
usermodehelper_disable();

// 执行设备的关闭操作,例如卸载文件系统、关闭网络接口等
device_shutdown();
}

do_kernel_restart_prepare()

linux 6.6

1
2
3
4
5
6
// static关键字意味着该函数只有在当前源文件内可见
static void do_kernel_restart_prepare(void) {
// 调用阻塞的通知链,通知所有注册的回调函数进行重启前的准备工作(注册此通知链的回调函数通常是与硬件相关的重启准备处理程序,比如watchdog)
// 这里的事件代码是0,表示没有特定的事件代码,回调函数只需要执行它们自己的重启准备逻辑
blocking_notifier_call_chain(&restart_prep_handler_list, 0, NULL);
}

migrate_to_reboot_cpu()

linux 6.6

作用: 将当前任务迁移到一个特定的CPU上运行,通常是为了解决重启或迁移操作时的CPU选择问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void migrate_to_reboot_cpu(void) {
// 启动cpu总是逻辑cpu0
int cpu = reboot_cpu;

cpu_hotplug_disable();

// 确保要重启的cpu处于在线状态
if (!cpu_online(cpu))
cpu = cpumask_first(cpu_online_mask);

// 防止与迁移此任务的其他任务竞争
current->flags |= PF_NO_SETAFFINITY;

// 确保只在合适的处理器上运行
set_cpus_allowed_ptr(current, cpumask_of(cpu));
}

syscore_shutdown()

作用: 执行所有注册的系统核心关机回调

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void syscore_shutdown(void) {
struct syscore_ops *ops;

mutex_lock(&syscore_ops_lock);

list_for_each_entry_reverse(ops, &syscore_ops_list, node)
if (ops->shutdown) {
if (initcall_debug)
pr_info("PM: Calling %pS\n", ops->shutdown);
ops->shutdown();
}

mutex_lock(&syscore_ops_lock);
}

machine_restart()

作用: 执行实际的重启操作

1
2
3
void machine_restart(char *cmd) {
machine_ops.restart(cmd);
}

native_machine_restart()

作用: 执行系统的重启操作

1
2
3
4
5
6
7
8
9
10
11
void native_machine_restart(char *__unused) {
// 打印日志,指示正在进行机器重启
pr_notice("machine restart\n");

// 如果没有强制重启(即reboot_force为假),则执行关机操作
if (!reboot_force)
machine_shutdown();

// 调用紧急重启函数,参数为0
__machine_emergency_restart(0);
}

machine_shutdown()

作用: 执行具体的关机操作

1
2
3
4
void machine_shutdown(void) {
// 调用machine_ops.shutdown()执行具体的关机操作
machine_ops.shutdown();
}

native_machine_shutdown()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
void native_machine_shutdown(void) {
#ifdef CONFIG_X86_IO_APIC
// 在禁用本地APIC之前禁用IO APIC
// 先停止外部中断的接收再禁用本地CPU的中断处理
clear_IO_APIC();
#endif

#ifdef CONFIG_SMP
// 禁用本地中断,以防止接收每个CPU的定时器中断,这可能会触发调度器的负载平衡
local_irq_disable();
// 停止所有非当前CPU的执行,这样可以确保所有CPU在关机过程中不再执行任何任务
stop_other_cpus();
#endif

// 清理和关闭本地APIC中断,涉及释放硬件资源
lapic_shutdown();
// 恢复引导时的中断模式。在系统启动时,中断控制器和处理器处于某种初始状态,在关机或重启过程中,需要恢复系统最初的中断模式,以确保关机操作能够顺利进行
// 因为DragonOS没有实现也没有启用legacy pic,所以暂时也没写
restore_boot_irq_mode();

#ifdef CONFIG_HPET_TIMER
// 禁用HPET
hpet_disable();
#endif

#ifdef CONFIG_X86_64
// 关闭IOMMU。IOMMU(输入输出内存管理单元)是一种硬件设备,用于管理内存映射。
x86_platform.iommu_shutdown();
#endif
}

__machine_emergency_restart()

1
2
3
4
5
6
// 当通过紧急方式重启时,这个变量被设置
static reboot_emergency;
static void __machine_emergency_restart(int emergency) {
reboot_emergency = emergency;
machine_ops.emergency_restart();
}

native_machine_emergency_restart()

linux 6.6

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
static void native_machine_emergency_restart(void) {
int i;
// 重试次数
int attempt = 0;
// 记录原始的重启类型
int orig_reboot_type = reboot_type;
// 存储重启模式
unsigned short mode;

// 如果发生了紧急重启,禁用所有VMX操作(虚拟化相关)
if (reboot_emergency)
emergency_reboot_disable_virtualization();

// 调用Tboot来关闭机器并准备重启
tboot_shutdown(TB_SHUTDOWN_REBOOT);

// 告诉BIOS我们想要冷重启还是热重启
mode = reboot_mode == REBOOT_WARM ? 0X1234 : 0;
// 写入0x472寄存器表示重启类型
*((unsigned short *)__va(0x472)) = mode;

// 如果EFI capsule已注册,强制执行EFI重启
if (efi_capsule_pending(NULL)) {
pr_info("EFI capsule is pending, forcing EFI reboot. \n");
reboot_type = BOOT_EFI;
}

// 死循环,逐步尝试不同的重启方式
for (;;) {
// 根据当前reboot_type选择不同的重启方法
switch (reboot_type) {
case BOOT_ACPI:
acpi_reboot();
// ACPI重启失败,转到键盘重启
reboot_type = BOOT_KBD;
break;

case BOOT_KBD:
// 执行与主板相关的特定修复(通常是对某些硬件的特定处理)
mach_reboot_fixups();

// 重试10次键盘重启方式,每次等待50微妙
for (i = 0; i < 10; i++) {
// 等待键盘事件
kb_wait();
udelay(50);
// 发送0xfe到键盘控制器以触发重启
// 0x64通常是键盘控制器的命令端口
outb(0xfe, 0x64);
udelay(50);
}

// 如果这是第一次尝试键盘重启,且原始重启类型是ACPI,则再次尝试ACPI
if (attempt == 0 && orig_reboot_type == BOOT_ACPI) {
attempt = 1;
reboot_type = BOOT_ACPI;
} else { // 否则转到EFI重启
reboot_type = BOOT_EFT;
}
break;

case BOOT_EFI:
efi_reboot(reboot_mode, NULL);
// 如果EFI重启失败,转到BIOS重启
reboot_type = BOOT_BIOS;
break;

case BOOT_BIOS:
// 尝试通过BIOS重启机器
machine_real_restart(MBR_BIOS);

// 如果到了这里,系统可能已经挂掉了,但我们依然会继续执行,转到CF9安全重启
reboot_type = BOOT_CF9_SAFE;
break;

case BOOT_CF9_FORCE:
// 设置标志
port_cf9_safe = true;
// 强制执行下一个重启方式(即CF9安全重启)
fallthrough;

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); // 请求硬重启
udelay(50);
// 执行实际的重启
outb(cf9|reboot_code, 0xcf9);
udelay(50);
}
// CF9重启失败后,转到三重重启方式
reboot_type = BOOT_TRIPLE;
break;

case BOOT_TRIPLE:
// 执行三重重启,通过int3触发调试中断
idt_invalidate();
// 执行int3指令,触发调试异常
__asm__ __volatile__("int3");

// 执行到这里系统很大可能挂掉了
reboot_type = BOOT_KBD; // 转到键盘重启
break;
}
}
}

后续有时间再继续整理重启的各种模式


sys_reboot-restart模式linux源码解读3103
http://example.com/2025/05/21/sys-reboot-restart模式linux源码解读/
作者
凌云行者
发布于
2025年5月21日
许可协议