ncloader  0.1
 모두 데이타 구조 파일들 함수 변수 타입정의 열거형 타입 열거형 멤버 매크로 그룹들 페이지들
Utility functions
+ Utility functions에 대한 협력 다이어그램:

2.3.2 사용 가능한 BIOS Interrupt Service Routines

INTFunction
5hPrint screen operation
10hVideo display services
11hEquipment determination
12hMemory size determination
13hDiskette and hard disk services
14hSerial I/O Services
15hMiscellaneous services
16hKeyboard services
17hPrinter services
18hBASIC
19hReboot
1AhReal time clock services

위 서비스들은 80x86 의 register들을 통해 파라미터를 전달받는다. 그 외 추가적인 파라미터는 특정 메모리 영역을 통해 전달 받기도 한다.

Interrupt INT 0x5
Function Print Screen
Description텍스트 화면을 출력한다. 이 서비스를 실행하면, 스크린 이미지를 프린터로 전달한다. 키보드에 있는 Print Screen 키를 누르는 것과 똑 같은 기능을 한다. 실제로 키보의 Print screen 키를 누르면, BIOS 는 int 5 명령을 실행한다. 그러므로 두 방식은 실제로 똑 같은 것이다. 80286 같은 경우에는 int 5 를 BOUNDS trap 으로 사용한다.
Interrupt INT 0x10
Function Video Services
DescriptionVIDEO I/O 서비스를 제공하는 이 인터럽트는 ax, bx, cx, dx 와 es:bp 레지스터를 사용해 파라미터를 전달받는다. 비디오 디스플레이를 초기화 하거나, 현재 커서 크기 및 위치를 지정, 현재 커서 위치를 읽는 기능을 제공한다. 이외에도 다양한 가닝을 제공한다. 일반적으로 가장 많이 사용되는 함수는 문자를 출력하는 것으로 사용되는 파라미터는 AH=0Eh, AL=AScII code 이다. 이 루틴은 단일 문자를 화면에 출력한다. MS-DOS 도 이 루틴을 이용해 화면에 글자를 출력한다. 사실 이 서비스 루틴은 매우 비 효율적으로 제작된 경우가 대부분이기 때문에, 실제 개발자들은 직접 개발해서 사용하는 경우가 대부분이다. 만약 DOS 를 사용하는 독자라면, ANSI.SYS 드라이버를 사용하는 것도 좋은 방법이다. (요즘도 DOS 를 쓰는 독자가 있을지는 모르겠지만… Linux 에서 개발하는 것이 목표인 이 책에서는 ANSI.SYS 드라이버를 사용하는 일은 없을 것이다.)
Interrupt INT 0x11
Function Equipment Installed
Description

장비 목록을 얻어올 때 쓰인다. int 11h 서비스 호출이 끝나고 나면, AX 레지스터에 bit-encoded 된 장비 목록을 반환 한다. 각 비트별 의미는 다음과 같다.

BitDescription
0Floppy disk drive installed
1Math coprocessor installed
2,3System borad RAM installed(obsolete)
4,5Install video mode 00 – none 01 – 40x25 color 10 – 80x25 color 11 – 80x25 b/w
6,7Number of disk drives
8DMA present
9,10,11Number of RS-232 serial cards installed
12Game I/O card installed
13Serial printer attached
14,15Number of printers attached

이 목록은 오래된 것으로 요즘에는 거의 의미가 없다.

Interrupt INT 0x12
Function Memory available
Description메모리 크기를 AX 레지스터를 통해 반환한다. motherboard 에 64 KB 메모리가 설치된 예전 IBM PC 에서나 사용되던 서비스이다. 하지만 요즘 PC 에서는 64 MB 이상 가질 수 있기 때문에, 이 서비스 또한 큰 의미가 없다.
Interrupt INT 0x13
Function Low level disk services
Description앞에서 잠깐 소개했던 Low level disk services 목록이다.
Interrupt INT 0x14
Function Serial I/O
Description

AX,DX 레지스터를 사용하며, 시리얼 통신 포트를 제어한다.

FunctionDescription
AH=0

Serial Port Initialization

