만족

[Linux Kernel] Interrupt 본문

[Linux Kernel] Interrupt

기타 CS 이론/Kernel Satisfaction 2021. 6. 15. 04:25

Interrupt란?

비동기적으로 하드웨어 신호를 발생시켜 장치가 프로세서의 주목을 요청하는 것

 

I/O 장치와 프로세서 간의 속도 차이로 인해

프로세서가 I/O 작업을 요청하고, I/O 디바이스가 작업이 완료되었을 때

그것을 프로세서에게 알리는 것 역시 인터럽트이다.

 

이러한 인터럽트는 모든 종류에 대해 핸들링되는 것은 아니다.

 

실제로 인터럽트가 발생하더라도, 인터럽트 핸들러가 없는 인터럽트는 아무런 일도 일어나지 않는다.

 

IRQ (Interrupt ReQuest)

그렇다면 OS는 인터럽트의 종류를 어떻게 구별할 수 있을까?

(HDD가 발생시킨 인터럽트와 cpu timer가 발생시킨 인터럽트를 어떻게 구분하는가?)

 

ubuntu에서는 /proc/interrupts에 인터럽트 정보를 기술한 파일이 존재하는데,

이 파일에서 각 디바이스에 대한 Interrupt Request Number, 즉 IRQ 정보가 있어

OS는 발생한 인터럽트가 어떤 디바이스에 의해 발생된 인터럽트인지 알 수 있게 된다.

 

Interrupt Handler

IRQ Number를 통해 발생된 인터럽트가 어떤 종류의 인터럽트인지는 알았다.

 

그렇다면 그 인터럽트가 발생했을 때 특정 작업을 해주고 싶다면 어떻게 할까?

 

Interrupt Vector Table에는 각 IRQ number에 해당하는 index에,

Interrupt Handler의 시작 주소(함수 포인터)가 존재하고 있다.

 

Interrupt가 발생하면,

해당 InterruptVectorTable에서 [IRQ_Number]에 있는 함수 포인터값으로 PC값을 변경시켜

인터럽트 핸들러로 jump하게 되는 것이다.

 

이처럼 Interrupt Handler를 실행하는 일련의 과정을 ISR(Interrupt Service Routine)이라고 한다.

 

Interrupt Handler Context

인터럽트 핸들러는 인터럽트 컨텍스트에서 실행된다.

 

이것이 의미하는 것은 일반 프로세스와는 달리

실행 중간에 다른 작업에게 주도권을 뺏기지 않는다는 것이다.

(not preempt)

 

그런데 Interrupt Handler의 실행이 비교적 오래 걸린다면 어떻게 될까?

 

당연하게도 다른 Process나 Interrupt가 끼어들 수 없게 되므로,

Interrupt는 무시되고 Process는 Block 상태가 되어 시스템이 느려진다.

 

이런 경우 Deferrable function, bottom half 등을 사용해 핸들링함으로써

처리 작업을 미룰 수 있다.

 

또한 인터럽트 핸들러 내부와 인터럽트 핸들러가 사용하는 함수들은

sleep(), delay()하거나 mutex를 사용할 수 없다.

 

인터럽트 핸들러는 우선순위가 매우 높은 항목인데,

얘가 잠들어버리면 깨우는 작업이 중간에 끼어들 수 없게 되기 때문에

(우선순위가 더 높은 인터럽트 핸들러의 경우 가능)

반드시 뭔가 wait해야하는 상황이라면 spin_lock()과 같은 busy-wait 한 방법을 사용해야 한다.

 

Interrupt Handler Stack

Interrupt Handler는 자신만의 Stack 영역을 갖지 않는다.

 

다른 프로세스가 커널 모드로 실행 중일 때 그 프로세스를 멈추고,

그 스택 위에 일정 크기의 스택을 만들어 사용하다가,

작업이 끝나게 되면 사용했던 스택을 제거하여 원래의 스택 상태로 복구시킨다.

 

즉 커널 모드의 스택인것은 확실하지만,

어떤 프로세스에서 진입한 커널 모드에서 실행되는지는 그때그때 다르다.

 

따라서 Interrupt Handler에서 User Space의 메모리를 접근하려고 해서는 안된다.

 

Enable/Disable Interrupt Request

process context에서 실행되는 어떤 커널 함수 foo에서 spin_lock으로 해당 영역을 잠궜다고 해보자.

 

아직 spin_unlock을 하지 않은 상태에서 Interrupt가 발생해 Interrupt Handler가 실행되었고,

그 Interrupt Handler도 foo를 호출한다.

 

그러면 동일하게 foo에서 spin_lock을 호출했는데,

이미 spin_lock으로 잠긴 영역이므로, 다른 곳에서 spin_unlock을 할 때까지 기다린다.

 

그런데 Interrupt Handler는 절대로 다른 프로세스나 인터럽트 핸들러에게 선점당하지 않으므로,

Interrupt Handler는 무한히 wait 하게 되어 시스템이 데드락 상태에 빠진다.

 

만약 우선순위가 A인 IRQ가 spin_lock을 이용해 어떤 영역을 잠그고 진입했다고 하자.

 

아직 spin_unlock이 호출되지 않은 시점에서 

우선순위가 A+1인 새로운 IRQ가 발생하였고,

그 IRQ역시 같은 키로 spin_lock을 시도하려고 하는 상황을 상상해 보자.

 

unlock하기 위해서는 우선순위가 A인 IRQ로 돌아가야 하는데

우선순위가 A+1인 IRQ가 busy-wait로 계속 진입을 시도하려 해서

시스템은 완전히 멈춰버린다.

=> Interrupt Handler는 non-preemptive하기 때문에 이런 일은 발생하지 않는다

 

이럴 경우에는 어떻게 하면 좋을까?

 

바로 IRQ를 일시적으로 활성/비활성 시키는 방법이 있다.

 

#include <linux/interrupt.h>

...
local_irq_disable();
...
local_irq_enable()
...

 

local_irq_disable()하게 되면 다른 irq가 더 이상 작동하지 않도록 막는다.

 

따라서 local_irq_disable()~local_irq_enable() 사이에서는 다른 irq가 실행되지 않으므로,

위에서 말한 데드락 문제가 발생하지 않게 된다.

 

local_irq_enable()하면 다시 irq가 작동할 수 있게 만들어 준다.

 

 

 

 



Comments