벌써 세번째 포스팅을 시작햇다. 내가 공부한 것을 표현하려고 정리하는 것이 쉬운 일은 아니다. 처음 어떻게 적을지에 대해 고민을 하다가 스트레스도 꽤 받았다. 하지만 언제나 그렇듯이 하면 된다!라는 일념으로 시도하니 결과는 어떻더라도 일단 마음은 놓인다. 푸념은 던져놓고 이제 커널로드에 대해 이야기 해보려 한다.
커널 로드 개괄도
부트코드에서 설명했듯이 부트스트랩 과정에서 디스크의 첫 512바이트를 메모리 0x07C0에 올리며 이 부분이 MBR(Master Boot Record)라고 했다. 512바이트안에 운영체제의 모든 것을 다 넣고 처리할 수는 없지 않겠는가? 그래서 이 MBR부분에 몇가지 초기화를 하거나 원하는 부분을 처리하는 것을 넣고 파티션 테이블을 넣지만 운영체제가 하는 역할을 하는 소스들을 더 불러와서 처리해야된다. 이 포스팅에서 운영체제의 핵심은 커널을 불러오는 코드의 핵심을 보여주려 한다. 그 핵심은 저장된 디스크의 다음 섹터(512바이트)를 가지고 오는 코드가 필요하다. 불러 온 512바이트에 필요한 것들을 하고 모자란다면 다음 이미지를 불러와서 처리하는 방식을 반복적으로 취하면 된다. 그래서 여기서 커널 로드라는 의미는 처음 디스크 이미지의 두번째 섹터를 MBR에서 부르는 것이라고 보면 되겠다.
※ 첫 MBR은 부트스트랩과정에서 BIOS가 MBR을 메모리 0x07C0로 올린다.
※ 커널코드는 메모리에 올라간 MBR의 코드가 실행되어 0x10000으로 올린다.
![](https://t1.daumcdn.net/cfile/tistory/2537AA3353E094AE18)
▲커널 로드 개괄도
커널 로드 소스코드 - boot.asm
커널 로드는 boot.asm과 kernel.asm으로 이루어져 있다. 핵심 소스만을 이야기하도록 하겠다. 16bit real mode에서 메모리에 접근하기 위해서는 segment:offset 방식으로 접근하며 아래와 같이 ax에 0x1000을 넣고 es레지스터에 넣어서 es=0x1000, bx에 0을 넣어 ES:BX인 0x1000:0000으로 메모리주소를 지정했다.
boot.asm
kernel.asm
![](https://t1.daumcdn.net/cfile/tistory/2753803A53E0D1C71E)
▲바이오스 인터럽트(0x13) 소스 - boot.asm
이전 포스팅 [헤드, 실린더, 섹터, 트랙, 플레터 그리고 CHS와 LBA ]에서 이 소스를 똑같이 썼다. 코드에서 직접 인터럽트 0x13번을 이용하여 CHS방식으로 디스크를 읽는다. 위의 개괄도에서 보였듯이 BIOS가 MBR을 읽었다면 이젠 MBR이 직접 다음 섹터를 읽어서 커널을 로드해야 하므로 해당 코드가 필요한 것이다.
바이오스 인터럽트 중 0x13h는 낮은 수준의 디스크 서비스를 실행하는 함수이며 인자로 들어가는 ah값에 따라 서비스가 달라진다. 해당 테이블이 너무 크므로 아래 테이블에서 확인하도록 하자. 소스코드에서도 확인 할 수 있듯이 ah에 0x02로 설정하여 섹터를 읽는다.
13h | 낮은 수준의 디스크 서비스AH | 설명 |
---|
00h | 디스크 드라이브 초기화 | 01h | 드라이브 상태 검사 | 02h | 섹터 읽기 | 03h | 섹터 쓰기 | 04h | 섹터 유효 여부 확인 | 05h | 트랙 포맷 | 08h | 드라이브 변수 가져오기 | 09h | 고정 드라이브 변수 초기화 | 0Ch | 지정된 트랙으로 찾기 | 0Dh | 고정 디스크 컨트롤러 초기화 | 15h | 드라이브 종류 가져오기 | 16h | 플로피 드라이브 미디어 변경 상태 가져오기 | 17h | 디스크 종류 설정 | 18h | 플로피 드라이브 미디어 종류 설정 | 41h | 확장 디스크 드라이브 (EDO) 설치 검사 | 42h | 섹터 확장 읽기 | 43h | 섹터 확장 쓰기 | 44h | 섹터 확장 유효 여부 확인 | 45h | 드라이브 잠금/잠금 해제 | 46h | 미디어 꺼내기 | 47h | 확장 찾기 | 48h | 드라이브 변수 확장 가져오기 | 49h | 미디어 변경 상태 확장 가져오기 | 4Eh | 하드웨어 구성 확장 설정 |
|
참조 : http://ko.wikipedia.org/wiki/바이오스_인터럽트_호출
위의 소스에서 보듯이 인터럽트 0x13의 인자로 사용되는 레지스터들이 많다. 그래서 이 레지스터들이 의미하는 것이 정확하게 뭔지에 대해 알아봤는데 잘 정리된 사이트를 찾았다. 주석으로도 정리해두었지만 아래 그림을 참고해서 다시 살펴보도록 하자. 아래에서 보듯이 AL은 읽을 섹터의 수이다. CH는 실린더 번호의 하위 8비트이고, CL은 읽을 섹터 번호이다. CL에서 하위 0~5비트만 사용되고 6~7비트는 하드디스크에서만 사용된다. DH는 헤드 번호이고, DL는 드라이브의 번호다. 그리고 아까전에 설정한 것과 같이 ES:BX는 데이터를 저장할 버퍼이다.
[jc read]
아래 그림에서 보듯이 erro가 발생했을 때는 carry flag가 set된다. 그러므로 jump if carry flag on인 jc를 통해 에러 발생시 다시 시도를 한다. 에러는 인터럽터를 통해 섹터를 읽기 위해서 디스크안의 모터가 충분히 빠르게 돌지 못해서 발생한다. 3번정도 시도해보고 ah에 0x00을 넣어서 디스크 리셋을 하고 다시 읽기를 시도 하면 에러를 처리할 수 있다.![](https://t1.daumcdn.net/cfile/tistory/22741C4653E1D29A28)
▲INT 0x13 - 정리 사이트
![](https://t1.daumcdn.net/cfile/tistory/25012F4653E1D29A14)
▲ INT 13/AH=00h 를 통해 DISK 리셋
[jmp 0x1000:0000]
커널 코드를 메모리 0x1000:0000에 불러 온 후 이동한다. 그 후에 불려진 커널 코드는 현재 지금 커널코드가불려졌다는 것을 판단 할 수 있도록 문자열 "We are in kernel program" 을 출력한다.
커널 소스코드 - kernel.asm
boot.asm에 의해 불려진 커널 소스이다.
![](https://t1.daumcdn.net/cfile/tistory/242ED83E53E1D77B20)
▲초기화 및 출력 - kernel.asm
[mov ax, cs
mov ds, ax
xor ax, ax
mov ss, ax]
코드 세그먼트에 있는 값을 ax에 옮겨서 다시 데이터 세그먼트로 옮긴다. 그러므로 결국 코드 세그먼트에 있는 값을 데이터 세그먼트에 있는 값으로 넣는거와 같다. 그 후 xor ax, ax 로 ax을 0으로 만든 후에 스택세그먼트에 ax의 값인 0을 넣는다.
[lea esi, [msgKernel]
mov ax, 0xB800
mov es, ax
mov edi, 0]
출력할 문자열이 저장된 주소를 esi에 넣는다. 그 후 컬러 텍스트모드 비디오 메모리에 쓰기 위해 es에 0xB800을 넣고 edi를 offset처럼 사용하기 위해 0으로 초기화한다.
[ push eax
add edi, 160]
책에 소스에는 add edi, 160이 없지만 새롭게 추가했다. "We are in kernel program"이 처음에 출력되어 "kcats"가 지워져서 가로열의 크기 80*2 를 해서 밑에 줄을 출력해줬다.
[ printf_loop]
책에 start에서 msgKernel의 주소를 esi에 넣고, es에 0xB800인 비디오 메모리 주소를 넣었다. esi를 한바이트씩 증가시키며 출력할 문자열을 넣고 or al, al을 하고 jz을 통해 al의 값이 0일때까지 루프를 돌고 있다. 그 후에 al의 문자가 있으면 0x06값을 넣어 글자색과 글자배경색을 넣는 반복적인 루프를 돈다.
마무리 하면서
마지막으로 boot.asm과 kernel.asm으로 만들어진 boot.img와 kernel.img을 합쳐야한다. 윈도우 cmd명령어에서 두 바이너리를 합치는 명령어가 존재한다. COPY 명령어를 평소에 복사에만 사용했겟지만 이렇게 두개의 파일을 하나의 파일로 합치는 것도 가능하니 앞으로 유용하게 쓰기를 바란다.
![](https://t1.daumcdn.net/cfile/tistory/2254C13653E6F56A1C)
▲ copy를 이용한 바이너리 합치기
![](https://t1.daumcdn.net/cfile/tistory/2368623853E341911E)
▲ 실행화면
바이오스 인터럽트는 int 0x13로 디스크에서 첫번째 섹터와 두번째 섹터를 가져와서 물리메모리에 적재한다. 첫 섹터는 BIOS에서 0x07C0주소로 불러주며, 그 후에 첫번째 섹터에서 두번째 섹터를 부르는 코드를 작성해야 한다. BIOS 인터럽트가 종류도 많고 갖춰야할 인자도 많지만 필요할 때 어떤 참조해서 사용할 정도가 되면 될 것 같다.
출처: https://kcats.tistory.com/155?category=554568 [kcats's mindstory]