Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

138199
1107
135866


[리눅스커널][인터럽트] 인터럽트 벡터 분석하기 5. 인터럽트

인터럽트가 발생했을 때 이를 처리하는 방식은 CPU 아키텍처에 의존적입니다. 라즈베리 파이는 ARMv7 CPU를 탑재했으니 이 기준으로 인터럽트 처리 방식을 살펴봅시다.

인터럽트가 발생하면 다음과 같이 커널 모드와 유저 모드별로 지정한 주소로 브랜치합니다.

__irq_svc: 커널 모드
__irq_usr: 유저 모드

이처럼 인터럽트가 발생하면 각 모드별로 실행하는 __irq_svc와 __irq_usr 같은 레이블을 인터럽트 벡터라고 부릅니다. 소프트웨어 관점에서 인터럽트가 발생하면 먼저 봐야 할 코드 분석의 출발점입니다.

__irq_svc 인터럽트 벡터 코드 분석으로 세부 동작 알아보기

커널 모드에서 인터럽트가 발생하면 ARMv7 프로세서는 인터럽트 벡터인 __irq_svc 레이블로 브랜치합니다. 이번에는 __irq_svc 레이블 코드를 분석해 세부 처리 방식을 알아보겠습니다.

__irq_svc 레이블 코드의 동작은 다음과 같이 분류할 수 있습니다.
스택 공간에 실행 중인 프로세스 레지스터 세트를 푸시
handle_arch_irq 전역변수에 지정한 함수인 bcm2836_arm_irqchip_handle_irq() 함수를 호출: 라즈비안에서 handle_arch_irq 변수는 bcm2836_arm_irqchip_handle_irq() 함수의 주소를 지정

_irq_svc 인터럽트 벡터 코드 분석

__irq_svc 레이블 어셈블리 코드는 다음과 같습니다.

