Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

230224
1178
109352


Reboot - Kernel Rebooting(커널 리부팅) Sequence Linux Kernel - Core Analysis

보통 시스템이 리부팅될 때 동작에 대해 상세히 다룬 글이 없는 것 같아요.
그래서 유저 스페이스에서 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에서 비슷한 디버깅 정보가 확인되었거든요.
http://rousalome.egloos.com/9966406
-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(프로세스 관리)
4.9 프로세스 컨택스트 정보는 어떻게 저장할까?
 4.9.1 컨택스트 소개
 4.9.2 인터럽트 컨택스트 정보 확인하기
 4.9.3 Soft IRQ 컨택스트 정보 확인하기
 4.9.4 선점 스케줄링 여부 정보 저장
4.10 프로세스 디스크립터 접근 매크로 함수
 4.10.1 current_thread_info()
 4.10.2 current 매크로란
4.11 프로세스 디버깅
 4.11.1 glibc fork 함수 gdb 디버깅
 4.11.2 유저 프로그램 실행 추적 

덧글

댓글 입력 영역