이 서비스 루틴은 baud rate 를 설정한다고 parity mode 와 stop bits, transmit bits 를 설정하는데 사용된다.

BitsFunction
5..7Select baud rate
000110 baud
001150
010300
011600
1001200
1012400
1104800
1119600
3..4Select parity
00No parity
01Odd parity
10No parity
11Even parity
2Stop bits
0One stop bit
1Two stop bits
0..1Character Size
107 bits
118 bits

최대 19,200 baud rate 를 지원하는 Serial port 하드웨어를 지원하는 PC 가 있지만, 몇몇 BIOS 는 이 속도를 지원하지 못하는 경우도 있다. 아래는 Serial port 를 초기화 하는 예제 코드이다.

mov ah, 0 ; initialize opcode
mov al, 10100111b ; parameter data
mov dx, 0 ; COM1: port
int 14h

인터럽트 호출 이후, AX 레지스터에 시리얼 포트의 상태 정보가 반환된다. AH=3 서비스 루틴을 이용해 확인할 수 있다.

AH=1

Transmit a Character to the serial port DX 레지스터로 선택한 시리얼 포트를 통해 AL 레지스에 있는 문자를 전송한다. 호출 한 후, AH 레지스터 값이 0 이면 문제 없이 전송된 것이고, AH 의 7 번 비트가 1 이면 오류가 발생한 것이다. 남은 7개의 비트로 오류 정보를 확인할 수 있다.

mov dx, 0 ; select COM1
mov al, ‘a’
mov ah, 1
int 14h
test ah, 80h
AH=2

Receive a character from the serial port

mov dx, 0 ; select COM1
mov ah, 2 ; Receive opcode
int 14h
test ah, 80h
jnz SerialError
; Received character is now in AL:
AH=3

Serial Port status

Seral port 의 상태 정보를 얻어 온다. DX 레지스터로 시리얼 포트를 선택하고, AX 레지스터의 값으로 상태를 확인한다.

AXBit Meaning
15Time out error
14Transmitter shift register empty
13Transmitter holding register empty
12Break detection error
11Framing error
10Parity error
9Overrun error
8Data available
7Receive line signal detect
6Ring indicator
5Data set ready (DSR)
4Clear to send (CTS)
3Delta receive line signal detect
2Trailing edge ring detector
1Delta data set ready
0Delta clear to send

Interrupt INT 0x15
Function Miscellaneous Services
Description원래 이 서비스는 카세트 테이프를 읽거나 쓰는 역할을 했다. 하지만, 카세트가 더 이상 필요 없는 역사속의 이야기가 되어 버리자, IBM 은 int 15h 서비스를 다른 용도로 사용하기 시작했다. 요즘에는 확장메모리를 접근하거나 조이스틱/게임 어뎁터 카드등과 같은 다양한 장치를 제어하는 용도로 사용한다.
Interrupt INT 0x16
Function Keyboard Services
Description

키를 읽거나, 테스트를 하거나, 키보드 상태를 읽는다. 파라미터는 AL 을 사용한다.

FunctionDescription
AH=0

Read a key from the keyboard System type ahead buffer 에 키 값이 저장 될 때까지 Blocking 형태로 동작한다. 반환값은 AL 에 버퍼로부터 읽은 키에 대한 아스키 코드 값이 저장되고, AH 에는 키보드 스캔 코드가 저장된다. 아스키 코드로 표현할 수 없는 키코드 값은 AL 에 0 이 반환되고, AH 의 스캔코드를 읽어야 한다. 16h 서비스를 사용하는 경우, 입력되는 키는 화면에 출력되지 않는다.(Echo off) 화면에 출력하고 싶다면(echo on) putc 함수를 사용하거나, int 10h 를 사용해야 한다.

ReadLoop:
mov ah, 0 ; Read key opcode
int 16h
cmp al, 0 ; Special function?
jz ReadLoop ; If so, don’t echo this keystroke
cmp al, 0dh ; Carriage return (ENTER) ?
jne ReadLoop
AH=1

