Part3. 인터럽트(Interrupt)

들어가기 전에...

0.시작하면서...

앞서 커널 개발시에 권장되는(??) 알아야하는 몇가지에 대해서 언급했었다.
그럼 이제 찬찬히 그것들에 대해서 알아볼텐데... 오늘은 인터럽트(Interrupt)에 대해서 한번 볼까 한다.

1.인터럽트(Interrupt)란?

1.1 인터럽트(Interrupt)의 정의

인터럽트(Interrupt)는 내부 or 외부에서 특정한 이벤트로 인해 실행중인 코드를 중단하고 해당 이벤트를 처리하는 예외적인 상황을 말한다. Intel Architecture에서는 이런 예외적인 상황을 아래와 같이 크게 2가지로 구분하고 있다.

  • 1.Interrupt : External(Hardware generated) Interrupt와 Software generated Interrupt를 포함.
  • 2.Exception : Processor-detected program-error exception과 Software-generated exception과 Machine-check exception을 포함. fault, traps, abort로 구분됨

뭔가 상당히 복잡한데, 위 2가지에 대한 자세한 내용은 여기서 설명하지 않을 것이고, 궁금한 사람은 Intel Architecture Manual의 Volume 2 : Software Developer's Manual을 살펴보기 바란다.
간단히 알아야 할 것만 설명하면, 저런 예외적인 상황은 Hardware 또는 Software 적인 방법으로 발생할 수 있고, 우리가 프로그램을 실행하면서 발생하는 Divide By Zero와 같은 에러도 Exception으로 처리가 된다는 사실이다.

편의상 위의 두가지 모두를 인터럽트(Interrupt)라고 부르기로 하자.

1.2 인터럽트(Interrupt)와 컨텍스트(Context)

내가 소시적에(??) OS를 만들면서 여러책을 보았다. 그때 한 OS 책에서 본 내용이 아직도 기억에 남는데... 그 책은 인터럽트에 대해서 이렇게 설명해 놓았다.

프로그램을 실행 도중, 인터럽트가 발생할 시 현재 작업을 중단하고 상태를 저장한 뒤 인터럽트 서비스 루틴(ISR)을 실행한다. 실행이 완료되면 중단된 시점부터 실행이 재개된다.

여기서 상태를 컨텍스트(Context)라고 이야기하는데, 컨텍스트는 실행중인 프로세스의 내용을 말한다. 위의 항목에서 내가 궁금했던 점은... "상태를 저장한 뒤" 라는 부분... 어느 부분을 어떻게 저장하는 것인지가 명확하지 않았다. 나중에 Intel 메뉴얼을 뒤져서 정확하게 알게 되었지만, 알고 난뒤의 허탈함 and 배신감이란 이로 말할 수 없었다.

과연 "상태"를 어디까지 저장해주는걸까? 아래는 Intel 메뉴얼에 Software Developer's Manual에 나오는 그림이다.

Intel Architecture의 Interrupt 발생시 Stack의 변화

벌써 눈치를 챈 사람들도 있겠지만... 그렇다. 스택(Stack)에 단순히 Flags/CS/EIP/Error Code가 저장되는 것이 전부였다. @0@)/~
그 말은 ISR에서 사용하는 레지스터들은 "알아서" 저장하고 사용한 뒤 "알아서" 복원해야 한다는 이야기다. 위에서 보면 Privilege Level이 변하지 않는 경우는 Flags/CS/EIP/Error Code를 저장하며, 변하는 경우는 SS/ESP를 추가로 더 저장한다. 추가로 저장된 SS/ESP는 인터럽트 처리가 끝났을 때 하위 레벨의 스택으로 다시 돌아가는 용도로 사용하는 것이다.

왜 추가로 더 저장하는가 하니... Intel Architecture는 총 4개의 Level( Ring0 ~ Ring3)을 가지고 Level간에 사용하는 스택을 다르게 설정할 수 있다. Level이 변경될 때 마다 CPU는 해당 Level의 Stack으로 변경해 주게되고 이것을 Stack Switching이라고 부른다(모든 레벨이 같은 스택을 사용할 수도 있다. 일단 Level이 다르면 Intel CPU는 SS/ESP를 Handler의 스택에 저장해 준다는 것만 알아두자.)

