OS/OS from Scratch

[OS] 07. Boot Sector Disk

jschang 2021. 6. 20. 22:10

서론

이번 강의에서는 가장 기본적인 Boot Sector에서 Disk에서 데이터를 불러오는 기능을 구현해 보겠습니다. 이번 강의는 조금 어려운 편에 속하는 것 같습니다. 해당 Github 강의는 다음과 같습니다.

https://github.com/cfenollosa/os-tutorial/tree/master/07-bootsector-disk

 

cfenollosa/os-tutorial

How to create an OS from scratch. Contribute to cfenollosa/os-tutorial development by creating an account on GitHub.

github.com


이론

Disk 구조

먼저 디스크 구조를 쉽게 설명한 동영상을 첨부합니다.

https://www.youtube.com/watch?v=gd5vKobmZ3Q

디스크에 대해 필요한 지식은 다음과 같습니다.

  • Sector: 디스크에 저장된 데이터는 512bytesector로 저장됨
  • Platter: 하드 디스크에 있는 하나의 디스크 판
  • Head: headplatter 표면 움직여 데이터 쓰거나 읽음
  • Cylinder: 하드 디스크에서 여러 platter에 있는 같은 위치의 track을 모아서 cylinder라 함
  • Track: sector의 모임 (하드 디스크 원판에서 한 둘레에 있음)

더 자세한 내용은 https://jhnyang.tistory.com/105을 참고하세요.

Carry Bit

Carry bit는 나머지 bit가 다 차 있을 때 사용하는 남겨진 bit입니다. 사용 가능한 bit들이 모두 1로 차있는 상태에서 1을 더하면 Carry bit가 1 증가합니다. Carry bit를 쉽게 설명한 자료를 첨부합니다. https://www.quora.com/Can-you-explain-in-an-easy-way-what-is-a-carry-bit

Int 0x13

디스크에 있는 데이터를 불러오기 위해서 직접 디스크를 조작할 필요없이 BIOS에 있는 기능을 쓰면 됩니다. 이를 가능하게 하는 것이 int 0x13 Interrupt입니다. ah레지스터를 0x02로 설정하면 디스크 'read' 모드에 들어가게 되고, cl 레지스터에 읽을 sector을 지정하며, ch 레지스터에는 cylinder 번호가 저장됩니다. dh 레지스터에는 head number가 저장되고, dl 레지스터에는 drive number가 들어가게 되는데, 이는 BIOS에 의해 disk number가 불러오게 되며, dl레지스터에 저장되는 값에 따라 읽는 디스크는 다음과 같습니다.

  • 0 = floppy
  • 1 = floppy2
  • 0x80 = hdd
  • 0x81 = hdd2

int 0x13 Interrupt가 발생하게 되면 es:bx 가 가리키는 위치에 데이터가 저장되게 됩니다. 따라서 Interrupt를 발생시키기 전에 bx 레지스터 데이터를 load 할 주소를 저장해두어야 합니다. 만약 Interrupt 발생 후 디스크를 읽는 과정에서 에러가 발생하면 Carry bit에 값이 저장됩니다. jc 명령어를 이용해 Carry bit에 데이터가 저장될 경우 error가 발생한 것을 알 수 있습니다. ah 레지스터에는 이때의 error code가 저장되게 되며 error code들은 다음과 같습니다.

디스크를 읽는 과정을 완료하면 BIOS는 al 레지스터에 읽은 sector의 번호를 반환하게 됩니다. 이를 통해 원하는 sector를 제대로 읽었는지 확인할 수 있습니다.


코드

boot_sect_disk.asm

boot_sect_disk.asm은 디스크에서 데이터를 읽는 기능을 가지고 있습니다. ah 레지스터를 0x02로 설정해 디스크 read 모드로 설정하며 al 레지스터에 읽어야할 sector의 개수를 저장합니다. 다음으로 cl 레지스터에 읽을 sector의 번호를 입력하는데, 이때 0x01이 boot sector이며 0x02부터 저희가 생성한 데이터가 저장됩니다. 다음으로 ch 레지스터(Cylinder)와 dh 레지스터(Head number)에 0x00을 저장하고 int 0x13 Interrupt를 발생시킵니다. 이때 디스크를 읽는 과정에서 에러가 발생하면 carry bit에 에러가 저장돼 jc 명령어로 에러 처리를 합니다. 다음으로는 읽은 sector의 개수를 확인하기 위해 디스크를 읽으며 BIOS에서 Sector 개수를 저장한 al 레지스터 값을 원래 설정한 sector 개수와 비교합니다. 만약 두 값이 다르면 에러 처리를 합니다.

disk_load:
    pusha
    push dx

    ; int 0x13 함수 0x02는 'read'
    mov ah, 0x02
    ; 읽을 sector 개수 
    mov al, dh
    ; cl은 sector 
    ; 0x01이 boot sector
    ; 0x02가 첫번째 sector 
    mov cl, 0x02

    ; ch는 cylinder 
    mov ch, 0x00

    ; dl은 drive number
    ; (0 = floppy, 1 = floppy2, 0x80 = hdd, 0x81 = hdd2)
    ; caller에서 BIOS에 의해 dl값 설정

    ; dh는 head number
    mov dh, 0x00

    ; [es:bx] 는 데이터가 저장될 곳에 대한 포인터
    ; caller가 설정

    ; BIOS Interrupt
    int 0x13
    ; Error 
    ; Carry Bit에 저장되는 경우
    jc disk_error

    pop dx
    ; BIOS는 al에 읽은 sector 번호 저장
    ; 이 값을 처음에 읽을 개수로 저장한 dh와 비교
    cmp al, dh
    jne sectors_error
    popa 
    ret

disk_error:
    mov bx, DISK_ERROR
    call print
    call print_nl
    ; ah에 에러 코드 저장
    ; http://stanislavs.org/helppc/int_13-1.html 에 에러 코드
    mov dh, ah
    call print_hex
    jmp disk_loop

sectors_error:
    mov bx, SECTORS_ERROR
    call print

disk_loop:
    jmp $

DISK_ERROR:
    db 'Disk read error', 0x00
SECTORS_ERROR:
    db 'Incorrect number of sectors read', 0x00

boot_sect_disk_main.asm

boot_sect_disk_main.asm 에서는 bx 레지스터에 데이터를 부를 주소를 저장하고 dh 레지스터에 읽을 Sector의 개수를 저장한 다음 boot_sect_disk.asm에 넘겨준다. 다음으로 저장된 값을 메모리 주소를 참조해 출력한다.

[org 0x7c00]

mov bp, 0x8000
mov sp, bp

mov bx, 0x9000
; 2개의 sector 읽음
mov dh, 2
; BIOS가 dl에 boot disk number 불러옴
call disk_load

mov dx, [0x9000]
call print_hex
call print_nl

mov dx, [0x9000+512]
call print_hex

jmp $

%include 'boot_sect_print_function.asm'
%include 'boot_sect_print_function_hex.asm'
%include 'boot_sect_disk.asm'

times 510-($-$$) db 0
dw 0xaa55

; sector 2
times 256 dw 0xABBA
times 256 dw 0xFACE

실행 결과

위 코드를 실행 bin파일로 컴파일하고, qemu로 실행시키면 다음과 같은 화면이 나옵니다. Sector 2, 3에 데이터를 쓰고 잘 불러온 것을 확인할 수 있습니다.

실행 결과