1. Bottom halves
저번시간에 Top-half와 Bottom-half에 대해서 설명을 했다. 이번 강의는 Bottom-half에 대해서 좀더 자세히 알아보는시간을 가져보자.
인터럽트 요청이 오면 Top Half 루틴이 수행되고, 큰 작업이 수행되야할때는 Top-Half에서 soft irq bit을 세팅한다고 했다. 왼쪽 아래에 보이는 게 바로 그 bit이다.
softirq_pending[] bit을 보면, 1로 세팅되어있는 부분이 현재 Bottom-half로 처리되야하는 기능이다. 이는 인덱스마다 정해져있다. 예를 들어 0번 인덱스는 디스크 관련, 1번은 TCP/IP 관련 이런식으로 말이다.
만약 현재 인덱스 1의 bit이 세팅되어있으면 그대로 sofirq_vec[1] 을 참조한다. 해당 action data 필드는 실제 구조체 형태로 되어있는데, 기능을 수행하는 action과 실 데이터를 가리키는 필드가 구조체 안에 들어있다.
실제 위에서 설명한 softirq_pending[cpu]는 내부에 각 인덱스마다 어떤 Softirq 기능인지 정의되어있다. 0번이 HI_SOFTIRQ, 2번이 NET_TX_SOFTIRQ 이다. 이러한 Softirq 기능은 추가할수있다.
1.2 Softirq
Top-half에서 do_irq() 함수가 호출됬다면 Bottom-half에서는 do_softirq() 함수가 호출된다. 코드 로직은 비슷하며, 왼쪽 아래를 보면 do-while 반복문을 볼수가 있다.
현재 pending 배열이 아까 말한 softirq_pending[]인데, 여기서 하위 한바이트를 확인하고, 1이면 이에 해당하는 softirq_vecc 즉, h→action을 호출한다. 그다음 pending을 쉬프트 연산을 통해 하나씩 반복해서 확인한다. 실제 h→action이 Softirq handler의 역할을 진행한다.
위 그림을 봐보자. 중간 화살표는 시간의 흐름을 뜻한다. 현재 t1 시간에 IRQm 라인에서 인터럽트 요청이 들어와서 CPU(i)가 선택되었다. 그리고 do_IRQ() 함수가 호출되면서 ISR 루틴이 실행된다. (NIC에서 건 인터럽트라고 가정하자)
그럼 ISR에서는 실제 수행은 여기서 못하고 NIC의 패킷만 메모리에 복사하기만 한다. 여기까지도 많은 일을 한거다. 이제 softirq bit를 세팅하고 끝나게 된다. 시간이 흘러서 t2 시간이 되었을때 CPU(i)가 do_softirq()를 호출했다.
현재 softirq_bit를 체크하여 세팅된 인덱스의 IP softirq handler를 호출한다. 이는 IP() 와 관련된 함수이고, 실제 수행된다. (라우팅의 기능을 처리하는 함수가 수행됨)
그리고 시간이 또 흘러서 t3때 동일한 IRQm라인에서 요청이 들어왔고, 이번엔 CPU(k)가 선택되어 do_IRQ()를 호출한다. 이 요청도 NIC에서 보낸 인터럽트이다. 그럼 동일하게 패킷을 메모리에 복사하고 softirq bit를 세팅한다.
t4시간이 되었을때 CPU(k)가 do_softirq() 함수를 호출하여 soft_irq handler를 통해 동일한 IP() 함수가 또 실행된다. IP() 함수가 호출되기 전에 함수를 위한 데이터를 스택에 저장하게 된다. 로컬 변수들은 문제가 되지 않지만 global 변수같은 것은 상호배제를 위해 lock을 잘 걸어야한다.
즉 매우 IP() 함수는 신중하게 코딩이 되어야 한다는 소리이다. 만약 CPU가 50개다? 그럼 50개 CPU가 각자 IP()함수를 동시에 처리할수 있고 병렬처리에 의한 효율성을 좋을것이다. 하지만 그만큼 신경을 많이 써야한다.
따라서 이러한 처리가 귀찮으면 그냥 IP() 함수 내에 global한 count 변수를 두고, 하나의 CPU가 처리중이면 lock을 걸어 다른 CPU가 접근하지 못하게 만들수도 있다. 이러한 신경써야 할 일이 줄어들지만 반대로 병렬처리에 의한 효율을 낼수가 없다. 이러한 메커니즘은 tasklet이라고 부른다.
1.3 Softirq vs Tasklet
정리를 해서 말하자면 Softirq는 여러개의 CPU가 동시에 ISR 핸들러를 실행시킬수 있다. 동시성이 높아지기 때문에 처리량이 많아지면 이러한 장점은 네트워크 패킷 핸들링 과 같은 곳에 매우 유용하다. 하지만 코딩하기에 많은 고려사항을 가진다. 복잡하단 소리이다.
taslket은 굳이 동시성의 장점이 필요없는 함수를 수행하기 위해 나온것이다. Softirq이긴 하지만, CPU가 한번에 하나의 ISR 핸들러만 수행시킬수 있다. 따라서 코드 구현은 간단하지만 Sofirq보다는 처리량이 떨어진다.
Tasklet의 구조체를 보면 state라는 필드가 있다. 이 필드를 보고 현재 1이면 접근을 못하고 0이면 아무도 사용안하고 있기때문에 사용가능하다.
Bottom-Half 핸들러 예를 들어 Bottom-Handler에 의해 실행되야할 함수가 IP() 함수이다라고 하면 이 함수를 등록시키는 방법은 3개가 있다. 위에서 말한 Softirq, Tasklet 이외에도 Work Queue라는 방식이 있다. 원하는 상황에 맞춰서 3가지 중 하나의 방법으로 등록시키면 된다
1.
Softirq
빠른 처리가 필요할때
2.
Tasklet
코드 구현이 간단하며 병렬처리가 딱히 필요가 없는 서비스일때
3.
Work Queue
위 두개는 프로세스 형태가 아니기 때문에 sleep 되면 안된다. 하지만 원하는 함수가 sleep이 필요하거 하는 경우는 따로 독립된 프로그램으로 핸들러에 등록시킬수 있다. 이렇게 되면 함수로 등록하는게 아니라 독립 프로그램(데몬이나 서버로 등록시킬수 있다는 뜻)을 등록시킴으로써 커널 쓰레드를 통해 등록하게 된다. (Context Switching에 따른 오버헤드 발생)
3가지 Bottom-half 핸들러의 특징은 아래의 그림으로 정리가 된다
2. 정리
인터럽트에 대한 정리는 여기까지로 마무리된다. 강의를 통해 인터럽트의 내부 동작 과정을 알게 되었다.