Linux Kernel(4.14) Hacks

rousalome.egloos.com

포토로그 Kernel Crash




[라즈베리파이] 인터럽트 백터 어셈블리 코드 및 동작 분석 [라즈베리파이][커널]인터럽트

인터럽트가 발생하면 __irq_svc 벡터로 점프합니다. 물론 해당 프로세스는 하던 일을 멈출 수 밖에 없는데요.
그럼 인터럽트 벡터에서 어떤 동작을 하는 지 어셈블리 코드를 분석하겠습니다.

#__irq_svc 코드 리뷰
[1]: 스택 공간을 0x4C 바이트만큼 확보합니다.

[2]--[3]: 현재 실행 중인 레지스터 R0부터 R14, PC까지 스택에 푸쉬합니다.

[4]: 0x80705398 메모리 공간에 있는 메모리 덤프 0x80c089ac를 로딩합니다.
    0x80c089ac는 handle_arch_irq란 함수 포인터 역할을 하는 변수인데 이 변수에는 gic_handle_irq가 지정되어 있습니다.
crash> p -x handle_arch_irq
handle_arch_irq = $3 = 0xc0100680
crash> sym 0xc0100680
c0100680 (t) gic_handle_irq /home001/austin/src/raspberry_kernel/drivers/irqchip/irq-gic.c: 439

