8. OS 이미지 로딩 기능 구현

디스크 읽기 기능 구현

  • 우리가 만들 os의 이미지는 크게 부트로더, 보호모드 커널, IA-32e모드 커널로 구성되어있음.
  • 각 부분은 섹터 단위로 정렬해서 하나의 부팅 이미지 파일로 합친다.
  • 디스크의 첫번째 섹터는 부트로더로 BIOS가 메모리에 로딩한다.
  • 두번째 섹터부터 os이미지 크기만큼을 읽어서 메모리에 복사하면 된다.
  • 디스크를 읽을 때는 섹터->헤드->트랙 순으로 읽어서 올린다.
  • 섹터번호를 순서대로 증가시키며 읽다가 마지막 섹터에서 헤드와 트랙번호를 증가시키는 것이 핵심 포인트

1024섹터 크기의 이미지를 메모리로 복사하는 소스코드(C언어)

int main(int argc, char* argv[]){
  int iTotalSectorCount = 1024;
  int iSectorNumber = 2;
  int iHeadNumber = 0;
  int iTrackNumber = 0;
  //실제 이미지를 복사할 어드레스(물리주소)
  char* pcTargetAddress = (char*)0x10000;

  while(1){
    //전체 섹터 수를 하나씩 감소시키면서 0이 될때까지 섹터를 복사
    if(iTotalSectorcount == 0){
      break;
    }
    iTotalSectorCount = iTotalSectorCount - 1;

    //1섹터를 읽어들여서 메모리 어드레스에 복사
    //BIOSReadOneSector: BIOS 섹터읽기 기능을 호출하는 임의의 함수
    if(BIOSReadOneSector(iSectorNumber, iHeadNumber, iTrackNumber, pcTargetAddress) == ERROR){
      HandleDiskError();
    }

    //1섹터는 512(0x200) 바이트이므로, 복사한 섹터 수만큼 어드레스 증가
    pcTargetAddress = pcTargetAddress + 0x200;

    //섹터->헤드->트랙 순으로 번호 증가
    iSectorNumber = iSectorNumber + 1;
    if(iSectorNumber < 19){
      continue;
    }

    iHeadNumber = iHeadNumber ^ 0x01; //헤드의 번호는 0과 1이 반복되므로 XOR 연산을 이용
    iSectorNumber = 1;

    if(iHeadNumber != 0){
      continue;
    }

    iTrackNumber = iTrackNumber + 1;
  }

  return 0;
}

void HandleDiskError(){
  printf("DISK Error!");
  while(1);
}

1024섹터 크기의 이미지를 메모리로 복사하는 소스코드(어셈블리어)

TOTALSECTORCOUNT: dw  1024  ; 부트로더를 제외한 os이미지의 크기
                            ; 최대 1151섹터(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)으로 최종 설정

  mov di, word [ TOTALSECTORCOUNT ] ; 복사할 os이미지의 섹터 수를 DI레지스터에 설정

READATA:                      ; 디스크를 읽는 코드의 시작
  ; 모든 섹터를 다 읽었는지 확인
  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, 0x01                  ; 읽을 섹터 수는 1
  mov ch, byte [ TRACKNUMBER ]  ; 읽을 트랙 번호 설정
  mov cl, byte [ SECTORNUMBER ] ; 읽을 섹터 번호 설정
  mov dh, byte [ HEADNUMBER ]   ; 읽을 헤드 번호 설정
  mov dl, 0x00                  ; 읽을 드라이브 번호(0=Floppy)설정
  int 0x13                      ; 인터럽트 서비스 수행
  jc HANDLEDISKERROR            ; 에러가 발생했다면 HANDLEDISKERROR로 이동

  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ; 복사할 어드레스와 트랙, 헤드, 섹터 어드레스 계산
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  add si, 0x0020                ; 512(0x200)바이트만큼 읽었으므로, 이를 세그먼트 레지스터 값으로 변환
  mov es, si                    ; ES 세그먼트 레지스터에 더해서 어드레스를 한 섹터만큼 증가

  ; 한 섹터를 읽었으므로 섹터 번호를 증가시키고 마지막 섹터(18)까지 읽었는지 판단
  ; 마지막 섹터가 아니면 섹터 읽기로 이동해서 다시 섹터 읽기 수행
  mov al, byte [ SECTORNUMBER ] ; 섹터번호를 AL 레지스터에 설정
  add al, 0x01                  ; 섹터번호를 1 증가
  mov byte [ SECTORNUMBER ], al ; 증가시킨 섹터번호를 SECTORNUMBER에 다시 설정
  cmp al, 19                    ; 증가시킨 섹터번호를 19와 비교
  jl READDATA                   ; 섹터번호가 19미만이라면 READDATA로 이동

  ; 마지막 섹터까지 읽었으면(섹터번호가 19이면) 헤드를 토글(0->1, 1->0)하고,
  ; 섹터번호를 1로 설정
  xor byte [ HEADNUMBER ], 0x01   ; 헤드번호를 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 [ TRACTNUMBER ], 0x01  ; 트랙번호를 1증가
  jmp READDATA                    ; READDATA로 이동

