본문 바로가기
computer science/운영체제

[운영체제] 인터럽트

by 박연호의 개발 블로그 2020. 2. 21.

인터럽트

대부분의 시스템에서 하나의 CPU를 두고 여러 프로세스들이 경쟁을 하면서 자신의 작업을 수행합니다. 여기서 각 프로세스는 정해진 시간만큼 CPU를 사용할 수 있으며 이 시간이 지나면 타이머 인터럽트에 의해 다른 프로세스에게 CPU가 이양 됩니다. CPU가 A프로세스에게 할당되어 있는데 급하게 처리해야할 작업이 발생하면 어떻게 될까요 ? 물론 급한 작업을 먼저 처리해주어여 하며 이런 경우를 인터럽트 라고 합니다. 

 

하드웨어 입출력 장치의 사용이나 프로그램에서 0으로 나누는 경우, 커널 함수를 사용하는 시스템 콜 등 우리가 시스템을 사용함에 있어 대부분이 인터럽트 입니다.

 

사전적 interrupt는 "방해"를 의미하는데, 컴퓨터의 세계에서는 방해보다는 "우선순위가 더 높은 작업을 우선적으로 처리한다"라는 의미가 더 강합니다. 일반적으로 마우스, 키보드, 입출력 장치등의 user interaction작업이 더 높은 우선순위를 가지게 됩니다. 

 

예를 들어 키보드가 마우스 작업의 경우 현재 하는 작업보다 우선적으로 처리해야 하기 때문에 인터럽트가 필요하게 됩니다. 또한 하드웨어 인터럽트는 프로세스의 클럭(clock)과 상관없이 비동기적으로 인터럽트를 발생시키기 때문에 인터럽트는 아무때나 발생할 수 있습니다.

 

인터럽트는 크게 소프트웨어 인터럽트와 하드웨어 인터럽트로 분류할 수 있습니다. 여기서 소프트웨어 인터럽트는 트랩이라고도 합니다. 

 

1. 소프트웨어 인터럽트

   - 허가되지 않은 메모리 주소 참조

   - 0으로 나누는 경우

   - 시스템 콜

   - 페이지 부재

   - ....

 

2. 하드웨어 인터럽트

   - 정전 또는 전원 공급의 이상

   - CPU 또는 기타 하드웨어의 오류

   - 타이머 인터럽트

   - 하드웨어 입출력 장치

   - ...

 

그리고 이런 이유로 발생한 인터럽트는 에 대한 정보와 해야할 일은 이미 커널에 정의 되어 있습니다. 이 부분은 앞으로 배울 IVT(Interrupt Vecotr Table), ISR(Interrypt Service Routine)에서 살펴 보겠습니다.


인터럽트 벡터 테이블

인터럽트 벡터 테이블(Interrupt Vector Table)이란 여러가지 인터럽트에 대해 해당 인터럽트 발생시 처리해야 할 루틴의 주소를 보관하고 있는 테이블을 의미합니다. 인피니언, 마이크로침, Atmel, 프리스케일, AMD, 인텔 등 대부분의 CPU들은 인터럽트 벡터 테이블을 가지고 있습니다.

 

인텔 x86에서는 이를 IDT(Interrupt Descriptor Table)이라고 하며, CPU는 IDTR(Interrupt Descriptor Table Register)라는 특수한 레지스터에 IDT 위치를 저장해놓습니다.

 

인터럽트 벡터 테이이블은 000000H–0003FFH 주소공간에 4바이트씩 할당하여 256개가 존재하는데 이것을 인터럽트 벡터라고 합니다. 인터럽트는 16진수 2자리로 이 벡터에서 해당 루틴의 번지가 기억된 순번을 지적하여 그 기능을 요청하면 그곳에 기억된 번지로 분기하여 인터럽트를 수행하게 됩니다.


인터럽트 서비스 루틴

인터럽트 서비스 루틴(Interrupt Service Routine)는 인터럽트 핸들러 라고도 하며 실제 인터럽트를 처리하는 루틴으로 실행 중이던 레지스터와 PC를 저장하여 실행중이던 CPU의 상태를 보종하고 인터럽트 처리가 끝나면 원래 상태로 복귀합니다. 운영체제 코드 부분에는 각종 인터럽트 별로 처리해야 할 내용이 이미 프로그램이 되어 있습니다. 


PIC

 

interl 8259A

만약 다양한 장치에서 인터럽트를 요청하면 이 중에 CPU는 어떤 장치의 인터럽트의 요청을 수용할 것이며, 또 어떻게 중재할까요 ?

이러한 역할을 하는 것이 PIC(Programmable Interrupt Controller)입니다. 말 그대로 Interrupt Controller이며 여러 장치들이 연결되어 있는데 서로 인터럽트를 요청하게 되는 상황에서 PIC는 이런 요청들에 대한 우선순위를 매기며 현재 인터럽트 처리 중인장치와 인터럽트를 요청한 장치들을 관리합니다.

 

