지난 포스트에 올린 부트로더가 동작을 안 한다. 왜 안되는지 모르겠다. 디버깅을 해봐도 뜻대로 동작하지 않았다. 어셈블리어 생각대로 코딩이 안 된다. 이쯤되면 하기 싫어진다…흐……….

여튼 시작은 했으니, 부트로더 만이라도 빨리 끝내야 겠다는 성급함이 기초 공부를 게을리 하게 된것 같다. 원인이 무엇인가 부터 찾아 봤다.

우선, 의심가는 부분이 헤드,트랙 계산 부분이다.


	; 헤드, 트랙, 섹터로 재 계산해서 저장하자.
	; 트랙 = 총섹터 / (헤드 갯수 * 트랙당 섹터수)
	; 헤드 = (총섹터/트랙 당 섹터수) % 헤드 갯수
	; 섹터 = (총섹터 % 트랙 당 섹터) + 1
	; EAX 는 총섹터 값이 있다.
CONVERT_CHS:
	xor dx, dx	; dx를 0으로 초기화 하자.
	mov cx, [BPB_SECTOR_PER_TRACK]
	div cx	; AX / CX = AX , AX % CX = DX

	inc dl	; 나머지 + 1 = 섹터다.
	mov [SECTOR], dl

	xor dx, dx	; dx를 0으로 초기화 하자. ax는 총섹터/트랙 당 섹터수 값이 있다.
	mov cx, [BPB_HEADS]
	div cx	; (총섹터/트랙 당 섹터수) / 헤드수 = 트랙, (총섹터/트랙 당 섹터수) % 헤드수 = 헤드

	mov [HEAD], dl
	mov [TRACK], al
	ret


여기저기 찾아봐도 계산에는 문제가 없다….


BPB_SECTOR_PER_TRACK이 잘못된 것일까? BPB_HEADS가 잘못된 것일까?? 아…..어려움과 디버깅을 할 수 없다는 답답함이 밀려온다…

여기 저기 검색해 본다..LBA 모드???? 기존 Cylinder, Head, Sector 파일 접근 방식을 그냥 섹터 번호로만 접근해서 읽을 수 있는 방식이란다. 오!!!!! 이제까지 CHS 모드로 접근 하다 보니 CONVERT_CHS함수 까지 만들어서 변환 작업을 했었는데 이 부분이 필요 없게 된다. 코드 량도 확실히 줄어 들것 같다. 한번 파일 접근을 CHS 모드에서 LBA 모드로 변경해 봐야 겠다.




CHS 모드 LBA 모드


CHS 모드는 Cylinder, Head, Sector를 직접 명기해서 디스크 섹터 위치를 접근 하는 방식이란다. 초기에는 이 방식으로 접근 했었는데 점점 디스크 용량이 증가함에 따라 표현 할 수 있는 용량이 한계에 다다르게 되었단다. (컴퓨터 하드웨어 시장이 용량, 속도 부분에서 초기 설계치 보다 급속도로 늘어나는거지.. 자고 일어나면 달라지는 세상이니… 테라바이트가 예전에는 꿈의 용량이었는데 이젠 현실이고.. 페타바이트도 뭐 꿈의 용량이 아니지..근데 존재하나?) 여튼 꼼수(ECHS : Extended CHS)로 버티다. GG 치고… LBA 모드가 나오게 되었단다.  LBA모드는 디스크를 0섹터부터 끝까지 일련된 숫자를 부여해서 접근하면 된다고 한다.  근데 CHS 모드가 나온뒤에 LBA가 나온게 아니고 CHS 모드와 LBA 모드는 초기 표준에 설계되어 있다가 CHS가 용량 한계에 부딪치자 LBA로 선회 했다고 한다. 또한 LBA도 28비트로 사용되었는데 128G 밖에 표현 못한다. 또 용량의 한계가 생겼고 28비트를 48비트로 확장 시켜서 이젠 144PB 까지 가능하다고 한다.


즉,  직관적인 LBA가 편하다는 거네… LBA 모드로 변경해야 겠다. 소스 코드도 줄고.. 그럼 어떻게 사용해야 하나??


LBA 모드로 데이터를 읽으려면 CHS 와 마찬가지로 int 13 인터럽트를 이용하면 된다고 한다. DAP(Disk Access Packet)이라는 16바이트의 구조체에 읽을 위치, 읽어서 저장할 메모리 위치, 읽을 섹터 수 를 설정해 주면 읽는단다.