https://github.com/raspberrypi/linux/blob/rpi-4.19.y/arch/arm/kernel/entry-armv.S
01 807a2a20 <__irq_svc>:
02 807a2a20: e24dd04c sub sp, sp, #76 ; 0x4c 
03 807a2a24: e31d0004 tst sp, #4
04 807a2a28: 024dd004 subeq sp, sp, #4
05 807a2a2c: e88d1ffe stm sp,{r1, r2, r3, r4, r5, r6, r7, r8, r9, sl, fp, ip}
06 807a2a30: e8900038 ldm r0, {r3, r4, r5}
...
07 807a2a5c: e3a0147f mov r1, #2130706432 ; 0x7f000000
08 807a2a60: e5891008 str r1, [r9, #8]
09 807a2a64: e58d004c str r0, [sp, #76] ; 0x4c  
10 807a2a68: ebe93147 bl 801eef8c <trace_hardirqs_off>
11 807a2a6c: e59f1024 ldr r1, [pc, #36] ; 807a2a98 <__irq_svc+0x78>  
12 807a2a70: e1a0000d mov r0, sp
13 807a2a74: e28fe000 add lr, pc, #0
14 807a2a78: e591f000 ldr pc, [r1]    
15 807a2a7c: ebe930e3 bl 801eee10 <trace_hardirqs_on>
16 807a2a80: e59d104c ldr r1, [sp, #76] ; 0x4c  
17 807a2a84: e5891008 str r1, [r9, #8]
...
18 807a2a98: 80c089ac .word 0x80c089ac

__irq_svc 레이블에서 가장 먼저 실행되는 어셈블리 코드를 보겠습니다.

02 807a2a20: e24dd04c sub sp, sp, #76 ; 0x4c

여기서 sp는 현재 실행 중인 스택 주소를 뜻합니다. sub는 뺄셈 연산을 하는 어셈블리 명령어입니다. 현재 스택 주소에서 0x4C만큼 빼는 연산은 스택 공간을 0x4C 바이트만큼 확보하는 동작입니다. 그런데 스택 주소를 0x4C만큼 빼는 연산이 왜 스택 공간을 0x4C 바이트만큼 확보하는 동작일까요? 스택은 높은 주소에서 낮은 주소로 방향으로 자라기 때문에 0x4C만큼 스택 주소를 빼는 연산을 수행하는 것입니다. 이렇게 스택 공간을 확보해서 현재 실행 중인 프로세스의 레지스터를 저장하는 동작을 수행합니다.

다음으로 5 번째와 16 번째 줄을 보겠습니다.

05 807a2a2c: e88d1ffe stm sp,{r1, r2, r3, r4, r5, r6, r7, r8, r9, sl, fp, ip}
..
16 807a2a80: e59d104c ldr r1, [sp, #76] ; 0x4c  

우선 stm이라는 명령어가 보입니다. 조금 낯선 ARM 어셈블리 코드 같지만 자주 쓰는 명령어입니다. stm #A {#B, #C}라는 명령어는 #A가 위치한 메모리 공간에 #B와 #C를 저장하는 동작입니다. 05번째 줄을 실행하면 스택이 위치한 메모리 공간에 r1부터 ip 레지스터까지 저장합니다. 이후 16번째 줄까지 실행하면 그림 5.11과 같이 레지스터가 저장됩니다. 
 
 
  [그림 5.11] 레지스터 세트를 프로세스 스택에 푸시되는 과정

앞에서 설명한 “인터럽트가 발생하면 현재 실행 중인 프로세스 정보를 저장한다”라는 내용이 이 코드에 담겨 있습니다. 즉, 인터럽트가 발생하기 전에 수행됐던 레지스터 세트를 스택 공간에 저장하는 것입니다.

다음으로 11번째 줄을 보겠습니다.

11 807a2a6c: e59f1024 ldr r1, [pc, #36] ; 807a2a98 <_irq_svc+0x78>  
...
18 807a2a98: 80c089ac .word 0x80c089ac

ldr이란 명령어는 다음과 같은 형식으로 씁니다.

”ldr r1, [#A]”

#A 메모리 공간에 있는 값을 r1에 저장하는 동작입니다. 이 내용을 참고해 16번째 줄을 분석하면 807a2a98 메모리 공간에 있는 0x80c089ac 값을 r1으로 로딩하는 동작입니다. 그런데 0x80c089ac 주소에는 handle_arch_irq 전역변수가 있습니다. 

다음으로 14 번째 줄을 보겠습니다.

14 807a2a78: e591f000 ldr pc, [r1]    

이 코드에서는 handle_arch_irq 전역변수에 저장된 주소를 프로그램 카운터로 지정합니다. 그런데 이 명령어를 실행하면 bcm2836_arm_irqchip_handle_irq() 함수를 호출합니다. 어셈블리 코드에서는 bcm2836_arm_irqchip_handle_irq() 함수가 보이지 않는데 이 함수는 어떤 원리로 호출하는 것일까요? 부팅할 때 handle_arch_irq 전역변수에 bcm2836_arm_irqchip_handle_irq() 함수의 주소를 저장하기 때문입니다.


bcm2836_arm_irqchip_handle_irq() 함수의 주소가 handle_arch_irq 전역변수에 언제 할당될까요? 

https://github.com/raspberrypi/linux/blob/rpi-4.19.y/drivers/irqchip/irq-bcm2836.c
01 static int __init bcm2836_arm_irqchip_l1_intc_of_init(struct device_node *node,
02       struct device_node *parent)
03 {
...
04 bcm2836_arm_irqchip_smp_init();
05
06 set_handle_irq(bcm2836_arm_irqchip_handle_irq);

bcm2836(브로드컴 SoC 이름)의 인터럽트 콘트롤러를 초기화하는 bcm2836_arm_irqchip_l1_intc_of_init() 함수의 06번째 줄을 보겠습니다. bcm2836_arm_irqchip_handle_irq 인자와 함께 set_handle_irq() 함수를 호출합니다.

이어서 set_handle_irq() 함수 코드를 보겠습니다.

https://github.com/raspberrypi/linux/blob/rpi-4.19.y/kernel/irq/handle.c
01 int __init set_handle_irq(void (*handle_irq)(struct pt_regs *))
02 {
03 if (handle_arch_irq)
04 return -EBUSY;
05
06 handle_arch_irq = handle_irq;
07 return 0;
08 }

06번째 줄과 같이 set_handle_irq() 함수의 인자인 handle_irq를 handle_arch_irq 전역변수에 저장합니다.

handle_arch_irq 전역변수는 시스템 아키텍처 설정에 따라 인터럽트 콘트롤러 함수를 저장하는 인터페이스의 기능을 수행합니다. 대부분의 ARMv7 아키텍처 기반 SoC는 gic_handle_irq() 함수를 handle_arch_irq에 등록합니다. 하지만 라즈베리 파이에 탑재된 bcm2836은 bcm2836_arm_irqchip_handle_irq() 함수를 등록합니다. 이는 bcm2836(브로드컴의 SoC 이름) 인터럽트 처리를 칩셋 구조에 맞게 설계한 것으로 보입니다. 


인터럽트가 발생하면 인터럽트 벡터로 실행 흐름을 브랜치합니다. 이후 다음과 같은 동작을 합니다.

인터럽트 벡터는 실행 중인 프로세스 레지스터 세트를 자신의 스택 공간에 푸시
bcm2836_arm_irqchip_handle_irq() 함수를 호출해 인터럽트를 처리하는 커널 내부 함수를 호출 

위 동작 중 프로세스 스택 공간에 레지스터 세트를 저장하는 과정을 다음 절에서 확인해보겠습니다.


"이 포스팅이 유익하다고 생각되시면 댓글로 응원해주시면 감사하겠습니다.  
"혹시 궁금한 점이 있으면 댓글로 질문 남겨주세요. 아는 한 성실히 답변 올려드리겠습니다!" 

Thanks,
Austin Kim(austindh.kim@gmail.com)

# Reference (인터럽트 처리)


    핑백

    덧글

    댓글 입력 영역