2.3.2 사용 가능한 BIOS Interrupt Service Routines
| INT | Function |
| 5h | Print screen operation |
| 10h | Video display services |
| 11h | Equipment determination |
| 12h | Memory size determination |
| 13h | Diskette and hard disk services |
| 14h | Serial I/O Services |
| 15h | Miscellaneous services |
| 16h | Keyboard services |
| 17h | Printer services |
| 18h | BASIC |
| 19h | Reboot |
| 1Ah | Real 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 |
| Description | VIDEO 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 된 장비 목록을 반환 한다. 각 비트별 의미는 다음과 같다.
| Bit | Description |
| 0 | Floppy disk drive installed |
| 1 | Math coprocessor installed |
| 2,3 | System borad RAM installed(obsolete) |
| 4,5 | Install video mode 00 – none 01 – 40x25 color 10 – 80x25 color 11 – 80x25 b/w |
| 6,7 | Number of disk drives |
| 8 | DMA present |
| 9,10,11 | Number of RS-232 serial cards installed |
| 12 | Game I/O card installed |
| 13 | Serial printer attached |
| 14,15 | Number 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 레지스터를 사용하며, 시리얼 통신 포트를 제어한다.
| Function | Description |
| AH=0 | Serial Port Initialization
이 서비스 루틴은 baud rate 를 설정한다고 parity mode 와 stop bits, transmit bits 를 설정하는데 사용된다.
| Bits | Function |
| 5..7 | Select baud rate |
| 000 | 110 baud |
| 001 | 150 |
| 010 | 300 |
| 011 | 600 |
| 100 | 1200 |
| 101 | 2400 |
| 110 | 4800 |
| 111 | 9600 |
| 3..4 | Select parity |
| 00 | No parity |
| 01 | Odd parity |
| 10 | No parity |
| 11 | Even parity |
| 2 | Stop bits |
| 0 | One stop bit |
| 1 | Two stop bits |
| 0..1 | Character Size |
| 10 | 7 bits |
| 11 | 8 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 레지스터의 값으로 상태를 확인한다.
| AX | Bit Meaning |
| 15 | Time out error |
| 14 | Transmitter shift register empty |
| 13 | Transmitter holding register empty |
| 12 | Break detection error |
| 11 | Framing error |
| 10 | Parity error |
| 9 | Overrun error |
| 8 | Data available |
| 7 | Receive line signal detect |
| 6 | Ring indicator |
| 5 | Data set ready (DSR) |
| 4 | Clear to send (CTS) |
| 3 | Delta receive line signal detect |
| 2 | Trailing edge ring detector |
| 1 | Delta data set ready |
| 0 | Delta clear to send |
|
|
| Interrupt | INT 0x15 |
| Function | Miscellaneous Services |
| Description | 원래 이 서비스는 카세트 테이프를 읽거나 쓰는 역할을 했다. 하지만, 카세트가 더 이상 필요 없는 역사속의 이야기가 되어 버리자, IBM 은 int 15h 서비스를 다른 용도로 사용하기 시작했다. 요즘에는 확장메모리를 접근하거나 조이스틱/게임 어뎁터 카드등과 같은 다양한 장치를 제어하는 용도로 사용한다. |
| Interrupt | INT 0x16 |
| Function | Keyboard Services |
| Description | 키를 읽거나, 테스트를 하거나, 키보드 상태를 읽는다. 파라미터는 AL 을 사용한다.
| Function | Description |
| 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
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 이 서비스는 현재 키보드에 있는 상태 키 정보들을 반환한다.
| Bit | Meaning |
| 7 | Insert state (toggle by pressing INS key) |
| 6 | Caps lock (1 = on) |
| 5 | Num lock(1 = on) |
| 4 | Scroll lock(1 = on) |
| 3 | Alt (1 = down) |
| 2 | Ctrl (1 = down) |
| 1 | Left shift (1 = down) |
| 0 | Right shift (1 = down) |
이 서비스의 결과는 항상 서비스를 호출한 그 순간의 상태를 반환하는 것으로써, Type ahead buffer 에서 읽을 키 값과 관계 있는 상태를 반환하는 것이 아니다.
|
|
| Interrupt | INT 0x17 |
| Function | Printer Services |
| Description | 데이터를 프린트 하거나 프린터의 상태를 테스트 한다. AX, DX 레지스터를 사용한다. DX 레지스터는 LPT 포트를 선택할 때 사용한다.(0..2 : LPT1..3) DOS 를 사용하는 경우라면, LPT 출력을 COM 출력으로 전환이 가능하다.
| Function | Description |
| AH=0 | Print a character
AL 레지스터의 값을 프린트 한다. AL 레지스터의 값을 처리하는 방식은 독자가 사용하는 프린터 자체에서 어떻게 처리하는 가에 전적으로 의존한다. 하지만 대부분의 프린터는 출력 가능한 ASCII 코드를 지원하고, ASCII 코드 중에 제어 문자들에 대해서도 일반적인 동작을 수행한다. AH 레지스터에는 현재 상태가 저장된다.
|
| AH=1 | Initialize printer
AH 레지스터에 프린터의 상태 값을 가지고 함수가 반환된다.
|
| AH=2 | Return printer status
| AH | Bit meaning |
| 7 | 1=Printer busy, 0=Printer not busy |
| 6 | 1=Acknowledge from printer |
| 5 | 1=Out of paper signal |
| 4 | 1=Printer selected |
| 3 | 1=I/O Error |
| 2 | Not used |
| 1 | Not used |
| 0 | Time out error |
프린터 제어 관련 서비스에 대한 설명은 여기서 마무리 한다, 자세한 설명이 필요한 독자는 관련 BIOS 문서를 참조하기 바란다.
|
|
| Interrupt | INT 0x18 |
| Function | Run BASIC |
| Description | ROM BASIC 인터프리터를 실행 시킨다. 일반적인 PC 호환 컴퓨터 시스템에서는 ROM BASIC 이 없는 경우도 있다. 그런 경우, 이 서비스에 대한 동작을 정의할 수 없다. |
| Interrupt | INT 0x19 |
| Function | Reboot computer |
| Description | Soft reboot(CTRL+ALT+DEL) 와 같은 동작을 한다. |
| Interrupt | INT 0x1A |
| Function | Real time clock |
| Description | AX,CX,DX 레지스터를 인자 전달을 위해 사용한다.
| Function | Description |
| AH=0 | Read the real time clock
AL:CX:DX 레지스터를 통해 33-bit 값을 반환한다.
| Register | Value returned |
| DX | LO word of clock count |
| CX | HO word of clock count |
| AL | Zero if timer has not run for more than 24 hours Non-zero therwise. |
CX:DX 레지스트에 저장된 32-bit 값은 55 ms(milli-sec) 단위 주기의 숫자를 나타낸다.
|
| AH=1 | Setting 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
현재 커서가 위치한 곳부터 인자로 전달 받은 문자열 포인터 값을 이용해 화면에 문자열을 출력한다.
| Function | print(str) |
| Brief | 주어진 문자열을 화면에 출력한다. |
| Param | [in] str - 출력할 문자열 |
| Return | void |
| 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
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]
je df_attr_unused
cmp al, FREE_NODE
je df_free_node
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
df_attr_normal:
mov dx, 1
mov cx, 8
call load_to_buffer
mov cx, 3
call load_to_buffer
add si, 21
df_attr_unused:
df_deleted:
add si, 32
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
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
gnc_cluster_env: ; 0x7db3
and ax, 0x0fff
gnc_exit:
mov [bp + 6], ax
popa
pop bp
ret
3.4 정리
지금까지 부트로더 구성 요소들에 대해 하나씩 살펴 보았다. 부트로더 이미지가 준비되었다면, 부트로더를 부트섹터에 쓰면 된다. 리눅스에서는 dd 명령을 이용할 수 있고, Windows (DOS) 에서는 biosdisk 라는 함수를 사용할 수 있다.
부트로더 개발은 다음과 같은 순서로 정리될 수 있다.
-
메모리 맵 파악
-
사용할 파일 시스템 결정
-
파일 시스템이 부트섹터와 관련이 있는 경우, 부트 섹터에 저장되는 정보 고려
-
파일 시스템에서 커널 이미지 찾는 방법
-
이미지를 메모리에 적재
-
해당 영역으로 jmp 하여 실행
아주 간단해 보이지만, 처음 개발하는 경우에는 사소한 실수 하나로 많은 고생을 할 수 도 있다. bochs 를 이용하면 debugging 에 많은 도움을 받을 수 있다.