[운영체제] 인터럽트

최지수·2022년 2월 18일
0

운영체제

목록 보기
6/13
post-thumbnail
post-custom-banner

인터럽트란?

인터럽트Interrupt는 CPU가 프로그램을 실행 중일때, I/O Deivce 등의 장치나 예외 상황이 발생해서 처리가 필요할 경우에 CPU에 알려서 이를 처리하는 기법이에요.

CPU는 어느 한 순간에 PCProgram Counter가 가리키는 Code를 실행해요. 그 중에 하드웨어 같은 장치 접근을 하는 경우 이를 처리할 때까지 프로세스는 기다려야waiting 해요. 그래야 장치를 통해 얻은 데이터를 기반으로 프로세스 로직을 수행할 수 있기 때문이죠.

인터럽트의 필요성은 장치 접근 뿐만 아니라 다양한 경우에도 필요로 해요. 무엇이 있는지 알아봐요.

인터럽트의 필요성

선점형 스케쥴러 구현

프로세스가 running 중에 스케쥴러가 이를 중단시켜서 다른 프로세스로 교체하기 위해 현재 프로세스를 중단 시킬 필요가 있어요. 그러기 위해선 스케쥴러 코드가 실행되어 현 프로세스를 중지시켜야 하죠. 이때 인터럽트를 발생시켜요.

I/O Device와 커뮤니케이션

위에서 언급한 내용이에요. 저장매체에서 데이터 처리가 완료되면 프로세스를 다시 깨울 때Block \to Ready에도 인터럽트를 발생 시켜요.

예외 상황 핸들링

CPU가 프로그램 실행 중에 I/O Device 등의 장치나 예외 상황이 발생하면 CPU가 이 상황을 처리할 수 있도록 CPU에게 알려야 할때도 사용되요. 프로세스의 Code 실행 중에 하드웨어에서의 이상 동작 등 외부에서 문제가 발생하면 이를 처리할 수 있게 하는거죠.

대표적인 예로는,

1. I/O Device에서 파일 불러오기 완료.
이 때 프로세스를 Block \to Ready 상태로 바꿔줍니다.
2.숫자를 0으로 나눴을 경우의 Exception 발생.
이때 운영체제와 스케줄러에게 인터럽트를 통해 Exception을 알려서 해당 프로세스를 Kill시켜요. 중지를 시키거나 에러 표시를 띄워줘요.

인터럽트 처리 예

I/O Device

  1. 프로세스에서 파일을 불러오기를 요청해요(Running \to Waiting).
  2. 이후 파일 불러오기가 완료되면 운영체제에 알려요.
  3. 운영체제는 해당 프로세스를 Waiting \to Ready 상태로 변경시켜요.
  4. 이후 컨텍스트 스위칭Context Switching을 통해 Ready \to Running으로 바꿔줘요.

Exeption

#include <stdio.h>

int main(int argc, char* argv[]){
	printf("Hello World");
    int data;
    int divider = 0;
    data = 1 / 0;	// 오웄...! 이때 인터럽트가 발생해요. 보통 빌드하면 에러를 띄워줘요.
    return 0;
}

추가 - 이벤트와 인터럽트의 상관관계

인터럽트는 일종의 이벤트로 불려요. 이벤트에 맞게 운영체제가 처리하는 것이라 보시면 되요.

인터럽트 종류

인터럽트의 종류는 크게 내부 인터럽트외부 인터럽트로 나뉘어요. 이에 대해 알아봐요.

내부 인터럽트

주로 프로그램 내부에서 잘못된 명령 또는 데이터를 사용할 경우 발생해요. 대표적인 예로는,

Deivde-by-zero

0으로 나눈 경우에요. 위에서 계속 언급되던 그거 맞아요.

사용자 모드에서 허용되지 않은 명령 또는 공간 접근

리눅스로 예를 들면, 리눅스는 기본 프로세스 공간이 4GB정도 되요. 그 중 1GB는 커널 모드에 대한 메모리에요. 사용자 모드에서 허가되지 않은 모드에 접근하려고 하면 인터럽트를 발생시켜요.

Overflow/Underflow

계산된 결과 값이, 예를 들면 결과 값을 int 타입 변수에 초기화를 하면, 32-bit CPU 환경 기준으로 231-2^{31} ~ 23112^{31} -1 범위 외 값이 초기화되면 Overflow나 Underflow 인터럽트를 발생시켜요.

내부 인터럽트는 프로그램 내부에서 주로 발생해서 소프트웨어 인터럽트라고도 표현해요.

외부 인터럽트

주로 하드웨어에서, 즉 프로그램 외부에서 발생하는 인터럽트에요.

대표적인 예로는,

1. 전원 이상
2. 기계 문제
3. 키보드 등 I/O 관련 이벤트
4. Timer 이벤트

