ARM Linux Kernel Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

1239
1625
172603


[리눅스커널] thread_info 구조체의 preempt_count - 인터럽트 컨텍스트 실행 저장 4. 프로세스(Process) 관리

리눅스 시스템에서 인터럽트는 언제든지 발생할 수 있습니다. 인터럽트가 발생하면 프로세스 실행을 멈추고 인터럽트에 해당하는 인터럽트 핸들러를 실행합니다. 커널에서는 인터럽트가 발생해서 인터럽트 서비스 루틴을 실행하는 동작을 인터럽트 컨텍스트라고 부릅니다.  

---
참고로 인터럽트는 외부 입출력 장치에 어떤 변화가 있을 때 발생하는 전기 신호 혹은 이를 CPU에게 알려서 처리하는 과정을 뜻합니다. 리눅스 커널은 인터럽트를 처리할 수 있는 함수를 지원하며, 자세한 내용은 5장의 5.1절에서 설명합니다.
---

thread_info 구조체의 preempt_count 필드에 인터럽트가 실행 중인 상태를 나타내는 비트를 설정합니다. 이 비트를 읽어 인터럽트 컨텍스트 유무를 식별합니다. 리눅스 커널에서는 현재 실행 중인 코드가 인터럽트 컨텍스트 내에 있는지 알려주는 in_interrupt() 함수를 제공합니다.

다음 그림을 보면서 인터럽트가 발생했을 때 어떤 흐름으로 thread_info 구조체의 preempt_count 필드가 바뀌는지 알아보겠습니다. 

 
그림 4.16 인터럽트 컨텍스트 설정 시의 함수 흐름

그림 4.16에서 인터럽트 컨텍스트를 설정하는 실행 흐름은 다음과 같습니다.

1. __wake_up_common_lock() 함수를 실행하는 도중에 인터럽트 발생
2. 인터럽트 벡터인 __irq_svc 레이블 실행
3. 다음과 같은 인터럽트 제어 함수를 호출
    bcm2836_arm_irqchip_handle_irq()
    __handle_domain_irq() 
4. __handle_domain_irq() 함수에서 irq_enter() 매크로 함수를 호출
   4.1 프로세스 스택 최상단 주소에 접근한 후 thread_info 구조체의 preempt_count 필드에 HARDIRQ_OFFSET(0x10000) 비트를 더함
5. 화살표 방향으로 함수를 계속 호출해서 인터럽트 핸들러 함수인 usb_hcd_irq() 함수를 호출
   5.1 서브루틴 함수를 실행

4.1단계 이후에 실행하는 모든 하위 함수에서 in_interrupt() 함수를 호출하면 true를 반환합니다. 예를 들면, 그림 4.16에서 irq_enter() 함수 이후로 호출되는 usb_hcd_irq() 함수에서 in_interrupt() 함수를 호출하면 true를 반환합니다.  

다음과 같이 if 문과 in_interrupt() 매크로를 함께 쓰면 실행 코드가 인터럽트 컨텍스트인지 확인할 수 있습니다.

if (in_interrupt()) {
// 인터럽트 컨텍스트에서 실행할  코드
}


in_interrupt() 함수는 thread_info 구조체의 preempt_count 필드에 대해 다음과 같은 AND 비트 연산을 수행합니다.

preempt_count &  0x001FFF00 (HARDIRQ_MASK | SOFTIRQ_MASK | NMI_MASK)

irq_enter() 함수를 실행하면 thread_info 구조체의 preempt_count 필드가 0x10000이니 true를 반환합니다.

0x10000 &  0x001FFF00 (HARDIRQ_MASK | SOFTIRQ_MASK | NMI_MASK) // true


그럼 인터럽트 컨텍스트라는 정보가 업데이트되는 __irq_enter() 함수를 봅시다.

https://elixir.bootlin.com/linux/v4.19.30/source/include/linux/hardirq.h
1 #define __irq_enter() \
2 do { \
3 account_irq_enter_time(current); \
4 preempt_count_add(HARDIRQ_OFFSET); \
5 trace_hardirq_enter(); \
6 } while (0)

4번째 줄을 실행하면 thread_info 구조체의 preempt_count 필드에 HARDIRQ_OFFSET(0x10000) 비트를 더합니다.

preempt_count_add() 매크로 함수는 프로세스 스택의 최상단 주소에 접근해서 preempt_count 필드에 val인자를 더하는 동작을 수행합니다.

https://elixir.bootlin.com/linux/v4.19.30/source/include/linux/preempt.h
#define preempt_count_add(val) __preempt_count_add(val)

인터럽트 서비스 루틴의 실행을 시작할 때 thread_info 구조체의 preempt_count 필드에 HARDIRQ_OFFSET 비트를 더하게 됩니다.

인터럽트 컨텍스트의 종료 상태 저장

이번에는 인터럽트 서비스 루틴을 종료한 다음에 호출하는 irq_exit() 함수를 봅시다.

https://elixir.bootlin.com/linux/v4.19.30/source/kernel/softirq.c
1 void irq_exit(void)
2 {
...
3 account_irq_exit_time(current);
4 preempt_count_sub(HARDIRQ_OFFSET);

4번째 줄을 실행해서 thread_info 구조체의 preempt_count 필드에 HARDIRQ_OFFSET 비트를 뺍니다.

인터럽트 핸들링을 마무리한 다음 irq_exit() 함수를 실행하는 코드의 흐름은 다음 그림과 같습니다.

 
그림 4.17 인터럽트 컨텍스트가 해제될 때의 함수 흐름

보다시피 인터럽트 핸들링을 끝냈으니 다시 검은색 화살 방향으로 함수가 호출됩니다. 이 과정에서 irq_exit() 함수를 실행해서 thread_info 구조체의 preempt_count 필드에서 HARDIRQ_OFFSET 비트를 뺍니다. 

이후 in_interrupt() 함수를 호출하면 false를 반환합니다. 그 이유는 irq_exit() 함수를 실행하면 thread_info 구조체의 preempt_count 필드가 0x0으로 바뀌기 때문입니다.

이를 연산하는 과정은 다음과 같습니다.

false = 0x0000 &  0x001FFF00 (HARDIRQ_MASK | SOFTIRQ_MASK | NMI_MASK)

irq_exit() 함수를 호출한 이후에는 리눅스 커널이 인터럽트 컨텍스트가 아니라고 판단하는 것입니다. 리눅스 커널에서 실행 중인 코드가 인터럽트 컨텍스트인지 점검하는 루틴은 자주 볼 수 있습니다. 

지금까지의 소스 분석으로 다음과 같은 내용을 알게 됐습니다.

인터럽트 컨텍스트의 실행 여부를 프로세스 스택의 최상단 주소에 있는 thread_info 구조체의 preempt_count 필드에 저장함

* 유튜브 강의 동영상도 있으니 같이 들으시면 좋습니다. 




#프로세스

프로세스 소개 
프로세스 확인하기  
프로세스는 어떻게 생성할까?  
유저 레벨 프로세스 실행 실습  
커널 스레드  
커널 내부 프로세스의 생성 과정   
프로세스의 종료 과정 분석  
태스크 디스크립터(task_struct 구조체)  
스레드 정보: thread_info 구조체  
프로세스의 태스크 디스크립터에 접근하는 매크로 함수  
프로세스 디버깅  
   * glibc의 fork() 함수를 gdb로 디버깅하기  


"혹시 궁금한 점이 있으면 댓글로 질문 남겨주세요. 아는 한 성실히 답변 올려드리겠습니다!" 

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

# Reference: For more information on 'Linux Kernel';

디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 1

디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 2








핑백

덧글

댓글 입력 영역