[5]: 라즈베리 파이 커널은 ARM 프로세서 이므로 gic_handle_irq 함수를 호출합니다.
80705320 <__irq_svc>:
80705320: e24dd04c sub sp, sp, #76 ; 0x4c  //<< --[1]
80705324: e31d0004 tst sp, #4
80705328: 024dd004 subeq sp, sp, #4
8070532c: e88d1ffe stm sp, {r1, r2, r3, r4, r5, r6, r7, r8, r9, sl, fp, ip} //<< --[2]
80705330: e8900038 ldm r0, {r3, r4, r5}
80705334: e28d7030 add r7, sp, #48 ; 0x30
80705338: e3e06000 mvn r6, #0
8070533c: e28d204c add r2, sp, #76 ; 0x4c
80705340: 02822004 addeq r2, r2, #4
80705344: e52d3004 push {r3} ; (str r3, [sp, #-4]!)
80705348: e1a0300e mov r3, lr
8070534c: e887007c stm r7, {r2, r3, r4, r5, r6}
80705350: e1a096ad lsr r9, sp, #13
80705354: e1a09689 lsl r9, r9, #13
80705358: e5990008 ldr r0, [r9, #8]  //<< --[3]
8070535c: e3a0147f mov r1, #2130706432 ; 0x7f000000
80705360: e5891008 str r1, [r9, #8]
80705364: e58d004c str r0, [sp, #76] ; 0x4c
80705368: ebeb60e3 bl 801dd6fc <trace_hardirqs_off>
8070536c: e59f1024 ldr r1, [pc, #36] ; 80705398 <__irq_svc+0x78>  //<< --[4]
80705370: e1a0000d mov r0, sp
80705374: e28fe000 add lr, pc, #0
80705378: e591f000 ldr pc, [r1]   //<< --[5]
8070537c: ebeb6091 bl 801dd5c8 <trace_hardirqs_on>
80705380: e59d104c ldr r1, [sp, #76] ; 0x4c
80705384: e5891008 str r1, [r9, #8]
80705388: e16ff005 msr SPSR_fsxc, r5
8070538c: e24d0004 sub r0, sp, #4
80705390: e1801f92 strex r1, r2, [r0]
80705394: e8ddffff ldm sp, {r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, sl, fp, ip, sp, lr, pc}^
80705398: 80c089ac .word 0x80c089ac

#handle_arch_irq 설정  코드 리뷰
handle_arch_irq 주소는 아래 함수에서 확인할 수 있습니다. 코드를 보아 set_handle_irq 함수 호출로 handle_arch_irq 전역 변수에 함수 포인터를 지정합니다.
void __init set_handle_irq(void (*handle_irq)(struct pt_regs *))
{
80b036d4: e1a0c00d mov ip, sp
80b036d8: e92dd800 push {fp, ip, lr, pc}
80b036dc: e24cb004 sub fp, ip, #4
if (handle_arch_irq)
80b036e0: e59f300c ldr r3, [pc, #12] ; 80b036f4 <set_handle_irq+0x20>
80b036e4: e5932000 ldr r2, [r3]
80b036e8: e3520000 cmp r2, #0
return;

handle_arch_irq = handle_irq;
80b036ec: 05830000 streq r0, [r3]
80b036f0: e89da800 ldm sp, {fp, sp, pc}
80b036f4: 80c089ac .word 0x80c089ac

gic_handle_irq 함수 포인터 지정은 아래 __gic_init_bases 함수 내 set_handle_irq(gic_handle_irq); 구문에서 수행됩니다.
[linux/drivers/irqchip/irq-gic.c]
 static int __init __gic_init_bases(struct gic_chip_data *gic,
                                    int irq_start,
                                    struct fwnode_handle *handle)
{
// .. 생략 
         if (gic == &gic_data[0]) {
                 /*
                  * Initialize the CPU interface map to all CPUs.
                  * It will be refined as each CPU probes its ID.
                  * This is only necessary for the primary GIC.
                  */
                 for (i = 0; i < NR_GIC_CPU_IF; i++)
                         gic_cpu_map[i] = 0xff;
 #ifdef CONFIG_SMP
                 set_smp_cross_call(gic_raise_softirq);
 #endif
                 cpuhp_setup_state_nocalls(CPUHP_AP_IRQ_GIC_STARTING,
                                           "AP_IRQ_GIC_STARTING",
                                           gic_starting_cpu, NULL);
                 set_handle_irq(gic_handle_irq);

#CONFIG_PREEMPT 코드 리뷰
__irq_svc 어셈블리 코드에서 아래 C코드에서 보이는 CONFIG_PREEMPT 컨피그로 묶여 있는 코드가 안 보입니다. 
__irq_svc:
        svc_entry
        irq_handler

#ifdef CONFIG_PREEMPT
        ldr     r8, [tsk, #TI_PREEMPT]          @ get preempt count
        ldr     r0, [tsk, #TI_FLAGS]            @ get flags
        teq     r8, #0                          @ if preempt count != 0
        movne   r0, #0                          @ force flags to 0
        tst     r0, #_TIF_NEED_RESCHED
        blne    svc_preempt
#endif

여기서 CONFIG_PREEMPT 컨피그가 켜져 있는 지 간단히 확인하는 유용한 팁을 공유 드리겠습니다. 
#error "CONFIG_PREEMPT" 란 매크로 코드를 추가하고 컴파일을 하면 컴파일 에러가 발생하지 않습니다.
즉 이 컨피그는 꺼져 있다는 말입니다.
diff --git a/arch/arm/kernel/entry-armv.S b/arch/arm/kernel/entry-armv.S
index 9f157e7..19f3115 100644
--- a/arch/arm/kernel/entry-armv.S
+++ b/arch/arm/kernel/entry-armv.S
@@ -219,6 +219,7 @@ __irq_svc:
        irq_handler

 #ifdef CONFIG_PREEMPT
+#error "CONFIG_PREEMPT"
        ldr     r8, [tsk, #TI_PREEMPT]          @ get preempt count
        ldr     r0, [tsk, #TI_FLAGS]            @ get flags
        teq     r8, #0                          @ if preempt count != 0

다른 플렛폼에서는 CONFIG_PREEMPT 컨피그가 켜져 있는데 라즈베리파이는 리눅스 커널 교육용 SoC라
안정성(Stability)을 위해 이 피쳐를 끈 것 같습니다.

덧글

댓글 입력 영역