Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

18113
1478
166888


[리눅스커널][인터럽트]인터럽트 벡터에서 스택 푸쉬 확인하기 5. 인터럽트


프로세스가 실행되는 도중 인터럽트가 발생하면 인터럽트 벡터로 프로그램 카운터를 브랜치합니다. 인터럽트 벡터가 가장 먼저 하는 일은 무엇일까요? 바로 실행 중인 프로세스의 레지스터 세트를 스택 공간에 저장(푸시)하는 것입니다. 그렇다면 인터럽트 벡터에서 이런 동작을 수행하는 이유는 무엇일까요? 프로세스의 실행 정보를 백업하기 위해서입니다. 

이번 절에서는 실제 프로세스 스택 공간에 있는 메모리 덤프를 보면서 인터럽트 벡터에서 프로세스 레지스터 세트를 푸시하는 과정을 살펴보겠습니다. 먼저 TRACE32 프로그램으로 본 콜스택을 소개합니다.  

-000|account_group_exec_runtime(inline)
-000|update_curr()
-001|check_spread(inline)
-001|put_prev_entity()
-002|put_prev_task_fair()
-003|pick_next_task_rt(inline)
-003|pick_next_task_rt()
-004|pick_next_task(inline)
-004|__schedule()
-005|arch_local_irq_disable(inline)
-005|preempt_schedule_irq()
-006|svc_preempt(asm)
-007|__irq_svc(asm)
 -->|exception
-008|blk_flush_plug_list()
-009|current_thread_info(inline)
-009|blk_finish_plug()
-010|ext4_writepages()
-011|__filemap_fdatawrite_range()
-012|filemap_write_and_wait_range()
-013|ext4_sync_file()
-014|vfs_fsync()
-015|fdput(inline)
-015|do_fsync()
-016|ret_fast_syscall(asm)

ret_fast_syscall() 함수에서 do_fsync() 함수 방향으로 호출해서 account_group_exec_runtime() 함수까지 실행되는 흐름입니다.

여기서 7번 콜스택에 __irq_svc라는 인터럽트 벡터가 보입니다. 이를 통해 인터럽트가 발생했음을 알 수 있습니다. 

조금 더 자세히 살펴보면 ret_fast_syscall() ~ blk_flush_plug_list() 함수 실행 구간에서 보이는 콜스택 흐름으로 동작하고 있었습니다. 그럼 인터럽트 관점에서 위 콜스택을 해석하자면 blk_flush_plug_list() 함수를 실행하는 도중 인터럽트가 발생해서 __irq_svc 인터럽트 벡터가 실행된 것입니다. 

위 콜스택에서 인터럽트 벡터인 __irq_svc() 함수가 호출된 시점의 스택 메모리 주소 0xCE4F9D84로 이동해서 스택 덤프를 확인하면 스택에 푸시된 레지스터를 확인할 수 있습니다. 

$ data.view %symbol.l 0xCE4F9D80
________address||value_______|symbol
NSD:CE4F9D80| 0x20070013
NSD:CE4F9D84| 0xFFFFFFFF   
NSD:CE4F9D88| 0xCE4F9DE4
NSD:CE4F9D8C| 0xC0FF97B4   \\vmlinux\Global\__irq_svc+0x74
NSD:CE4F9D90| 0xCE4F8000
NSD:CE4F9D94| 0xCE4F8000
NSD:CE4F9D98| 0xCE4F9DAC
NSD:CE4F9D9C| 0xC0FF4E2C   \\vmlinux\sched/core\preempt_schedule_irq+0x50
NSD:CE4F9DA0| 0xC039B9DC   \\vmlinux\blk-core\blk_flush_plug_list+0x1A4
NSD:CE4F9DA4| 0xC039B9E0   \\vmlinux\blk-core\blk_flush_plug_list+0x1A8
NSD:CE4F9DA8| 0x1           
NSD:CE4F9DAC| 0xC0FF97D8   \\vmlinux\Global\svc_preempt+0x8
NSD:CE4F9DB0| 0x0           /* R0  */ 
NSD:CE4F9DB4| 0xCECC72B4   /* R1 */   
NSD:CE4F9DB8| 0x1           /* R2 */         
NSD:CE4F9DBC| 0x0           /* R3 */
NSD:CE4F9DC0| 0xCE4F9E00   /* R4 */
NSD:CE4F9DC4| 0xEAD60A60   /* R5 */
NSD:CE4F9DC8| 0x1           /* R6 */
NSD:CE4F9DCC| 0xCE4F9E00   /* R7 */ 
NSD:CE4F9DD0| 0x0           /* R8 */
NSD:CE4F9DD4| 0x60070013   /* R9 */
NSD:CE4F9DD8| 0xCE4F8000   /* R10 */
NSD:CE4F9DDC| 0x1           /* R11 */
NSD:CE4F9DE0| 0x60070093   /* R12 */
NSD:CE4F9DE4| 0xCE4F9E00  /* R13, 스택 주소 */
NSD:CE4F9DE8| 0xC039B9DC  blk_flush_plug_list+0x1A4 /* R14 */
NSD:CE4F9DEC| 0xC039B9E0  blk_flush_plug_list+0x1A8 /* PC */
NSD:CE4F9DF0| 0x20070013
NSD:CE4F9DF4| 0xFFFFFFFF


