본 내용은 본인 공부에 대한 정리용으로, 자세한 내용이 포함되지 않음을 참고 바랍니다.
Ⅰ. 부팅과 부트로더
1. 부팅과 BIOS
- 부팅(Booting): PC가 켜진 후, OS가 실행되기 전까지 수행되는 일련의 작업 과정이다.
- BIOS(Basic Input Ouput System)
- 부팅 과정 중, 하드웨어와 관련된 작업이다.
- 보통 PC 메인보드에 ROM이나 플래시 메모리로 존재한다.
- 전원이 켜짐과 동시에 프로세서가 가장 먼저 실행되는 코드이다.
- 부팅 옵션 설정이나 시스템 전반적인 설정 값을 관리하는 역할도 겸하고 있으며, 설정 값으로 시스템을 초기화하여 OS를 실행할 수 있는 환경을 만들어주는 역할이다.
- BIOS가 제공하는 기능은 인터럽트를 통해, 사용할 수 있다.
- POST(Power On Self Test): BIOS에서 수행하는 각종 테스트나 초기화하는 작업이다.
- 부트로더
- 부트스트랩(Bootstrap) 코드라고도 불린다.
- BIOS에서 처음으로 제어를 넘겨받는 부분이다.
- 플로피 디스크나 하드디스크 등 저장매체의 가장 앞부분에 존재한다.
- PC는 디스크나 플래스 메모리 등 다양한 장치로 부팅할 수 있으며, BIOS는 POST가 완료된 후 여러 장치를 검사하여 앞부분에 부트로더가 존재하는지 확인한다.
- 부트로더가 존재하면, 코드를 0x7C00 주소에 복사한 후, 프로세서가 0x7C00주소부터 코드를 수행하도록 한다.
- BIOS가 부트로더를 찾지 못했다면, “Operating System Not Found”와 같은 에러 메시지를 출력하고 작업이 중단된다.
- 부트로더가 디스크에서 메모리로 복사되어 실행되었다는 것은 BIOS에 의해 PC가 정상 구동되었음을 의미한다.
2. 부트 로더의 역할과 구성
- 부트 로더는 플로피 디스크나 하드 디스크 같은 외부 저장 매체에 존재한다.
- 저장 매체에서 가장 첫번째 섹터 MBR(Master Boot Record)에 있는 작은 프로그램으로, 섹터(Sector)는 디스크를 구성하는 데이터의 단위로, 섹터 하나는 512byte로 구성되어 있다.
- 부트 로더의 가장 큰 역할
- OS실행에 필요한 환경을 설정한다.
- OS 이미지를 메모리에 복사하는 일을 수행한다.
- 부트로더는 BIOS가 가장 먼저 실행하는 프로그램으로, 크기는 512byte로 정해져 있다.
- 부트로더의 공간제약으로 인해, 대부분 OS이미지를 메모리로 복사하고 제어를 넘겨주는 정형화된 작업이 수행된다.
- 첫번째 섹터에 부트로더가 있는지 확인하는 방법
- 디스크를 부팅할 용도로 사용하지 않을 경우, 첫번째 섹터에는 부트로더 대신 일반데이터가 저장된다. (부트로더로 인식되어 일반데이터가 실행될 경우, 컴퓨터가 망가지는 불상사가 발생한다.)
- BIOS의 첫번째 섹터가 부트로더일 경우, 512byte 중 가장 마지막 2byte값은 0x55, 0xAA 로 작성되어 있다.
- 따라서, BIOS는 마지막 2byte를 통해 부트로더인지 검증하고 부트로더일 경우 실행한다.
Ⅱ. 부트 로더 제작을 위한 준비
1. MINT64 OS의 디렉토리 구조 생성
- MINT64 OS는 리얼모드, 보호모드, IA-32e모드용 코드를 나눠서 관리한다.
2. makefile 파일 생성
- make 프로그램
- 소스파일을 이용해서 자동으로 실행파일 또는 라이브러리 파일을 만들어주는 빌드 관련 유틸리티이다.
- make 프로그램이 빌드를 자종으로 수행하려면, 각 소스 파일의 의존 관계나 빌드 순서, 빌드 옵션 등에 대한 정보가 필요하다. ( 이러한 저장된 파일을 “makefile”이라고 한다. )
- make 문법
- 참고: https://www.gnu.org/software/make/manual/
( MINT64 OS 빌드에 필요한 기능 위주로 작성되어 있다. ) - make 문법의 기본 형식은 Target, Dependency, Command 세부분으로 구성되어 있다.
- Target : 일반적으로 생성할 파일을 나타내며, 특정 레이블(Label)을 지정하여 해당 레이블과 관련된 부분만 빌드하는 것이 가능하다.
- Dependency : Target 생성에 필요한 소스 파일이나 오브젝트 파일 등을 나타낸다.
- Command : Dependency에 관련된 파일이 수정되면 실행할 명령을 의미한다.
( 명령창이나 터미널에서 실행할 명령 또는 프로그램을 기술한다. ) - <tab> : 반드시 TAB 문자로 띄어야 한다.
- 참고: https://www.gnu.org/software/make/manual/
Target: Dependency ...
<tab> Command
<tab> Command
<tab> ...
- 예제
- a.c 파일과 b.c 파일을 gcc로 빌드하여, output.exe를 생성하는 makefile 예제이다.
# a.c와 b.c를 통해, output.exe 파일 생성 --> 주석
all: output.exe
a.o: a.c
gcc -c a.c
b.o: b.c
gcc -c b.c
output.exe: a.o b.o
gcc -o output.exe a.o b.o
3. MINT64용 makefile생성
(1) 최상위 디렉터리의 makefile 생성
- 최상위 makefile목적은 OS이미지 생성을 위해 각 디렉터리의 makefile을 실행하는 것이다.
- 현재, 부트 로더만 존재하므로 해당 디렉토리로 이동해서 빌드하고 빌드한 결과물을 복사하여 OS이미지를 생성하는 코드이다.
- @echo는 다음에 오는 문자열을 화면에 출력해주는 명령어이다.
- clean Target은 빌드 과정에서 생성된 파일을 삭제할 목적으로 정의하였다.
all: BootLoader Disk.img BootLoader: @echo @echo =================== Build Boot Loader =================== @echo make -C 0.BootLoader @echo @echo ==================== Build Complete ==================== @echo Disk.img: 0.BootLoader/BootLoader.bin @echo @echo =================== Disk Image Build Start =================== @echo cp 0.BootLoader/BootLoader.bin Disk.img @echo @echo =================== All Build Complete =================== @echo clean: make -C 00.BootLoader clean rm -f Disk.img
- 실행결과
- make입력 시, 아래와 같은 에러가 발생하면 정상적으로 동작하는 것이다.
- 이는 아직 BootLoader.asm과 BootLoader.bin파일이 존재하지 않으므로 발생하는 빌드 에러이다.
(2) 부트 로더 디렉터리의 makefile 생성
- 해당 makefile은 BootLoader.asm파일을 nasm 어셈블리어 컴파일러로 빌드하여 BootLoader.bin파일을 생성하기 위한 목적이다.
- clean target을 정의하여, 디렉토리에 존재하는 BootLoader.bin 파일을 삭제한다.
all: BootLoader.bin
BootLoader.bin: BootLoader.asm
nasm -o BootLoader.bin BootLoader.asm
Clean:
rm -f BootLoader.bin
- 실행결과
- make입력 시, 아래와 같은 에러가 발생하면 정상적으로 동작하는 것이다.
- 이는 아직 BootLoader.asm과 BootLoader.bin파일이 존재하지 않으므로 발생하는 빌드 에러이다.
Ⅲ. 부트 로더 제작과 테스트
1. 세상에서 가장 간단한 부트 로더
- 부트 로더를 메모리에 정상적으로 복사하려면
- 부트 섹터 512byte에서 마지막 2byte에 0x55, 0xAA로 저장하면 된다.
- 0.BootLoader에서 BootLoader.asm파일 생성
- BootLoader.asm
- times 510 - ( $ - $$)
:현재 어드레스부터 어드레스가 510이 되는 시점까지 작업을 반복 수행 - times : times 명령 다음에 오는 횟수만큼 작업을 반복하라는 의미
- $$ : 현재 어드레스가 포함된 섹션의 시작 어드레스
- $ - $$ : 섹션의 시작을 기준으로 하는 오프셋
- db 0x00 : 현재 어드레스에 1byte 크기의 0x00을 삽입하라는 의미
- db : 현재 어드레스에 값을 할당하고 저장
(db: 1byte / dw: 2byte / dd: 4byte / dq: 8byte)
- times 510 - ( $ - $$)
- BootLoader.asm
[ORG 0x00] ; 코드의 시작 어드레스를 0x00으로 설정
[BITS 16] ; 이하의 코드는 16비트 코드로 설정
SECTION .text ; text 섹션(세그먼트 정의)
jmp $
; 현재 위치에서 무한 루프 수행
; c언어의 'while(1);'과 같은 의미
times 510 - ($ - $$) db 0x00
; $: 현재 라인의 어드레스
; $$: 현재 섹션(.text)의 시작 어드레스
; $ - $$: 현재 섹션을 기준으로 하는 오프셋
; 510 - ( $ - $$ ): 현재부터 어드레스 510까지
; db 0x00: 1byte를 선언하고 값은 0x00
db 0x55 ; 1byte를 선언하고 값은 0x55
db 0xAA ; 1byte를 선언하고 값은 0xAA
; 어드레스 511, 512에 0x55, 0xAA를 써서 섹터로 표기함
- 정상적으로 빌드될 경우, MINT64 OS의 최상위 디렉터리에 Disk.img파일이 생성된다.
- 이전 최상위 디렉토리에서 만든 makefile실행시 생성된다.
2. QEMU 실행
(1) 실행
- 명령어: qemu-system-x86_64 -L . -m 64 -fda ./Disk.img -M pc -curses
- 64MB의 메모리를 할당하고 MINT64 OS의 이미지 파일을 플로피 디스크로 설정
- -m 64 : 64MB의 물리 메모리 할당
- -fda /mint64/Disk.img : 플로피 디스크 이미지로 MINT64 OS이미지(Disk.img) 설정
- 플로피 디스크 이미지 경로 설정 시, ‘/’ 문자를 사용해야 한다.
( \입력 시, QEMU가 정상적으로 실행되지 않을 수 있으니 주의)
- 플로피 디스크 이미지 경로 설정 시, ‘/’ 문자를 사용해야 한다.
- -M pc : 가상머신을 일반 PC 환경으로 설정
(2) 결과
- 정상 실행 시
- 부트 로더가 정상적으로 실행되었는지 확인
- 부트 로더의 마지막에 있는 0x55와 0xAA값을 0x00, 0x00으로 바꿔본 후 실행해본다.
3. 화면 버퍼와 화면 제어
- 화면에 문자를 출력하려면, 현재 동작 중인 화면 모드와 관련된 비디오 메모리의 어드레스를 알아야 한다.
- 비디오 메모리는 모드별로 정해진 형식에 따라, 데이터를 채우면 화면에 원하는 문자나 그림을 출력하는 구조로 되어 있다.
- PC부팅 후, 기본으로 설정되는 화면모드는 텍스트 모드로 화면 크기는 가로 80문자, 세로 25문자이며 비디오 메모리 어드레스는 0xB8000에서 시작한다.
- 리얼모드 어드레스 계산 방식이 세그먼트 레지스터에 정의된 기준 주소에 범용 레지스터를 조합하여 계산한다.
- 이를 이용하여, 세그먼트 값을 0xB800으로 설정하면 범용 레지스터를 0x0000으로 설정하여 편리하게 처리할 수 있다.
- 빨간색 배경에 밝은 녹색으로 ‘M’ 출력
[ORG 0x00] ; 코드의 시작 어드레스를 0x00으로 설정
[BITS 16] ; 이하의 코드는 16비트 코드로 설정
SECTION .text ; text 섹션(세그먼트 정의)
mov ax, 0xB800 ;AX 레지스터에 0xB800복사
mov ds, ax ; DS 세그먼트 레지스터에 AX 레지스터 값 (0xB800)을 복사
mov byte [0x00], 'M' ;DS 세그먼트: 오프셋 0xB800:0x0000에 'M' 복사
mov byte [0x01], 0x4A
;DS 세그먼트: 오프셋 0xB800:0x0001에 0x4A(빨간배경에 밝은 녹색 속성)를
복사
jmp $
times 510 - ($ - $$) db 0x00
; $: 현재 라인의 어드레스
; $$: 현재 섹션(.text)의 시작 어드레스
; $ - $$: 현재 섹션을 기준으로 하는 오프셋
; 510 - ( $ - $$ ): 현재부터 어드레스 510까지
; db 0x00: 1byte를 선언하고 값은 0x00
db 0x55 ; 1byte를 선언하고 값은 0x55
db 0xAA ; 1byte를 선언하고 값은 0xAA
; 어드레스 51all: BootLoader.bin
BootLoader.bin: BootLoader.asm
nasm -o BootLoader.bin BootLoader.asm
Clean:
rm -f BootLoader.bin1, 512에 0x55, 0xAA를 써서 섹터로 표기함
4. 화면 정리
- C 코드로 작성
- 문자는 0으로 설정해주고, 속성은 녹색배경에 자홍색으로 설정하였다.
#include <stdio.h>
int main(){
int i = 0;
char* pcVideoMemory = (char*) 0xB8000;
while(1){
pcVideoMemory[i] = 0;
pcVideoMemory[i+1] = 0x2D;
i += 2;
if(i >= 80 * 25 * 2){
break;
}
}
}
- 작성한 C코드를 Object파일로 변경
gcc -c print_str.c -o print_str -O2
- objdump를 이용하여 어셈블리어 코드 추출
- 기본은 AT&T형식으로 추출된다.
$ objdump -M intel -d clear.o clear.o: file format elf64-x86-64 Disassembly of section .text: 0000000000000000 <main>: 0: 55 push rbp 1: 48 89 e5 mov rbp,rsp 4: c7 45 f4 00 00 00 00 mov DWORD PTR [rbp-0xc],0x0 b: 48 c7 45 f8 00 80 0b mov QWORD PTR [rbp-0x8],0xb8000 12: 00 13: 8b 45 f4 mov eax,DWORD PTR [rbp-0xc] 16: 48 63 d0 movsxd rdx,eax 19: 48 8b 45 f8 mov rax,QWORD PTR [rbp-0x8] 1d: 48 01 d0 add rax,rdx 20: c6 00 00 mov BYTE PTR [rax],0x0 23: 8b 45 f4 mov eax,DWORD PTR [rbp-0xc] 26: 48 98 cdqe 28: 48 8d 50 01 lea rdx,[rax+0x1] 2c: 48 8b 45 f8 mov rax,QWORD PTR [rbp-0x8] 30: 48 01 d0 add rax,rdx 33: c6 00 2d mov BYTE PTR [rax],0x2d 36: 83 45 f4 02 add DWORD PTR [rbp-0xc],0x2 3a: 81 7d f4 9f 0f 00 00 cmp DWORD PTR [rbp-0xc],0xf9f 41: 7f 02 jg 45 <main+0x45> 43: eb ce jmp 13 <main+0x13> 45: 90 nop 46: b8 00 00 00 00 mov eax,0x0 4b: 5d pop rbp 4c: c3 ret
5. 부팅 메시지 출력
- C 코드로 작성
- 문자는 0으로 설정해주고, 속성은 녹색배경에 자홍색으로 설정함
#include <stdio.h>
int main(){
int i = 0;
int j = 0;
char* pcVideoMemory = (char*) 0xB8000;
char* pcMsg = "MINT64 OS Boot Loader Start~!!";
char cTemp;
while(1){
cTemp = pcMsg[i];
if(cTemp ==0){
break;
}
pcVideoMemory[j] = cTemp;
i += 1;
j += 2;
}
}
- 작성한 C코드를 Object파일로 변경
gcc -c print_str.c -o print_str
- objdump를 이용하여 어셈블리어 코드 추출
$ objdump -M intel -d print_str
print_str: file format elf64-x86-64
Disassembly of section .text.startup:
0000000000000000 <main>:
0: 48 8d 0d 00 00 00 00 lea rcx,[rip+0x0] # 7 <main+0x7>
7: ba 00 80 0b 00 mov edx,0xb8000
c: b8 4d 00 00 00 mov eax,0x4d
11: 0f 1f 80 00 00 00 00 nop DWORD PTR [rax+0x0]
18: 88 02 mov BYTE PTR [rdx],al
1a: 0f b6 01 movzx eax,BYTE PTR [rcx]
1d: 48 83 c2 02 add rdx,0x2
21: 48 83 c1 01 add rcx,0x1
25: 84 c0 test al,al
27: 75 ef jne 18 <main+0x18>
29: 31 c0 xor eax,eax
2b: c3 ret
6. 최종 코드
- 자홍색은 별로 안 예뻐서 검은색으로 변경한 코드 (0x20 적용함)
[ORG 0x00] ; 코드의 시작 어드레스를 0x00으로 설정
[BITS 16] ; 이하의 코드는 16비트 코드로 설정
SECTION .text ; text 섹션(세그먼트 정의)
jmp 0x07C0:START
START:
mov ax, 0x07C0
mov ds, ax ;segment reset
mov ax, 0xB800
mov es, ax ; video
mov si, 0
.SCREENCLEARLOOP:
mov byte [es:si], 0
mov byte [es:si+1], 0x20
add si, 2
cmp si, 80*25*2
jl .SCREENCLEARLOOP
mov si, 0
mov di, 0
.MESSAGELOOP:
mov cl, byte [si + MSG1]
cmp cl, 0
je .MESSAGEEND
mov byte [es:di], cl
add si, 1
add di, 2
jmp .MESSAGELOOP
.MESSAGEEND:
jmp $
MSG1: db 'MINT64 OS Boot Loader Start~!!', 0;
times 510 - ($ - $$) db 0x00
db 0x55
db 0xAA ; 1byte를 선언하고 값은 0xAA
; 어드레스 511, 512에 0x55, 0xAA를 써서 섹터로 표기함
'MINT64 OS 개발' 카테고리의 다른 글
6. 32비트 보호 모드로 전환 (0) | 2023.05.06 |
---|---|
5. OS이미지 메모리에 복사 (0) | 2023.05.06 |
4. 32비트 보호 모드로 전환 (0) | 2023.04.13 |
3. OS이미지 메모리에 복사 (0) | 2023.04.10 |
1. 개요 & 환경 구축 (0) | 2023.04.10 |