보통 시스템이 리부팅될 때 동작에 대해 상세히 다룬 글이 없는 것 같아요.
그래서 유저 스페이스에서 reboot 시스템 콜을 수행하면 어떤 흐름으로 시스템이 리셋되는지 살펴볼께요.
가끔 시스템이 리부팅하는 과정에서 락업이나 커널 크래시가 발생하거든요.
이럴 때 어떤 흐름으로 리부팅 되는지에 대한 정보를 알면 어느 포인트에서 디버깅을 해야 할 지 빨리 파악할 수 있어요.
sys_reboot이란 함수는 아래 코드로 정의되어 있구요. kernel_restart() 함수가 호출되요.
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) && !pm_power_off)
cmd = LINUX_REBOOT_CMD_HALT;
mutex_lock(&reboot_mutex);
switch (cmd) {
case LINUX_REBOOT_CMD_RESTART:
kernel_restart(NULL);
break;
kernel_restart() 함수가 시스템 리부팅을 여러 함수들을 호출하는데요.
각각 함수들에 대해서 점검을 해보도록 할께요.
요약하면,
kernel_restart_prepare() 함수는 reboot_notifier_list로 등록된 notifier call을 수행해서 시스템을 종료하고,
플렛폼 버스에 등록된 디바이스 드라이버의 shutdown callback 함수를 호출해서 각각 디바이스 드라이버 shutdown sequence를 타게 되죠.
syscore_shutdown() &syscore_ops_list에 등록된 함수들의 shutdown 콜백함수를 호출해서 시스템 core 드라이버 종료 Sequence를 타요.
machine_restart() 함수는 각각 SoC 머신에 따른 Sequence가 수행되요.
SoC업체 Mediatek, Qualcomm, nVidia에 따라 동작이 달라져요.
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);
}
kernel_shutdown_prepare() 함수를 상세히 살펴보면 reboot notifier call을 호출하고
device_shutdown() 호출로 플렛폼 버스에 등록된 디바이스 드라이버 shutdown 콜백 함수를 호출해요.
static void kernel_shutdown_prepare(enum system_states state)
{
blocking_notifier_call_chain(&reboot_notifier_list,
(state == SYSTEM_HALT) ? SYS_HALT : SYS_POWER_OFF, NULL);
system_state = state;
usermodehelper_disable();
device_shutdown();
}
reboot notifier call 소개를 잠깐 소개하면,
notifier call이라는게 특정 함수를 특정 시점에 호출하고 싶을 때 등록하거든요.
만약 특정 디바이스를 종료하는 함수를 리부팅 되는 시점에 꼭 호출하고 싶으면 reboot_notifier_list에
reboot notifier call을 등록하면 되요.
아래 코드 조각은 perf_reboot() 함수를 reboot notifier call로 등록하는 루틴이니 참고하시면 좋을 것 같아요.
extern struct blocking_notifier_head reboot_notifier_list;
void __init perf_event_init(void)
{
int ret;
idr_init(&pmu_idr);
perf_event_init_all_cpus();
init_srcu_struct(&pmus_srcu);
perf_pmu_register(&perf_swevent, "software", PERF_TYPE_SOFTWARE);
perf_pmu_register(&perf_cpu_clock, NULL, -1);
perf_pmu_register(&perf_task_clock, NULL, -1);
perf_tp_register();
perf_cpu_notifier(perf_cpu_notify);
register_reboot_notifier(&perf_reboot_notifier);
// .. 생략..
static int perf_reboot(struct notifier_block *notifier, unsigned long val, void *v)
{
int cpu;
for_each_online_cpu(cpu)
perf_event_exit_cpu(cpu);
return NOTIFY_OK;
}
static struct notifier_block perf_reboot_notifier = {
.notifier_call = perf_reboot,
.priority = INT_MIN,
};
이제 아래 device_shutdown() 함수 코드를 보면
플렛폼 버스에 등록된 디바이스 드라이버의 shutdown call함수를 호출해서 각각 디바이스 드라이버 shutdown sequence를 타게 되죠.
void device_shutdown(void)
{
// .. 생략 ..
while (!list_empty(&devices_kset->list)) {
dev = list_entry(devices_kset->list.prev, struct device,
kobj.entry);
// .. 생략 ..
if (dev->class && dev->class->shutdown) {
if (initcall_debug)
dev_info(dev, "shutdown\n");
dev->class->shutdown(dev);
} else if (dev->bus && dev->bus->shutdown) {
if (initcall_debug)
dev_info(dev, "shutdown\n");
dev->bus->shutdown(dev);
} else if (dev->driver && dev->driver->shutdown) {
if (initcall_debug)
dev_info(dev, "shutdown\n");
dev->driver->shutdown(dev);
}
// .. 생략 ..
syscore_shutdown() 함수는 syscore_ops_list에 등록된 모듈 중에 shutdown callback가
정의되어 있으면 해당 콜백 함수를 호출해요.
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 %pF\n", ops->shutdown);
ops->shutdown();
}
mutex_unlock(&syscore_ops_lock);
}
cpufreq_syscore_ops로 syscore_ops_list에 등록하는 코드 조각을 아래와 같은데요,
shutdown callback 함수로 cpufreq_suspend() 함수가 호출되요.
void cpufreq_suspend(void)
{
//.. 생략..
for_each_active_policy(policy) {
if (__cpufreq_governor(policy, CPUFREQ_GOV_STOP))
pr_err("%s: Failed to stop governor for policy: %p\n",
__func__, policy);
else if (cpufreq_driver->suspend
&& cpufreq_driver->suspend(policy))
pr_err("%s: Failed to suspend driver: %p\n", __func__,
policy);
}
//.. 생략 ..
static struct syscore_ops cpufreq_syscore_ops = {
.shutdown = cpufreq_suspend,
};
static int __init cpufreq_core_init(void)
{
if (cpufreq_disabled())
return -ENODEV;
cpufreq_global_kobject = kobject_create_and_add("cpufreq", &cpu_subsys.dev_root->kobj);
BUG_ON(!cpufreq_global_kobject);
register_syscore_ops(&cpufreq_syscore_ops);
return 0;
}
void register_syscore_ops(struct syscore_ops *ops)
{
mutex_lock(&syscore_ops_lock);
list_add_tail(&ops->node, &syscore_ops_list);
mutex_unlock(&syscore_ops_lock);
}
reboot 과정을 각 시스템 드라이버 관점에서 요약하면 아래와 같아요.
1> blocking_notifier_call_chain(&reboot_notifier_list,...) -> reboot_notifier_list로 등록된 notifier call을 수행
2> device_shutdown() -> 플렛폼 버스에 등록된 디바이스 드라이버의 shutdown callback 함수를 호출해서 각각 디바이스 드라이버 shutdown sequence
3> syscore_shutdown() -> &syscore_ops_list에 등록된 함수들의 shutdown 콜백함수를 호출해서 시스템 core 드라이버 종료 Sequence
이 과정에서 특정 shutdown 함수에서 stuck되면 리부팅 하다가 락업이 되거든요.
아래 Case Study에서 비슷한 디버깅 정보가 확인되었거든요.
-000|context_switch(inline)
-000|__schedule()
-001|schedule_preempt_disabled()
-002|spin_lock(inline)
-002|__mutex_lock_common(inline)
-002|__mutex_lock_slowpath(lock_count = 0xC1327164)
-003|current_thread_info(inline)
-003|mutex_set_owner(inline)
-003|mutex_lock(lock = 0xC1327164) // cpu_add_remove_lock
-004|cpu_hotplug_disable()
-005|migrate_to_reboot_cpu()
-006|kernel_restart(cmd = 0x0)
-007|SYSC_reboot(inline)
-007|sys_reboot(magic1 = -18751827, ?, cmd = 19088743, arg = 0)
-008|ret_fast_syscall(asm)
-->|exception
-009|__reboot(asm) // android/bionic/libc/arch-arm/syscalls/__reboot.S|\9
-010|restart(?)
# Reference: For more information on 'Linux Kernel';
디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 1
디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 2
'Core BSP 분석 > 리눅스 커널 핵심 분석' 카테고리의 다른 글
LKML - [PATCH v2] clk: fix reentrancy of clk_enable() on UP systems (0) | 2023.05.06 |
---|---|
와치독 Watchdog Kick 동작 비교 (Qualcomm vs Intel vs Mediatek vs nVidia) (0) | 2023.05.06 |
[Linux][Kernel] 타이머(Timer) Overview (0) | 2023.05.06 |
파레트 (0) | 2023.05.06 |
[Kernel]panic @__wake_up (0) | 2023.05.06 |