2.인터럽트의 처리

2.1 인터럽트 서비스 루틴(Interrupt Service Routine-ISR)의 컨텍스트(Context) 저장

커널을 만들려면 위에서 언급했던 인터럽트들을 처리해 주는 인터럽트 서비스 루틴(Interrupt Service Routine-ISR)을 만들어야 하는데, 보통 아래와 같은 형태를 띈다.

_kIsr :  
    **  pushad**
    **  push ds  
    push es  
    push fs  
    push gs  <== 여기까지가 컨텍스트(Context)를 저장하는 부분**

    ; 일단 커널 쪽에 진입해야 하므로 세그먼트 재 설정   
    mov ax, 0x10  
    mov es, ax  
    mov ds, ax
    mov fs, ax  
    mov gs, ax  

    **call HandlerFunction  <== Handler 함수 호출 부분 **  

    **  pop gs  
    pop fs   
    pop es  
    pop ds  
    popad  <== 여기까지가 컨텍스트(Context)를 복구하는 부분  

    iretd  <== 인터럽트 처리 완료후 복구하는 부분**

간단히 설명하면 위의 파란색 부분은 사용할 레지스터들은 저장하고 핸들러 루틴을 불러서 필요한 처리를 한다음 다시 복원하는 코드이다. 붉은 색 부분은 실제로 스택에 저장된 Flags/CS/EIPSS/ESP를 복원하고 코드로 돌아가는 코드이다.

크게 어려운 어셈블리어 명령이 아니기 때문에 설명은 다음으로 미룬다. @0@)/~ 궁금한 사람은 역시 메뉴얼을 참조하면 된다.

2.2 핸들러 루틴(Handler Routine)

지금까지 ISR의 컨텍스트 저장 코드에 대해 간단히 보았다. 이제 HandlerFunction이 무엇을 하며 어떻게 작성해야 하는지 한번 알아보자.

인터럽트가 발생하면 원인이 Interrupt인가 Exception인가에 따라서 HandlerFunction의 실제 역할이 다르고, Interrupt or Exception의 세부분류에 따라 그 역할이 다르다.

보통 Handler는 C 함수로 작성되고 우리가 흔히 알고 있는 일반 함수 코드 처럼 작성된다. 아래는 프레임워크에서 사용된 키보드 핸들러의 소스코드이다.

   //-----------------------------------------------------------------------------  
    //  
    // 키보드 핸들러 버퍼에 값을 집어 넣는다.  
    //  
    //-----------------------------------------------------------------------------  
    void kIsrKeyboard( void )  
    {  
        BYTE  bCh;
        bCh = kReadPortB( KBD\_PORT\_BUFFER );
        kPrintchxy\_low( GDT\_VAR\_VIDEOMEMDESC, 11, 0, bCh, 0x05 );
        // 키를 버퍼에 넣는다.  
        kAddKeyToBuffer( bCh );

        // Bottom Half 사용  
        g_stBottomHalfManager.vstBottomHalfUnit[ 1 ].bFlag = TRUE;  
        kSendMasterEoi();  
    }

우리가 흔히 쓰는 함수와 같은 형태를 하고 있으니 이해하는데는 문제가 없을 것이다. 코드를 보면 키보드 포트에서 값을 읽어서 버퍼에 저장하는 역할을 한다는 것을 금방 알 수 있다.

그렇다면 ISR 함수와 우리가 사용하는 일반 함수와 차이점이 무엇일까? 함수가 불리어지는 시점의 차이가 가장 큰 차이다.

ISR은 인터럽트가 발생한 시점에서 호출되는 코드이기 때문에 장시간 걸리는 작업을 실행하면 시스템 전체의 성능에 영향을 미치게 된다. 인터럽트가 발생하면 CPU에서 기본적으로 인터럽트를 불가 상태로 만든다음 ISR 함수를 호출하게 된다. 다시 말하면 ISR 처리 루틴이 완전히 끝나지 않는 한 다른 인터럽트가 발생하지 못한다는 것이다. 이것은 아주 치명적인데, 예를 들어 특정 인터럽트 루틴에서 무한루프를 돌면 키보드/마우스/타이머 등등이 먹통이된다. @0@)/~!!!!