종류

크기

설명

DAP 크기 1 Byte 구조체 크기 총 16 (0x10) 으로 설정
예약 1 Byte 사용 안한다고 한다. 그냥 0
읽을 섹터 수 2 Byte 읽어야 할 섹터 수
저장할 메모리 4 Byte 세그먼트:오프셋 으로 읽은 데이터를 저장 할 메모리 위치
읽을 위치 8 Byte 읽을 시작 섹터 번호(0 base index)


기존 CONVERT_CHS함수가 필요 없어져서 512바이트 제한에서 조금 해방되나 싶었는데 위 16바이트 구조체 변수 때문에 그닥 해방되지 않는다. 그래서 검색 좀 해봤더니 호~~ 그냥 임의의 사용하지 않는 메모리에다가 값을 설정하고 사용해 버리네..


READ_DISK:
	pusha				; 모든 레지스터를 스택에 넣는다.
DISK_RESET:
	; Disk Address Packet (DAP)
	xor ax,ax			;INT 13h AH=00h: Reset Disk Drive
	mov dl,BYTE 0x80		;DL = Drive Number
	int 0x13
	jc DISK_RESET
	mov	al, 0x10 ; 크기
	mov	[0x500], al

	mov al, [READ_SIZE]	; 읽을 섹터 수
	mov	[0x502], ax
	mov ax, [READ_MEMORY] ; 데이터가 들어갈 주소
	mov [0x504], eax ; Segment:offset
	mov eax, [READ_SECTOR] ; 접근 할 LBA 번호
	mov	[0x508], eax

	mov ax, 0x500 ; DAP의 Segment:offset
	mov	si, ax
	mov	ax, 0
	mov ds, ax
	mov ah, 0x42 ; LBA 읽기 명령
	mov dl, [DRIVE]

	int 0x13

         popa
	ret


0x500 위치에 DAP를 구성해서 읽게 했다.

그러면 총 소스 코드는


org 0x7C00
bits 16

BOOTLOADER_ENTRY:
	;; start로 이동한다. (BPB의 영역의 3바이트는 점프 명령 코드로 이용한다. )
	jmp BOOTLOADER_MAIN
	nop ; 코드에서 2바이트 밖에 안되서 nop 1바이트 추가.

;; FAT32 파일시스템 구조
BPB_OEM: db "EOS Boot"				; 8 바이트 OEM 이름..
BPB_BYTE_PER_SECTOR:	dw 512		; 섹터당 바이트 크기.
BPB_SECTOR_PER_CLUSTER: db 0x02		; 클러스터당 섹터 갯수
BPB_RESERVED_SECTOR:	dw 0x186E   ;  예약 섹터 갯수
BPB_FAT_NUM:			db 0x02		; FAT 갯수 (FAT1, FAT2 의 카피본을 두므로 보통 2개)
BPB_ROOT_ENTRY_NUMBER:	dw 0x0000	; 루트 디렉토리에 최대 디렉토리 포함할 수 있는 갯수
BPB_FAT16_SECTORS:		dw 0x0000	; FAT16일 때 총 섹터 갯수
BPB_MEDIA_TYPE:			db 0xF8		; 장치 타입 (0xF8 는 하드디스크)
BPB_FAT16_SIZE:			dw 0x0000	; FAT16일때 FAT테이블 크기
BPB_SECTOR_PER_TRACK:	dw 0x003F	; 트랙당 섹터 갯수
BPB_HEADS:				dw 0x00FF 	; 헤드 갯수
BPB_HIDDEN_SECTOR:		dd 0x00000080	; 숨겨진 섹터 갯수
BPB_FAT32_SECTORS:		dd 0x0003E800	; FAT32 파일시스템일 때 총 섹터 갯수
BPB_FAT32_SIZE:			dd 0x000003C9	; FAT32일때 FAT테이블 크기
BPB_EXTENSION_FLAGS:	dw 0x0000		; 확장 플래그 ( 0-3 : 활성화 FAT )
BPB_FILESYSTEM_VER:		dw 0x0000		; FAT32의 버젼
BPB_ROOT_ENTRY_CLUSTER: dd 0x00000002	; 데이터 시작 클러스터 위치
BPB_FILESYSTEM_INFO:	dw 0x0001		; 파일시스템 정보 위치
BPB_BACKUP_BOOT_SECTOR: dw 0x0006		; 백업 부트 섹터 위치
BPB_RESERVED:							; 예약된 정보 
						dd 0x00
						dd 0x00
						dd 0x00