READEND:

HANDLEDISKERROR:                  ; 에러를 처리하는 코드
;;;색략;;;
  jmp $

스택초기화와 함수구현

  • 함수를 사용하려면 stack이 꼭 필요하다.

  • 스택은 Last In First Out 형태의 자료구조이다.

  • x86프로세서에서는 함수를 호출한 코드의 다음 어드레스, 되돌아갈 어드레스를 저장하는 용도로 스택을 사용한다.

  • 함수를 호출하면 프로세서가 자동으로 되돌아올 어드레스를 스택에 저장, 호출된 함수에서 ret 명령어를 요청하면 자동으로 스택에서 어드레스를 꺼내 호출한 다음 어드레스로 이동하는 것.

  • 함수 호출을 위해 가장 먼저 해야할 일은 스택 생성이다.

  • SS(스택 세그먼트 레지스터)는 스택영역으로 사용할 세그먼트의 기준주소를 지정

  • SP(스택 포인터 레지스터)는 데이터를 삽입하고 제거하는 top을 지정

  • BP(베이스 포인터 레지스터)는 스택의 기준주소를 임시로 지정할 때 사용

  • 16비트모드는 최대 64kb(0x10000)를 스택영역으로 지정할 수 있다.

    • SS에 0x0000을 설정한다면 사용가능한 영역은 0x00000 ~ 0x0FFFF까지가 된다.
  • SS로 스택세그먼트의 범위는 지정할 수 있지만, 실제 스택의 크기는 지정할 수 없다.

  • 스택의 실제 크기는 sp와 bp의 초기값으로 지정한다.

  • sp가 움직이면서 항상 top의 값을 가지고 있다.

<스택 초기화 코드>

  • 0x10000(64kb)어드레스 부터는 os이미지가 로딩 되므로 0x10000이하 부터 스택을 사용한다.*
  • 스택이 쌓이는 방향은 0xffff 부터 시작해서 0x0000까지 이다.

다시 화면 출력함수로 돌아와서,
화면에서 원하는 위치에 문자열을 출력하려면 x좌표, y좌표, 출력할 문자열 어드레스가 필요하다.
이 3가지를 함수 파라미터로 정의하고 스택에 삽입하는 순서를 정할 것이다.
c언어와 연계를 고려한다면 중복작업을 피할 수 있게 c언어의 호출규약(cdecl 방식)을 따르는 것이 좋다.
c언어는 파라미터의 역순(오른쪽에서 왼쪽)으로 삽입하여 스택에서 꺼낸 순서가 파라미터순서와 같게 한다.

c언어의 함수 호출 코드

PrintMessage( iX, iY, pcString );

어셈블리어의 함수 호출 코드

push word [ pcString ]  ; 문자열의 어드레스를 스택에 삽입
push word [ iY ]        ; 화면의 y좌표를 스택에 삽입
push word [ iX ]        ; 화면의 x좌표를 스택에 삽입
call PRINTMESSAGE       ; PRINTMESSAGE 함수 호출
add sp, 6               ; 스택에 삽입된 함수 파라미터 3개 (2바이트 * 3)제거
  • sp에 6을 더하는 이유는 함수파라미터로 스택에 삽입된 값을 제거하기 위함
  • 16비트 모드에서는 스택에 2바이트(word)크기로 삽입/제거되고 삽입은 sp를 아래로 이동시킨다.

호출되는 쪽 코드를 살펴보자.
스택의 특정위치를 기준으로 오프셋을 이용해 접근하면 파라미터를 찾게 된다.
그러나 스택의 top을 가리키는 sp는 push, pop에 따라 계속 변하기 때문에 문제가 된다.
고정된 값을 가리키는 레지스터를 사용하는 것이 편리하다.
이러한 역할을 하는 것이 bp이며 호출된 함수는 bp + 오프셋으로 파라미터에 접근하게 된다.

그리고 호출되기 전후의 레지스터 상태가 같아야 한다.
이를위해 호출되는 함수에서는 레지스터의 값을 미리 스택에 저장해두고 수행이 끝나면 이를 복원한다.

어셈블리어 함수의 일반적인 형식

push bp                 ; bp를 스택에 삽입
mov bp, sp              ; bp에 sp의 값을 설정
                        ; bp를 이용해서 파라미터에 접근할 목적
                        ; 호출된 직후의 sp값을 저장하여 bp와 고정된 오프셋으로 파라미터에 접근하게 함(아래 그림 참고)