2.3 주의해야 할 점

** ISR에서 절때 시간이 많이 걸리는 작업을 해서는 않된다.**

이것은 진리이며, 자칫 잘못하면 시스템 전체의 성능을 느리게 만든다. 예외 핸들링 시 레벨에 따라서 우선순위가 다르기 때문에 낮은 Level의 예외는 지연되게 되므로 더욱 세심한 배려가 필요하다. 최상위 Level의 핸들링에서 처리가 늦게되면 그 외에 낮은 Level의 예외는 당연히 지연될 수 밖에 없다.

인터럽트의 Level에 대한 분류는 가장 높은 것이 Hardware Reset과 Machine Checks이고 그 밑으로 이것 저것 있는데, 역시 궁금한 사람은 Intel Architecture 메뉴얼을 참조하도록 하자.

Intel Architecture의 예외 수준(Exception Level)

그리고 잊지 말아야 할 사실을 다시 한번 강조한다.

*인터럽터가 발생하면 기본적으로 ISR 핸들러 호출 시 EFLAG 레지스터의 인터럽터 가능 플래그가 0으로 되어 Disable된 상태라는 것이다. *

이 말은 인터럽터 핸들러 안에서 무한루프를 돌면, 그 외에 다른 인터럽터도 발생하지 않는다는 말과 동일하다. 따라서 각별한 주의가 필요하다.

3.인터럽트(Interrupt)의 처리흐름

인터럽트가 발생하면 수행중인 태스크는 중지되고 인터럽트 핸들러가 호출된다고 했다. 이것을 그림으로 표현하면 아래와 같다.

인터럽트 발생 시 흐름

특정 태스크가 실행중이다가 인터럽트가 발생하면 인터럽트 핸들러에서 인터럽트에 대한 처리가 되고, 그것이 완료된 다음에야 태스크로 복귀한다.

핸들러에서 작업이 늦게 끝나면 끝날수록 태스크로 복귀하는 시간이 늦어지며 이 시간 동안 거의 인터럽트가 불가능해서 전체적인 지연을 초래하는 것이다. 물론 요즘 커널들은 인터럽트 핸들러 안에서도 인터럽트가 발생할 수 있도록 하여 더욱 가용성을 높이고 있다.

인터럽트의 중복을 허용함으로써 오는 이득은 특정 인터럽트가 완료되지 않은 상태에서 다른 인터럽트의 처리가 가능하므로 많은 인터럽트를 다중으로 처리할 수 있다는 점이다. 대신 인터럽트가 다중으로 발생할 수 있으니 커널코드 자체도 재진입(reenterance) 가능하도록 해야 되니 커널 코드가 굉장히 복잡해 지는 단점을 가지고 있다.

커널 코드 내에서 인터럽트 발생이 가능하도록 한 좋은 예제로 Linux Kernel 2.6 버전이 있다. 커널이 2.6 버전으로 올라가면서 스케줄러 부분 및 메모리 관리 부분이 비약적으로 향상되었다는데, 궁금한 사람은 참고하는 것도 괜찮을 듯 하다.

5.마치면서...

인터럽트에 대한 위 내용 정도의 지식은 거의 필수라고 볼 수 있다. 이번 내용에서 깊은 내용은 다루지 않고 개론 정도만 언급했으므로 알아두도록 하자.

4.첨부

142147_part3.ppt
13.3 kB

OS 프레임워크 전체글 보기

Part1. 커널 개발이 힘든 이유

들어가기 전에...

0.시작하면서...

프로그래머라면 누구나 자기가 만든 OS에서 프로그램을 실행시키는 꿈을 꿔본 적 있을 것이다.
OS... 그것은 프로그래머의 로망(??)이라고도 말 할 수 있는데....

