본문 바로가기

23년 1학기 학교공부/운영체제및실습

[OS] Interrupt Handling

목차

    728x90
    반응형
    SMALL


    인터럽트란, 디바이스가 자신에게 발생한 어떤 일을 처리해달라고 커널에게 신호를 보내 요청하는 방법이다.

    발생 가능한 상황별로 대처 코드가 운영체제 내부에 구현되어 있으므로, 해당 이벤트를 처리하는 커널 코드를 실행해야한다.

    이때 커널에서 이벤트를 처리하는 코드를 실행하게끔 유도하는 방법이 총 3가지 있다.
    여기서 커널 코드를 실행한다는 것은 커널에 들어가야하기 때문에 커널 진입 방법, kernel entry point라고도 한다.

    1. interrupt
    인터럽트가 걸리면 디바이스가 커널에게 처리해달라고 신호를 보내고, 결국 커널에서 실행되므로 커널에 진입하는 방법이 된다.
    2. trap
    소프트웨어 인터럽트라고도 말한다. 인터럽트의 한 종류이므로 이것도 커널에 들어갈 수 있는 방법이다.
    3. system call
    유저 프로세스가 커널에게 뭔가 기능을 요청하는 것이다.


    1.Interrupt

    인터럽트란 비동기적인 이벤트가 발생했다는 사실을 주변 장치가 운영체제에게 알리는 방법이다.
    여기서 비동기적이라는 것은 이벤트가 장치에서 언제 발생할지, cpu에 언제 도착할지 등 발생하는 시점이 미리 정해지지 않은 경우를 말한다.
    이벤트가 아무때나 발생할 수 있으면 비동기적인거고, 발생이나 도착이 정해진 시점에만 가능하면 동기적(syncronos)인것이다.

    입출력 장치는 비동기적이라고 말할 수 있다.


    인터럽트 관련 하드웨어는 다음과 같이 구성된다.


    키보드에서 입력이 일어나고, 디스크에서 I/O가 끝났다고 가정하자.
    입출력장치의 컨트롤러가 cpu까지 연결된 컨트롤러를 통해 디바이스 드라이버가 pic칩이 장치와 연결되어있는데 그 선에 디지털 신호를 보낸다.
    디지털 신호를 보내면 pic칩에 신호가 오고, pic칩이 cpu와 연결된 핀에 신호를 중계한다.
    이외에도 시스템 clock은 시간의 흐름을 알려주는 중요한 하드웨어이고 자주 인터럽트가 걸려서 pic칩을 거쳐서 오는것보다 직접 연결하는게 효율적이기 때문에 별도의 선으로 연결된다.

    어쨌든 보통 pic칩을 거쳐서 cpu로 신호가 연결되면 cpu는 인스트럭션 하나를 실행한 후 인터럽트가 걸렸는지, 선에 신호가 떴는지 확인한다.
    만약 신호가 안떴으면 다음 인스트럭션을 실행하고, 다음 인스트럭션 실행 후에 또 선에 신호가 걸렸는지 반복적으로 체크한다.
    만약 신호가 걸렸다고 해보자.
    방금 실행했떤 인스트럭션 후 값이 레지스터 영역에 저장되어 있을것이다.
    이 레지스터 값들을 커널 스택에 저장하고 모드체인지로 커널에 진입한다.
    이것이 인터럽트의 처리 과정이다.
    만약 인터럽트가 유저 프로그램을 실행하고 있던 도중인 유저 모드에서 걸렸다면, 위와 같이 cpu의 레지스터 값들을 커널 스택에 저장하고 모드체인지한다.
    만약 인터럽트가 커널 코드를 실행하고 있던 도중인 커널 모드에서 걸렸다면, 모드체인지 할 필요 없이 레지스터 값만 커널 스택에 저장한다.
    결과적으로 실행하던 프로세스를 잠시 중단하는 것이고, 이후에는 커널모드로 들어가있다.
    여기서 인터럽트 핸들러라는 함수를 실행한다.
    이 인터럽트 함수는 누가 인터럽트를 걸었는지 조사한다.
    cpu에 clock으로부터 직접적으로 오는 선이 있고, pic칩과 연결된 선이 있다.
    clock 선에서 신호가 떴음을 확인하면 clock이 인터럽트를 걸었다는 것을 확인할 수 있지만,
    pic칩과 연결된 선에서 신호가 떴음을 확인하면 pic칩과는 여러 종류의 장치가 연결되어 있기 때문에 cpu가 인터럽트 핸들러를 실행하여 어떤 장치가 인터럽트를 걸었는지 pic칩에게 확인한다.
    그러면 pic칩은 자신과 연결된 핀들 중 몇 번 핀에서 신호가 왔는지를 인터럽트 핸들러에게 회신한다.
    인터럽트 핸들러는 인터럽트 디스크립터 테이블(idt, interrupt vector table)에서 해당되는 번호의 엔트리를 찾아간다.
    위 사진에서 보면, 만약 2번이라고 하면 2번 칸에는 tty_interrupt() 라는 함수의 주소가 저장되어있다.
    이 주소의 함수를 실행한다. 이 함수가 바로 실제로 키보드에서 걸린 interrupt를 처리하는 함수이다.
    그래서 특정 장치에 대해서 인터럽트를 처리하는 함수를 interrupt service 루틴이라고 부른다.
    즉 인터럽트 핸들러는 인터럽트 서비스 루틴을 불러주는 중간 역할인 것이다.
    만약 시스템 클럭이 인터럽트를 건 것이라면, idt에서 바로 0번 칸으로 찾아간다.
    이때 system clock은 1ms마다 interrupt가 걸리도록 실행되고, 보편적으로는 총 100번 실행되면 타임 슬라이스가 발생한다.
    이때 0번칸의 timer_interrupt()는 컨텍스트 스위치를 해줘야 할때 스케줄러를 실행시킨다.

    system clock이 100ms가 아닌 1ms마다 interrupt가 걸리는 이유는 유저 프로그램 실행 중에 가능한 자주 운영체제가 실행되어 시스템 체크를 하도록 하기 위해서이다.

    만약 유저모드를 실행하고 있었다면 모드 체인지를 해서 유저모드에서 커널모드로 바꿔야하고,
    커널모드를 실행하고 있었다면 모드체인지가 필요없다.
    다음으로 커널 안에서 인터럽트 서비스 루틴을 실행하는데, 이것이 바로 실제로 인터럽트를 위해 해주어야 하는 일인데, 인터럽트에 따라서 일의 양이 제법 많을 수 있어 처음부터 끝까지 한번에 다 해주면 오랜 시간이 걸릴 수 있다.

    예를 들어 커널에서 중요한 일을 하던 중에 clock인터럽트가 걸렸다고 가정해보자.
    이때는 interrupt 처리를 해줘야하는데, clock이 아닌 다른 장치에서 인터럽트를 걸었다고 가정하자.
    이때 그 장치로부터 처리해야할 일이 많다면 이 인터럽트를 바로 한번에 처리하게 되면 중요한 일을 미루는 셈이 된다.

    그래서 운영체제 개발자들은 인터럽트 처리를 해야하는 일을 두 부분으로 나눴다.
    인터럽트를 처리하는데 급히 해야하는 부분을 먼저 실행하고, 급하지 않은 부분은 bottom half라는 이름으로 남겨둔다.
    먼저 인터럽트 서비스 루틴에서는 급히 해야하는 부분을 먼저 실행하는데, 위 예시에서는 커널에서 중요한 일을 하고 있었으므로  나머지 부분을 미뤄두고 급히 처리해야하는 인터럽트의 일부를 먼저 실행 후 원래 하던 일로 돌아가게 한다.
    그런데 이때 급히 처리해야하는 인터럽트의 일부를 처리하던 중에 또 새로운 인터럽트가 걸렸다고 가정하자.
    이런 경우는 커널 모드에서 인터럽트가 걸린 경우이다.
    모드체인지 없이, 인터럽트 핸들러를 거쳐 인터럽트 서비스 루틴을 실행하여 처리하고 원래 하던 이전 인터럽트 처리로 돌아간다.
    이런식으로 인터럽트는 걸릴때마다 바로 처리하러 가도록 운영체제가 구현되어 있다.

     

     

    2. trap
    software interrupt라고도 한다.
    동기적인 이벤트이다.
    cpu가 소프트웨어를 실행하면서 인스트럭션 실행 사이클 때 인터럽트를 발생시키기 때문에 동기적이다.

    트랩의 예시는 다음과 같은 경우가 있다.
    인스트럭션에서 0으로 나눗셈을 하려는 경우 더이상 프로세스를 실행할 수 없고, 즉 인스트럭션을 실행할 수 없기 때문에 프로그램을 중단시키라는 신호를 운영체제에 알리기 위해 트랩을 건다.
    혹은 인스트럭션 안의 머신 코드가 명령어를 갖고 있는데, 이 명령어가 유효하지 않은 경우 운영체제에게 알려서 프로그램 실행을 중단시키도록 한다.
    혹은 cpu가 인스트럭션을 실행하려고 하는데 인스트럭션 안에 포함된 메모리 주소가 잘못된 주소라면 cpu가 미리 체크하고 메모리로 접근하기 전에 실행을 중단시키는 세그멘테이션 폴트가 있다.
    혹은 프로텍션 폴트 정보보호의 오류가 있다. 접근은 가능한 메모리 영역인데 쓸 수는 없을 때, 값을 쓰려고 하는 경우 프로세스를 중단하게 만들어야한다.
    혹은 페이지 폴트가 있다. 실행해야할 메모리, 실행해야할 내용이 메인 메모리에 안들어온 것이다. 아래 포스팅에서 더 자세히 설명한다. 이 경우에는 프로그램을 중단하지는 않고 해당 내용을 메인 메모리로 갖고 들어오는 작업을 시킨다.

    트랩에 해당되는 이벤트를 감지하는 것은 cpu라는 하드웨어이다.
    cpu가 인스트럭션을 실행하다가 감지하는 것이다. 그래서 소프트웨어 인터럽트라고 하지만, 실제로는 cpu라는 하드웨어가 운영체제의 트랩 핸들러 코드를 실행하게끔 하기 위해 자기 자신에게 인터럽트를 거는것이다.

    트랩에 걸리면 유저 프로세스 도중인것이므로 프로세스 실행을 중단하고 모드체인지로 커널로 들어가 레지스터 값을 커널 스택에 저장한다. 이후 인터럽트 핸들러를 실행하고 idt에서 해당하는 서비스 루틴을 찾아 실행한다.
    처리 방법은 인터럽트와 같기 때문에 트랩과 인터럽트를 구별하지 않기도 한다.

    대부분의 경우 트랩 서비스 루틴은 프로그램 실행 중단을 한다.
    이를 메모리 관리자에게 처리하도록 한다.

     

     

    3. 시스템 콜
    커널 함수를 불러서 유저 프로세스가 원하는 운영체제 서비스를 받는 것이다.
    프로그램 실행 중에 입출력, 자식 프로세스 생성, 파일 열기, 다른 프로세스와 통신하기 위해 소켓 만들기 등은 시스템 콜을 부르는 것이다.

    유저 프로그램에서 시스템콜이 등장하면 프로그램 컴파일때 컴파일러가 시스템 콜에 해당하는 어셈블리 인스트럭션으로 번역한다.

     

    위 그림에서, 시스템 콜을 어셈블리 인스트럭션으로 컴파일하게 되면 여러개의 어셈블리 인스트럭션이 되는데, 그 중 interrupt(int) 0x80이라는 어셈블리 인스트럭션이 들어있다.

    해당 인스트럭션은 interrupt의 번호가 0x80임을 알려준다.

    즉 IDT의 128번칸에 인터럽트 서비스 루틴의 주소가 들어있다는 뜻이다.

     

    위 그림에서는 IDT 128칸에 system_call handler라는 함수의 주소가 들어있다.

    이 함수로 점프하면 시스템 콜 핸들러가 실행되면서, 시스템 콜의 고유 번호에 따라 시스템 콜 테이블로 간 후, 그곳에서 시스템 콜을 실제로 처리하는 함수를 다시 부른다.

     

    이런식으로 시스템 콜이 서로 다른 테이블을 두 번 거치는데, 그 이유는 시스템콜의 종류가 많기 때문이다.

    255개의 칸만 존재하는 IDT에 시스템 콜의 모든 종류를 넣을 수 없다.

    728x90
    반응형
    LIST