-
반응형
1. 인터럽트 컨텍스트란?
인터럽트 컨텍스트와 관련된 코드를 분석하면서 인터럽트 컨텍스트의 세부 동작 방식을 알아보기에 앞서 인터럽트 컨텍스트의 개념을 알아보자. 먼저 인터럽트 컨텍스트를 간단히 정의하자면 "현재 실행 중인 프로세스가 현재 인터럽트를 처리 중"이라는 것을 의미한다. 즉, 현재 실행 중인 함수가 인터럽트 핸들러이거나 인터럽트 핸들러에서 호출된 함수라는 것이다.
리눅스 커널에서 컨텍스트란?
인터럽트 컨텍스트의 의미를 파악하기 위해 먼저 '컨텍스트'가 무엇인지 알아볼 필요가 있다. 컨텍스트란 '프로세스 실행 그 자체'를 의미하며 현재 실행 중인 프로세스 정보를 담고 있는 레지스터 세트로 표현할 수 있다. 만약 커널이 schedule() 함수를 실행하고 있는데 이 함수의 주소가 0xC000D000이다. 그렇다면 프로그램 카운터 레지스터는 0xC000D000이다. 이처럼 프로그램 카운터 레지스터를 포함한 레지스터 세트로 현재 실행 중인 프로세스의 상태를 표현할 수 있다.
프로세스가 스케줄링으로 휴면 상태로 진입할 때는 현재 실행 중인 레지스터 세트를 특정 공간(스택의 최상단 주소)에 저장한다. 스케줄링으로 의해 다시 깨어나면 이전에 동작했던 지점의 코드부터 실행해야 하기 때문이다. 그럼 프로세스는 다시 실행하기 전에 휴면할 때 저장했던 레지스터 세트를 로딩한다. 그 이유는 휴면할 때 저장했던 레지스터에 프로세스 실행 정보가 담겨 있기 때문이다.
cpu_context_save 구조체의 각 필드는 프로세스가 실행 중인 레지스터 값을 나타내며, 이 필드에 현재 실행 중인 프로세스의 레지스터가 저장된다. 그럼 cpu_context_save 구조체는 프로세스 스택의 최상단 주소에 위치한 thread_info 구조체의 cpu_context 필드에 저장된다. 쉽게 설명하면 컨텍스트란 "프로세스가 실행 중인" 상태라고 할 수 있다. 레지스터 세트로 현재 실행 중인 상태를 저장하기 때문이다.
인터럽트 컨텍스트란?
인터럽트 컨텍스트란 용어는 "인터럽트를 처리 중" 이런 뜻이다. 인터럽트가 발생하면 인터럽트 벡터 주소부터 인터럽트 핸들러까지 함수 흐름으로 인터럽트를 처리한다. 인터럽트 컨텍스트란 이 흐름 중 하나라고 볼 수 있다. 인터럽트 컨텍스트를 처음 접하는 분은 이 같은 의미를 파악하기 어려우니 조금 더 풀어서 정리하면 현재 인터럽트 핸들러를 실행 중이면 인터럽트 컨텍스트이다. 인터럽트를 핸들링하는 중이기 때문이다. 그럼 인터럽트 핸들러에서 호출된 서브 함수 중 하나가 실행될 때도 인터럽트 컨텍스트라고 볼 수 있다. 현재 인터럽트가 발생한 다음 인터럽트를 핸들링하는 도중이기 때문이다. 그렇다면 라눅스 커널에서 인터럽트 컨텍스트를 정의한 이유는 인터럽트가 발생하면 이를 핸들링하는 코드는 빨리 실행돼야 하기 때문이다. 리눅스 커널 및 디아비스 드라이버에서 실행되는 함수는 인터럽트 핸들러에서 실행될 수도 있고 아닌 경우도 있다.
2. ftrace와 커널 로그로 인터럽트 컨텍스트 확인
커널 로그를 이용한 인터럽트 컨텍스트 확인
커널 로그에서 __irq_svc(asm) ~ unwind_backtrace() 함수들은 인터럽트 컨텍스트에서 수행되며, start_kernel() ~ arch_cpu_idle() 함수 구간은 프로세스 컨텍스트라고 볼 수 있다. 커널 로그에서 __irq_svc 레이블은 개발 도중 자주 보게 된다. __irq__svc 레이블을 보면 "인터럽트가 발생해서 인터럽트를 처리 중이다" 라고 해석하면 된다.
3. in_interrupt() 함수란?
현재 실행 중인 코드가 인터럽트 컨텍스트 구간인지 어떻게 알 수 있는가?
in_interrupt() 함수가 이 정보를 알려주며, 함수가 true를 반환하면 인터럽트 컨텍스트이고, 반대로 false를 반환하면 프로세스 컨텍스트이다.
그렇다면 in_interrupt() 함수를 호출해 현재 실행 중인 코드가 인터럽트 컨텍스트인지 왜 알려고 할까?
커널코드를 잠시 보면 알겠지만 커널 코드 내에서 수많은 함수를 호출한다. 함수들은 복잡하게 호출되므로 함수 호출 흐름을 간단히 파악하기 어렵다. 그렇다 보니 커널 혹은 드라이버 코드에서 볼 수 있는 함수가 '인터럽트 컨텍스트'에서 실행 중인지 분간하기 어렵다. 그런데 현재 실행되는 코드가 인터럽트 핸들러의 서브루틴으로 실행 중이면 되도록 더 빨리 동작해야 한다. 인터럽트 서비스 루틴은 실행 중인 프로세스를 멈추고 동작하기 때문이다. 그래서 디바이스 드라이버 코드에서 인터럽트 컨텍스트 조건에서만 신속하게 동작하는 코드를 추가하는 것이다.
in_interrupt() 함수를 써서 만든 패치 코드
in_interrupt() 함수가 true를 반환하면 GFP_ATOMIC 플래그, 반대의 경우 GFP_KERNEL 플래그를 적용해 kmalloc() 함수를 호출한다. in_interrupt() 함수가 true를 반환하는 경우 인터럽트 컨텍스트이면 '인터럽트 처리 중'이라고 볼 수 있다. 이 조건에서 gfp_flag를 GFP_ATOMIC으로 설정하고 메모리를 할당한다. 대신 GFP_ATOMIC 옵션으로 kmalloc() 함수를 호출하면 커널 내부에서 메모리를 할당할 때 스케줄링을 수행하지 않는다. 반대로 in_interrupt() 함수가 false를 반환하면 현재 코드가 프로세스 컨텍스트에서 수행 중이니 GFP_KERNEL 옵션으로 메모리를 할당한다. 참고로 GFP_ATOMIC 옵션으로 메모리를 할당하면 프로세스는 휴면하지 않고 메모리를 할당하고, GFP_KERNEL 옵션인 경우 메모리 할당 실패 시 휴면할 수 있다.
반응형