Linuxにおけるshutdown
shutdown
昨年のアドカレでLinuxのrebootについて調べた。のと、shutodown internalsというネタを提供してもらったので、とりあえずLinuxのshutdownを読む。
読みながらメモってる感じの記事です。
shutdown(2)
手癖でman 2 shutdown
したら引っ掛かり、システムコールあったんだ。と思ったら違った。
shutdown(2)
はソケットのshutdownだった。
shutdown(1)
/sbin/shutdown
systemd/initや各プロセスのKill、swapのunmountなど、ユーザランドの後処理をいい感じにした上でshutdownしている。
ユーザランドはどうせsystemdがいい感じにしてくれていてあんまり興味がわかないので、カーネルを読む。
kernel/reboot.c
去年読んだkernel/reboot.c
を思い出すと、shutdownっぽいのもそこにあった
/*
* Reboot system call: for obvious reasons only root may call it,
* and even root needs to set up some magic numbers in the registers
* so that some mistake won't make this reboot the whole machine.
* You can also set the meaning of the ctrl-alt-del-key here.
*
* reboot doesn't sync: do that yourself before calling this.
*/
SYSCALL_DEFINE4(reboot, int, magic1, int, magic2, unsigned int, cmd,
void __user *, arg)
{
struct pid_namespace *pid_ns = task_active_pid_ns(current);
char buffer[256];
int ret = 0;
/* We only trust the superuser with rebooting the system. */
if (!ns_capable(pid_ns->user_ns, CAP_SYS_BOOT))
return -EPERM;
/* For safety, we require "magic" arguments. */
if (magic1 != LINUX_REBOOT_MAGIC1 ||
(magic2 != LINUX_REBOOT_MAGIC2 &&
magic2 != LINUX_REBOOT_MAGIC2A &&
magic2 != LINUX_REBOOT_MAGIC2B &&
magic2 != LINUX_REBOOT_MAGIC2C))
return -EINVAL;
/*
* If pid namespaces are enabled and the current task is in a child
* pid_namespace, the command is handled by reboot_pid_ns() which will
* call do_exit().
*/
ret = reboot_pid_ns(pid_ns, cmd);
if (ret)
return ret;
/* Instead of trying to make the power_off code look like
* halt when pm_power_off is not set do it the easy way.
*/
if ((cmd == LINUX_REBOOT_CMD_POWER_OFF) && !kernel_can_power_off())
cmd = LINUX_REBOOT_CMD_HALT;
mutex_lock(&system_transition_mutex);
switch (cmd) {
case LINUX_REBOOT_CMD_RESTART:
kernel_restart(NULL);
break;
case LINUX_REBOOT_CMD_CAD_ON:
C_A_D = 1;
break;
case LINUX_REBOOT_CMD_CAD_OFF:
C_A_D = 0;
break;
case LINUX_REBOOT_CMD_HALT:
kernel_halt();
do_exit(0);
case LINUX_REBOOT_CMD_POWER_OFF:
kernel_power_off();
do_exit(0);
break;
case LINUX_REBOOT_CMD_RESTART2:
ret = strncpy_from_user(&buffer[0], arg, sizeof(buffer) - 1);
if (ret < 0) {
ret = -EFAULT;
break;
}
buffer[sizeof(buffer) - 1] = '\0';
kernel_restart(buffer);
break;
#ifdef CONFIG_KEXEC_CORE
case LINUX_REBOOT_CMD_KEXEC:
ret = kernel_kexec();
break;
#endif
#ifdef CONFIG_HIBERNATION
case LINUX_REBOOT_CMD_SW_SUSPEND:
ret = hibernate();
break;
#endif
default:
ret = -EINVAL;
break;
}
mutex_unlock(&system_transition_mutex);
return ret;
}
こことかそれっぽい。
case LINUX_REBOOT_CMD_POWER_OFF:
kernel_power_off();
do_exit(0);
break;
kernel_power_off
してからdo_exit(0)
してる。
poweroff
してexit
してるの何だ…
kernel_power_off
/**
* kernel_power_off - power_off the system
*
* Shutdown everything and perform a clean system power_off.
*/
void kernel_power_off(void)
{
kernel_shutdown_prepare(SYSTEM_POWER_OFF);
do_kernel_power_off_prepare();
migrate_to_reboot_cpu();
syscore_shutdown();
pr_emerg("Power down\n");
kmsg_dump(KMSG_DUMP_SHUTDOWN);
machine_power_off();
}
EXPORT_SYMBOL_GPL(kernel_power_off);
最後のmachine_power_off
が物理的に落としていそうな雰囲気。辿っていけばACPIとかありそう。
machine_power_off
見たところアーキテクチャ毎に定義が違うので、今回はx86のものを読む
static void native_machine_power_off(void)
{
if (kernel_can_power_off()) {
if (!reboot_force)
machine_shutdown();
do_kernel_power_off();
}
/* A fallback in case there is no PM info available */
tboot_shutdown(TB_SHUTDOWN_HALT);
}
struct machine_ops machine_ops __ro_after_init = {
.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_CRASH_DUMP
.crash_shutdown = native_machine_crash_shutdown,
#endif
};
void machine_power_off(void)
{
machine_ops.power_off();
}
てな感じで、machine_power_off()
→machine_ops.power_off()
→native_machine_power_off()
の順番。
native_machine_power_off
を読んでみると、カーネルで電源を切れる場合はmachine_shutdown
、do_kernel_power_off
が、そうでない場合はtboot_shutdown
が呼ばれている。
先にtboot_shutdown
の方を見てみると、arch/x86/kernel/tboot.cに定義されていた。tboot
はtrusted bootの略かな。
machine_shutdown
とdo_kernel_power_off
を読もうかと思ったが、machine_shutdown
の方はいらんな。shutdown
なので。
do_kernel_power_off
do_kernel_power_off
を見るといきなりコメントでゴールっぽい文字列が見えて嬉しかった。
/**
* do_kernel_power_off - Execute kernel power-off handler call chain
*
* Expected to be called as last step of the power-off sequence.
*
* Powers off the system immediately if a power-off handler function has
* been registered. Otherwise does nothing.
*/
void do_kernel_power_off(void)
{
struct sys_off_handler *sys_off = NULL;
/*
* Register sys-off handlers for legacy PM callback. This allows
* legacy PM callbacks temporary co-exist with the new sys-off API.
*
* TODO: Remove legacy handlers once all legacy PM users will be
* switched to the sys-off based APIs.
*/
if (pm_power_off)
sys_off = register_sys_off_handler(SYS_OFF_MODE_POWER_OFF,
SYS_OFF_PRIO_DEFAULT,
legacy_pm_power_off, NULL);
atomic_notifier_call_chain(&power_off_handler_list, 0, NULL);
unregister_sys_off_handler(sys_off);
}
と思ったらpower_off_handler_list
`を追いかけなきゃいけなそうで面倒。
これはregister_sys_off_handler
で登録されていて、legacy_pm_power_off
もそう。
とりあえずdrivers/acpi以下の奴を読んでみる。
acpi_power_off
acpi_power_off_prepare
とacpi_power_off
が登録されているので、acpi_power_off
の方を読む。SYS_OFF_MODE_POWER_OFF
で登録されてるし。
static int acpi_power_off(struct sys_off_data *data)
{
/* acpi_sleep_prepare(ACPI_STATE_S5) should have already been called */
pr_debug("%s called\n", __func__);
local_irq_disable();
acpi_enter_sleep_state(ACPI_STATE_S5);
return NOTIFY_DONE;
}
acpi_enter_sleep_state(ACPI_STATE_S5)
が本質っぽい
ACPI_STATE_S5
はlinux/acpi.h
経由でincludeされているinclude/acpi/actypes.h
に(u8) 5
で定義されている。
acpi_enter_sleep_state
はdrivers/acpi/acpica/hwxfsleep.c
かな。
acpi_enter_sleep_state
drivers/acpi/acpica/hwesleep.c
この先潜るのが結構厄介だな。
引数のsleep_state
が渡されるのはacpi_os_enter_sleep
で、そのあとにacpi_write
されてるのがそれっぽい。
acpi_os_enter_sleep
を辿ってみるとacpi_os_prepare_sleep
が呼ばれそう。
で、そこでは__acpi_os_prepare_sleep
が呼ばれていて、その関数はacpi_os_set_prepare_sleep
で設定される。
どこで設定されてるか探してたらarch/x86/kernel/tboot.c
に戻ってきた…
tboot_sleep
static int tboot_sleep(u8 sleep_state, u32 pm1a_control, u32 pm1b_control)
{
static u32 acpi_shutdown_map[ACPI_S_STATE_COUNT] = {
/* S0,1,2: */ -1, -1, -1,
/* S3: */ TB_SHUTDOWN_S3,
/* S4: */ TB_SHUTDOWN_S4,
/* S5: */ TB_SHUTDOWN_S5 };
if (!tboot_enabled())
return 0;
tboot_copy_fadt(&acpi_gbl_FADT);
tboot->acpi_sinfo.pm1a_cnt_val = pm1a_control;
tboot->acpi_sinfo.pm1b_cnt_val = pm1b_control;
/* we always use the 32b wakeup vector */
tboot->acpi_sinfo.vector_width = 32;
if (sleep_state >= ACPI_S_STATE_COUNT ||
acpi_shutdown_map[sleep_state] == -1) {
pr_warn("unsupported sleep state 0x%x\n", sleep_state);
return -1;
}
tboot_shutdown(acpi_shutdown_map[sleep_state]);
return 0;
}
そう、これはさっき読んだtboot_shutdown
を呼んでいる…
shutdown = (void(*)(void))(unsigned long)tboot->shutdown_entry;
shutdown();
/* should not reach here */
while (1)
halt();
で、これよ。tboot->shutdown_entry
が実体っぽいんだけど、tboot
はtboot_probe
で設定されていそう。
この先はかなり深そうなので、今回は一旦ここまでで気合が出たときに探します…それよりBSDとか他のコードが気になってきた。
終わりに
またこの記事はn01e0 Advent Calendar 2024の4日目の記事とします。
Comments