[OS 개발 13] 인터럽트와 예외처리(3) PIC와 인터럽트 구현하기


1. 코드 실행하기


 

앞서 두 포스팅에 걸쳐서 IDT에 대해 알아보았습니다. IDT와 PIC, 예외의 개념들을 토대로 다음과 같이 어셈블리 코드로 구현해 보았습니다.

(init.inc는 이전 파일과 내용은 그대로이며, 이름만 바뀌었습니다.)

 

<Boot.asm>

 

 

 


 

 

<Kernel.asm>

 

 

 

 


2. 코드 분석


 

Boot.asm 분석

 

설명1.

 

플로피 디스크 드라이브의 모터를 끄는 부분인데, 0x3F2번지에 out I/O명령을 사용하여 XOR 연산을 통해 프로그램 실행 도중에는 모터가 멈추도록 합니다.

 

 

설명2.

 

이 부분이 앞서 포스팅에서 다뤘던 PIC를 직접 설정해주는 부분입니다. 하드웨어 인터럽트를 지정해주기 위해 마스터와 슬래이브 PIC를 구현하였습니다.

PIC 포스팅을 살펴보면서 코드를 이해하면 좋을 듯 합니다.

 

2016/02/12 - [분석연구소/운영체제] - [OS 만들기 12] 인터럽트와 예외 구현하기(2) PIC와 예외처리

 

첫 시작은 ICW1 루틴으로 시작됩니다.

51번 줄에서 명령어가 시작되는데, 0x11을 레지스터에 복사해 둡니다. 여기에서 0x11은 비트로 표현하면 0001 0001 입니다.

이때 4번 비트의 1과 0번 비트의 1을 통해 PIC 초기화와 ICW4를 사용함을 알립니다.

다음 out 0x20, al 명령어를 통해 마스터 PIC에 이 사실을 알립니다.

 

그리고 dw 0x00eb, 0x00eb 명령을 통해 명령 간에 약간의 딜레이를 줍니다. 0x00eb는 jmp $+2를 기계어 코드로 변환한 것입니다. 즉, 이 명령어가 실행되면, 첫 번째 0x00eb -> 두 번째 0x00eb 로 우선 점프하고, 두 번째 00eb에서 다음 명령어인 out 0xA0, al로 점프하게 되는 것입니다.

 




이 후에는 0x20으로 마스터 PIC 인터럽트의 시작을 알립니다. 즉, 이제 ICW2 루틴이 시작되는 것이죠. 마스터 PIC는 0x20, 슬레이브 PIC는 0x28 부터 시작합니다.

 

해당 루틴이 끝나면 mov al, 0x04를 시작으로 ICW3을 시작하고 여기에서 슬레이브 PIC의 인터럽트 라인이 하드웨어로 직접 연결되어 있음을 0x04를 전달함으로써 알리게 됩니다.

 

마지막으로 ICW4 루틴은 mov al, 0xFF를 시작으로 이루어집니다. 추가적으로 지시 사항을 전달하기 위한 부분인데, 여기에서는 0xFF를 전달함으로써 모든 인터럽트를 막아놓았습니다.

 

추가적으로 몇 가지 더 설명드립니다.

1. 각 ICW 루틴에서 처음 al 레지스터에 전달하는 값을 비트로 전환해서 살펴봐야 하며, 특히 ICW2의 경우에는 비트 값을 PIC이 CPU에 전달할 때, 약간의 변환이 발생하는데, 이에 대해 구체적으로 설명하면 내용이 길어지므로, 일단 값을 전달한다는 것에 대해서만 설명하였습니다.

2. ICW 루틴은 1~4까지 차례로 진행되어야 하며, 제일 처음에는 당연히 PIC 초기화 루틴이 설정 되어야 합니다.

 

 

 

Kernel.asm 분석

 

지난번에 간단하게 커널부분을 구현해봤는데, 이번에 인터럽트를 구현하면서 많은 내용이 추가되거나 수정되었다.

그리고 IDT에 대한 개념을 32비트 보호 모드에서 구현하는데, 아래 지난 포스팅을 통해 다시 한번 그 내용을 확인해 보고 코드를 살펴보자.

 

2016/02/11 - [분석연구소/운영체제] - [OS 만들기 11] 인터럽트와 예외 구현하기(1) IDT의 개념

 

 

설명1.

 

우선, IDT 디스크립터는 최대 256개를 만들 수 있다고 했는데, EAX와 ECX를 0으로 초기화한 후에 mov ax, 256 명령을 통해 비어있는 디스크립터들을 복사해 둡니다.

 

그 다음 loop_idt를 통해 루프가 실행됩니다. 저장된 ax값 (256)을 1씩 줄여서 0이 될 때까지 실행하며, 여기에서 esi에 idt_ignore가 있는 주소를 넣습니다. 그리고 8을 cx에 넣고 rep movsb 명령을 실행합니다. 이게 의미하는 바가 뭘까요?

 

rep 명령은 repeat의 약자고 movsb는 move single Byte을 의미합니다. 즉, 반복해서 바이트 단위로 값을 넣는다는 의미이죠. 즉 이전 명령어를 통해 DS:ESI에서 ES:EDI로 8바이트 값을 넣겠다는 의미인 것입니다. 그러고 나서 dec이 0이 되면 다음 명령문으로 넘어갑니다.

 

