Linux Kernel(4.14) Hacks

rousalome.egloos.com

포토로그 Kernel Crash




[라즈베리파이][리눅스커널] 인터럽트 컨택스트에서 스케쥴링을 하면? [라즈베리파이][커널]인터럽트

인터럽트 컨택스트에서 프로세스가 휴면하면 어떤 일이 벌어질까요? 커널은 이를 감지하고 커널 패닉을 유발시킵니다.

인터럽트 컨택스트에서 스케쥴링을 하면 안 됩니다. 왜냐면, 짧은 시간에 인터럽트 핸들러를 실행하고 인터럽트 벡터로 다시 돌아가 이미 중단시킨 프로세스를 다시 동작시켜야 하기 때문입니다. 그런데 인터럽트 컨택스트에서 스케쥴링을 하면 커널 입장에서 많은 동작을 수행해야 합니다. 당연히 시간이 오래 걸립니다.
 
이제 인터럽트 컨택스트에서 스케쥴링 할 때 어떤 흐름으로 커널 패닉이 발생하는지 살펴보겠습니다. 프로세스가 스케쥴링 즉 휴면할 때 __schedule() 함수를 호출합니다. 이 함수를 열어보면 앞 단에 schedule_debug()란 함수를 호출해서 현재 프로세스가 휴면할 수 있는 조건인지 점검합니다. 

혹시 세니티 체크(Sanity Check)이란 용어를 들어보신 적이 있나요? 어떤 함수에 전달되는 파라미터가 정상인지 점검하는 에러 체크 루틴입니다. 보통 커널 핵심 함수 구현부 앞 단에 함수 파라미터들이 정상값인지 점검하는 코드가 많습니다. 위 코드 흐름에서는 schedule_debug() 함수가 이 임무를 수행합니다.

인터럽트 컨택스트에서 다음 schedule_debug() 함수를 호출하면 in_atomic_preempt_off [1] 조건에 걸려 커널 패닉으로 리셋됩니다.
static inline void schedule_debug(struct task_struct *prev)
{
#ifdef CONFIG_SCHED_STACK_END_CHECK
if (unlikely(task_stack_end_corrupted(prev)))
panic("corrupted stack end detected inside scheduler\n");
#endif
/*
* Test if we are atomic. Since do_exit() needs to call into
* schedule() atomically, we ignore that path. Otherwise whine
* if we are scheduling when we should not.
*/
if (unlikely(in_atomic_preempt_off() && prev->state != TASK_DEAD)) // <<-- [1]
__schedule_bug(prev); //<<--  
rcu_sleep_check();

profile_hit(SCHED_PROFILING, __builtin_return_address(0));

schedstat_inc(this_rq(), sched_count);
}
#define in_atomic_preempt_off() \
((preempt_count() & ~PREEMPT_ACTIVE) != PREEMPT_CHECK_OFFSET)


인터럽트 컨택스트에서 스케쥴링을 하다가 발생하는 커널 패닉은 리눅스 커널 커뮤니티에서 자주 볼 수 있는 로그입니다. 