See if a key is available at the keyboard AH=0 번 서비스와는 달리 Non-blocking 형태로 동작하는 것으로, System type ahead buffer 에 키가 입력되지 않더라도 Block 되지 않고 바로 호출한 함수로 제어권을 돌려 준다. 전달하는 파라미터는 없고, 결과 값으로 ZF(Zero flag) 를 확인하면 된다. ZF 가 1 인 경우, 데이터가 없는 것이고, ZF 가 0 인 경우, 데이터가 있는 것이다. 만약 버퍼에 키가 있다면 (ZF=0), AX 레지스터에 아스키 코드 값과 스캔 코드 값을 반환한다. 하지만, 이 함수는 버퍼에서 키 값을 삭제 하지 않는다. 키 값을 버퍼에서 삭제하기 위해서는 AH=0 번 서비스를 사용해야 한다. 다음 예제는 test keyboard 함수를 이용해 난수를 생성한다.

; First, clear any characters out of the type ahread buffer
ClrBuffer:
mov ah, 1 ; Is a key available
int 16h
jz BufferIsClr ; If not, Discontinue flushing
mov ah, 0 ; Flush this character from the buffer and try again
int 16h
jmp ClrBuffer
BufferIsClr:
mov cx, 0 ; Initialize “random” number.
GenRandom:
inc cx
mov ah, 1 ; See if a key is available yet.
int 16h
jz GenRandom
xor cl, ch ; Randomize event more.
mov ah, 0 ; Read character from buffer
int 16h
; Random number is now in CL, key pressed by user is in AX
AH=2

Return keyboard shift key status 이 서비스는 현재 키보드에 있는 상태 키 정보들을 반환한다.

BitMeaning
7Insert state (toggle by pressing INS key)
6Caps lock (1 = on)
5Num lock(1 = on)
4Scroll lock(1 = on)
3Alt (1 = down)
2Ctrl (1 = down)
1Left shift (1 = down)
0Right shift (1 = down)

이 서비스의 결과는 항상 서비스를 호출한 그 순간의 상태를 반환하는 것으로써, Type ahead buffer 에서 읽을 키 값과 관계 있는 상태를 반환하는 것이 아니다.

Interrupt INT 0x17
Function Printer Services
Description

데이터를 프린트 하거나 프린터의 상태를 테스트 한다. AX, DX 레지스터를 사용한다. DX 레지스터는 LPT 포트를 선택할 때 사용한다.(0..2 : LPT1..3) DOS 를 사용하는 경우라면, LPT 출력을 COM 출력으로 전환이 가능하다.

FunctionDescription
AH=0

Print a character

AL 레지스터의 값을 프린트 한다. AL 레지스터의 값을 처리하는 방식은 독자가 사용하는 프린터 자체에서 어떻게 처리하는 가에 전적으로 의존한다. 하지만 대부분의 프린터는 출력 가능한 ASCII 코드를 지원하고, ASCII 코드 중에 제어 문자들에 대해서도 일반적인 동작을 수행한다. AH 레지스터에는 현재 상태가 저장된다.

AH=1

Initialize printer

AH 레지스터에 프린터의 상태 값을 가지고 함수가 반환된다.

AH=2

Return printer status

AHBit meaning
71=Printer busy, 0=Printer not busy
61=Acknowledge from printer
51=Out of paper signal
41=Printer selected
31=I/O Error
2Not used
1Not used
0Time out error

프린터 제어 관련 서비스에 대한 설명은 여기서 마무리 한다, 자세한 설명이 필요한 독자는 관련 BIOS 문서를 참조하기 바란다.

Interrupt INT 0x18
Function Run BASIC
DescriptionROM BASIC 인터프리터를 실행 시킨다. 일반적인 PC 호환 컴퓨터 시스템에서는 ROM BASIC 이 없는 경우도 있다. 그런 경우, 이 서비스에 대한 동작을 정의할 수 없다.
Interrupt INT 0x19
Function Reboot computer
DescriptionSoft reboot(CTRL+ALT+DEL) 와 같은 동작을 한다.
Interrupt INT 0x1A
Function Real time clock
Description

AX,CX,DX 레지스터를 인자 전달을 위해 사용한다.

FunctionDescription
AH=0

Read the real time clock