각 장치들은 각각 IRQ line을 통해 PIC에게 인터럽트 요청을 하며, 이 둘은 연결되어 있으며 각각의 PIC는 8개의 핀을 가지며 이는 각각의 핀은 서로 다른 장치들과 연결되어 있습니다. 

 

아키텍처마다 인터럽트 컨트롤러 구성이 다른데 초기 CPU를 하나만 사용하는 X86 시스템에서는 PIC로 8259A 칩을 사용하였습니다.

8259A 칩은 총8개의 입력을 처리할 수 있고 PIC의 경우 2개의 8259A 칩 2개를 마스터-슬레이브(master - slave)방식으로 연결하여 총 15개의 입력을 처리할 수 있습니다. 16개가 아닌 15개인 이유는 마스터 8259A 칩의 2번 핀은 슬레이브 컨트롤러와 연결하는데 사용하기 때문입니다.

 

 

PIC 컨트롤러의 INT핀은 CPU의 INTR 핀과 직접 연결되어 있으며, 인터럽트가 발생했을 때 INT 핀에 신호를 발생시켜 CPU가 인터럽트를 처리하게 합니다. /INTA핀은 인터럽트가 CPU에 잘 전달되었음을 CPU가 PIC에게 알려주는 핀입니다. 

 

또한 PIC는 선택된 장치의 IRQ 번호를 확인하며 내부 base번호를 더한값을 data bus를 통해 CPU에게 전달합니다. CPU는 전달받은 값을 통해 IVT에서 ISR의 주소를 찾습니다.여기서 base값은 리눅스에서 기본적으로 32인데, 이는 0 ~ 31은 이미 예약된 인터럽트가 있기 때문입니다.

 

PIC 내부에서는 8비트의 IRR, IMR, ISR 레지스터를 사용합니다. 

현재 마스터 PIC의 핀에 각각의 핀이 [A장치,B장치,X,C장치,D장치,E장치,F장치]가 연결되어 있다고 가정해 봅시다.(IRQ2번 값을 슬레이브PIC와 연결되어 있습니다.)

 

IRR(Interrupt Request Regitster)

외부 장치와 연결된 인터럽트 핀 중에서 인터럽트를 요청한 장치를 관리합니다. 총 8비트로 이루어져 있으며 외부 장치가 인터럽트를 발생시키면 PIC 컨트롤러는 IRR 레지스터에 해당 비트는 1로 설정하여 인터럽트 발생 여부는 저장합니다. 즉 비트가 1로 세팅 되어있는 장치는 인터럽트를 요청한 장치입니다.

 

예를 들어 A장치와 F장치가 인터럽트를 요청 했다면 IRR(10000001)이 됩니다.

 


ISR(Interrupt Service Register)

현재 인터럽트 핸들러가 실행중인 인터럽트의 정보를 나타냅니다. PIC 컨트롤러는 특별한 옵션을 사용하지 않는 한 IRQ 0에 가까울 수록 우선순위를 높게 설정하므로 ISR 레지스터에 설정된 비트는 IRR 레지스터에 설정된 비트 중에서 비트 0에 가까운 비트와 같습니다.

 

입력단자에 연결된 주변장치에 인터럽트가 발생하면 8259A 칩은 해당 입력단자의 번호(IRQ 번호)에 자신의 내부 메모리에 기록된 base 값을 더하여 그 값을 방금 발생한 인터럽트의 번호로 CPU에게 알립니다. 이 base값을 세팅함으로써 운영체제는 8259A가 cpu에게 전달하려는 인터럽트 번호를 제어할 수 있습니다.

 

예를 들어 현재 A장치가 인터럽트를 받고 있다면 ISR(00000001)이 되며, 실제로 CPU에게 장치번호를 넘길때는 INT32(IRQ 0 + 32)가 됩니다.

 

 

IMR(Interrupt Mask Register)

비트가 1로 설정된 인터럽트 핀에서 발생한 요청을 무시하는 역할을 합니다. 특정 장치의 인터럽트 신호를 무시하고 싶다면, IMR 레지스터의 해당 비트는 1로 설정하면 됩니다. 

 

예를 들어 장치 중에서 A장치와 C장치는 인터럽트를 받고 싶지 않다면 IMR(00001001)이 됩니다.

 

 

8259A 칩에 대해 좀 더 알아보고 싶으면 아래의 아래의 문서를 읽어 보는것도 좋을 것 같습니다.

https://pdos.csail.mit.edu/6.828/2012/readings/hardware/8259A.pdf


인터럽트 처리 과정

이제 실제로 인터럽트가 어떻게 발생되는지 한번 살펴 보겠습니다. 예제로는 하드웨어 인터럽트의 경우입니다.

 