BS_DRIVE:							; 바이오스콜(0x13) 드라이브 정보
						db 0x80
BS_RESERVED1:						; 예약
						db 0x00
BS_BOOT_SIGNATURE:					; 부트 서명 (0x29)
						db 0x29		;
BS_VOLUME_ID:						; 볼륨 시리얼 번호
						dd 0xC1C286C
BS_VOLUME_LABEL:					; 볼륨 레이블명
						dd 0x4E4F204E
						dd 0x414D4520
						dw 0x2020
						db 0x20
BS_FILESYSTEM_TYPE:					; 파일 시스템 이름
						dd 0x46415433
						dd 0x32202020

BOOTLOADER_MAIN:
	cli
	;; CS, DS, ES, & SS을 초기화 하자.
	xor eax, eax
	mov ds, ax
	mov ss, ax
	mov es, ax

	mov sp, 0x8000  ; 스택포인터를 잡자.
	mov bp, sp		; bp도 0x8000으로 잡자.
	sti

	;mov [DRIVE], dl	; 현재 드라이브를 저장하자.

	; 루트 디렉토리 엔트리 위치를 구하자.
	; 루트 디렉토리 엔트리 = (FAT 갯수 * FAT 테이블크기) + 예약 섹터 + 히든 섹터
	mov al, [BPB_FAT_NUM]	
	mov ebx, [BPB_FAT32_SIZE]
	mul ebx		; AL * EBX = EAX
	xor ebx, ebx ; EBX를 0으로 초기화 하자.
	mov bx, [BPB_RESERVED_SECTOR] ; 예약 섹터를 bx 저장.
	add eax, ebx	; EAX = EAX + EBX
	mov ebx, [BPB_HIDDEN_SECTOR] ; 히든 섹터를 ebx로 저장.
	add eax, ebx	; EAX = EAX + EBX
	mov [ROOT_ENTRY], eax

	; FAT1 위치를 찾자.
	; FAT1 = 예약 섹터 + 히든 섹터
	xor eax, eax	; eax는 0으로 초기화 한다.
	mov ax, [BPB_RESERVED_SECTOR] ; 예약 섹터를 ax 저장.
	add eax, ebx	; ebx 는 이미 히든 섹터가 저장되어 있다.
	mov [FAT1], eax

	; 클러스터를 바이트로 변환하자.
	; 클러스터 총 바이트 = (섹터당 바이트 * 클러스터당 섹터 갯수)
	xor ebx, ebx
	mov ax, [BPB_BYTE_PER_SECTOR]
	mov bl, [BPB_SECTOR_PER_CLUSTER]
	mul bx	; EAX = AX * BX  총 바이트를 구했다.
	mov [CLUSTER_BYTES], eax

	mov ax, 0x8000	; OS_LDR 저장될 위치
	mov [LDR_MEMORY], ax
		
	; 현재 위치 클러스터를 저장하자.
	mov eax, [BPB_ROOT_ENTRY_CLUSTER]
	mov [CURRENT_CLUSTER], eax

	; 0x9000 에 임시로 루트 디렉토리 엔트리를 읽어서 저장해 놓자.
	mov ax, 0x9000	; 임시
	mov [READ_MEMORY], ax

	mov al, [BPB_SECTOR_PER_CLUSTER]
	mov [READ_SIZE], al

FILE_LIST:
	mov ebx, [ROOT_ENTRY]	; 루트 디렉토리 섹터 위치.
	mov eax, [CURRENT_CLUSTER]
	sub eax, 2
	add eax, ebx

	mov [READ_SECTOR], eax
	call READ_DISK	; 데이터를 읽는다.

FILE_FIND:
	; 파일이름을 찾자. 
	mov ax, 0x9000	; 0x9000에 루트 디렉토리 엔트리 데이터 존재.