; 레지스터 백업
push es
push si
push di
push ax
push cx
push dx

;;; 생략 ;;;

mov ax, word [ bp + 4 ] ; word: 메모리에 접근할 때  2byte단위로 접근
mov bx, word [ bp + 6 ] ; 해석하면 메모리의 bp + 6 어드레스에 있는 값을 2byte로 끊어서 가져와라
mov cx, word [ bp + 8 ]

;;; 생략 ;;;

; 레지스터 복원
pop dx
pop cx
pop ax
pop di
pop si
pop es
pop bp
ret                       ; 복원 후 리턴

  • 함수를 호출하기 전에는 스택에 함수 파라미터만 들어 있음
  • call명령 후
    • 자동으로 복귀 어드레스를 스택에 저장하고
    • sp 값에 2를 빼서 복귀 어드레스를 가리키게 한다.
    • 호출된 함수는 파라미터 사용을 위해 bp의 값을 스택에 저장하고
    • sp의 값으로 바꾼다.
  • 저장 후 bp의 값은 자신이 저장된 스택의 위치를 가리키고 있음
    • 이때 함수 파라미터는 bp를 기준으로 일정한 값만큼 증가하는 어드레스에 위치하고 있다.

보호모드에서 사용되는 세가지 함수 호출 규약

호출규약(calling convention)은 함수를 호출할 때 파라미터와 복귀 어드레스 등을 지정하는 규칙

  • stdcall 방식은 파라미터를 스택에 저장하면, 호출된 쪽에서 스택을 정리한다.
  • cdecl 방식은 파라미터를 스택에 저장하면, 함수를 호출한 쪽에서 스택을 정리한다.
  • fastcall 방식은 일부 파라미터를 레지스터에 저장하는 것을 제외하면 stdcall방식과 같다.

이전에는 16비트모드 기준으로 레지스터와 스택을 설명했음.
보호모드는 32비트 이므로 레지스터와 스택의 크기가 리얼모드의 2배이다.
따라서 스택의 기본크기는 4byte(dword)가 된다.
당연히 오프셋도 리얼모드의 2배이다.

함수 호출 예(c언어)

int Add(int iA, int iB, int iC)
{
  return iA + iB + iC;
}

void main()
{
  int iReturn;

  iReturn = Add(1, 2, 3);
}

함수 호출 예(어셈블리어 - stdcall 방식)

  • stdcall은 파라미터를 스택에 넣을 때 오른쪽에서 왼쪽 순서로 집어 넣는다.
  • stdcall은 함수의 반환값은 eax레지스터(32비트 ax 레지스터)를 사용하며 스택에서 파라미터를 제거하는 작업을 호출된 함수가 처리하게 한다.

함수 호출 예(어셈블리어 - cdecl 방식)

  • cdecl은 파라미터를 스택에 넣을 때 오른쪽에서 왼쪽 순서로 집어 넣는다.
  • 함수의 반환값은 eax 레지스터를 사용
  • stdcall과 단한가지 차이점은 스택에서 파라미터를 제거하는 작업을 호출한 함수가 처리하게 한다.

함수 호출 예(어셈블리어 - fastcall 방식)

  • 처음 2개의 파라미터를 ecx와 edx에 삽입한다는 점을 제외하고는 stdcall과 같다.

최종 부트로더 소스코드

os이미지가 정상적으로 로딩되었다면 0x10000위치로 이동해서 보호모드 커널 코드를 실행하도록 했다.

./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

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; OS에 관련된 환경설정값
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
TOTALSECTORCOUNT:    dw 1024    ; 부트로더를 제외한 os이미지의 크기
                ; 최대 1152 섹터(0x90000byte)까지 가능

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 코드 영역
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
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)

    ;  스택을 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    ; SI 레지스터(문자열 원본 인덱스 레지스터)를 초기화

