TOTALSECTORCONT: dw 1024 ;부트로더를 제외한 MINT64 OS 이미지의 크기. 최대 1152 섹터(0x90000byte)까지 가능
SECTORNUMBER: db 0x02 ;OS 이미지가 시작하는 섹터 번호를 저장하는 영역
HEADNUMBER: db 0x00 ;OS 이미지가 시작하는 헤드 번호를 저장하는 영역
TRACKNUMBER: db 0x00 ;OS 이미지가 시작하는 트랙 번호를 저장하는 영역
;디스크의 내용을 메모리로 복사할 어드레스(ES:BX)를 0x10000으로 설정
mov si, 0x1000 ;OS 이미지를 복사할 어드레스(0x10000)를 세그먼트 레지스터 값으로 변환
mov es, si ;ES 세그먼트 레지스터에 값 설정
mov bx, 0x0000 ;BX 레지스터에 0x0000을 설정하여 복사할 어드레스를 0x1000:0000(0x10000)으로 최종 설정
mob di, word [TOTALSECTORCOUNT] ;복사할 os이미지의 섹터 수를 di레지스터에 설정
;디스크를 읽는 코드의 시작
READDATA: ;모든 섹터를 다 읽었는지 확인
cmp di, 0 ;복사할 OS 이미지의 섹터 수를 0과 비교
je READEND ;복사할 섹터수가 0이라면 다 복사했으므로 READEND로 이동
sub di, 0x1 ;복사할 섹터 수 1 감소
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; BIOS READ Function 호출
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
mov ah, 0x02 ;BIOS 서비스 번호 2 (Read Sector)
mov al, 0x1 ;읽을 섹터 수는 1
mov ch, byte [TRACKNUMBER] ;읽을 트랙 번호 설정
mov cl, byte [SECTORNUMBER] ;읽을 섹터 번호 설정
mov dh, byte [HEADNUMBER] ;읽을 헤드 번호 설정
mov dl, 0x00 ;읽을 드라이브 번호(0==Floppy) 설정
int 0x13 ;인터럽트 서비스 수행
jc HANDLEDISKERROR ;에러가 발생했다면 HANDELEDISKERROR로 이동
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 복사할 어드레스, 트랙, 헤드, 섹터 어드레스 계산
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
add si, 0x0020 ;512(0x200)바이트만큼 읽었으므로, 이를 세그먼트 레지스터 값으로 변환
mov es, si ;ES 세그먼트 레지스터에 더해서 어드레스를 한 섹터만큼 증가
;한 섹터를 읽었으므로 섹터 번호를 증가시키고 마지막 섹터(18)까지 읽었는지 판단
;마지막 섹터가 아니면 섹터 읽기로 이동해서 다시 섹터 읽기 수행
mov al, byte [SECTORNUMBER] ;섹터 번호를 AL레지스터 설정
add al, 0x1 ;섹터 번호를 1증가
mov byte [SECTORNUMBER], al ;증가시킨 섹터 번호를 SECTORNUMBER에 다시 설정
cmp al, 19 ;증가시킨 섹터 번호를 19와 비교
jl READDATA ;섹터 번호가 19미만이라면 READDATA로 이동
;마지막 섹터까지 읽었으면(섹터 번호가 19일 경우) 헤드를 토글 (0->1, 1->0)하고, 섹터 번호를 1로 설정
xor byte [HEADNUMBER], 0x1 ;헤드 번호를 0x01과 xor하여 토글(0->1, 1->0)
mov byte [SECTORNUMBER], 0x01 ;섹터 번호를 다시 1로 설정
;만약 헤드가 1->0으로 바뀌었다면 양쪽 헤드를 모두 읽은 것이므로 아래로 이동하여 트랙번호를 1 증가
cmp byte [HEADNUMBER], 0x00 ;헤드 번호를 0x00과 비교
jne READDATA ;헤드 번호가 0이 아니면 READDATA로 이동
;트랙을 1 증가시킨 후, 다시 섹터 읽기로 이동
add byte [TRACKNUMBER], 0x01 ;트랙 번호를 1 증가
READEND:
HANDELEDISKERROR: ;에러 처리하는 코드
```생략```
2. 스택 초기화와 함수 구현
(1) 스택 사용할 영역
0x010000(64KB) 주소부터 OS이미지가 로딩되므로 0x01000이하로 지정함
본 실습에서는 0x0000:0000 ~ 0x0000:FFFF 영역으로 지정함
스택 세그먼트 레지스터(SS)의 값은 0x0000로 설정하였으며, SP와 BP를 0xFFFF로 설정하여 스택 영역의 크기는 세그먼트의 최대 크기로 지정하였음
(2) 스택 초기화 코드
부트 로더 앞부분에 추가될 코드
;stack을 0x0000:0000~0x0000:FFFF 영역에 64KB 크기로 생성
mov ax, 0x0000 ;스택 세그먼트의 시작 어드레스(0x0000)를 세그먼트 레지스터 값으로 변환
mov ss, ax ;SS 세그먼트 레지스터에 설정
mov sp, 0xFFFE ;SP 레지스터의 어드레스를 0xFFFE로 설정
mov bp, 0xFFFE ;BP 레지스터의 어드레스를 0xFFFE로 설정
(3) 메시지 출력 함수
화면에서 원하는 위치에 문자열을 출력하려면 X좌표, Y좌표, 출력할 문자열 어드레스가 필요함
push word [pcString] ;문자열 어드레스를 스택에 삽입
push word [iY] ;화면의 Y좌표를 스택에 삽입
push word [iX] ;화면의 X좌표를 스택에 삽입
call PRINTMESSAGE ;PRINTMESSAGE 함수 호출
add sp, 6 ;2byte*3하여, 6만큼 sp 증가
어셈블리 함수의 일반적인 형식
push bp ;베이스 포인터 레지스터(BP)를 스택에 삽입
mov bp, sp ;베이스 포인터 레지스터(BP)에 스택 포인터 레지스터(SP)의 값 설정
;베이스 포인터 레지스터(BP)를 통해 파라미터에 접근할 목적
push es ;ES 세그먼트 레지스터부터 DX레지스터까지 스택에 삽입
push si ;함수에서 임시로 사용하는 레지스터로 함수의 마지막 부분에서
push di ;스택에 삽입된 값을 꺼내 원래 값으로 복원
push ax
push cx
push dx
```생략```
mov ax, word [bp+4] ;파라미터 1(iX, 화면 X좌표)
mov bx, word [bp+6] ;파라미터 2(iY, 화면 Y좌표)
mov cx, word [bp+8] ;파라미터 3(pcString, 출력할 문자열의 어드레스)
```생략```
pop dx ;함수에서 사용이 끝난 DX 레지스터부터 ES레지스터까지 스택에 삽입된 값을 이용해서 복원
pop cx ;스택은 가장 마지막에 들어간 데이터가 가장 먼저 나오는 자료구조이므로
pop ax ;삽입의 역순으로 제거해야함
pop di
pop si
pop es
pop bp ;베이스 포인터 레지스터(BP) 복원
ret ;함수를 호출한 다음 코드의 위치로 복원
PRINTMESSAGE함수의 코드
;메시지 출력하는 함수 (PARAM: x좌표, y좌표, 문자열)
PRINTMESSAGE:
push bp ;베이스 포인터 레지스터(BP)를 스택에 삽입
mov bp, sp ;베이스 포인터 레지스터(BP)에 스택 포인터 레지스터(SP)의 값 설정
;베이스 포인터 레지스터(BP)를 통해 파라미터에 접근할 목적
push es ;ES 세그먼트 레지스터부터 DX레지스터까지 스택에 삽입
push si ;함수에서 임시로 사용하는 레지스터로 함수의 마지막 부분에서
push di ;스택에 삽입된 값을 꺼내 원래 값으로 복원
push ax
push cx
push dx
; ES 세그먼트 레지스터에 비디오 모드 어드레스 설정
mov ax, 0xB800 ;비디오 메모리 시작 어드레스(0x0B8000)를 세그먼트 레지스터 값으로 변환
mov es, ax ;ES 세그먼트 레지스터에 설정
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; X, Y의 좌표로 비디오 메모리의 어드레스를 계산함
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Y좌표를 이용해서 라인 어드레스를 먼저 구함
mov ax, word [bp+6] ;파라미터 2(화면 Y좌표)를 AX레지스터에 설정
mov si, 160 ;한 라인의 바이트 수(2*80 컬럼)을 SI레지스터에 설정
mul si ;AX레지스터와 SI레지스터를 곱하여, 화면 Y어드레스 계산
mov di, ax ;계산된 화면 Y어드레스를 DI레지스터에 설정
;X좌표를 이용해서 2를 곱한 후, 최종 어드레스 구함
mov ax, word [bp+4] ;파라미터 1(화면 X좌표)를 AX레지스터에 설정
mov si, 2 ;한 문자를 나타내는 바이트수(2)를 SI레지스터에 설정
mul si ;AX레지스터와 SI레지스터를 곱하여, 화면 X어드레스를 계산
add di, ax ;화면 y어드레스와 계산된 X 어드레스를 더해서 실제 비디오 메모리 어드레스 계산
; 출력한 문자열의 어드레스
mov si, word [bp+8] ;파라미터 3(출력한 문자열의 어드레스)
.MESSAGELOOP: ;메시지를 출력하는 루프
mov cl, byte [si] ;SI레지스터가 가르키는 문자열 위치에 한 문자를 CL레지스터에 복사.
;CL레지스터는 CX레지스터의 하위 1byte를 의미
cmp cl, 0 ;복사된 문자와 0을 비교
je .MESSAGEEND ;복사한 문자의 값이 0이면 문자열이 종료되었음을 의미하며, .MESSAGEEND로 이동하여 문자 출력 종료
mov byte [es:di], cl ;0이 아니라면 비디오 메모리 어드레스 0xB800:di에 문자를 출력
add si, 1 ;SI레지스터에 1을 더하여, 다음 문자열로 이동
add di, 2 ;DI레지스터에 2을 더하여, 비디오 메모리의 다음 문자 위치로 이동
;비디오 메모리는 (문자, 속성)의 쌍으로 구성되므로 문자만 출력하려면 2를 더해야함
jmp .MESSAGELOOP ;메시지 출력 루프로 이동하여 다음 문자 출력
.MESSAGEEND:
pop dx ;함수에서 사용이 끝난 DX 레지스터부터 ES레지스터까지 스택에 삽입된 값을 이용해서 복원
pop cx ;스택은 가장 마지막에 들어간 데이터가 가장 먼저 나오는 자료구조이므로
pop ax ;삽입의 역순으로 제거해야함
pop di
pop si
pop es
pop bp ;베이스 포인터 레지스터(BP) 복원
ret ;함수를 호출한 다음 코드의 위치로 복원
3. 보호 모드에서 사용되는 3가지 함수 호출 규약
이전까지는 리얼모드인 16비트 모드 기준이었음. 보호모드는 32bit 이므로 레지스터&스택 크기가 리얼 모드의 2배임
함수 규약은 함수를 호출할 때, 파라미터와 복귀 어드레스 등을 지정하는 규칙을 의미함
보호 모드에서 사용하는 대표적 호출 규약: stdcall, cdecl, fastcall
stdcall: 파라미터를 스택에 저장하며, 호출된 쪽에서 스택을 정리함
cdecl: 파라미터를 스택에 저장하지만, 함수를 호출한 쪽에서 스택을 정리함
fastcall: 일부 파라미터를 레지스터에 저장하는 것을 제외하면, stdcall방식과 같음
4. 최종 부트 로더 소스코드_BootLoader.asm
OS이미지가 정상적으로 로딩되었다면, 0x1000위치로 이동해서 보호모드 커널 코드를 실행하도록 진행되었음
현재 로딩할 커널 이미지가 없으므로 이후 과정에서 구현해야함
부트 로더를 제외한 나머지 섹터 크기를 1024로 설정하였으므로 부트 로더를 포함해서 최소한 1025개 이상의 섹터로 구성된 OS이미지가 필요함 (but, 부트 로더 테스트 단계로 가상 OS이미지로 대체할 예정)
[ORG 0x00] ; 코드의 시작 어드레스를 0x00으로 설정
[BITS 16] ; 이하의 코드는 16비트 코드로 설정
SECTION .text ; text 섹션(세그먼트 정의)
jmp 0x07C0:START
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; MINT64 OS에 관련된 환경설정 값
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
TOTALSECTORCOUNT: dw 1024 ;부트로더를 제외한 MINT64 OS 이미지의 크기. 최대 1152 섹터(0x90000byte)까지 가능
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 코드 영역
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
START:
mov ax, 0x07C0 ;부트 로더의 시작 어드레스(0x7C00)를 세그먼트 레지스터 값으로 변환
mov ds, ax ;DS 세그먼트 레지스터에 설정
mov ax, 0xB800 ;비디오 메모리 시작 어드레스(0x0B8000)를 세그먼트 레지스터 값으로 변환
mov es, ax ;ES 세그먼트 레지스터에 설정
;stack을 0x0000:0000~0x0000:FFFF 영역에 64KB 크기로 생성
mov ax, 0x0000 ;스택 세그먼트의 시작 어드레스(0x0000)를 세그먼트 레지스터 값으로 변환
mov ss, ax ;SS 세그먼트 레지스터에 설정
mov sp, 0xFFFE ;SP 레지스터의 어드레스를 0xFFFE로 설정
mov bp, 0xFFFE ;BP 레지스터의 어드레스를 0xFFFE로 설정
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 화면 모두 지우고, 배경 녹색 & 글자 검은색으로 설정
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
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
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 화면 상단에 시작 메시지 출력
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
push MESSAGE1 ;출력할 메시지의 어드레스를 스택에 삽입
push 0 ;화면 Y좌표(0)을 스택에 삽입
push 0 ;화면 X좌표(0)을 스택에 삽입
call PRINTMESSAGE
add sp, 6 ;삽입한 파라미터 제거
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; OS 이미지를 로딩한다는 메시지 출력
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
push IMAGELOADINGMESSAGE
push 1 ;화면 Y좌표(1)을 스택에 삽입
push 0 ;화면 X좌표(0)을 스택에 삽입
call PRINTMESSAGE
add sp, 6
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 디스크에서 OS이미지를 로딩
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 디스크를 읽기 전에 먼저 리셋
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
RESETDISK: ;디스크를 리셋하는 코드의 시작
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; BIOS Reset Function 호출
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;서비스 번호 0, 드라이브 번호(0=Floppy)
mov ax, 0
mov dl, 0 ;읽을 드라이브 번호(0==Floppy) 설정
int 0x13 ;인터럽트 서비스 수행
jc HANDLEDISKERROR ;에러가 발생했다면 HANDELEDISKERROR로 이동
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 디스크에서 섹터를 읽음
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;디스크의 내용을 메모리로 복사할 어드레스(ES:BX)를 0x10000으로 설정
mov si, 0x1000 ;OS 이미지를 복사할 어드레스(0x10000)를 세그먼트 레지스터 값으로 변환
mov es, si ;ES 세그먼트 레지스터에 값 설정
mov bx, 0x0000 ;BX 레지스터에 0x0000을 설정하여 복사할 어드레스를 0x1000:0000(0x10000)으로 최종 설정
mov di, word [TOTALSECTORCOUNT] ;복사할 os이미지의 섹터 수를 di레지스터에 설정
READDATA: ;디스크 읽는 코드 시작 & 모든 섹터를 다 읽었는지 확인
cmp di, 0 ;복사할 OS 이미지의 섹터 수를 0과 비교
je READEND ;복사할 섹터수가 0이라면 다 복사했으므로 READEND로 이동
sub di, 0x1 ;복사할 섹터 수 1 감소
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; BIOS READ Function 호출
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
mov ah, 0x02 ;BIOS 서비스 번호 2 (Read Sector)
mov al, 0x1 ;읽을 섹터 수는 1
mov ch, byte [TRACKNUMBER] ;읽을 트랙 번호 설정
mov cl, byte [SECTORNUMBER] ;읽을 섹터 번호 설정
mov dh, byte [HEADNUMBER] ;읽을 헤드 번호 설정
mov dl, 0x00 ;읽을 드라이브 번호(0==Floppy) 설정
int 0x13 ;인터럽트 서비스 수행
jc HANDLEDISKERROR ;에러가 발생했다면 HANDELEDISKERROR로 이동
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 복사할 어드레스, 트랙, 헤드, 섹터 어드레스 계산
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
add si, 0x0020 ;512(0x200)바이트만큼 읽었으므로, 이를 세그먼트 레지스터 값으로 변환
mov es, si ;ES 세그먼트 레지스터에 더해서 어드레스를 한 섹터만큼 증가
;한 섹터를 읽었으므로 섹터 번호를 증가시키고 마지막 섹터(18)까지 읽었는지 판단
;마지막 섹터가 아니면 섹터 읽기로 이동해서 다시 섹터 읽기 수행
mov al, byte [SECTORNUMBER] ;섹터 번호를 AL레지스터 설정
add al, 0x1 ;섹터 번호를 1증가
mov byte [SECTORNUMBER], al ;증가시킨 섹터 번호를 SECTORNUMBER에 다시 설정
cmp al, 19 ;증가시킨 섹터 번호를 19와 비교
jl READDATA ;섹터 번호가 19미만이라면 READDATA로 이동
;마지막 섹터까지 읽었으면(섹터 번호가 19일 경우) 헤드를 토글 (0->1, 1->0)하고, 섹터 번호를 1로 설정
xor byte [HEADNUMBER], 0x1 ;헤드 번호를 0x01과 xor하여 토글(0->1, 1->0)
mov byte [SECTORNUMBER], 0x01 ;섹터 번호를 다시 1로 설정
;만약 헤드가 1->0으로 바뀌었다면 양쪽 헤드를 모두 읽은 것이므로 아래로 이동하여 트랙번호를 1 증가
cmp byte [HEADNUMBER], 0x00 ;헤드 번호를 0x00과 비교
jne READDATA ;헤드 번호가 0이 아니면 READDATA로 이동
;트랙을 1 증가시킨 후, 다시 섹터 읽기로 이동
add byte [TRACKNUMBER], 0x01 ;트랙 번호를 1 증가
jmp READDATA ;READDATA로 이동
READEND:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; OS 이미지가 완료되었다는 메시지 출력
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
push LOADINGCOMPLETEMESSAGE ;출력할 메시지의 어드레스를 스택에 삽입
push 1 ;화면 Y좌표(1)를 스택에 삽입
push 20 ;화면 X좌표(20)를 스택에 삽입
call PRINTMESSAGE
add sp, 6
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 로딩한 가상 OS 이미지 실행
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
jmp 0x1000:0x0000
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 함수 코드 영역
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
HANDLEDISKERROR: ;에러 처리하는 코드
push DISKERRORMESSAGE
push 1 ;화면 Y좌표(1)를 스택에 삽입
push 20 ;화면 X좌표(20)를 스택에 삽입
call PRINTMESSAGE
jmp $ ;현재 위치에서 무한 루프 수행
;메시지 출력하는 함수 (PARAM: x좌표, y좌표, 문자열)
PRINTMESSAGE:
push bp ;베이스 포인터 레지스터(BP)를 스택에 삽입
mov bp, sp ;베이스 포인터 레지스터(BP)에 스택 포인터 레지스터(SP)의 값 설정
;베이스 포인터 레지스터(BP)를 통해 파라미터에 접근할 목적
push es ;ES 세그먼트 레지스터부터 DX레지스터까지 스택에 삽입
push si ;함수에서 임시로 사용하는 레지스터로 함수의 마지막 부분에서
push di ;스택에 삽입된 값을 꺼내 원래 값으로 복원
push ax
push cx
push dx
; ES 세그먼트 레지스터에 비디오 모드 어드레스 설정
mov ax, 0xB800 ;비디오 메모리 시작 어드레스(0x0B8000)를 세그먼트 레지스터 값으로 변환
mov es, ax ;ES 세그먼트 레지스터에 설정
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; X, Y의 좌표로 비디오 메모리의 어드레스를 계산함
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Y좌표를 이용해서 라인 어드레스를 먼저 구함
mov ax, word [bp+6] ;파라미터 2(화면 Y좌표)를 AX레지스터에 설정
mov si, 160 ;한 라인의 바이트 수(2*80 컬럼)을 SI레지스터에 설정
mul si ;AX레지스터와 SI레지스터를 곱하여, 화면 Y어드레스 계산
mov di, ax ;계산된 화면 Y어드레스를 DI레지스터에 설정
;X좌표를 이용해서 2를 곱한 후, 최종 어드레스 구함
mov ax, word [bp+4] ;파라미터 1(화면 X좌표)를 AX레지스터에 설정
mov si, 2 ;한 문자를 나타내는 바이트수(2)를 SI레지스터에 설정
mul si ;AX레지스터와 SI레지스터를 곱하여, 화면 X어드레스를 계산
add di, ax ;화면 y어드레스와 계산된 X 어드레스를 더해서 실제 비디오 메모리 어드레스 계산
; 출력한 문자열의 어드레스
mov si, word [bp+8] ;파라미터 3(출력한 문자열의 어드레스)
.MESSAGELOOP: ;메시지를 출력하는 루프
mov cl, byte [si] ;SI레지스터가 가르키는 문자열 위치에 한 문자를 CL레지스터에 복사.
;CL레지스터는 CX레지스터의 하위 1byte를 의미
cmp cl, 0 ;복사된 문자와 0을 비교
je .MESSAGEEND ;복사한 문자의 값이 0이면 문자열이 종료되었음을 의미하며, .MESSAGEEND로 이동하여 문자 출력 종료
mov byte [es:di], cl ;0이 아니라면 비디오 메모리 어드레스 0xB800:di에 문자를 출력
add si, 1 ;SI레지스터에 1을 더하여, 다음 문자열로 이동
add di, 2 ;DI레지스터에 2을 더하여, 비디오 메모리의 다음 문자 위치로 이동
;비디오 메모리는 (문자, 속성)의 쌍으로 구성되므로 문자만 출력하려면 2를 더해야함
jmp .MESSAGELOOP ;메시지 출력 루프로 이동하여 다음 문자 출력
.MESSAGEEND:
pop dx ;함수에서 사용이 끝난 DX 레지스터부터 ES레지스터까지 스택에 삽입된 값을 이용해서 복원
pop cx ;스택은 가장 마지막에 들어간 데이터가 가장 먼저 나오는 자료구조이므로
pop ax ;삽입의 역순으로 제거해야함
pop di
pop si
pop es
pop bp ;베이스 포인터 레지스터(BP) 복원
ret ;함수를 호출한 다음 코드의 위치로 복원
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 데이터 영역
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 부트 로더 시작 메시지
MESSAGE1: db 'MINT64 OS Boot Loader Start~!!', 0 ;마지막 0은 .MESSAGELOOP에서 문자열이 종료되었음을 알 수 있도록 함
DISKERRORMESSAGE: db 'DISK Error~!!', 0
IMAGELOADINGMESSAGE: db 'OS Image Loading...', 0
LOADINGCOMPLETEMESSAGE: db 'Complete~!!', 0
;디스크 읽기에 관련된 변수들
SECTORNUMBER: db 0x02 ;OS 이미지가 시작하는 섹터 번호를 저장하는 위치
HEADNUMBER: db 0x00 ;OS 이미지가 시작하는 헤드 번호를 저장하는 위치
TRACKNUMBER: db 0x00 ;OS 이미지가 시작하는 트랙 번호를 저장하는 위치
times 510 - ($ - $$) db 0x00
db 0x55
db 0xAA ; 1byte를 선언하고 값은 0xAA
; 어드레스 511, 512에 0x55, 0xAA를 써서 섹터로 표기함
결과
5. 테스트를 위한 가상 OS 이미지 생성
테스트를 위해, 잠깐 사용하므로 세세한 부분까지 구현하지 않아도 됨
따라서, 부트 로더 코드를 기반으로 기능을 간소화하여 OS가 실행되었음을 표시하는 기능만 생성함
자신의 섹터 번호를 화면 위치에 대응시켜 0~9까지 번호를 출력한다면 화면에 출력된 문자의 위치와 수를 확인하여 정상여부를 판단할 수 있음
가상 OS이미지는 거의 같은 코드를 반복하는 구조이므로, 첫번째 섹터에 삽입할 코드를 구현하고 이를 반복하여 나머지 섹터도 생성하였음
본 코드는 “01.Kernel32 디렉터리에 VirtualOS.asm”파일로 생성하였음
VirtualOS.asm파일은 임의로 지정한 가상 OS 소스 파일임
해당 코드는 부트로더 코드와 동일한 방식으로 레지스터 초기화를 수행 후, 화면 2번째 라인의 가장 왼쪽 위치에 0을 출력하도록 하였음
SECTORCOUNT라는 메모리 어드레스는 정상적으로 처리된 섹터의 수를 기록하고 이를 이용하여 화면에 출력될 좌표를 계산할 용도록 추가하였음
(1) 1섹터 크기의 가상 OS 소스코드(VirtualOS.asm)
[ORG 0x00] ;코드의 시작 어드레스는 0x00으로 설정
[BITS 16] ;이하의 코드는 16비트 코드로 설정
SECTION .text ;text 섹션(세그먼트)을 정의
jmp 0x1000:START ;CS 세그먼트 레지스터에 0x1000을 복사하면서, START레이블로 이동
SECTORCOUNT: dw 0x0000 ;현재 실행 중인 섹터 번호를 저장
START:
mov ax, cs ;CS 세그먼트 레지스터의 값을 AX레지스터에 설정
mov ds, ax ;AX 레지스터의 값을 DS 세그먼트 레지스터에 저장
mov ax, 0xB800 ;비디오 메모리 어드레스인 0xB8000을 세그먼트 레지스터 값으로 변환
mov es, ax
mov ax, 2 ;한 문자를 나타내는 바이트 수(2)를 AX레지스터에 설정
mul word [SECTORCOUNT] ;AX레지스터와 섹터 수를 곱함
mov si, ax ;곱한 결과를 si레지스터에 설정
mov byte [es:si + (160*2)], '0' ;계산한 결과를 비디오 메모리에 오프셋으로 삼아 세번째 라인부터 화면에 0을 출력
add word [SECTORCOUNT], 1 ;섹터 수를 1 증가
jmp $ ;현재 위치에서 무한루프 수행
times 512 - ($-$$) db 0x00 ;512 - ($-$$): 현재부터 어드레스 512까지
;현재 위치에서 어드레스 512까지 0x00으로 채움
(2) 1024섹터를 만드는 방법
1024섹터 중 마지막 섹터를 제외한 1023섹터의 코드는 화면에 자신을 출력하는 코드 및 다음 섹터의 어드레스로 이동하는 코드를 반복하면 됨
마지막 섹터의 경우, 더 이상 섹터가 없으므로 자신을 출력하고 무한 루프를 수행하도록 만들면 됨
NASM 어셈블러는 반복되는 구문을 위해, %rep라는 전처리문을 제공함
times와 동일하게 반복 횟수와 반복할 대상을 입력하여 사용할 수 있음
%endrep사이의 코드를 반복횟수만큼 확장해줌
이 외에도 변수할당, 대입문(%assign), 조건문(%if-%elif-%else-%endif)도 지원함
각 섹터의 상위에 화면 출력 코드를 삽입하고, 512byte단위로 정렬하기 위해 남은 영역을 0으로 채움
[ORG 0x00] ;코드의 시작 어드레스는 0x00으로 설정
[BITS 16] ;이하의 코드는 16비트 코드로 설정
SECTION .text ;text 섹션(세그먼트)을 정의
jmp 0x1000:START ;CS 세그먼트 레지스터에 0x1000을 복사하면서, START레이블로 이동
SECTORCOUNT: dw 0x0000 ;현재 실행 중인 섹터 번호를 저장
TOTALSECTORCOUNT equ 1024 ;가상OS의 총 섹터 수 (최대 1152섹터인 0x90000byte까지 가능)
START:
mov ax, cs ;CS 세그먼트 레지스터의 값을 AX레지스터에 설정
mov ds, ax ;AX 레지스터의 값을 DS 세그먼트 레지스터에 저장
mov ax, 0xB800 ;비디오 메모리 어드레스인 0xB8000을 세그먼트 레지스터 값으로 변환
mov es, ax
%assign i 0 ;i라는 변수를 지정하고 0으로 초기화
%rep TOTALSECTORCOUNT ;TOTALSECTORCOUNT에 저장된 값만큼 아래 코드를 반복
%assign i i+1 ;i를 1증가
;현재 실행 중인 코드가 포함된 섹터의 위치를 화면 좌표로 변환
mov ax, 2 ;한 문자를 나타내는 바이트 수(2)를 AX레지스터에 설정
mul word [SECTORCOUNT] ;AX레지스터와 섹터 수를 곱함
mov si, ax ;곱한 결과를 si레지스터에 설정
mov byte [es:si + (160*2)], '0' + (i % 10) ;계산한 결과를 비디오 메모리에 오프셋으로 삼아 세번째 라인부터 화면에 0을 출력
add word [SECTORCOUNT], 1 ;섹터 수를 1 증가
%if i == TOTALSECTORCOUNT ;마지막 섹터이면 현재 위치에서 무한 루프 수행
jmp $ ;현재 위치에서 무한루프 수행
%else
jmp (0x1000 + i * 0x20): 0x0000 ;다음 섹터 오프셋으로 이동
%endif ;if문 끝
times (512 - ($-$$) %512) db 0x00 ;512 - ($-$$): 현재부터 어드레스 512까지
;현재 위치에서 어드레스 512까지 0x00으로 채움
%endrep ;반복문의 끝