부트로더를 메모리에 정상적으로 복사하려면 부트섹터 512바이트에서 마지막 2바이트를 0x55, 0xAA로 저장하면 된다.
./00.BootLoader/BootLoader.asm
[ORG 0x00] ; 코드의 시작 어드레스를 0x00으로 설정
[BITS 16] ; 이하의 코드는 16비트 코드로 설정
SECTION.text ; text 섹션(세그먼트)을 정의
jmp $ ; 현재 위치에서 무한 루프 수행
time 510 - ($ - $$) db 0x00 ; $: 현재 라인의 어드레스
; $$: 현재 섹션(.text)의 시작 어드레스
; $ -$$: 현재 섹션을 기준으로 하는 오프셋
; 510 - ($ - $$): 현재부터 어드레스 510까지
; db 0x00: 1바이트를 선언하고 값은 0x00
; time: 반복수행
; 현재 위치에서 어드레스 510까지 0x00으로 채움
db 0x55 ; 1바이트를 선언하고 값은 0x55
db 0xAA ; 1바이트를 선언하고 값은 0xAA
; 어드레스 511, 512에 0x55, 0xAA를 써서 부트 섹터로 표기함
qemu실행
$ sudo qemu-system-x86_64 -m 64 -fda ./Disk.img -localtime -M pc
실행결과
화면버퍼와 화면제어
기본으로 설정되는 화면 모드는 텍스트모드로 크기는 가로 80character, 세로 25character이다.
비디오 메모리 어드레스는 0xB8000에서 시작한다.
총 메모리 크기는 80252=4000바이트
속성값은 하위 4비트의 전경색, 상위 4비트의 배경색으로 구분된다.
각각 전경색, 배경색에서 최상위의 특수기능 비트가 있따.
<0xB8000와 0xB8001 어드레스에 값을 설정하는 코드>
물리주소 0xB8000이 기준 어드레스로 사용
화면 맨위는 0xB8000이므로 0xB8000과 0xB8001에 'M'과 0x4A를 쓰면 빨간색 배경에 밝은 녹색으로 'M'을 출력할 수 있다.
DS 세그먼트의 값이 0xB800이므로 0x00, 0x01을 지정하면 세그먼트:오프셋이 0xB800:0xB0000과 0xB800:0x0001이 되며 이는 물리주소 0xB8000, 0xB8001을 뜻한다.
세그먼트 레지스터 초기화
세그먼트 레지스터를 초기화하는 코드가 필요하다.
세그먼트 레지스터에는 BIOS가 사용하던 값이 들어 있기 때문
그렇다면 어떤 값으로?
BIOS가 부트로더를 디스크에서 읽어 메모리에 복사하는 위치가 0x7C00이기 때문에 0x7C0로 초기화 한다.
부트로더의 코드(Code Segment)와 데이터(Data Segment)는 0x7C00부터 512바이트 범위에 존재 하므로 CS와 DS 세그먼트 레지스터를 모두 0x7C0으로 설정했다.
CS 세그먼트 레지스터는 mov명령으로 처리할 수 없으며, 수정하려면 jmp명령과 세그먼트 레지스터 접두사를 이용해야 한다.
세그먼트 레지스터 초기화
SECTION .text ;text 섹션(세그먼트)을 정의
jmp 0x07C0:START ; CS세그먼트 레지스터에 0x07C0을 복사하면서 START 레이블로 이동
START:
mov ax, 0x07C0 ; 부트로더의 시작 어드레스(0x7C00)를 세그먼트 레지스터 값으로 변환
mov ds, ax ; DS 세그먼트 레지스터에 설정
mov ax, 0xB800 ; 비디오메모리의 시작어드레스(0xB800)를 세그먼트 레지스터 값으로 변환
mov ex, ax ; ES 세그먼트 레지스터에 설정
비디오 모드에 관련된 세그먼트 레지스터가 DS -> ES 로 되었으니 이후 출력에 관계된 코드는 모두 ES세그먼트 레지스터를 기준으로 하게 수정 해야한다.
세그먼트 레지스터 접두사를 쓰는 방법은 [세그먼트 레지스터:오프셋] 형식으로 쓰면 된다.
./00.BootLoader/BootLoader.asm
[ORG 0x00] ; Code start address : 0x00
[BITS 16] ; 16-bit environment
SECTION.text ; text section(Segment)
jmp 0x07C0:START ; copy 0x0C70 to cs, and goto START
START:
mov ax, 0x07C0 ; convert start address to 0x0C70
mov ds, ax ; set ds register
mov ax, 0xB800 ; base video address
mov es, ax ; set es register(videos address)
.SCREENCLEARLOOP:
mov byte [ es: si ], 0 ; delete character at si index
mov byte [ es: si + 1], 0x0A ; copy 0x)A(black / gree)
add si, 2 ; go to next location
cmp si, 80 * 25 *2 ; compare si and screen size
jl .SCREENCLEARLOOP ; end loop if si == screen size
mov si, 0 ; initialize si register
mov di, 0 ; initialize di register
.MESSAGELOOP:
mov cl, byte [ si + MESSAGE1 ] ; copy charactor which is on the address MESSAGE1's addr + SI register's value
cmp cl, 0 ; compare the charactor and 0
je .MESSAGEEND ; if value is 0 -> string index is out of bound -> finish the routine
mov byte [ es : di], cl ; if value is not 0 -> print the charactor on 0xB800 + di
add si, 1 ; go to next index
add di, 2 ; go to next video address
jmp .MESSAGELOOP ; loop code
.MESSAGEEND:
jmp $ ; infinite loop
MESSAGE1: db 'OS Boot Loader Start!!', 0 ; define the string tha I want to print
times 510 - ($ - $$) db 0x00 ; $ : current line's address
; $$ : current section's base address
; $ - $$ : offset!
; 510 - ($ - $$) : offset to addr 510
; db - 0x00 : declare 1byte and init to 0x00
; time : loop
; fill 0x00 from current address to 510
db 0x55 ; declare 1byte and init to 0x55
db 0xAA ; declare 1byte and init to 0xAA
; Address 511 : 0x55
; 512 : 0xAA -> declare that this sector is boot sector