'data.view %symbol.l 0xCE4F9D80'은 TRACE32에서 지원하는 명령어로 메모리 덤프를 심벌 형식으로 출력합니다. 즉, 위 명령어로 0xCE4F9D80 메모리 주소에 담긴 값을 심벌로 볼 수 있습니다.


0xCE4F9DB0 ~ 0xCE4F9DEC 메모리 주소 사이에 인터럽트가 발생하기 전의 실행 정보인 레지스터 세트가 보입니다. 오른쪽에 /* Rx */ 형식으로 적힌 주석문 안의 텍스트는 레지지스터를 의미합니다.

위 메모리 덤프를 보면 r0부터 pc까지 레지스터가 보입니다. 이 레지스터 세트는 인터럽트가 발생하기 전의 프로세스 실행 정보를 담고 있습니다. 그럼 이 레지스터를 언제 다시 로딩할까요? 이 레지스터 세트들은 인터럽트를 처리하고 난 후 이전에 실행했던 프로세스가 다시 ARMv7 코어 레지스터로 로딩합니다. 그럼 그 이후에는 어떤 동작을 할까요? 인터럽트가 발생해서 멈췄던 코드 다음부터 실행하게 됩니다.

인터럽트 벡터를 어셈블리 코드로 분석하는 것은 매우 어려운 문제입니다. 하지만 몇 번 메모리에 레지스터가 쌓이는 동작을 머릿속에 그리면 프로세스 정보를 저장하는 동작이 더 오랫동안 기억에 남을 것입니다.

이제 인터럽트 벡터에서 프로세스 레지스터를 저장하고 서브루틴을 실행하는 코드까지 확인했으니 다음 절에서는 인터럽트 핸들러를 처리하는 흐름을 살펴보겠습니다.


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

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

# Reference (인터럽트 처리)

인터럽트 소개  
   * 리눅스 커널에서의 인터럽트 처리 흐름    
인터럽트 컨텍스트  
인터럽트 핸들러는 언제 호출될까?  
인터럽트 핸들러는 어떻게 등록할까?  
인터럽트 디스크립터  
인터럽트 디버깅  

핑백

덧글

  • 컴린이 2020/05/20 00:53 # 삭제 답글

    안녕하세요, 인터럽트 처리 과정에 대해 공부하고 싶었는데 이 블로그를 통해 엄청 많이 배우고 갑니다!!
    알기 쉽게, 자세하게 알려주셔서 감사해요!
    그런데 제가 알기론 인터럽트 벡터가 '인터럽트를 처리할 수 있는 서비스 루틴들의 주소를 가지고 있는 배열'로 알고 있는데
    여기선 __irq_svc를 실행하는 것으로 표현하던데 무엇이 맞는건가요?
  • AustinKim 2020/05/20 09:49 #

    리눅스 시스템에서 인터럽트를 처리하는 방식을 다양한 쉽게 표현으로 설명하다 보니 좀 오해가 생길 여지가 있는 것 같습니다.
    인터럽트 벡터가 실행되는 과정은 다음과 같이 정리할 수 있겠네요.

    ■ 인터럽트가 발생하면 어떤 코드가 동작할까?

    ; ARM 프로세서는 __irq_svc 벡터로 프로그램 카운터를 바꿉니다.

    ■ __irq_svc 벡터는 어떤 동작을 할까?

    ; 기존에 실행 중인 코드의 실행 정보가 담긴 레지스터 세트를 프로세스의 스택 공간에 푸시합니다.

    ■ __irq_svc 벡터가 실행된 후 커널 내부에서는 어떤 동작을 할까?

    ; 시스템에서 설정된 인터럽트 컨트롤러 관련 동작을 처리합니다.
    ; 리눅스 커널의 IRQ(인터럽트) 서브시스템 관련 커널 코드가 처리됩니다.
    ; IRQ(인터럽트) 서브시스템 내부의 __handle_irq_event_percpu() 함수에서 인터럽트 핸들러를 호출한다.

    Thanks,
    Austin Kim
  • AustinKim 2020/05/20 09:49 # 답글

    리눅스 시스템에서 인터럽트를 처리하는 방식을 다양한 쉽게 표현으로 설명하다 보니 좀 오해가 생길 여지가 있는 것 같습니다.
    인터럽트 벡터가 실행되는 과정은 다음과 같이 정리할 수 있겠네요.

    ■ 인터럽트가 발생하면 어떤 코드가 동작할까?

    ; ARM 프로세서는 __irq_svc 벡터로 프로그램 카운터를 바꿉니다.

    ■ __irq_svc 벡터는 어떤 동작을 할까?

    ; 기존에 실행 중인 코드의 실행 정보가 담긴 레지스터 세트를 프로세스의 스택 공간에 푸시합니다.

    ■ __irq_svc 벡터가 실행된 후 커널 내부에서는 어떤 동작을 할까?

    ; 시스템에서 설정된 인터럽트 컨트롤러 관련 동작을 처리합니다.
    ; 리눅스 커널의 IRQ(인터럽트) 서브시스템 관련 커널 코드가 처리됩니다.
    ; IRQ(인터럽트) 서브시스템 내부의 __handle_irq_event_percpu() 함수에서 인터럽트 핸들러를 호출한다.

    Thanks,
    Austin Kim
  • 컴린이 2020/05/20 19:20 # 삭제

    아하! 그렇군요
    답변 감사합니다!
    덕분에 많이 배우고 갑니다ㅎㅎ
댓글 입력 영역