FIND_LOOP:
	add ax, 0x20		; 첫 데이터는 C:\ 데이터므로 넘어간다.

	mov ebx, [CLUSTER_BYTES]
	add ebx, 0x9000
	cmp ax, bx
	je NO_FIND

	; 파일 이름을 비교한다.
	mov si, ax
	mov di, OS_LDR_FILENAME
	mov cx, 11
	repe cmpsb

	jnz FIND_LOOP


	; 파일을 찾았다.!!! ^^

	; 데이터 클러스터 위치를 구하자.
	mov ax, [si+9]	; 클러스터 상위 2바이트
	push ax
	mov ax, [si+15]	; 클러스터 하위 2바이트
	push ax
	pop eax
	mov [CURRENT_CLUSTER], eax

LOOP_DATA_READ:

	call DATA_READ	; 데이터 클러스터에서 데이터 읽기
	call FAT_SEARCH	; FAT 테이블에서 다음 클러스터를 찾는다.
	cmp al, 0x01	; al에 다음 클러스터가 있으면 0x00, 없으면 0x01 
	je RUN_LDR		; 다음 클러스터가 없으면 OS_LDR 실행하러 가자.
	jmp LOOP_DATA_READ	; 다음 데이터 클러스터 읽으로 가자.

RUN_LDR:
	jmp 0x8000		; 0x8000(OS_LDR.SYS) 실행하자.

	; 루트 디렉토리 엔트리에서 OS_LDR.SYS를 못 찾으면
	; 루트 디렉토리 엔트리의 다음 클러스터를 찾아서
	; 파일 목록을 비교 하러간다.
NO_FIND:
	call FAT_SEARCH	; FAT 테이블에서 다음 클러스터를 찾자.
	cmp al, 0x00	; 다음 클러스터를 찾으면 0x00 
	je FILE_FIND	; 다시 파일이름 찾으로 가자.

	jmp $			; 다음 클러스터가 없으면 무한루프.

	; 데이터 클러스터에서 데이터를 읽어서
	; LDR_MEMORY에 로딩하자.
DATA_READ:
	mov ax, [LDR_MEMORY]	
	mov [READ_MEMORY], ax	; READ_MEMORY에 LDR_MEMORY 복사하자. (세그먼트임을 기억하자)
	mov al, [BPB_SECTOR_PER_CLUSTER]
	mov [READ_SIZE], al	; 클러스터(섹터 2) 만큼 읽자.
	mov ebx, [ROOT_ENTRY]	; 루트 디렉토리 섹터 위치.
	mov eax, [CURRENT_CLUSTER] ; 현재 클러스터.
	sub eax, 2		; 클러스터가 2부터 시작한다고 하니 2를 뺀다.
	mov cl, [BPB_SECTOR_PER_CLUSTER]
	mul cl
	add eax, ebx	; EAX = EAX+EBX. EAX는 루트디렉토리부터 현재 클러스터(-2) 더한값.
	mov [READ_SECTOR], eax

	call READ_DISK	; 데이터를 읽는다.

	mov ax, [LDR_MEMORY]	; 데이터를 읽었으니까 데이터 주소를
	mov ebx, [CLUSTER_BYTES]	; 읽은 만큼 증가시켜준다.(클러스터의 바이트수)
	add ax, bx	
	mov [LDR_MEMORY], ax	; 데이터 읽을만큼 LDR_MEMORY를 증가.

	ret

	; FAT 테이블에서 다음 클러스터를 찾자.
FAT_SEARCH:
	; FAT 리스트
	; 0x9000 에 임시로 FAT테이블을 읽어서 저장해 놓자.
	mov ax, 0x9000	; ES 세그먼트
	mov [READ_MEMORY], ax

	; FAT 테이블의 클러스터는 4바이트 이루어져 있으므로
	; 512 / 4 = 128 . 한 섹터당 128개의 클러스터로 이루어져 있다.
	; 현재 클러스터의 FAT 테이블에서 위치를 찾아보자.
	; 현재클러스터 / 128 은 FAT에서 클러스터 위치.
	mov al, 1
	mov [READ_SIZE], al	; FAT 테이블은 섹터 하나만 읽자.
	mov eax, [CURRENT_CLUSTER]
	mov bx, [BPB_BYTE_PER_SECTOR]
	shr bx, 2	; 4로 나누자.
	xor edx, edx
	div ebx	; 128로 AX를 나누자.
	push edx	; AX를 128로 나눈 나머지는 DX로.
	add eax, [FAT1] ; 나눈 몫에 FAT테이블 위치를 더한다.
	mov [READ_SECTOR], eax

	; FAT 테이블을 읽자.
	call READ_DISK	; 데이터를 읽는다.

	pop edx	; 나눠서 나머지 스택에서 빼내고.
	mov esi, 0x9000	;
	sal edx, 2	; 클러스터 X 4 = 클러스터 메모리 위치.
	add esi, edx	; 0x9000 + 클러스터 메모리 위치.
	mov eax, [esi]	; eax에 현재 클러스터의 다음 클러스터값을 읽자.

	cmp eax, 0x0FFFFFFF	; 끝이라면
	je FAT_END			; al에 0x01 실패!
	
	mov [CURRENT_CLUSTER], eax	; 다음 클러스터를 저장.
	mov al, 0			; al에 0x00 성공!
	ret

