Open KIMBIBLE opened 7 years ago
이전에 제작한 IPL은 실제로 프로그램을 로드하지 않았음. 해당 장에서는 실제로 프로그램을 로드하는 부분까지 진행 할 예정.
helloos5 까지의 ipl과 bbkimos_00a 부터의 ipl의 차이
디스크 읽기
; 디스크를 읽는다
MOV AX,0x0820 MOV ES,AX MOV CH, 0 ; 실린더 0 MOV DH, 0 ; 헤드 0 MOV CL, 2 ; 섹터 2 MOV AH, 0x02 ; AH=0x02 : 디스크 read MOV AL, 1 ; 1섹터 MOV BX,0 MOV DL, 0x00 ; A드라이브 INT 0x13 ; 디스크 BIOS 호출 JC error
에러 처리
error:
MOV AX,0 MOV ES,AX MOV SI,msg
msg:
DB 0x0a, 0x0a ; 개행을 2개 DB "load error" DB 0x0a ; 개행 DB 0 RESB 0x7dfe-$ ; 0x7dfe까지를 0x00로 채우는 명령 DB 0x55, 0xaa
배치 파일을 알맞게 수정하고 make run
을 입력하여 실행하게 되면
플로피 디스크에서 이미지를 읽어올 때 오류가 발생하기 않았기 때문에
기본 QEMU 출력 문자를 제외한 아무런 문자 출력이 없는 화면 상태로
정상적으로 동작하는 것을 확인할 수 있다.
바이오스 인터럽트 호출(영어: BIOS interrupt call)은 도스용 프로그램과, 부트 로더와 같은 일부 기타 소프트웨어가 IBM PC 호환기종의 컴퓨터에 위치한 바이오스의 기능을 불러내는데 이용하는 방식이다. 일부 운영 체제 또한 초기 시동 단계에서 바이오스를 이용하여 하드웨어 자원을 탐지하고 초기화할 수 있다.
AH 설명 00h >>> 디스크 드라이브 초기화 01h >>> 드라이브 상태 검사 02h >>> 섹터 읽기 03h >>> 섹터 쓰기 04h >>> 섹터 유효 여부 확인 05h >>> 트랙 포맷 08h >>> 드라이브 변수 가져오기 09h >>> 고정 드라이브 변수 초기화 0Ch >>> 지정된 트랙으로 찾기 0Dh >>> 고정 디스크 컨트롤러 초기화 15h >>> 드라이브 종류 가져오기 16h >>> 플로피 드라이브 미디어 변경 상태 가져오기 17h >>> 디스크 종류 설정 18h >>> 플로피 드라이브 미디어 종류 설정 41h >>> 확장 디스크 드라이브 (EDO) 설치 검사 42h >>> 섹터 확장 읽기 43h >>> 섹터 확장 쓰기 44h >>> 섹터 확장 유효 여부 확인 45h >>> 드라이브 잠금/잠금 해제 46h >>> 미디어 꺼내기 47h >>> 확장 찾기 48h >>> 드라이브 변수 확장 가져오기 49h >>> 미디어 변경 상태 확장 가져오기 4Eh >>> 하드웨어 구성 확장 설정
; 디스크를 읽는다
MOV AX,0x0820 MOV ES,AX MOV CH, 0 ; 실린더 0 MOV DH, 0 ; 헤드 0 MOV CL, 2 ; 섹터 2 MOV AH, 0x02 ; AH=0x02 : 디스크 read MOV AL, 1 ; 1섹터 MOV BX,0 MOV DL, 0x00 ; A드라이브 INT 0x13 ; 디스크 BIOS 호출 JC error
MOV AX,0x0820 MOV ES,AX MOV BX,0
위 코드에 따르면
ES : 0x0820, BX: 0x00
이므로 0x8200 + 0x00인 0x8200이 디스크에서 읽은 데이터를 저장할 메모리의 위치가 된다.0x8000이후의 메모리를 사용한 이유는 AT 메모리 맵에 의하면 사용하지 않고 있는 메모리 영역이기 때문,
MOV CH, 0 ; 실린더 0 MOV DH, 0 ; 헤드 0 MOV CL, 2 ; 섹터 2 (MBR 1번 섹터 이후의 섹터)
실린더 번호 : 디스크 바깥(외부)부터 세어 몇 번째 트랙인지. (한면에 0 ~ 79 : 80개) 섹터 번호 : 트랙 안의 몇 번째 블록인가. (각 실린더 내에 1 ~ 18번 : 18개) 헤드 번호 : 헤드0(위), 헤드(아래)
MOV AH, 0x02 ; AH=0x02 : 디스크 read MOV AL, 1 ; 1섹터 MOV BX,0 MOV DL, 0x00 ; A드라이브
AH레지스터에 0x02를 저장해 디스크 읽기 방식을 설정하고 읽을 디스크의 단위를 1섹터로 설정한 후 (BX레지스터는 버퍼 어드레스로 위에 설명) DL레지스터에 드라이버 번호를 설정(드라이브를 1개만 사용중이므로 0x00 설정)
INT 0x13 ; 디스크 BIOS 호출 JC error
위에서 설정한 레지스터를 토대로 인터럽트 INT 0x13을 호출해 디스크 BIOS를 호출한다. 디스크 READ 에러 발생 시 CF 레지스터에 1이 저장된다. INT 13 호출 결과 CF 레지스터의 값은 아래의 값이 리턴된다. 에러 발생 시 : 1 정상 동작 시 : 0
INT 0x13 호출 결과 CF 레지스터에 리턴되는 에러 코드를 통해 정상 동작 시 JC error 이후 부분을 실행하고 에러 발생 시 error 레이블로 점프한다.
Parameters: AH >>> 02h AL >>> Sectors To Read Count CH >>> Cylinder CL >>> Sector DH >>> Head DL >>> Drive ES:BX >>> Buffer Address Pointer
Results: CF >>> Set On Error, Clear If No Error AH >>> Return Code AL >>> Actual Sectors Read Count
실린더 번호 : 디스크 바깥(외부)부터 세어 몇 번째 트랙인지. (한면에 0 ~ 79 : 80개) 섹터 번호 : 트랙 안의 몇 번째 블록인가. (각 실린더 내에 1 ~ 18번 : 18개) 헤드 번호 : 헤드0(위), 헤드(아래)
Linux에서는 fdisk -l 옵션을 이용하여 실린더 번호 정보 확인 가능
CHS -> LBA
LBA = ( ( CYLINDER heads per cylinder + HEAD ) sectors per track ) + SECTOR – 1
LBA -> CHS
CYLINDER = LBA / ( heads per cylinder * sectors per track ) HEAD = ( LBA / sectors per track ) % heads per cylinder SECTOR = ( LBA % sectors per track ) + 1
; 디스크를 읽는다 MOV AX,0x0820 MOV ES,AX MOV CH, 0 ; 실린더 0 MOV DH, 0 ; 헤드 0 MOV CL, 2 ; 섹터 2 MOV SI, 0 ; 실패 회수를 세는 레지스터 retry: MOV AH, 0x02 ; AH=0x02 : 디스크 read MOV AL, 1 ; 1섹터 MOV BX,0 MOV DL, 0x00 ; A드라이브 INT 0x13 ; 디스크 BIOS 호출 JNC fin ; 에러가 일어나지 않으면 fin에 ADD SI, 1 ; SI에 1을 더한다 CMP SI, 5 ; SI와 5를 비교 JAE error ; SI >= 5이면 error에 MOV AH,0x00 MOV DL, 0x00 ; A드라이브 INT 0x13 ; 드라이브의 리셋트 JMP retry
실패 횟수 카운트
MOV SI, 0 ; 실패 회수를 세는 레지스터
SI 레지스터를 0으로 초기화하여 실패 횟수를 카운트합니다. 에러 발생 시 5회 반복을 하기 위해 이후 CMP SI, 5 명령을 통해 실패 횟수를 체크합니다.
retry 레이블
retry: retry 레이블을 정의해 조건에 만족하지 않을 경우(에러 발생 && 에러 발생 횟수<5) 마지막의
JMP retry
를 통해 retry 레이블로 점프해 다시 명령을 수행하도록 합니다.
retry 레이블에서 조건을 통해 반복을 제어하는 부분은 아래와 같습니다.
JNC fin ; 에러가 일어나지 않으면 fin에 ADD SI, 1 ; SI에 1을 더한다 CMP SI, 5 ; SI와 5를 비교 JAE error ; SI >= 5이면 error에
위 명령을 통해 에러가 5회 미만 발생할 경우 다시 retry 레이블로 점프하여 다시 반복을 수행하고 반복 결과 에러가 5회 이상 발생할 경우 error 레이블로 점프, 정상 수행 시 fin레이블로 점프 동작을 하게 됩니다.
MOV AH,0x00 MOV DL, 0x00 ; A드라이브 INT 0x13 ; 드라이브의 리셋트
위와 같이 AH 레지스터와 DL 레지스터를 0으로 초기화하여 INT 0x13 인터럽트를 호출하면 드라이브가 리셋되어 처음부터 다시 시작할 수 있습니다. (AT-BIOS에서 시스템 리셋)
retry: MOV AH, 0x02 ; AH=0x02 : 디스크 read MOV AL, 1 ; 1섹터 MOV BX,0 MOV DL, 0x00 ; A드라이브 INT 0x13 ; 디스크 BIOS 호출 JNC next ; 에러가 일어나지 않으면 next에 ADD SI, 1 ; SI에 1을 더한다 CMP SI, 5 ; SI와 5를 비교 JAE error ; SI >= 5 이면 error에 MOV AH,0x00 MOV DL, 0x00 ; A드라이브 INT 0x13 ; 드라이브의 리셋트 JMP retry next:
MOV AX, ES ; 주소를 0x200 진행한다 ADD AX,0x0020 MOV ES, AX ; ADD ES, 0x020라고 하는 명령이 없기 때문에 이렇게 하고 있다 ADD CL, 1 ; CL에 1을 더한다 CMP CL, 18 ; CL와 18을 비교 JBE readloop ; CL <= 18 이라면 readloop에
; 다 읽었지만 우선 할일이 없기 때문에 sleeve
디스크의 한 섹터는 512 Byte이기 때문에 한 섹터를 메모리 상에 매핑시키게 되면 메모리의 512바이트를 사용하여야한다. 여기서 십진수 512바이트는 0x200을 뜻하며, 읽은 디스크 데이터만큼(섹터) 메모리 주소의 위치를 증가시키기 위해 ES 세그먼트 레지스터에 0x20을 더해 ES(0x20) * 0x10(1자리 증가) + BX(0)인 0x200이 되도록 ES 레지스터를 증가시킨다.
디스크를 읽을 때 섹터번호에 해당하는 CL레지스터의 값 또한 증가시켜 CL이 18이 될 때(CL : 0~17까지 동작, 18회) 읽은 섹터가 18섹터가 되므로 디스크 읽기를 멈추게 된다.
AL 레지스터에 저장될 수 있는 값(0x00제외)인 0x01 ~0xff 이 한번에 읽을 수 있는 디스크의 범위가 된다.
C0-H0-S18 -> C0-H1-S1
아래는 이와 같은 동작을 수행하도록 코드를 변경한 부분이다.
CYLS EQU 10 ; 어디까지 Read할까
ORG 0x7c00 ; 이 프로그램이 어디에 Read되는가
먼저 EQU 명령을 통해 literal 상수 10를 CYLS라는 이름으로 사용할 것을 명시한다.
next: MOV AX, ES ; 주소를 0x200 진행한다 ADD AX,0x0020 MOV ES, AX ; ADD ES, 0x020 라고 하는 명령이 없기 때문에 이렇게 하고 있다 ADD CL, 1 ; CL에 1을 더한다 CMP CL, 18 ; CL와 18을 비교 JBE readloop ; CL <= 18 이라면 readloop에 MOV CL,1 ADD DH,1 CMP DH,2 JB readloop ; DH < 2 라면 readloop에 MOV DH,0 ADD CH,1 CMP CH,CYLS JB readloop ; CH < CYLS 라면 readloop에 ; 다 읽었지만 우선 할일이 없기 때문에 sleeve
Unsigned 계열(부호가 없는 값)
je: jump equal - 비교 결과가 같을 때 점프 jne: jump not equal - 비교 결과가 다를 때 점프 jz: jump zero - 결과가 0일 때 점프, je와 같음(cmp 명령에서 결과가 같으면 0을 출력합니다). jnz: jump not zero - 결과가 0이 아닐 때 점프
ja: jump above - cmp a, b에서 a가 클 때 점프 jae: jump above or equal - 크거나 같을 때 점프 jna: jump not above - 크지 않을 때 점프 jnae: jump not above or equal - 크지 않거나 같지 않을 때 점프
jb: jump below - cmp a, b에서 a가 작을 때 점프 jbe: jump below or equal - 작거나 같을 때 점프 jnb: jump not below - 작지 않을 때 점프 jnbe: jump not below or equal - 작지 않거나 같지 않을 때 점프
jc: jump carry - 캐리 플래그가 1일 때 점프 jnc: jump not carry - 캐리 플래그가 0일 때 점프
jnp/jpo: jump not parity / parity odd - 패리티 플래그가 0일 때 / 홀수일 때 점프 jp/jpe: jump parity / parity even - 패리티 플래그가 1일 때 / 짝수일 때 점프
jecxz: jump ecx zero - ecx 레지스터가 0일때 점프
Signed 계열(부호가 있는 값)
jg: jump greater - cmp a, b에서 a가 클 때 점프 jge: jump greater or equal - 크거나 같을 때 점프 jng: jump not greater - 크지 않을 때 점프 jnge: jump not greater or equal - 크지 않거나 같지 않을 때 점프
jl: jump less - cmp a, b에서 a가 작을 때 점프 jle: jump less or equal - 작거나 같을 때 점프 jnl: jump not less - 작지 않을 때 점프 jnle: jump not less or equal - 작지 않거나 같지 않을 때 점프
jo/jno: jump overflow / not overflow - 오버플로 플래그가 1일 때 / 0일 때 점프 js/jns: jump sign / not sign - 사인(부호) 플래그가 1일 때(음수) / 0일 때(양수) 점프
위의 조건 점프 명령을 결합해 다양한 조건문을 구현할 수 있다.
http://pyrasis.com/blog/entry/ReverseEngineeringForNewbieAssemblyOperation
초반 OS 제작 과정 자체에서 많은부분 Makefile을 활용한 툴 사용이 중점적으로 이루어져 제작 과정에 사용하는 툴들을 관리하기 위한 디렉토리 z_tools 추가를 통해 Makefile에서 툴 경로 설정 용이하게 만듬
(아니면 Makefile 수정해야 할 부분이 너무 많아짐)