AL:CX:DX 레지스터를 통해 33-bit 값을 반환한다.

RegisterValue returned
DXLO word of clock count
CXHO word of clock count
ALZero if timer has not run for more than 24 hours Non-zero therwise.

CX:DX 레지스트에 저장된 32-bit 값은 55 ms(milli-sec) 단위 주기의 숫자를 나타낸다.

AH=1Setting the real time clock 현재 시스템 시간을 설정한다. CX 는 H.O word 를 DX 는 L.O word 를 표시한다. 이상으로 몇 가지 BIOS 서비스 루틴들에 대해 알아 보았다. 실질적으로 Boot loader 를 만드는 것과 같은 Real mode program 을 O/S 없이 작성할 때에는 BIOS 서비스 루틴을 사용하기도 한다.

3.3.3 Utility functions

앞서 설계한 함수 처리를 위한 스택 관리와 바로 앞 절에서 설명한 다양한 BIOS Services 들을 이용해 Boot loader 에서 필요한 몇 가지 간단한 함수들을 만든다.

1) Print

현재 커서가 위치한 곳부터 인자로 전달 받은 문자열 포인터 값을 이용해 화면에 문자열을 출력한다.

Functionprint(str)
Brief주어진 문자열을 화면에 출력한다.
Param[in] str - 출력할 문자열
Returnvoid
Code
print:
push bp
mov bp, sp
pusha
mov si, [bp + 4] ; si: address of a string
cld ; Direction forward
print_loop:
lodsb
cmp al, 0
je print_done
mov ah, 0x0E
mov bx, 0x0007
int 0x10
jmp print_loop
print_done:
popa
pop bp
ret

코드 print 는 SI 에 지정된 메모리 주소에서 NULL(0) 을 만날 때까지 출력한다. 위에서 사용된 10 번 Interrupt 는 다음 기능들을 가진다.

2) Read filename

FAT12 형식으로 포맷된 디스크에서 파일 이름을 읽어 내는 함수를 작성한다. 디스크에 기록된 Root directory 의 모든 entry 가 메모리로 적재된 이 후, 메모리를 탐색하면서 파일 이름을 읽어 들인다.

; si : start address of entry
; di : buffer to save found filename
read_filename:
push ax
push cx
push dx
df_start_loop:
mov al, [si]
cmp al, DELETE_FLAG
je df_deleted
mov al, [si+11]
cmp al, ATTR_UNUSED
je df_attr_unused
cmp al, FREE_NODE
je df_free_node
cmp al, ATTR_EXT
jne df_attr_normal
df_attr_ext:
mov al, [si]
and al, 0x40
cmp al, 0x40
jne df_slot_loop_exit
mov al, [si]
sub al, 0x40
movzx cx, al
mov dl, [si + 13]
push cx
dec cx
df_find_last_slot:
add si, 32
loop df_find_lasst_slot
pop cx
push si
df_slot_read:
push dx
push cx
inc si
mov dx, 2
mov cx, 5
call load_to_buffer
add si, 3
mov cx, 6
call load_to_buffer
add si, 3
mov cx, 6
call load_to_buffer
add si, 2
mov cx, 2
call load_to_buffer
pop cx
pop dx
sub si, 64
cmp dl, [si+13]
jne df_slot_loop_exit_pop
loop df_slot_read
df_slot_loop_exit_pop:
pop si
add si, 32
df_slot_loop_exit:
add si, 32
jmp df_exit
df_attr_normal:
mov dx, 1
mov cx, 8
call load_to_buffer
mov cx, 3
call load_to_buffer
add si, 21
jmp df_exit
df_attr_unused:
df_deleted:
add si, 32
jmp df_exit
df_free_node:
df_exit
mov BYTE[di], 0x0
pop dx
pop cx
pop ax
ret

안타깝지만 이 함수는 부트로더에서 끝내 사용할 수 없었다. 이 함수는 FAT 에 기록된 긴 파일 이름을 읽어 오기 위해 사용한 것인데, 부트 섹터의 크기가 제한 적이기 때문에 미쳐 함께 사용할 수 가 없었다. 아쉽지만, 또 힘들게 구현했지만, 구현한 것만으로 만족해야 했다. 물론, 이 후 필요한 경우 재활용 할 수 있을 것이다.

