[Kernel of Linux] 8. Interrupt (1)
Kernel/Theory

[Kernel of Linux] 8. Interrupt (1)

반응형

지난 강의 요약 - Scheduling & Interrupt (0)

리눅스에서 사용하는 Timer에 대한 동작을 알아보았다. HZ의 크기는 아키텍처마다 다를 수 있다.

Interrupt는 CPU cycle을 진행하는 도중 다른 PC로 jump 하게 만드는 효과가 있다. 그리고 Interrupt를 발생시키는 여러 devices를 통제하기 위해 PIC(Programmable Interrupt Controller)가 존재한다. PIC에서는 프로그래머가 원하는 대로 interrupt를 masking 할 수 있고 각 interrupt 별로 handler를 두어 원하는 동작을 실행하게 할 수 있다. Interrupt를 처리하고 있는 동안에는 해당 인터럽트 라인의 PIC는 block 상태이므로 인터럽트는 가능한 짧은 시간 내에 실행되고 마쳐야 한다.

* keyword: IRQ(Interrupt ReQest), INTR(INTerrupt Request), ISR(Interrupt Service Routine)

Single CPU가 아닌 Multiprocessing에서는 APIC(Advanced PIC)가 어떤 CPU에게 interrupt를 요청할지의 문제가 생기는데, 이를 해결하기 위한 두 가지 방식의 알고리즘이 있다. Static IRQ distribution algorithm, Dynamic IRQ distribution algorithm. static은 static table에 따라 그냥 주면 되지만 dynamic은 priority에 따라 interrupt를 선택한 후에 CPU에게 보낸다. 그런데 여기서 SMP(Symmetric MultiProcessing) 환경에서는 CPU들이 동일한 가치를 지니기 때문에 priority에 tie가 발생하면 CPU에 존재하는 counter 변수의 값에 따라 인터럽트 처리를 요청할 CPU를 선택하게 된다.

옛날에는 Asymmetric Multiprocessing 방식(Master CPU & Slave CPUs)이었지만 극명한 한계점으로 인해 구세대 멀티프로세싱 방식으로 전락했다.


1. Data Structure for Interrupt Handling

interrupt는 line에 달린 여러 devices가 발생시킬 수 있는데 하나의 line에서 아무리 많은 interrupt가 발생해도 결국 하나의 interrupt만이 라인을 따라 내려가게 될 것이다. 이런 interrupt들을 control 해주기 위해 PIC라는 것이 필요하다가 배웠다. 그렇다면 IRQ line이 가지는 정보에는 어떤 것들이 있을까?

  • Status
    ┌ IRQ_DISABLED : interrupt가 비활성화된 상태 (masked or not)
    ├ IRQ_WAITING  : interrupt가 활성화는 되어 있지만 아직 해당 라인에 interrupt가 발생하지 않은 상태
    ├ IRQ_PENDING : interrupt가 발생했지만 아직 kernel이 처리하지 않은 상태
    └ IRQ_INPROGRESS : kernel이 ISR을 수행 중인 상태
  • Handler
    - 현재 line이 연결되어 있는 PIC를 가리키고 있다.
    - 어떤 PIC에서 보낸 것인지 알기 위해 존재한다.
  • Lock
    - interrupt가 짧은 시간 내에 많은 CPU에 request 되고 요청을 받은 CPU들은 제일 먼저 irq line마다 존재하는 status를 확인하고 이후 처리를 하게 될 것이다.
    - 이 말인즉슨, irq_desc[] array가 shared variable이라는 것이다. 여러 CPU가 동시에 접근할 가능성이 있다는 의미다.
    - mutual exclusion을 위해 각 irq_desc마다 lock을 두면 해결할 수 있으므로 해당 field가 존재하는 것이다.
  • action
    - 포인터 변수
    - linked list 방식으로 연결된 ISR 정보들

