Arm Linux Kernel Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

84112
549
416224


IRQ Stack(ARM64) - Overview [Linux][Kernel] IRQ(Interrupt)

IRQ Stack란 뭘까요. IRQ가 쓰는 스택이라고 말할 수 있나요? 이번 시간에 IRQ Stack에 대한 내용을 조금 더 짚어 보겠습니다. 


IRQ Stack이란 IRQ가 떳을 때 사용하는 스택 공간입니다. 달리 설명하면 인터럽트 컨택스트로 코드가 실행될 때 스택에 지역 변수를 할당 받고 함수를 호출할 때 프레임 포인터 레지스터를 스택에 푸쉬합니다. 


프로세스가 생성될 때 스택 공간을 할당 받고 이 공간에서 코드가 실행됩니다. 스택이 어떻게 사용되는지 간단히 설명하면 아래와 같습니다. ARM32 비트 아키덱처에서 프로세스에게 0x2000 바이트 만큼 스택을 할당합니다.

아래 그림에서는 0xD000_2000 스택 Top 주소에서 함수가 호출되다가 인터럽트가 발생하면 같은 스택 공간에 인터럽트 벡터 함수로  점프합니다. 이후 인러텁트 서비스 루틴이 호출됩니다.

[스택주소]
0xD000_0000 // <<-- 스택 Bottom 주소
 // .. 생략 ...
0xD000_0FF0
0xD000_0FF8   D
0xD000_0FFC
0xD000_1000  irq_el1  //<<-- IRQ Stack Bottom 주소

// .. 생략 ...
0xD000_1FE8
0xD000_1FEC
0xD000_1FF0     C
0xD000_1FF4
0xD000_1FF8     B
0xD000_1FFC
0xD000_2000    A  //<-- 스택 Top 주소



이렇게 인터럽트가 발생하면 프로세스에 할당된 스택에서 인터럽트 서브 루틴을 실행하는데 그럼 왜 IRQ 용 스택을 따로 쓸까요? 그 이유는 ARM64 비트 아키텍처에서 커널 공간에서 쓰이는 스택 사이즈 0x4000 바이트가 충분하지 않다는 게 가장 큰 이유죠. 아래는 ARM32 비트 아키텍처에서 network packet을 처리하는 함수 흐름입니다. IPI call을 하는 동작인데, 콜 스택이 매우 깁니다.
-000|do_undefinstr()
-001|__und_svc_fault(asm)
 -->|exception
-002|__list_add(new = 0xEC4B3C80, prev = 0xEC4B3C80, next = 0xEC4B3BC8)  // <<-- kernel panic
-003|____napi_schedule(inline)
-003|rps_trigger_softirq()
-004|csd_unlock(inline)
-004|flush_smp_call_function_queue()
-005|handle_IPI() // ipinr = 4 //<<--IPI_TIMER
-006|gic_handle_irq()
-007|__irq_svc(asm)
 -->|exception
-008|__preempt_count_sub(inline)
-008|preempt_count_sub()
-009|should_resched(inline)
-009|__local_bh_enable_ip()
-010|qtaguid_mt()
-011|ipt_do_table()
-012|nf_iterate()
-013|nf_hook_slow()
-014|NF_HOOK_THRESH(inline)
-014|NF_HOOK(inline)
-014|ip_local_deliver()
-015|ip_rcv_finish()
-016|__netif_receive_skb_core()
-017|rcu_read_unlock(inline)
-017|process_backlog()
-018|static_key_count(inline)
-018|static_key_false(inline)
-018|trace_napi_poll(inline)
-018|net_rx_action()
-019|static_key_count(inline)
-019|static_key_false(inline)
-019|trace_softirq_exit(inline)
-019|__do_softirq()
-020|do_softirq_own_stack(inline)
-020|invoke_softirq(inline)
-020|irq_exit()
-021|handle_IPI() // ipinr = 4 //<<--IPI_TIMER
-022|gic_handle_irq()
-023|__irq_svc(asm) 
 -->|exception
