[Linux Kernel] Interrupt 후반부 처리
인터럽트 후반부 처리
Top Half, Bottom Half 중 Bottom Half 처리. 인터럽트가 실행되는 동안인 인터럽트 컨텍스트 상태일 때는 프로세스가 멈춰있으므로 가능한 한 빨리 실행되고 컨텍스트를 돌려줘야 하는데, 꼭 인터럽트 컨텍스트일 때 처리하지 않아도 되는 것들은 인터럽트 핸들러가 끝나고 빠르게 여러 가지 방법으로 마무리한다.
IRQ 스레드 (threaded IRQ)
"irq/인터럽트 번호-인터럽트 이름" 형식의 이름을 가진 프로세스가 바로 IRQ 스레드다. 어찌됐던 스레드이기 때문에 스케줄링하는 과정이 필요하고 그러므로 1초에 몇 천 번 이상 인터럽트가 걸리는 핸들러는 IRQ 스레드를 이용하면 부하가 많이 걸리니 추천하지 않는다. 부팅 단계에서 request_threaded_irq() 함수가 불려서 딱 한 번 만들어진다. 이후 __handle_irq_event_percpu() 함수 내에서 인터럽트 핸들러 실행을 하는 도중 IRQ 스레드가 필요할 것 같아 리턴 값을 IRQ_WAKE_THREAD로 반환하면 IRQ 스레드를 깨우고 후반부를 처리하게 된다.
Soft IRQ
인터럽트 후반부 처리 기법 중 하나다. 부팅될 때 딱 한 번 정해진 softirq 핸들러를 softirq_vec에 open_softirq() 함수로 등록한다. 이후 각 cpu 별로 선언된 percpu 타입의 전역 변수 irq_stat의 속성 중 하나인 __softirq_pending에 인터럽트 핸들러가 실행 중에 필요한 soft irq를 비트로 설정한다. 그리고 인터럽트 핸들러가 종료될 때 호출하는 irq_exit() 함수에서 인터럽트 핸들러가 요청한 soft irq가 존재하는지 체크(irq_stat.__softirq_pending != 0)하고 있다면 __do_softirq() 함수를 호출한다. 내부에서는 while문을 돌면서 존재하는 soft irq 요청들을 처리한다(즉 여러 개의 soft irq 처리가 가능하다).
처리가 끝난 후 한 번 더 soft irq가 존재하는지 체크한다. __do_softirq() 함수가 2밀리초를 넘었거나 soft irq 서비스를 10번 이상 실행했다면 ksoftirqd 스레드를 깨운다. 실행 시간을 체크하는 건 논리적으로 오래 걸리는 걸 방지하기 위해서임을 알 수 있다. 하지만 서비스 실행 최대 횟수를 10번으로 설정한 건 좀 의문이었는데 생각해보니 아마 모든 Soft IRQ 서비스가 요청되었을 경우를 가정했을 때 아직도 처리할 Soft IRQ가 남았다는 뜻이니 더 오래 실행하지 않도록 나머지는 ksoftirqd에서 처리하게 끝내는 것임이 아닐까 생각했다.
1. Soft IRQ 서비스는 인터럽트 발생 후 인터럽트 핸들러에서 Soft IRQ 서비스를 요청한다.
2. Soft IRQ 서비스 실행의 출발점은 인터럽트 핸들러의 수행이 끝난 후 Soft IRQ 서비스 요청을 체크하면서 이루어진다.
__handle_domain_irq() -> irq_exit() -> invoke_softirq() -> __do_softirq()
3. Soft IRQ 전용 스레드인 ksoftirqd 스레드 깨우는 시기
if (Soft IRQ 서비스 핸들러 호출 후 Soft IRQ 서비스 요청이 있었는지 체크)
-> if (__do_softirq() 함수의 실행 시간이 2ms 이상이거나 Soft IRQ 핸들러를 10번 이상 호출)
-> wakeup_softirqd() 함수로 ksoftirqd 스레드 깨우고 __do_softirq() 함수는 바로 종료.
ksoftirqd 스레드
/kernel/softirq.c 안에 spawn_ksoftirqd() 함수에서 생성된다. percpu 타입의 프로세스이며 cpu 코어 개수만큼 생성되어 정해진 cpu 내에서만 실행된다. percpu 타입의 스레드는 smp 핫플러그 스레드로 등록해서 실행한다. 시스템 부하에 따라 cpu를 끄고 켜는 동작을 수행할 때 동작하는 스레드가 바로 smp_boot 타입의 스레드다. 그리고 이 smpboot를 관리하는 함수는 smpboot_thread_fn() 함수다.
ksoftirqd 스레드를 깨우는 상황은 2가지가 있을 수 있다.
1. __do_softirq() 함수에서 Soft IRQ 서비스를 실행한 후
2. 인터럽트 컨텍스트가 아닌 상황에서 Soft IRQ 서비스를 요청할 때
태스크릿 (Tasklet)
Soft IRQ 서비스 중 하나로 Soft IRQ를 동적으로 쓸 수 있는 인터페이스 기능이다. 디바이스 드라이버 레벨에서 Soft IRQ 이용할 때 태스크릿을 쓴다. 참고로 이름 때문에 프로세스와 관련 있다고 생각하기 쉽지만 전혀 상관없는 개념이다.
태스크릿 등록하는 두 가지 방법
1. 태스크릿 전역 변수 선언
DECLARE_TASKLET() 혹은 DECLARE_TASKLET_DISABLED() 매크로 함수 호출
tasklet_struct 구조체 필드 중 atomic_t count 변수의 값을 설정함.
0 = 태스크릿 활성화, 1 = 태스크릿 비활성화
태스크릿이 비활성화되어 있더라도 tasklet_enable() 함수를 통해 활성화할 수 있음.
2. 태스크릿 초기화 함수 호출
taskelt_init() 함수
기본적으로 Soft IRQ 서비스의 루틴과 같지만 인터럽트 발생 후 인터럽트 핸들러에서 tasklet_schedule() 함수를 호출한다. 이를 '태스크릿 스케줄링'이라고 부른다. 내부에서는 해당 태스크릿이 스케줄링 중이라고 state를 설정하고 tasklet_vec 연결 리스트에 요청된 태스크릿 구조체 주소를 연결하고 Soft IRQ의 서비스 비트를 설정(raise_softirq_irqoff() 함수)하고 빠져나온다. 이후 Soft IRQ 루틴과 똑같이 irq_exit() 함수로 진입하고 __do_softirq() 함수를 호출한다. __do_softirq() 함수에서 태스크릿 실행 요청을 확인하고 tasklet_action() 태스크릿 핸들러 함수가 실행된다. tasklet_action() 함수는 taskelt_vec 연결 리스트를 돌면서 예외 처리를 수행하고 예외가 발생하지 않는다면 해당 tasklet_struct 구조체에 등록된 태스크릿 핸들러 함수를 호출한다.