다음 명령에서는 mov edi, 8*0x20을 통해 edi에 값을 넣어줍니다. 앞서 PIC에서 리매핑되었으므로 10진수 32(0x20)번의 인터럽트가 발생했음을 CPU에 알립니다. 그리고 다음 명령어를 실행하여 rep movsb까지 하나의 루틴이 실행됨으로써, idt_ignore가 있던 자리에 idt_timer 디스크립터를 덮어 씌워 복사하게 됩니다. 다음 키보드 IDT 디스크립터도 마찬가지입니다.

 

그리고 lidt[idtr]을 통해 지금까지 등록된 IDT를 레지스터에 등록합니다. 이때 등록하는 주소 포인터 이후의 데이터는 설명6 구간에 정의되어 있습니다.

 

그 다음, 타이머는 IRQ 0번이므로 0xFC (2진수 11111100)를 레지스터에 넘깁니다.

즉, 0번 비트(타이머) ->0, 1번 비트(키보드) ->0, 나머지->1 로 셋함으로써 해당 인터럽트만 유효하도록 설정해 두는 것입니다.

그러고 나서 sti 명령을 내린다. 이는 CPU가 PIC에서 인터럽트를 받고 이를 /INTA를 통해 신호를 되돌려 줍니다. 그러고 나서 점프(jmp $)함으로써 프로그램을 사실상 멈추게 합니다.

 

 

설명2.

 

지난 GDT 포스팅에서 문자를 출력하는 부분이 생각날지 모르겠네요. 그 때는 문자를 케릭터별로 나누어 입력하였는데, 굉장히 비효율적인 방법입니다. 따라서 이를 변형하여 마치 C언어의 while문과 같은 방식으로 구성한 부분입니다.

 

먼저 [es:edi]에 바이트 단위로 1바이트는 글자, 1바이트는 색상(배경,글자)을 넣어줍니다. 그리고 esi와 edi는 이 루틴에서 지속적으로 증가시킴으로써, 화면에서 문자를 계속 다음 칸에 입력할 수 있도록 합니다. 이 때, al 레지스터를 or 연산하여 이 값이 마지막에 0이 되면, printf_end로 이동시킴으로써 문자 출력을 마칩니다.

 

 

설명3, 4, 5

 

앞서 설명1에서 256개의 빈 인터럽트 디스크립터와 타이머, 키보드 인터럽트가 있는 주소를 호출하는 부분을 볼 수 있었는데, 그 호출되는 지점이 이 부분입니다. 이 루틴들을 통해 CPU의 모든 레지스터와 EFLAGS 레지스터를 사전에 미리 메모리 스택에 저장, 즉 PUSH 해야한다. 인터럽트가 진행되기 전에 사전에 이를 저장해두고 나중에 인터럽트가 끝나면 다시 그 시점부터 재개하기 위함입니다.

 

그리고 isr_ignore는 80*2*2를 edi에 복사하는 부분이 있는데, 이는 아시다시피 화면의 2번째 줄에 메시지를 출력하기 위함이며, 이 후에 printf를 호출함으로써 해당 메시지를 출력합니다.

 

그리고 POP 명령을 실행함으로써 인터럽트 이전에 저장되어 있던 레지스터와 플래그들을 다시 내보내게 됩니다.

(설명 3, 4, 5의 역할이 모두 비슷하므로 별도로 설명을 덧붙이지는 않겠습니다)

 

설명6

이 부분은 정의 부분입니다.

 

우선, idtr: 영역에 256개의 IDT를 저장할 수 있다고 앞서 언급했는데, 각 8바이트씩 가능하므로 8을 곱한다. 그리고 0부터 시작하므로 사실상 1을 빼줘야 합니다.

 

다음으로 idt_ignore, idt_timer, idt_keyboard가 나오는데, 이 부분은 전부 각 디스크립터에 대한 정의 부분이다. idt_ignore를 대표적으로 살펴보면,핸들러의 물리주소를 찾아가려면 0x08:0x10000+isr_ignore와 같은 형식으로 찾아가야 합니다. 그러기 위해 오프셋에 프로그램이 위치한 RAM 상의 주소를 더해주고자 추가한 것입니다 (이 부분이 좀 헷갈릴 수 있는데, 나중에 다시 정리하도록 하겠습니다. 일단 이렇게 알고 있으면 될 듯 하네요).

 

 


3. 실행하기


 

이제 실행을 해보겠습니다. 지난번 GDT 실행 예제와 마찬가지로 아래와 같은 방법으로 컴파일 해보도록 하겠습니다.

 

nasm -f bin -o Boot.bin Boot.asm
nasm -f bin -o Kernel.bin Kernel.asm
copy Boot.bin+Kernel.bin /b Kernel.img

 

그리고 생성된 img 파일을 VMWare에서 부팅했을 때 다음과 같은 실행화면이 출력되면 성공입니다.

 

 

 

이 때, This is the timer interrupt 앞의 문자는 계속 바뀌고(타이머 인터럽트), 그 다음 This is the keyboard interrupt의 앞 문자는 키보드를 입력할 때마다 문자가 바뀌어야 합니다.

 

IDT 코드 작성부터 분석, 실행까지 진행해 보았습니다. 정리하고 다시 내용을 쭉 한번 살펴보니 깔끔하게 정리되지 못한 면이 있는데, 앞으로 시간이 날 때마다 보시는 분들의 입장에서 어떻게 해야 가독성을 높일 수 있을지 고민하고 보완하도록 하겠습니다.

 

 


TAGS.

Comments