(1)

CPU는 PC(Program Counter)가 가리키는 주소공간의 명령어를 실행하고 있습니다. 주소는 byte단위이며 보통의 경우 하나의 명령어는 4byte로 되어있기 때문에 PC는 다음 주소를 가리키기 위해 PC = PC + 4(byte)가 됩니다. CPU는 다음 명령어를 수행하기 전에 Interrup Request Bit가 set되어 있는지 확인합니다. 만약 set되어 있으면 interrupt를 처리해야 하고 그렇지 않으면 PC가 가리키는 다음 주소공간의 명령어를 실행합니다(여기서 PC가 가리키는 공간의 주소에 따라 커널모드/사용자모드로 나뉩니다).

 

(2)

CPU가 어떤 프로세스를 처리하는데, 사용자가 키보드를 칩니다. 주변장치(키보드)로부터 인터럽트가 들어왔네요. 동시에 시리얼포트에 어떤 장치를 연결합니다. 이렇게 되면 두개의 인터럽트가 걸리게 되고 각각 IRQ1, IRQ 3이 됩니다. 

 

(3) 

여러 장치가 인터럽트를 요청하는 경우 PIC는우선순위가 가장 높은 장치를 선정해 CPU에게 전달하는데 다음과 같은 단계를 거칩니다.

 

  1. 여러 장치로부터 인터럽트 요청이 들어오면 IMR로 인터럽트를 요청을 거부할건지 먼저 검사합니다. 이 작업을 masking 작업이라고 하며, 이후 인터럽트 요청을 받을 장치에 대해 IRR의 해당 위치이 bit set작업을 합니다.
  2. 이후 PIC는 자신의 INT에 신호를 싣고 CPU의 INT에  보냅니다.
  3. CPU는 이 신호를 받아 EFLAGS의 레지스터의 IE비트가 1로 세팅되어 인터럽트를 받을 수 있는 상황이라면 /INTA를 통해 PIC에 인터럽트를 잘 받았다는 신호를 보냅니다. 
  4. PIC는 /INTA 신호를 받자 마자 IRR중 가장 우선순위가 높은 장치를 선정하고 해당 비트를 ISR에 set합니다. 그리고 ISR에 set된 비트는 IRR에서 unset을 해줍니다.
  5. IRQ number는 PIC 내부적으로 base값을 더하여 data bus를 통해 CPU에 전달됩니다.

 

두개의 인터럽트에 대해 PIC는 어떤 녀석을 먼저 처리시킬지 선택해야 합니다. 보통 우선순위가 높은 순서대로 처리하는데 IRQ 0은 IRQ 3보다 그 우선순위가 높기 때문에 키보드 인터럽트를 먼저 처리합니다. 그리고 현재 PIC register들의 값을 확인해보면,

 

IMR : 00000000, 딱히 인터럽트를 막고 싶은 장치가 없기 때문에 모두 0으로 처리합니다.

IRR : 00001000, 원래는 00001010이었는데 IRQ 0의 인터럽트가 우선순위가 더 낮아 선택되었기 때문에 0부분은 지워졌습니다.

ISR : 00000001, 현재 인터럽트 서비스를 받고 있는 녀석은 IRQ 0 입니다.

 

(4)

CPU는 PC가 가리키고 있는 주소공간의 명령어를 실행하다 Interrupt Request Bit가 set되어 있는 것을 확인하고 Interrupt를 인지합니다. ISR을 처리하기 전에 ISR을 처리하고 나서 원래 처리하던 작업을 다시 진행해야 하기 때문에 현재 진행중인 작업을 스택에 저장합니다. 이는 곧 context 저장을 의미하는데 CPU의 상태는 코드의 수행에 관계된 레지스터의 집합이라고 할 수 있습니다. 이렇게 프로세스의 상태와 관련된 레지스터의 집합을 다른 말로 context라고 합니다.

 

(5)

앞서 PIC가 data bus로 전달한 값을 사용하여 IVT에서 ISR주소를 알아냅니다. 그리고 ISR값을 PC에 저장하여 CPU가 인터럽트를 처리할 수 있게 합니다. 

 

(6)

현재 CPU는 인터럽트 처리중이며, 다른 말로 ISR의 코드를 하나씩 읽어가면서 작업을 수행하고 있습니다. 각각의 인터럽트에 대한 작업(ISR)은 이미 커널공간에 프로그래밍 되어 있으며 커널은 항상 메모리에 올라와 있기 때문에 ISR 코드 역시 메모리 공간안에 존재하게 됩니다.

 

(7)

인터럽트 작업이 모두 끝나면 이전에 스택에 저장해놓았던 context를 복구하고 PC의 값을 바꿔 줌으로써 인터럽트 이전에 수행하던 프로세스의 작업을 마저 이어갈 수 있습니다.