문제는 OS 구현이 아니라 커널 개발도 힘들다는 것...
커널 개발에 시도했던 많은 초보 프로그래머들이 격는 수많은 벽....

Assembler, CPU Architecture, Device Control, C Language Skill... 등등...

난관은 아주 많다. 부트로더까지는 어셈블리어를 대충 공부해서 어찌 어찌 해본 사람들이 꽤나 많을 것인데, 보호모드로 진입하여 32bit 커널을 구동시키고 유저레벨과 커널레벨을 구분하여 응용프로그램을 생성하고 이후 기타 등등 까지 해본 사람은 크게 많지 않다고 생각한다. (왜?? 여기서 부터는 CPU Architecture를 이해해야하고 C 언어의 약간 고급 스킬이 필요하기 때문이다. 물론 요즘은 좋은 책이 많이 나와있어서 앵무새 처럼 치기만 하면 되지만, 이 또한 커널 디버깅이 필요한 상황이 닥치면 쉽게 포기하고 만다... ㅡ_ㅡ;;;; )

1. OS 제작... 그 높은 벽

나도 대학교 1학년 말에 아는 것 없이 개발을 시도했다가 어셈블리어의 장벽과 CPU Architecture에 대한 장벽에 부딪혀서 한번 좌절을 했었다. 그뒤 소스가 오픈되어있다는 리눅스를 깔아서 미친듯이 코딩을 하고 커널 코드를 뒤져보고해서 벽을 넘어보려했지만 역시 무리....
결국은 기초가 문제라는 것을 깨닫고 체계적으로 어셈블리어 공부를 시작했고 IBM 호환 PC의 구성에 대해서 공부했다. 그렇게 시간이 흐르고나니 자연스레 부트코드, CPU Architecture에 대해서 이해가 되었다.

이것이 2002년도 말쯤이었던 걸로 기억된다.

그 뒤로 커널을 만들고 유저레벨/커널레벨로 분리하고, 어플리케이션을 위한 시스템 콜(System Call)과 라이브러리(Library)를 만들면서 OS라고 부르기에는 많이 부족하지만 간단한 OS를 만들 수 있었다.
<궁금하신 분들은 여기 참조.. http://kkamagui.egloos.com/3071201 >

2. OS 포팅(Porting)의 어려움

운이 좋아서 ARM 보드(ARM7TMDI 코어 사용)를 구하여 Kernel을 제작할 수 있었는데, 기존 소스를 포팅해보려했지만 워낙 INTEL Architecture에 의존해서 구현했기 때문에 실패하였다. Kernel 코드가 너무 CPU Architecture와 긴밀하게 연결되어 분리가 거의 불가능한 지경이었다. 결국 Kernel을 처음부터 다시 구현하기 시작했는데, OS까지도 못가보고 도중 포기(사실 중단이란 표현이 더 맞을 것 같다. 삽질을 다시 하려니 죽을것 같은 귀차니즘이 몰려와서... ㅡ_ㅡ;;; )

3. OS 제작과 프레임워크(Framework)의 시작

ARM 커널을 마지막으로 3년이라는 시간이 흘렀다. OS 제작은 점점 기억에서 멀어져가던 차에 어느날 갑자기 NDS(Nintendo Dual Screen, 게임기)에 OS를 올려보면 좋겠다는 생각이 들었다. 하지만 다시 처음부터할 생각을 하니 아득하고... 그러다 문득 든 생각이 있었다.

"왜?? OS를 만들려고 시작하면 알아야 하는게 이렇게 많은 걸까?"
"좀 더 쉽게 시작하거나 간단하게 테스트할 수 있는 그런 방법은 없나?"

그래서 생각한 것이 프레임워크(Framework) 였다. CPU Architecture에 관련된 부분이나 Hardware에 관련된 부분은 wrapping하여 CallBack과 API 형태로 만들고 CallBack을 어느정도 구현하고 API를 사용하면 간단히 Kernel을 제작할 수 있는 프레임워크... 당장 시작해야겠다는 생각이 들었다.

이것이 OS 프레임워크 프로젝트의 시작이었다.

