1. Data Structure for Interrupt Handling
우선 소프트웨어로서의 인터럽트를 설명하기 위해 Data Structure와 Function 인터럽트 2가지로 분류해서 설명하겠다.
저번시간에 설명한 부분을 간략하게 다시 설명해보자. IRQ 라인에는 많은 디바시으들이 물려있고, 여러 디바이스들의 인터럽트 요청을 컨트롤하기 위해 PIC가 존재한다. 결국 PIC를 통해 나가는 요청은 하나고, 이를 프로그램 적으로 마스킹을 걸어 인터럽트를 제어할수 있기 때문에 PIC라고 부른다.
이젠 IRQ Lines 에 대해서 좀더 자세히 알아보자. IRQ Lines에는 4개의 정보가 담겨져 있다
•
Handler
PIC는 APIC과 Local APIC이 존재한다고 했다. 인터럽트 요청이 어느 PIC로 부터 왔는지 확인하기 위해 존재
•
Lock
공유 자원을 이용시 상호배제를 위해 존재. 뒤에서 설명함
•
Status
1.
IRQ_Disabled : 인터럽트가 현재 마스킹 되어 disabled 된 상태
2.
IRQ_Wating : 인터럽트가 마스킹되지 않았지만 요청이 아직 안온상태이므로 대기인 상태이다
3.
IRQ_Pending : 인터럽트 요청이 왔지만, 아직 커널이 이를 서비스 해주지 못한 상태
4.
IRQ_Inprogress : 드디어 커널이 인터럽트 서비스 루틴을 수행하는 상태(ISR)
이렇게 IRQ Lines에는 4가지의 상태가 존재한다.
•
Action
실제 IRQ Lines에는 많은 디바이스가 연결되어있다. 따라서 요청이 어느 라인의 어느 디바이스로부터 왔는가에 대한 정보가 여기 담겨있다. 즉, Action 필드를 따라가보면 ISR이 리스트로 쭉 연결되어 있다.
이러한 4개의 정보는 하나의 IRQ Lines에 존재한다. 따라서 만약 IRQ Lines이 여러개라면 4개의 정보를 하나의구조체로 하여 배열 형태로 IRQ Lines 정보가 관리된다.
위 사진을 봐보자. 만약 IRQ Lines이 3개라면, 3개의 구조체가 존재하고 이는 irq_desc배열형태로 관리된다. 또한 아까말한 action 필드를 보면, ISR에 연결된다. 다양한 디바스이가 하나의 IRQ Lines에 연결된 경우 해당 포인터를 따라가서 특정 디바이스를 찾는다. 찾는게 아니면 next로 다음껄 찾는다.
저번시간에 멀티 프로세싱에 대한 얘기를 끝에 잠깐했었다. CPU가 만약 3개라면 어떤 CPU라도 해당 요청을 처리할수 있다. 보통 CPU가 인터럽트 요청을 처리하기 위해 제일 먼저 irq_desc[] 배열의 IRQn→status를 참조한다. 따라서 irq_desc[] 배열은 shared variable이다.
SMP 멀티프로세싱인 경우 상호배제 원칙을 잘 지켜야하므로 특정 CPU가 irq_desc[] 중 하나를 처리하면 다른 CPU가 접근하지 못하게 막아야하므로 IRQ Lines 구조체는 Lock 필드를 가진다. Lock 이 걸려있으면 다른 CPU는 접근하지 못하게 상호배제를 시킨다.
2. Function for interrupt
그럼 실제로 인터럽트가 걸렸을때 어떤 코드가 실행되는지 알아보자. 제일먼져 IRQn_interrupt() 가 호출되는데, 이는 어셈블러 함수이다. 해당 함수가 호출되면 간단한 동작을 한후 바로 do_IRQ() 함수를 호출한다.
위 코드는 실제 do_IRQ() 함수 코드이다.
do_IRQ() 함수는 struct pt_regs 라는 자료형으로 regs 변수를 하나 가지고 들어온다. 그리고 reg.irg_eax & 0xff 연산을 통해 irq line number를 뽑아낸다.
아까 irq_de sc[] 배열에는 각 irq_lines 구조체가 존재한다고 했다. 위에서 뽑은 line number가 바로 irq_desc 배열의 인덱스라고 생각하면 된다. 따라서 irq_desc + irq 연산을 통해 현재 irq_desc[] 의 특정 인덱스에 들어있는 구조체 주소를 가져오게 된다. (desc)
그리고 spin_lock(desc→lock) 함수를 통해 사용가능한 놈인지 체크를 하고 사용중인 놈이 없으면 lock을 걸고 진행을한다. 만약 누가 사용중이면 계속 기다리게 된다.
진행이 되면, 이제 desc→handler를 참조하여 현재 어떤 PIC가 요청했는지 찾고 그 PIC에 ack 신호를 보낸다.(desc→handler→ack(irq)). 그래야 다른 인터럽트를 처리할수 있다.
그리고 desc→status를 가져와서 이제 실제로 인터럽트 요청이 왔기 떄문에 irq_lines의 waiting status bit를 없애고 처리를 기다리는 상태인IRQ_pendding bit을 세팅한다. (왜냐하면 아직 ISR까지는 안갔기 때문)
•
(desc → status & ~(IRQ_waiting))
•
(desc → status |= ~IRQ_pending)
그다음 쭉 실행되고 사진의 우측을 보면 for문을 돌면서 desc→lock을 unlock한다. 왜냐하면 critical_section 즉, irq_desc[] 배열에서 요청들어온 라인을 찾았기 때문에다.
그다음 handle_IRQ_event()를 호출한다. handle_IRQ_event() 안에서 이제 실제로 action→에 연결되어있는 각 디바이스들의 요청된 작업을 수행한다. 이는 do-while ()로 되어있기때문에 action 필드가 NULL일 때까지 모든 해당 IRQ Lines 에 연결되어있는 요청한 디바이스의 인터럽트 요청을 처리한다.
정리를 한번 해보자. 왼쪽 박스는 HW, 오른쪽 박스는 SW이다. 분홍색 박스는 인터럽트의 Data Structure이다.
1.
프린터 같은 디바이스에서 인터럽트 요청이 오면 PIC가 이를 관리하여 실제로 CPU에게 보낸다
2.
4개의 CPU중 CPU0가 요청을 받고, 처리를 시작하면 do_IRQ()함수가 실제 호출된다.
3.
do_IRQ() 함수는 실제 메모리 안에 존재하는 SW적으로 구현된 로직이다. do_IRQ()에서 어느 라인에서 온건지 확인을하고 IRQ3에서 온것을 확인했다
4.
그럼 이제 실제 IRQ3→action을 뒤지면서 ISR을 수행한다.
이상태에서 CPU0가 ISR을 수행하고있는데, 아까 코드를 보면 아까 desc→handler→ack(irq)를 통해 ack를 보냈기 때문에 다른 디바이스들의 요청을 처리할수 있게된다.
만약 CPU0가 ISR을 수행하고 있는데 동일한 Line IRQ3에서 또 요청이 오게되면, 서로다른 CPU들이 동일한 ISR 루틴을 수행하여 충돌이 발생할수 있다. 따라서 do_IRQ() 왼쪽 아래쯤을 보면 action=NULL을 초기화시켜주고, if문으로 현재 Line의 status의 IRQ_inprogress 가 세팅되어있으면, 이러한 충돌을 방지하기 위해 goto out 종료되게 처리한다. 어짜피 종료되도 이미 현재 CPU0가 다 ISR을 수행시킨다.
나머지는 다음 글에 이어서 설명하겠당