.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

    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ; 화면 상단에 시작 메시지 출력
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    push MESSAGE1        ; 출력할 메시지의 어드레스를 스택에 삽입
    push 0            ; 화면 Y좌표(0)를 스택에 삽입
    push 0            ; 화면 x좌표(0)를 스택에 삽입
    call PRINTMESSAGE    ; PRINTMESSAGE 함수 호출
    add sp, 6        ; 삽입한 파라미터 제거

    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ; OS이미지를 로딩한다는 메시지 출력
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    push IMAGELOADINGMESSAGE    ; 출력할 메시지의 어드레스를 스택에 삽입
    push 1                ; 화면 y좌표(1)를 스택에 삽입
    push 0                ; 화면 x좌표(0)를 스택에 삽입
    call PRINTMESSAGE        ; PRINTMESSAGE 함수 호출
    add sp, 6            ; 삽입한 파라미터 제거

    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ; 디스크에서 os이미지를를 로딩
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ; 디스크를 읽기 전에 먼저 리셋
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
RESETDISK:                    ; 디스크를 리섹하는 코드의 시작
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ; BIOS Reset Function호출
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ; 서비스 번호 0, 드라이브 번호(0=Floppy)
    mov ax, 0
    mov dl, 0
    int 0x13
    ; 에러가 발생하면 에러 처리로 이동
    jc HANDLEDISKERROR

    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ; 디스크에서 섹터를 읽음
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ; 디스크의 내용을 메모리로 복사할 어드레스(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=플로피) 설정
    int 0x13            ; 인터럽트 서비스 수행
    jc HANDLEDISKERROR        ; 에러가 발생했다면 HANDLEDISKERROR로 이동

    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ; 복사할 어드레스와 트랙, 헤드, 섹터 어드레스 계산
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    add si, 0x0020            ; 512(0x200)바이트만큼 읽었으므로 이를 세그먼트레지스터 값으로 변환
    mov es, si            ; es세그먼트 레지스터에 더해서 어드레스를 한 섹터 만큼 증가

    ; 한섹터를 읽었으므로 섹터 번호를 증가시키고 마지막 섹터(18)까지 읽었는지 판단
    ; 마지막 섹터가 아니면 섹터 읽기로 이동해서 다시 섹터 읽기 수행
    mov al, byte[ SECTORNUMBER ]    ; 섹터번호를 al레지스터에 설정
    add al, 0x01            ; 섹터 번호를 1증가
    mov byte[ SECTORNUMBER ], al    ; 증가시킨 섹터번호를 SECTORNUMBER에 다시 설정
    cmp al, 19            ; 증가시킨 섹터 번호를 19와 비교
    jl READDATA            ; 섹터번호가 19미만이라면 READDATA로 이동

    ; 마지막 섹터까지 읽었으면(섹터 번호가 19이면) 헤드를 토글(0->1, 1->0)하고, 섹터 번호를 1로 설정
    xor byte[ HEADNUMBER ], 0x01    ; 헤드 번호를 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        ; PRINTMESSAGE 함수 호출
    add sp, 6            ; 삽입한 파라미터 제거

    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ; 로딩한 가상 os이미지 실행
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    jmp 0x1000:0x0000

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 함수코드 입력
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 디스크 에러를 처리하는 함수
HANDLEDISKERROR:
    push DISKERRORMESSAGE    ; 에러 문자열의 어드레스를 스택에 삽입
    push 1            ; 화면 y좌표(1)를 스택에 삽입
    push 20            ; 화면 x좌표(20)를 스택에 삽입
    call PRINTMESSAGE    ; 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 ]        ; copy charactor which is on the address SI register's value
                    ; cl은 cx의 하위 1바이트를 의미
                    ; 문자는  1바이트면 충분하므로 cx레지스터의 하위 1바이트만 사용

    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    ; 0이 아니라면 비디오 메모리 어드레스 0xB800:di에 문자를 출력
    add si, 1            ; go to next index
    add di, 2            ; di 레지스터에 2를 더하여 비디오 메모리의 다음 문자 위치로 이동
                    ; 비디오 메모리는 (문자, 속성)의 쌍으로 구성되므로 문자만 출력하려면 2를 더해야함

    jmp .MESSAGELOOP        ; loop code

.MESSAGEEND:
    pop dx        ; 함수에서 사용이 끝난 dx부터 es까지를 스택에 삽입된 값을 이용해서 복원
    pop cx        ; pop(제거)
    pop ax
    pop di
    pop si
    pop es
    pop bp        ; bp 복원
    ret        ; 함수를 호출한 다음 코드의 위치로 복귀

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 데이터 영역
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
MESSAGE1:    db 'OS Boot Loader Start!!', 0 ; define the string tha I want to print

DISKERRORMESSAGE:    db 'DISK Error!', 0
IMAGELOADINGMESSAGE:    db 'OS image loading...', 0
LOADINGCOMPLETEMESSAGE:    db 'Complete!', 0

; 디스크 읽기에 관련된 변수들
SECTORNUMBER:        db 0x02    ; os이미지가 시작하는 섹터번호를 저장하는 영역
HEADNUMBER:        db 0x00    ; " 헤드번호를 "
TRACKNUMBER:        db 0x00    ; " 트랙번호를 "

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

sudo qemu-system-x86_64 -m 64 -fda ./Disk.img -localtime -M pc

'Project > OS' 카테고리의 다른 글

10. 32비트 보호모드로 전환하자  (0) 2019.07.18
9. 테스트를 위한 가상 os이미지 생성  (0) 2019.07.16
7. 디스크에서 os이미지 로딩  (0) 2019.07.16
6. Bootloader 만들기  (0) 2019.07.11
5. 부팅과 부트로더  (0) 2019.07.11

+ Recent posts