FAT_END:
	mov al, 1
	ret

READ_DISK:
	;pusha				; 모든 레지스터를 스택에 넣는다.
DISK_RESET:
	; Disk Address Packet (DAP)
	xor ax,ax								;INT 13h AH=00h: Reset Disk Drive
	mov dl,BYTE 0x80				;DL = Drive Number
	int 0x13
	jc DISK_RESET
	mov	al, 0x10 ; 크기
	mov	[0x500], al

	mov al, [READ_SIZE]	; 읽을 섹터 수
	mov	[0x502], ax
	mov ax, [READ_MEMORY] ; 데이터가 들어갈 주소
	mov [0x504], eax ; Segment:offset
	mov eax, [READ_SECTOR] ; 접근 할 LBA 번호
	mov	[0x508], eax

	mov ax, 0x500 ; DAP의 Segment:offset
	mov	si, ax
	mov	ax, 0
	mov ds, ax
	mov ah, 0x42 ; LBA 읽기 명령
	mov dl, 0x80

	int 0x13
	;popa
	ret

PRINT:
	pusha				; 모든 레지스터를 스택에 넣는다.
PRINT_LOOP:
	mov al, [si]		; al에 글자 하나 저장한다.
	inc si				; si 주소를 증가한다.
	or al, al			; or 연산. 
	jz PRINT_END		; 문자열 끝이 or 연산이 0 이라면. 종료
	mov ah,0x0E			; ah = 0xE
	int 0x10			; 인터럽트 0x10h
	jmp PRINT_LOOP		; 다음 문자 출력 루프	

PRINT_END:
	popa				;모든 레지스터 스택에서 뺀다.
	ret					;호출 한곳으로 리턴.

; 변수를 선언하자.
DRIVE		equ 0x0550
ROOT_ENTRY	equ 0x0551
FAT1		equ 0x0555
CLUSTER_BYTES	equ 0x0559
CURRENT_CLUSTER	equ 0x055D
READ_MEMORY		equ 0x0561
READ_SIZE		equ 0x0563
LDR_MEMORY		equ 0x0564
READ_SECTOR		equ 0x0566

OS_LDR_FILENAME db "OS_LDR  SYS",0

	times 510-($-$$) db 0x00
	dw	0xAA55


휴~ 512 바이트에 맞추려고 아주 삽질좀 했다. 동작???

동작도 잘된다!!!


LOAD MY KERNEL


스트레스 만땅 받았는데 동작 되니 후련하다. CHS 모드에서 왜 동작 안되었는지는 모르겠다.

BPB의 트랙, 헤드 정보가 잘못 된 건지… 여튼 원인은 밝혀 내지 못하고 LBA로 처리해서 되긴하지만 좀 뒤 안 닦은듯한 느낌이다.

성공은 했으나, 기쁘지 않는 기분….



이제 보호모드? 로 진입하고 실 커널 호출 하는 부분을 진행해야 한다.. 막막하다…

 

'프로그래밍 > OS 만들기' 카테고리의 다른 글

OS 만들기 #11 - 리얼모드 & 보호모드  (0) 2013.08.14
OS 만들기 #10  (0) 2013.08.14
OS 만들기 #8 - 부트로더  (0) 2013.08.14
OS 만들기 #7 - FAT32  (1) 2013.08.14
OS 만들기 #6 - MBR  (0) 2013.08.14

+ Recent posts