line마다 해당 data structure가 존재하므로 IRQ line이 4개면 4개의 data structure가 존재하게 되는 것이다.

IRQ line의 정보들을 저장하고 있는 array가 바로 irq_desc[] 다. irq descriptor의 약자다. action field는 device마다 ISR을 처리할 수 있는 정보를 가진 irqaction 구조체의 주소를 포인팅 하고 있다. next 포인터를 통해 다음 irqaction struct를 가리키는 것으로 보아 linked list 형태로 존재하는 것을 알 수 있다.

 

2. Functions for Interrupt

interrupt가 걸리면 제일 처음 어셈블러로 된 함수를 호출한다. (ARM architecture를 예로 들면 interrupt를 익셉션(예외)으로 처리하면서 모드가 바뀌고 irq_vec 라벨로 분기한 뒤 stack에 현재 context를 저장하는 등 간단한 일을 처리한다.) 그 후에는 C로 작성된 do_IRQ() 함수를 호출한다.

do_IRQ() 함수는 v2.6.4 기준으로 다음과 같은 코드를 가지고 있다. parameter로 pt_regs struct를 받아온다.

IRQ line number를 가져오기 위해 eax의 마지막 8-bit를 & 연산한다. 그리고 irq_desc + irq로 해당 line의 시작 주소를 desc 포인터에 저장한다.

그리고 spin_lock() 함수를 통해 해당 라인의 lock을 획득하기 위한 시도를 한다. lock을 획득했다면 아래 code로 진입하게 될 것이다.

handler에 ack 신호를 보냄으로써 다음 interrupt를 처리할 수 있게 한다. 해당 라인을 다시 활성화시키는 것이다.

status를 설정하는 code를 보자. 먼저 status에서 IRQ_REPLAY와 IRQ_WAITING 상태를 해제시킨다. 이제 interrupt를 처리하기 위한 상태를 설정하는 것이다. 다음은 IRQ_PENDING 상태를 활성화시킨다. 현재 interrupt가 처리되기를 기다리는 상태라는 것이다.

이후 action에다가 현재 IRQ line의 action을 넣고 handle_IRQ_event() 함수를 호출한다. 이 함수에서는 해당 라인에 존재하는 per-device function들 중 요청된 function을 실행하게 된다.

정리하자면,

  1. mth IRQ line에서 interrupt가 발생해서 PIC에게 전달된다.
  2. PIC는 dynamic IRQ distribution algorithm을 기반으로 interrupt를 처리할 CPU를 선정한다.
  3. 해당 CPU는 interrupt가 발생한 mth IRQ line의 lock을 획득한다.
  4. 이후 device가 요청한 interrupt를 처리하기 위해 ISR을 돌게 된다.

2.1 Is there any problem?

그런데 이 함수를 실행하기 에 지금 가지고 있는 현재 라인의 lock을 unlock 시켜준다. 이유는 critical section인 status access를 벗어났기 때문이다.

하지만 여기서 생길 수 있는 문제는 어떤 한 CPU가 그 m번째 line에서 handle_IRQ_event() 함수를 통해 ISR을 처리 중임에도 같은 line의 또 다른 device에서 interrupt가 발생되었을 때이다. 이 상태에서는 m line의 lock이 풀려있는 상태라서 다른 CPU가 PIC에 의해 선정되어 m line의 lock을 획득하고 들어올 수도 있다. 마찬가지로 handle_IRQ_event() 함수를 호출해서 ISR을 처리하려고 하겠지만 먼저 처리하고 있는 CPU와 뒤따라 들어온 CPU가 충돌이 날 가능성이 매우 높다. 왜냐하면 뒤따라 온 CPU는 앞서 온 CPU를 앞지르지 못하고 한 줄로 ISR을 처리해야 하기 때문이다.

그렇다면 이걸 해결하기 위해서는 어떻게 해야할까? 

이 질문에 대한 답은 다음 포스팅에서 다루도록 하겠다.

반응형