가 있어요.

덜 설명한 거, Timer 이벤트란?

Timer 이벤트는 선점형 스케줄러를 위해 꼭 필요해요. 일정 주기로 하드웨어가 운영체제 한테 Timer 이벤트를 알려서 다른 프로세스를 사용 할 수 있게끔 하는거에요.

외부 인터럽트는 프로그램 외부에서 주로 발생해서 하드웨어 인터럽트라고도 표현해요.

내부 동작

그럼 조금 더 디테일하게 들어가봐요.

시스템 콜 인터럽트

시스템 콜 실행을 위해서는 강제로 Code에 인터럽트 명령을 넣어서 CPU에게 해당 시스템 콜을 실행시켜야 해요.

내부 Code

mov eax, 0x01 // 시스템 콜 번호
mov ebx, 0x00 // 인자 값
int 0x80 // 소프트웨어 인터럽트 명령(CPU op code + 인터럽트 번호)

위 언어는 CPU에게 명령을 내리는 어셈블리어에요. 조금 더 설명하자면,

  1. eax 레지스터에 호출할 시스템 콜 번호를 넣어요( 각 시스템 콜은 번호가 등록되어 있어요). 어떤 시스템 콜을 호출하는지를 여기서 초기화해요.
  2. ebx 레지스터에는 시스템 콜에 해당하는 인자 값을 넣어줘요. open 함수로 보면 O_READONLY가 여기에 속해요.
  3. int라는 소프트웨어 인터럽트 명령을 호출하면서 0x80 값을 넣어요. 여기서 int는 프로그래밍 언어의 그 int는 아니에요. 정확하겐 CPU Opcode(명령 코드)에요. 0x80는 인터럽트 정보를 관리하는 Interrupt Descriptor TableIDT에서 system_call()이라는 함수가 있는 주소Code를 의미해요.

3에 대한 내용을 좀 더 알아보자면, 시스템 콜 인터럽트 명령을 호출하면서 0x80(Opcode)을 CPU에 넘겨줘요. 그러면서,

  1. CPU는 사용자 모드를 커널 모드로 바꿔줘요.
  2. IDT에서 0x80에 해당하는 주소Code(함수)를 찾아내 실행해요. 이 때 eaxebx 값도 함께 넣어줘요.
  3. 이 때 eax 레지스터를 확인해서 시스템 콜 번호를 확인하고, 이 번호에 해당하는 시스템 콜을 호출해요.
  4. 그리고 ebx에서 시스템 콜 함수의 인자를 넘겨주고 함수를 실행해요.
  5. 함수의 결과를 프로세스에 보내고 커널 모드에서 다시 사용자 모드로 돌아가 프로세스를 실행해요.

IDT에 관리되는 시스템 콜 함수는 매애애우 많지만, 간단하게 추려내면 아래와 같아요.

사용자/커널 모드와 프로세스 그리고 인터럽트

정리해서 말씀드리자면,

  1. 프로세스가 실행되요.
  2. 실행 도중에 시스템 콜이 호출됐어요! int 0x80 \to mov eax, 시스템 콜 번호 \tomov ebx, 시스템 콜 인자 값 순으로 CPU에게 인터럽트를 알려요.
  3. 이후 작업이 완료되면 다시 프로세스를 재개해요.
  4. 선점형 스케줄러로 인해 타이머가 만료되면 프로세스가 Running\toWaiting 상태로 돌아가고, 다음 프로세스를 실행해요.

하나의 인터럽트 흐름을 자세히 보면 아래와 같아요.

인터럽트와 IDT

IDT와의 상관관계에 대해 알아봐요. 인터럽트는 IDT에 컴퓨터가 부팅되었을 때 운영체제가 미리 정의해서 각 번호와 실행 코드를 가리키는 주소가 기록되어 있어요. 사하원칙(?)으로 말씀드리면 아래와 같아요.

  1. Where : IDT
  2. Who : 운영체제
  3. Whene : 컴퓨터 부팅
  4. What : Code의 주소

IDT에 정의된 인터럽트 정보는 운영체제에 따라 달라요. 일단 리눅스에 대해서 말씀드리면

  • 0~31 : 예외 상황 인터럽트(내부 인터럽트)
  • 32~47 : 하드웨어 인터럽트
  • 128 : 시스템 콜

인터럽트에 대하여 다뤄봤어요. 단순히 강의 내용 기록한 걸 정리하는 것도 제법 일이네요 ㅎㅎ. 그래도 복기하는 느낌으로 하니 지식이 견고해지는 느낌이 들어요. 여기까지입니다. 추가로 수정할 일 있으면 계속 업데이트할게요.

profile
#행복 #도전 #지속성
post-custom-banner

0개의 댓글