00 KKAMA OS

들어가기 전에...

옛날 kkamagui.osx86.org를 운영할 때 썼던 글을 옮겨왔습니다. ^^;;; 사실 이제는 64비트 멀티코어 OS 구조와 원리를 만들 때 개발한 MINT64로 발전해서 KKAMA OS는 더이상 볼 수 없지만... MINT64를 KKAMA OS라고 생각해주시면 좋겠습니다. ^^;;;

1.소개

KKAMA OS는 KKAMAGUI가 만든 OS란 뜻입니다.

프로젝트를 시작한건 작년 10월이나 11월쯤 되겠군요. (글고보니 이놈 정확한 생일도 모르고 있네요. 쩝쩝.. -_-a..)

처음엔 부트로더로 시작했던 녀석이 어느새 시간이 지나 32Bit 허접 Kernel로 진화를했습니다. 제가 주로 쓰는 Nic Name이 까마귀(KKAMAGUI)인지라 이녀석의 이름 또한 제 별명을 따라 지었습니다.

왜 KKAMAGUI OS가 아니라 KKAMA OS인지 궁금해 하실 분이 있으신지 모르겠으나 (-_-a.. 없으려나...) 어느날 녀석의 이름을 KKAMAGUI라고 지었을때, 어느분이

'어?? 그거 새로나온 GUI냐??' 라고 물으시던데...

이때 머리를 스치는 생각 'CUI(console user interface)를 가진 녀석은 KKAMA OS로 짓고 GUI를 갖추면 그때 KKAMAGUI OS로 만들자!!'

그래서 일단은 KKAMA OS로 지었습니다. 지금 어느정도 GUI도 있으니까 KKAMAGUI OS가 맞겠네요. ^-^

KKAMA OS의 이상은 Simplist, Light, Easy Implementation 입니다. 이중에서 Simplist와 Easy Implementation을 가장 중요하게 생각하고 있는데, 간단하고 쉬운 알고리즘이 가장 좋다고 생각하기 때문입s니다. 사실.. 생각만 그리하고, 실제로 실천 하고 있는지는 저도 알 수 없죠. 쿨럭..;;;

2.기능

2.1 동작 환경

KKAMA OS는 아래의 환경에서 동작합니다.

  • Intel 386 이상 Protected Mode 동작 가능 CPU
  • 하나 이상의 Floppy Disk ( Kernel이 Floppy를 통해 부팅되기 때문이죠 )
  • PS/2 방식의 Keyboard 및 Mouse
  • Vesa방식이 지원되는 Video Card ( 제건 3년 전 모델인데 지원하더라구요.. 앵간하면 다 될듯 GUI시 필수 CUI시 옵션 )
  • FAT32 또는 16방식으로 포멧된 HDD ( 옵션 )

2.2 지원 기능

KKAMA OS가 지원하는 기능은 아래와 같습니다.

*1. FAT32 또는 16방식으로 포멧된 HDD Read/Write
( 파일 읽기쓰기, 디렉토리 읽기쓰기 가능 )
2. Serial 제어
( Serial을 통한 파일 송/수신 )
3. Floppy Disk 제어
( 섹터 읽기쓰기 지원, FAT12는 아직 미지원 )
4. Mouse 제어
( PS/2 방식만 가능, GUI때는 필수 )
5. 간단한 kernel shell
( Kernel 테스트를 위한 Shell, 그리 큰 기능은 없음 )
6. 응용프로그램 Loader
( djgpp를 통해 컴파일 링크된 Binary또는 djgpp-coff파일 실행가능,
단 djgpp-coff는 특별히 제작된 프로그램을 한번 통해 실행해야 됨 )
7. 약간의 GUI
( 최근에 구현된 허접한 GUI, 윈도우 겹침 처리 및 마우스 처리 외에 별다른
기능 없음 )
8. RAM Disk Drive
( 2004/03/23에 개발 1차 완료, 4Mbyte의 크기 )
9. IPC 중 파이프, 공유메모리 지원
( 파이프는 2004/04/11에 개발 1차 완료 ) *

+ Recent posts