다만, 이 함수는 위에서 말한 스택 구조를 사용하지 않는다. 스택을 사용하지 않았을 때, 구현된 함수이기 때문이다.

3) strcmp

사실 이 함수는 strncmp 라고 명명해야 올바를 것이다. 가장 처음 push 되는 인자는 비교 문자열의 최대 개수, 두번째 push 되는 인자는 source string, 세번째 push 되는 인자는 target string 이다.

비교 결과는 가장 처음 push 된 stack entry 에 저장한다. 즉 함수 처리가 종료된 후에 pop 을 두번 한 후 세번째 pop 에서 받아 들이거나, sp 에 4 를 더한 후 pop 을 해서 읽어 오면 된다.

push bp
mov bp, sp
pusha
mov cx, [bp+8]
mov si, [bp+6]
mov di, [bp+4]
mov ah, 0
cld
strcmp_loop:
lodsb ; ds:si -> al
cmp al, [di]
jne strcmp_loop_diff
cmp al, 0
je strcmp_loop_exit
inc di
loop strcmp_loop
mov al, 0
jmp strcmp_loop_exit
strcmp_loop_diff:
mov al, 1
strcmp_loop_exit:
mov [bp+8], ax
popa
pop bp
ret

4) load_to_buffer

si 가 가리키는 곳의 값을 di 가 가리키는 곳으로 옮긴다. 이 때, dx 값을 참조하여 다음 읽을 글자의 offset 을 계산한다. cx 에 기록된 횟수 만큼 옮긴다.

; di: address of buffer
; si: address of source
; cx: count
; dx: incremental value
load_to_buffer:
cld
ltb_loop:
mov al, [si]
stosb
add si, dx
loop ltb_loop
ret

이 함수 또한 read_filename 과 함께 사용되지 않는다. 유니코드 문자에서 알파뱃 긴 파일 이름인 경우, 짝수 바이트에 위치한 영문을 읽어 일반 문자열로 만들면서 메모리에서 메모리로 복사하는 함수 였다. 위와 마찬 가지고 인자 전달이 스택을 통해 일어나지 않는다.

6) get_next_cluster

처음 push : 클러스터 번호 두번째 push : FAT 테이블이 시작되는 메모리 주소

주어진 클러스터 번호를 이용해 다음 클러스터 번호를 구한다. 이 때, FAT 테이블을 참조하므로 인자로 FAT 테이블의 시작 주소도 함께 전달해 주어야 한다.

get_next_cluster:
push bp
mov bp, sp
pusha
mov ax, [bp + 6] ; first argument: idx of cluster(3)
xor cx, cx ; default : even, so add 0
clc
rcr ax, 1 ; / 2
inc gnc_even ; even
inc cx ; ODD, add 1
gnc_even:
mov bl, 3
mul bl ; *3
add ax, cx ; Even or Odd
mov bx, ax ; save ax
gnc_read_entry: ; Read FAT entry
move s, [bp+4] ; second argument: next address of FAT
mov ax, [es:bx] ; read 2 bytes from es(FAT base)+bx(offset)
and cx, 0x01
jz gnc_cluster_even
shr ax, 4
jmp gnc_exit
gnc_cluster_env: ; 0x7db3
and ax, 0x0fff
gnc_exit:
mov [bp + 6], ax
popa
pop bp
ret

3.4 정리

지금까지 부트로더 구성 요소들에 대해 하나씩 살펴 보았다. 부트로더 이미지가 준비되었다면, 부트로더를 부트섹터에 쓰면 된다. 리눅스에서는 dd 명령을 이용할 수 있고, Windows (DOS) 에서는 biosdisk 라는 함수를 사용할 수 있다.

부트로더 개발은 다음과 같은 순서로 정리될 수 있다.

아주 간단해 보이지만, 처음 개발하는 경우에는 사소한 실수 하나로 많은 고생을 할 수 도 있다. bochs 를 이용하면 debugging 에 많은 도움을 받을 수 있다.