앞 과정에서 만든 프로그램을 부팅시키는 과정이었다.
이번엔 MBR에 올린 프로그램이 다른 프로그램을 로드시켜 실행시키는 과정을 따라가 보려한다.
우선 두 개의 소스 파일을 작성하였다.
boot.asm
[org 0]
jmp 07C0h:start
start:
mov ax, cs
mov ds, ax
mov es, ax
mov ax, 0xB800
mov es, ax
mov di, 0
mov ax, word [msgBack]
mov cx, 0x7FF
paint:
mov word [es:di], ax
add di, 2
dec cx
jnz paint
read:
mov ax, 0x1000
mov es, ax
mov bx, 0
mov ah, 2
mov al, 1
mov ch, 0
mov cl, 2
mov dh, 0
mov dl, 0
int 0x13
jc read
jmp 0x1000:0000
msgBack db '.', 0x67
times 510-($-$$) db 0
dw 0AA55h
kernel.asm
[org 0]
[bits 16]
start:
mov ax, cs
mov ds, ax
xor ax, ax
mov ss, ax
lea esi, [msgKernel]
mov ax, 0xB800
mov es, ax
mov edi, 0
call printf
jmp $
printf:
push eax
printf_loop:
mov al, byte [esi]
mov byte [es:edi], al
or al, al
jz printf_end
inc edi
mov byte [es:edi], 0x06
inc esi
inc edi
jmp printf_loop
printf_end:
pop eax
ret
msgKernel db "We are in kernel program", 0
nasm -f bin -o boot.bin boot.asm
nasm -f bin -o kernel.bin kernel.asm
입력 후 해당 파일 경로의 cmd에서 다음 명령어를 통해 바이너리 파일을 합쳐야 한다.
copy boot.bin+kernel.bin /b kernel.img
/b 인자 값은 바이너리 형식으로 합친다는 의미이다.
이렇게 합친 파일을 전 챕터처럼 플로피디스크의 올려 실행시키면 다음과 같은 결과가 나온다.

소스 해설
boot.asm
read:
mov ax, 0x1000
mov es, ax
mov bx, 0
mov ah, 2
mov al, 1
mov ch, 0
mov cl, 2
mov dh, 0
mov dl, 0
int 0x13
jc read
jmp 0x1000:0000
위 코드는 boot.asm의 코드이다.
앞 부분은 전과 같으며 해당 코드는 램의 0x10000 번지로 kernel.bin을 복사하는 루틴이다.
0x1000:0000은 다음과 같은 과정을 거쳐 0x10000이 된다.
0x1000 * 0x10 + 0000h = 0x10000
0x10을 곱하는 이유는 CPU가 물리주소를 계산하는 공식이다.
해당 루틴에서 int 0x13은 소프트웨어 인터럽트를 걸어 BIOS 콜을 사용하겠다는 의미이다.(운영체제가 없기에 BIOS에서 제공하는 시스템 콜이라고 생각하면 편하다.)
mov ah, 2는 BIOS 콜의 번호를 의미한다. 2번은 디스크를 읽는다는 의미이다.
mov al, 1은 kernel.bin의 1섹터를 읽는다는 의미인데, 1섹터는 512byte이므로 kernel.bin 전체를 의미한다.
ch, cl, dh는 디스크 위치를 지정하는 값이다.
mov dl, 0은 드라이브 번호를 의미하는데, hdd의 경우 0x80부터 시작한다.
플로피디스크는 0x0번부터 시작한다. 우리는 0번째 플로피디스크 드라이브를 사용하게 된다.
jc read에서 Carry Flag를 사용하는데, BIOS 인터럽트 상황에서 CF는 성공/실패를 의미한다.
CF 값이 0이;라면 성공, 1이라면 실패이다.
kernel.asm
[org 0]
[bits 16]
start:
mov ax, cs
mov ds, ax
xor ax, ax
mov ss, ax
lea esi, [msgKernel]
mov ax, 0xB800
mov es, ax
mov edi, 0
call printf
jmp $
printf:
push eax
printf_loop:
mov al, byte [esi]
mov byte [es:edi], al
or al, al
jz printf_end
inc edi
mov byte [es:edi], 0x06
inc esi
inc edi
jmp printf_loop
printf_end:
pop eax
ret
msgKernel db "We are in kernel program", 0
부팅 코드를 통해 실행된 커널 프로그램이다.
물리주소 0x10000번지로 점프하여 start: 부터 시작하게 된다.
boot.asm에서 jmp 0x1000:0000을 수행하면 cs 세그먼트에 저장이 된다.
그렇기에 코드 영역인 0x10000의 0번지서부터 시작하라는 [org 0]을 명시해주게 된다.
lea esi, [msgKernel]
lea는 load effective address이며 msgKernel이 가리키는 메모리 주소를 esi에 넣는다는 뜻이다.
esi는 msgKernel 문자열의 시작 주소를 가리키게 된다.
mov는 해당 메모리 내 값을 넣는다면, lea는 메모리 주소를 넣는다.
mov ax, 0xB800
mov es ,ax
mov edi, 0
call printf
0xB800은 비디오메모리 주소를 의미한다.
es에 비디오 메모리 주소 값을 저장하고 edi는 0으로 초기화한다.
printf:
push eax
printf_loop:
mov al, byte [esi]
mov byte [es:edi], al
or al, al
jz printf_end
inc edi
mov byte [es:edi], 0x06
inc esi
inc edi
jmp printf_loop
printf_end:
pop eax
ret
push eax를 통해 함수 호출 전 eax 값을 미리 저장해두고 함수를 실행하게 된다.
esi에 [msgKernel] 메모리 주소가 들어있으므로 mov al, byte [esi]에서 al은 msgKernel 값을 갖게 된다.
mov byte [es:edi], al에서 es는 비디오 메모리 주소를, edi는 해당 구역의 위치를 가리킨다.(화면 위치)
해당 구역의 위치에 al 레지스터에 담긴 ASCII 코드 값을 넣게 된다.
or al, al 에서 al이 0이 아니라면, 0이 나올 수 없는데, jz(jump if zero)를 통해 0이라면 printf_end로 점프하게 된다.
0이라는 건 msgKernel의 문자열이 끝났다는 의미이다.
만약 끝나지 않았으면 mov byte [es:edi], 0x06 를 통해 주황색을 입힌다.
esi를 증가시켜 다음 문자열을 가리키게 되고, edi를 증가시켜 화면의 다음 칸으로 이동하게 된다.
'OS > 만들면서 배우는 OS 커널의 구조와 원리' 카테고리의 다른 글
| 6. 보호 (0) | 2026.04.13 |
|---|---|
| 5. 태스크 스위칭, 문맥 교환 (0) | 2026.04.13 |
| 4. 인터럽트와 예외 (0) | 2026.04.10 |
| 3. Protected Mode로 변환 (1) | 2026.04.08 |
| 1. 부트스트랩 (0) | 2026.04.03 |