-024|arch_local_irq_restore(inline)
-024|__slab_alloc.constprop.8()
-025|slab_alloc_node(inline)
-025|slab_alloc(inline)
-025|kmem_cache_alloc()
-026|new_handle(inline)
-026|jbd2__journal_start()
-027|__ext4_journal_start_sb()
-028|ext4_da_write_begin()
-029|generic_perform_write()
-030|__generic_file_write_iter()
-031|ext4_file_write_iter()
-032|new_sync_write()
-033|vfs_write()
-034|sdcardfs_write()
-035|vfs_write()
-036|SYSC_write(inline)
-036|sys_write()
-037|ret_fast_syscall(asm)

위 코드가 ARM64 비트 아키텍처에서 수행되면 스택 오버플로우가 발생합니다. 그래서 각 CPU별로 IRQ Stack 공간을 부팅 도중 따로 할당해 놓고 인터럽트가 발생할 때 마다 IRQ 스택에서 함수를 호출합니다.

IRQ Stack 공간은 per-cpu 타입 irq_stack 변수로 확인할 수 있습니다.

아래 코드를 보면 IRQ Stack irq_stack이란per-cpu 타입 변수로 처리된다는 사실을 있구요. IRQ Stack 사이즈는0x4000이군요.

staticinline bool on_irq_stack(unsigned long sp, int cpu)

{

/* variable names the same as kernel/stacktrace.c */

unsigned long low = (unsigned long)per_cpu(irq_stack, cpu);

unsigned long high = low + IRQ_STACK_START_SP; //IRQ_STACK_START_SP = 0x3FFC

 

return (low <= sp && sp <= high);

}

 
이제 좀 더 깊게 프로세스가 인터럽트가 발생하면 IRQ Stack으로 이동하며 코드를 실행하는지 분석 하겠습니다.
IRQ가 Trigger되면 el1_irq란 벡터 주소로 점프합니다.
el1_irq:
kernel_entry 1
enable_dbg
#ifdef CONFIG_TRACE_IRQFLAGS
bl trace_hardirqs_off
#endif

get_thread_info tsk
irq_handler  //<<--
 
위 코드에서 irq_handler는 사실 매크로로 정의됐습니다. 네번 째 줄에 irq_stack_entry 코드를 볼 수 있습니다.
.macro irq_handler
ldr_l x1, handle_arch_irq
mov x0, sp
irq_stack_entry
blr x1
irq_stack_exit
.endm

.text

아래 irq_stack_entry  코드가 인터럽트가 발생하면 IRQ Stack으로 스택 실행 공간을 이동하는 핵심 코드입니다.

[1]: 해당 CPU에 해당 하는 irq_stack 주소를 가져와 x25에 저장합니다.
[2]: x26에 IRQ Stack 사이즈를 대입하네요.
[3]: x26에 irq_stack 주소(x25)에 스택 사이즈(x26)를 더해요.
[4]: 스택 주소를 IRQ Stack 주소로 바꾸네요.
[5]: 원래 스택 주소가 담겨져 있는 x19와 x29를 IRQ Stack bottom address에서 -0x10 만큼 떨어진 위치에 Push하네요.
.macro irq_stack_entry
mov x19, sp // preserve the original sp

/*
* Compare sp with the current thread_info, if the top
* ~(THREAD_SIZE - 1) bits match, we are on a task stack, and
* should switch to the irq stack.
*/
and x25, x19, #~(THREAD_SIZE - 1)
cmp x25, tsk
b.ne 9998f

this_cpu_ptr irq_stack, x25, x26  //<<--[1]
mov x26, #IRQ_STACK_START_SP //<<--[2]
add x26, x25, x26 //<<--[3]

/* switch to the irq stack */
mov sp, x26 //<<--[3]

/*
* Add a dummy stack frame, this non-standard format is fixed up
* by unwind_frame()
*/
stp     x29, x19, [sp, #-16]!  //<<--[4]
mov x29, sp

정리하면 인터럽트가 발생하면 irq_stack_entry 매크로 코드가 실행되어 인터럽트 서비스 루틴을 처리하기 위한 전용 인터럽트 스택 주소로 점프합니다. 이렇게 인터럽트는 아키텍처와 연관된 동작이 많은 것 같습니다.

다음에는 실제 코어 덤프에서 IRQ Stack이 어떻게 Push되는지 점검해볼께요


덧글

댓글 입력 영역