이와 관련된 기존 이슈 하나를 소개하겠습니다. (https://patchwork.kernel.org/patch/4864261/) 아래 커널 로그는 인터럽트 컨택스트에서 뮤텍스락을 걸다가 커널 패닉이 발생했다고 말해줍니다. 참고로 뮤텍스락은 뮤텍스락을 다른 프로세스가 잡고 있으면 바로 휴면에 들어가기 때문에 인터럽트 컨택스트에서 쓰면 안 되는 함수입니다. 
BUG: sleeping function called from invalid context at ../kernel/locking/mutex.c:583
in_atomic(): 1, irqs_disabled(): 128, pid: 0, name: swapper/0
------------[ cut here ]------------
WARNING: CPU: 2 PID: 4828 at ../kernel/locking/mutex.c:479 mutex_lock_nested+0x3a0/0x3e8()
DEBUG_LOCKS_WARN_ON(in_interrupt()) // <<--[3]
Modules linked in:
CPU: 2 PID: 4828 Comm: Xorg.bin Tainted: G        W      3.17.0-rc3-00234-gd535c45-dirty #819
[<c0216690>] (unwind_backtrace) from [<c0212174>] (show_stack+0x10/0x14)
[<c0212174>] (show_stack) from [<c0867cc0>] (dump_stack+0x98/0xb8)
[<c0867cc0>] (dump_stack) from [<c02492a4>] (warn_slowpath_common+0x70/0x8c)
[<c02492a4>] (warn_slowpath_common) from [<c02492f0>] (warn_slowpath_fmt+0x30/0x40)
[<c02492f0>] (warn_slowpath_fmt) from [<c086a3f8>] (mutex_lock_nested+0x3a0/0x3e8)
[<c086a3f8>] (mutex_lock_nested) from [<c0294d08>](irq_find_host+0x20/0x9c)// [2]
[<c0294d08>] (irq_find_host) from [<c0769d50>] (of_irq_get+0x28/0x48)
[<c0769d50>] (of_irq_get) from [<c057d104>] (platform_get_irq+0x1c/0x8c)
[<c057d104>] (platform_get_irq) from [<c021a06c>] (cpu_pmu_enable_percpu_irq+0x14/0x38)
[<c021a06c>] (cpu_pmu_enable_percpu_irq) from [<c02b1634>] (flush_smp_call_function_queue+0x88/0x178)
[<c02b1634>] (flush_smp_call_function_queue) from [<c0214dc0>] (handle_IPI+0x88/0x160)
[<c0214dc0>] (handle_IPI) from [<c0208930>] (gic_handle_irq+0x64/0x68)
[<c0208930>] (gic_handle_irq) from [<c0212d04>] (__irq_svc+0x44/0x5c)
[<c0212d04>] (__irq_svc) from [<c02a2e30>] (ktime_get_ts64+0x1c8/0x200) // [1]
[<c02a2e30>] (ktime_get_ts64) from [<c032d4a0>] (poll_select_set_timeout+0x60/0xa8)
[<c032d4a0>] (poll_select_set_timeout) from [<c032df64>] (SyS_select+0xa8/0x118)
[<c032df64>] (SyS_select) from [<c020e8e0>] (ret_fast_syscall+0x0/0x48)

이제까지 배운 내용을 활용해서 위 로그를 분석해보겠습니다.

[1] 로그에서 보이는 __irq_svc는 인터럽트 벡터 함수입니다. 
[<c0214dc0>] (handle_IPI) from [<c0208930>] (gic_handle_irq+0x64/0x68)
[<c0208930>] (gic_handle_irq) from [<c0212d04>] (__irq_svc+0x44/0x5c)
[<c0212d04>] (__irq_svc) from [<c02a2e30>] (ktime_get_ts64+0x1c8/0x200) //<<--[1]

이 정보로 인터럽트가 발생했다고 판단할 수 있습니다. 이후 gic_handle_irq와handle_IPI 순서로 인터럽트를 처리하는 함수가 호출합니다. 

자 그럼 [1] 코드 흐름을 우리는 어떻게 해석할 수 있죠? 인터럽트가 발생해서 인터럽트 벡터가 실행됐으므로 당연히 인터럽트 컨택스트라 할 수 있습니다. 

[2] 로그에서 뮤텍스락을 획득합니다. 
[<c02492f0>] (warn_slowpath_fmt) from [<c086a3f8>] (mutex_lock_nested+0x3a0/0x3e8)
[<c086a3f8>] (mutex_lock_nested) from [<c0294d08>](irq_find_host+0x20/0x9c) // <<--[2]

마지막 [3] 로그를 보겠습니다. 
DEBUG_LOCKS_WARN_ON(in_interrupt()) //<<--[3]

현재 인터럽트 컨택스트인지 확인하여 커널 패닉을 유발합니다. 다음 [3] 로그와 같이 in_interrupt() 함수가 true를 리턴하니 DEBUG_LOCKS_WARN_ON 란 WARN() 함수가 실행됩니다. 참고로 WARN() 매크로 함수가 실행하면 그 시점의 콜스택을 뿌려줍니다.

이번 시간에는 인터럽트 컨택스트에 대해서 알아봤습니다. 인터럽트 컨택스트는 인터럽트가 발생 후 인터럽트를 처리하는 코드 흐름이라 빨리 코드를 실행해야 합니다. 이를 알려주는 매크로 함수는 in_interrupt() 입니다.

다음에는 인터럽트 핸들러는 언제 어떻게 호출하는지 알아보겠습니다.

#Reference 시스템 콜


Reference(워크큐)
워크큐(Workqueue) Overview


핑백

덧글

댓글 입력 영역