너무 많은 시간을 블로그에 신경을 못썼다. 바쁘다는 핑계도 있고 어쩌면 OS를 만들다가 지쳐 버린 것인지도 모르겠다. 내일 해야지.. 하고 미뤘던게 벌써 몇달이 지나 버렸다. ㅎㅎㅎ

지난 포스트 읽고 다시 복습중….



초기 바람OS는 부트로더에서 바로 커널을 호출 하는 방법을 사용하려고 했었다. OS 이미지 파일에서 부트로더 다음 섹터를 읽는 방법으로 커널을 호출했다. 여기에서 좀 더 확장해서 파일 시스템(FAT32)를 추가해서 부트로더가 KERNEL.SYS 파일을 찾아서 로딩 하는 형태로 구현하려고 했다. 그런데 부트로더의 512바이트 한계에서 파일시스템에서 KERNEL.SYS를 찾아서 로딩 하는 구현부까지는 무리라고 생각되었다. 또 보호모드 진입을 위한 처리를 하기 위해선 부트로더와 커널간 이 처리를 해줘야 하는 무언가가 필요하다고 판단하여 OS_LDR.SYS 라는 커널 로더를 추가 하기로 했다.

부트로더->커널 로더->커널

자… 커널 로더에서 보호 모드를 진입하고 커널을 읽어 주면 된다.

말만 쉬웠다.

보호 모드 진입이 이렇게 어려울 줄은 몰랐다. 또한 커널을 Visual Studio 툴로 개발 하려고 한 난 , 컴파일이 된 생성 파일이 PE포맷 이라는 사실에 좌절하게 되었다. 그래서 부랴 부랴 보호모드와 PE 포맷을 열공 했다. (~~라고 쓰고 그냥 이론서들을 읽는 수준…)

우선, 커널 로더에서 보호 모드로 진입을 해 봐야 겠다.

그전에, 커널 로더와 커널이 메모리 상에 어디에 적재 되는지 정의부터 해야 겠다.

기존 포스트를 보니

부트로더가 0x7C00에 적재되고 FAT 관련 TEMP 데이터는 0x9000을 이용했다. 그리고 커널로더를 0x8000에 저장해 놓은 형태이다. 그러면 음.. 커널을 0x100000 에 놓으면 될까??

구분 메모리 주소
부트로더 0x7C00
커널 로더 0x8000
커널 0x10000

이렇게 대충(?) 성의 없고 계획없는 정의는 해 놓고 커널 로더를 작성하잣!!!!!!!

 

 

- 보호 모드 진입

이제 보호모드 진입하자!!~~ 보호 모드 진입은 CR0 레지스터를 이용하면 진입 할 수 있다고 한다. 흠 CR0 레지스터 넌 또 뭐하는 놈이냐!!!

CR0 레지스터 : 32비트 레지스터로 운영 모드를 변경, 제어하는 컨트롤 레지스터 (X86 프로세서에는 CR0~CR4 등의 5개의 컨트롤 레지스터가 있으며 그중 하나)

Bit 설명 엑세스
31 페이징 활성화 R/W
30 캐시 비활성화 R/W
29 NOT-WriteThrough  이라는데 모르겠음. R/W
28~19 예약  
18 Alignment Mask 몰라. R/W
17 예약  
16 Write Protect (몰라) R/W
15~6 예약  
5 Numeric Error R/W
4 Extension Type R
3 Task Switched R/W
2 Emulation R/W
1 Monitor Coprocessor R/W
0 Protection Enabled (보호 모드 활성화) R/W

요렇게 구성된다는 데 아직 다~~~ 모르겠다. 다만, 0비트 Protection Enabled 여기를 1로 바꾸면 보호 모드로 진입한다는데… 쉽네?

or CR0, 1 하면 되나???

ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ

CR0은 컨트롤 레지스터로 요케 하면 안되고!! 다른 레지스터를 이용해서 하라는 삽질이 필요하단다 ㅋㅋ

mov EAX, CR0 ; CR0 을 eax 레지스터로 옮기고!
or EAX, 1         ; EAX 값에다가 1을 OR 연산 시킨다.
mov CR0, EAX ; 다시 CR0으로 복사.

 

그러면 한번 해볼까???

;; creator : cyj (mangg@manggong.org)
;; date : 2014. 3. 27
;; desc : 커널 로더.
;;        1. 보호모드로 진입한다.

org 0x8000
bits 16

OS_LDR_ENTRY:

	cli
	;; CS, DS, ES, & SS을 초기화 하자.
	xor eax, eax
	mov ds, ax
	mov ss, ax
	mov es, ax
	mov sp, 0xFFFF
	mov bp, sp
	sti

	mov EAX, CR0
	or  EAX, 1
	mov CR0, EAX

자, 이걸 가상 이미지 파일에 넣고!!!

 

짜잔~~~~~~~!!!

무한 재부팅이다! ㅋㅋㅋㅋ

아~ 재미없어…..

왜 안되지???

아~~~~~~~

GDT 테이블을 우선 GDT 레지스터에 등록해야 한다고 한다.

그럼 GDT 테이블 먼저 만들어야 겠네?.. 지난 포스트(http://manggong.org/41)에서 GDT레지스터에 테이블 위치 넘기는 부분에 대해서 간단히 언급한적이 있다.

struct
{
   unsigned short GDT_SIZE;
   unsigned int GDT_ADDRESS;
} GDT_INFO

LGDT GDT_INFO

GDT_INFO는 GDT 테이블의 크기와 GDT 테이블의 주소를 가지고 있는 구조체.

그러면 GDT_INFO는 이렇게 어셈으로 코딩하고…

LGDT GDT_INFO

GDT_INFO:

GDTSIZE      dw     GDT테이블 크기
GDTADDRESS   dd     GDT_TABLE

GDT 테이블만 만들어 주면 되겠네… GDT 테이블은 GDT 디스크립터 모음이라고 한다고 하니 디스크립터 배열인듯 하다. 전 포스트에 열심히 표로 그려놓은 디스크립터….

os11-1

8바이트로 구성 된 이상한 구조체…

막상 값을 넣으려고 봤더니 뭘 넣어 줘야 할지 막막하다…

그냥 예전에 설명 해 놓은 글을 참고로

Segment Limit(0:15)라는 놈은 세그먼트 크기를 나타낸다고 하고 4GB까지 가능하다고 하니 다 0xFFFF

Base Address(15:0) 라는 놈은 세그먼트 시작 주소라고 하니 음…음…뭘 넣어주지?? 커널 로더가 0x8000에서 시작하니까 그냥 세그먼트 시작 주소를 0x8000 넣어주면 되나??? 음… 아 모르겠다.. 그냥 0x0

Base Address(23:16) 이놈도 그냥 모르겠으니 0x0

Type : 이놈… 코드 세그먼트 인지 데이터 세그먼트 인지 접근 권한 뭐 이런거 있는데 이놈은 뭘 넣어줘야 할지 모르겠다. 실행되고 읽기 지원 하는 놈을 선택했다.(잘한것 같다. 검색해보니 이렇게 하더라 ㅡㅡ ) 0xA 그러고 보니 4비트군 ㅡㅡ;;

S : 이놈 모르겠다…시스템 디스크립터면 0, 걍 디스크립터(코드,데이터) 구분자면 1이라면 넌 그냥 1

DPL : 커널과 유저 접근권한 .. 그래도 너도 0이다.

P : 유효??? 하면 1 유효하지 않으면 0  …넌…넌… 너 모르는데…너도 그냥 1 모르겠다. 그냥 1주자…

그래서 Type,S,DPL, P 요넘들이 1바이트 이니까 전체 설정한 값을 보면 0x9A가 된다.

Segment Limit(19:16) 요놈..그냥 너도 0xF

AVL: 임의의 용도?? 그럼 0

L 요건 64비트면 1 32비트면 0 이라니 난 32비트 0

D/B 이것도 난 32비트 1

G 이건 내가 아까 4G까지 Segment Limit를 쓴다고 했으니 1로 설정해야 한다.

그러면 이것들(AVL, L, D/B, G,SegmentLimit) 조합해보면 11001111 = 0xCF

Base Address(31:24) 이것도 그냥 0

휴~~~ 값 넣기 힘들다…

그럼 다시 정리 하면

Segment Limit : 0xFFFF
Base Address(0~15) : 0x0000
Base Address(15~23) : 0x00
Type,S,DPL,P : 0x9A
Segment Limit, AVL, L, D/B, G : 0xCF
Base Address(24~32) : 0x00

S값을 1로 줬기 때문에 이 디스크립터는 코드 디스크립터 이고 실행과 읽기만 가능하다.

주소는 0~4G까지이다.

코드 디스크립터 이므로 CS 세그먼트에서 참조될 꺼다…..CS 외에 DS,ES 세그먼트에서 사용할 데이터 디스크립터도 만들다…

다 똑같고..

Segment Limit : 0xFFFF
Base Address(0~15) : 0x0000
Base Address(15~23) : 0x00
Type,S,DPL,P : 0x92
Segment Limit, AVL, L, D/B, G : 0xCF
Base Address(24~32) : 0x00

Type만 데이터 디스크립터임을 나타내고 읽고/쓰기 가능으로 바꿨다.

자~~ 이제 GDT 테이블을 다 완성했닷!!!!!!!!!!!!!

그러면..

GDT_TABLE:
                  dw 0xFFFF    ; Code
                  dw 0x0000
                  db  0x00
                  db  0x9A
                  db  0xCF
                  db  0x00
                  dw 0xFFFF    ; Data
                  dw 0x0000
                  db  0x00
                  db  0x9A
                  db  0xCF
                  db  0x00
GDT_TABLE_END

이렇게 GDT_TABLE은 구성될수 있겄다. 하지만… GDT_TABLE 구조체 첫번째 인덱스는 모두 NULL로 채워라고 했다.. (이게 이제 기억이 나네 ..흠…) 그래서 0으로 채우고 전체 소스는

;; creator : cyj (mangg@manggong.org)
;; date : 2014. 3. 27
;; desc : 커널 로더.
;;        1. 보호모드로 진입한다.

org 0x8000
bits 16

OS_LDR_ENTRY:

	;; CS, DS, ES, & SS을 초기화 하자.
	xor eax, eax
	mov ds, ax
	mov ss, ax
	mov es, ax
	mov sp, 0xFFFF
	mov bp, sp
	
    ; GDT 등록
    xor     eax, eax
    lgdt    [GDT_INFO]  
    cli
    mov     eax, cr0
    or      eax, 1
    mov     cr0, eax
	
	jmp		$    ; 무한루프.
	

GDT_INFO:

GDTSIZE      dw     GDT_TABLE_END-GDT_TABLE-1
GDTADDRESS   dd     GDT_TABLE

GDT_TABLE:
			dw 0x0000 ; null
			dw 0x0000
			db 0x00
			db 0x00
			db 0x00
			db 0x00
            dw 0xFFFF    ; Code
            dw 0x0000
            db 0x00
            db 0x9A
            db 0xCF
            db 0x00
            dw 0xFFFF    ; Data
            dw 0x0000
            db 0x00
            db 0x9A
            db 0xCF
            db 0x00
GDT_TABLE_END:

잘 되나 한번 확인!!!

반응이 없다…되었는지 안되었는지 모르겠지만 재부팅은 안된다..ㅎㅎㅎ

보호모드가 성공하면 화면에 출력하는 루틴을 넣어야 겠다.

어랏 , 보호모드에서는 BIOS 인터럽트를 호출 못하네?? 이제까지 int 0x10으로 처리했는데 안된단다.. 그럼 어떻게?? 흠… 전 포스트에서 리얼모드 설명하면 1M 가 다 예약 되어 있다는 글을 쓴적이 있다. 거기에 비디오 메모리 라고 해서.. 컬러, 흑백 주소가 있다고 했는데 거기 0xB8000 컬러 텍스트 비디모 메모리 영역에다가 알파벳 쓰면 출력된다고 한다. 그래?? 쉽겠군 .. 메모리 번지에 아스키 값만 던져주면 되니..

음….

32비트 보호모드로 바꿔 주고 나서 해줘야 하는게 있단다.

CPU 파이프 라인 때문에 mov cr0, eax 다음 명령어를 의미 없는 코드로 바꿔란다.. nop 요걸로..

왜?? CPU가 명령어를 받을 때 mov cr0, eax 명령 처리 한다음 다음 명령어를 읽는게 아니고 미리 명령어를 읽어 두기 때문에.. 그래서 보통

jmp $+2
nop
nop

요렇게 쓴단다.. 뭐 별수 있나.. 남들이 하니 해야지..

그리고 이제까지 리얼 모드에서 돌아가는 코드니깐 [bits 16] 이렇게 컴파일러 한테 알려줬는데 보호모드로 들어가면 32비트 이므로 [bits 32] 라고 해줘야 한단다.. 뭐 그까짓거 하면 되지 ~~~

그리고 제일 중요한데… 주소 어드레싱이 보호모드로 가면 틀려진단다..

리얼모드 일 때는 그냥 선형 주소라서 그냥 쓰면 되는데.. 이제 세그먼트(CS,DS,ES등등) 라고 부른던 놈을 셀렉터 라고 부르고 그 셀렉터는 위에서 정의한 GDT 테이블의 디스크립터를 가리킨다고 한다. 그럼 어떻게 가리킬까 생각 해 보다 GDT 테이블이 배열이고 그 배열 인덱스 번호를???

mov ax, 0x01
mov cs, ax

요렇게 말이지… ㅎㅎ 비슷한데 0x01이 아니란다…

os11-3

요게 셀렉터 모습인데 Index가 3~15비트 부터 이다.. 그래 0x01은 RPL 0비트에 1이라고 설정하는 것 뿐이다. 그러므로 GDT 테이블의 코드 디스크립터를 가리키려면

mov ax, 0x08
mov cs, ax

라고 위와 같이 해줘야 한단다..

os13-1

그리고 주소공간에는 위 그림과 같이 세그먼테이션 작업을 거치고 페이징 작업을 거치면 물리 주소 공간에 읽고/쓴다는데.. ㅎㅎㅎ 복잡하네..

그러니까 세그먼트 셀렉터는 GDT 테이블의 디스크립터를 가르키고 디스크립터는 선형 주소 공간에서 Base Address를 결정하고 다시 여기에서 Offset 만큼 떨어진 위치를 가르킨다. 맞나?? 맞나?? 맞겠지??

페이징은 패스… 아직 모르겠다.. 필요 할때 다시 찾아 봐야 할것 같다. 페이징은 다음에 하고 보호모드 진입하고 “OK” 단어 출력해 보는걸로 오늘은 마무리 해야겠다…

지친다…

;; creator : cyj (mangg@manggong.org)
;; date : 2014. 3. 27
;; desc : 커널 로더.
;;        1. 보호모드로 진입한다.
;;        2. OK 문자 화면에 출력

[bits	16]
org 0x8000

OS_LDR_ENTRY:
	;; CS, DS, ES, & SS을 초기화 하자.
	xor eax, eax
	mov ds, ax
	mov ss, ax
	mov es, ax
	mov sp, 0xFFFF
	mov bp, sp

    ; GDT 등록
    xor     eax, eax
    lgdt    [GDT_INFO]  

    cli
    mov     eax, cr0
    or      eax, 1
    mov     cr0, eax

    jmp     $+2
    nop
    nop

    jmp     0x08:Entry32


GDT_INFO:
			dw     GDT_TABLE_END - GDT_TABLE - 1
		    dd     GDT_TABLE

GDT_TABLE:
			dw 0x0000 ; null
			dw 0x0000
			db 0x00
			db 0x00
			db 0x00
			db 0x00
            dw 0xFFFF    ; Code
            dw 0x0000
            db 0x00
            db 0x9A
            db 0xCF
            db 0x00
            dw 0xFFFF    ; Data
            dw 0x0000
            db 0x00
            db 0x92
            db 0xCF
            db 0x00
GDT_TABLE_END:

[bits 32]
Entry32:
	xor		eax, eax
    mov     ax, 0x10
	mov		ds, ax
	mov		es, ax
	mov		ss, ax
    xor     esp, esp
    mov     esp, 0xFFFF

    mov     BYTE [0xB8000], 'O'
    mov     BYTE [0xB8002], 'K'

 hang:
	jmp hang

대충 이렇게 만들어 주고…

되나???

오~~~~~~~~~~~~잘된다..

 

 

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

OS 만들기 #15 커널-키보드 입력  (0) 2015.02.09
OS 만들기 #14 PE 재배치  (0) 2014.03.28
OS 만들기 #12 - PE 파일 포맷  (0) 2013.09.10
OS 만들기 #11 - 리얼모드 & 보호모드  (0) 2013.08.14
OS 만들기 #10  (0) 2013.08.14

PE 파일 포맷

지금 BaramOS는 커널을 Visual studio로 컴파일 하기 때문에 파일 포맷이 PE 포맷으로 생성된다. 그.래.서 또 PE 포맷이 어떻게 생겼는지를 확인해 봐야 한다. 역시나 쉬운게 없다. OS 만들기가 이리 힘들줄이야~ 여차 하면 컴파일러도(허~~세 작렬) 만들어 볼 생각인데 아마 더 죽어 버릴 듯하다. 그냥 컴파일러는 포기!!! 이거 하나 끝내기 힘드네..

여튼. PE 포맷 요놈이 어떻게 생겼는지를 알아야 한다. 그럼 PE 파일이 뭐냐~?~~

PE(Portable Executable) 포맷은 Windows 에서 만든 포맷으로 유닉스의 COFF(Common Object file format)을 기반으로 나왔다. 다양한 운영체제에서의 이식성을 보여준다는 뜻에서 이식이 가능한 실행 형식(Portable Executable)이라는 이름을 붙였다.                           - 위키백과

이식이 가능한 실행 형식이라는데 뭘 이식 한다는 건진 모르겠다. 찾아보니 파일을 다른곳에서 실행해도 실행이 될 수 있도록 규정한 형식이란다. 예전에는 파일이 만든 곳에서만 실행 되었나??? 흠.. 모르겠다. 패스~

갑자기 실행파일 이란 무엇일까 라는 의문이 들었다. 그냥 막연하게 프로그램을 실행시킬 수 있는 파일 이라고 머리속에서 떠올랐다. 그런데 이게 어떤 원리로?? (또 생각에 꼬리를 물기 시작한다 ㅡㅡ;;;)

나름 실행파일 이란 정의를 내려봤다. CPU가 연산, 제어 할수 있는 코드들을 파일안에 모아 놓고 또 연산, 제어만 하다 보니 데이터들이 필요 했을 것이다. 이 데이터들도 파일 안에 모아 놓았을 것이고.. 그럼 실행파일은 CPU 실행 코드와 데이터 코드들을 모아 놓은 파일. 이게 실행파일 이지 않을까 하고 생각해 봤다… 그런데 왜 굳이 PE포맷으로 실행파일들을 구성해 놨을까??? 음..동적 라이브러리 때문에??? 흠.. 실행파일 포맷이 만들어진 이유는 다음에 좀더 찾아 봐야 겠다. 너무 멀리 가는것 같으니깐…

PE 파일 포맷이라고 인터넷에서 검색해 보니 많은 글들이 찾아 진다. 그리고 자세히 분석 해 놓은 곳도 많다. PE파일에 관한 자료 찾는건 어렵지 않아 보인다. (아싸~) ㅎㅎ

파일 구조는

IMAGE_DOS_HEADER

IMAGE_NT_HEADER

IMAGE_SECTION_HEADER

.TEXT SECTION

DATA SECTION

IMAGE_DOS_HEADER 도스에서 쓰던 구조체란다. 총 64바이트로 구성되어 있단다.

typedef struct _IMAGE_DOS_HEADER {     // DOS .EXE header
    WORD   e_magic;                  // Magic number
    WORD   e_cblp;                    // Bytes on last page of file
    WORD   e_cp;                       // Pages in file
    WORD   e_crlc;                     // Relocations
    WORD   e_cparhdr;               // Size of header in paragraphs
    WORD   e_minalloc;              // Minimum extra paragraphs needed
    WORD   e_maxalloc;             // Maximum extra paragraphs needed
    WORD   e_ss;                      // Initial (relative) SS value
    WORD   e_sp;                      // Initial SP value
    WORD   e_csum;                 // Checksum
    WORD   e_ip;                       // Initial IP value
    WORD   e_cs;                      // Initial (relative) CS value
    WORD   e_lfarlc;                   // File address of relocation table
    WORD   e_ovno;                   // Overlay number
    WORD   e_res[4];                 // Reserved words
    WORD   e_oemid;                 // OEM identifier (for e_oeminfo)
    WORD   e_oeminfo;              // OEM information; e_oemid specific
    WORD   e_res2[10];             // Reserved words
    LONG   e_lfanew;                // File address of new exe header
  } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

이 실행파일 첫부분에 64바이트를 위와 같은 구조로 저장되어 있다.

여기서 필요한건 e_magic 과 e_lfanew 이 두 변수만 필요하단다.

e_magic 은 Dos Signature로 4D5A 기본값이 저장되어 있다. 이게 아스키 값으로 “MZ” 이라는데 만든 사람 Mark zbikowski 이니셜이라고 한다.

e_lfanew는 IMAGE_NT_HEADER 구조체 위치란다. ???  IMAGE_DOS_HEADER 64바이트 다음에 IMAGE_NT_HEADER 아닌가?? 라고 생각해서 검색해 봤더니 IMAGE_DOS_HEADER와 IMAGE_NT_HEADER 사이에 도스에서 실행되는 코드가 있다고 한다..DOS Stub 란다. 16비트 명령어로 구성되어 있어서 32비트 윈도우즈에서는 실행되지 않는단다. (윈도우즈 프로그램을 도스에서 실행시킬 때 “This is program cannot be run in DOS mode” 이렇게 화면에 출력하는 코드) e_lfanew는 파일에서 IMAGE_NT_HEADER가 있는 절대 위치를 가르킨단다.

윈도우즈 실행파일 로더는 프로그램의 첫 2바이트의 MZ 이니셜을 찾고 IMAGE_NT_HEADER로 건너 뛴단다.(물론 도스모드에서 실행하면 바로 에러 메시지 출력하고 종료…)

자 그럼 IMAGE_NT_HEADER 구조체..

이 구조체가 본격적으로 PE 구조체란다.. (아…귀찮다..)

typedef struct _IMAGE_NT_HEADERS {
    DWORD Signature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32

Signature는 단어 그대로 올바른 PE 포맷인지 확인하기 위한 Signature란다. 위에 IMAGE_DOS_HEADER의 첫 MZ와 마찬가지로 PE 포맷을 나타내는 PE00 (0x00004550)이 기록되어 있어야 한단다. 이게 없으면 실행한된단다.

IMAGE_FILE_HEADER는 현재 이 파일이 DLL(동적 라이브러리)인지 EXE 실행 파일인지 구분 여부와 실행될 플랫폼(CPU), 섹션등이 저장된 구조체 란다. (점점… 복잡하게 추적해야 할 구조체가 많아진다.. ㅡㅡ)

typedef struct _IMAGE_FILE_HEADER {
    WORD    Machine;
    WORD    NumberOfSections;
    DWORD   TimeDateStamp;
    DWORD   PointerToSymbolTable;
    DWORD   NumberOfSymbols;
    WORD    SizeOfOptionalHeader;
    WORD    Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

Machine 은 어떤 CPU 에서 실행되는지 정보. 뭐 Intel 386인지, Intel64,MIPS, ALPHA 에 관한 정보가 기록된단다.

NumberOfSections은 단어 그대로 섹션의 갯수가 기록되어 있단다. 자. 그럼 섹션은 무엇일까??? 실행파일은 적어도 코드 와 데이터 이렇게 구분된단다. 이걸 코드와 데이터로 분류해서 섹션?이라는 이름으로 저장하는걸 말한단다. 기본적으로 코드(text), 데이터(data), 리소스(.rsrs) 이렇게 이름이 된다고 한다. 이게 프로그램이 크면 섹션이 많이 존재하게 되는데 이 섹션의 갯수를 말한단다.

TimeDataStamp는 파일이 생성된 날짜와 시간이 기록된단다.

PointerToSymbolTable, NumberOfSymbols 은 유닉스 시스템에서 사용하던 것으로 지금은 사용 안한단다.

SizeOfOptionHeader요놈은 IMAGE_OPTIONAL_HEADER32구조체의 크기이다. 배열도 아니고 포인터도 아닌데 왜 크기를 명시해야 한단다. 왜? 그냥 찾아보긴 했는데 명확한 곳은 없고 64비트 일 경우에 IMAGE_OPTIONAL_HEADER32구조체가 크기가 다르단다. (뭐, 64비트 일 경우 IMAGE_OPTIONAL_HEADER64 구조체를 사용할텐데….) 그래서 크기를 명시해 놔야 한다는데 맞는건진 모르겠다.

Characteristics 파일 속성 값으로 DLL 인지 실행파일인지 등을 정의 해 놓은 속성이란다.

자… IMAGE_FILE_HEADER 했으니 IMAGE_OPTIONAL_HEADER32 해야 겠네… 이 구조체는 PE 헤더 구조체중 제일 크기가 크단다. 이 말은 알아야 할게 많다는 말이지..흠..

typedef struct _IMAGE_OPTIONAL_HEADER {
  WORD                 Magic;
  BYTE                 MajorLinkerVersion;
  BYTE                 MinorLinkerVersion;
  DWORD                SizeOfCode;
  DWORD                SizeOfInitializedData;
  DWORD                SizeOfUninitializedData;
  DWORD                AddressOfEntryPoint;
  DWORD                BaseOfCode;
  DWORD                BaseOfData;
  DWORD                ImageBase;
  DWORD                SectionAlignment;
  DWORD                FileAlignment;
  WORD                 MajorOperatingSystemVersion;
  WORD                 MinorOperatingSystemVersion;
  WORD                 MajorImageVersion;
  WORD                 MinorImageVersion;
  WORD                 MajorSubsystemVersion;
  WORD                 MinorSubsystemVersion;
  DWORD                Win32VersionValue;
  DWORD                SizeOfImage;
  DWORD                SizeOfHeaders;
  DWORD                CheckSum;
  WORD                 Subsystem;
  WORD                 DllCharacteristics;
  DWORD                SizeOfStackReserve;
  DWORD                SizeOfStackCommit;
  DWORD                SizeOfHeapReserve;
  DWORD                SizeOfHeapCommit;
  DWORD                LoaderFlags;
  DWORD                NumberOfRvaAndSizes;
  IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER, *PIMAGE_OPTIONAL_HEADER;

많다….….

Magic 이 구조체가 32비트인지(IMAGE_OPTIONAL_HEADER32) 64비트인지 (IMAGE_OPTIONAL_HEADER64) 구분자란다. 32비트는 10Bh, 64비트는 20Bh .

MajorLinkerVersion, MinorLinkerVersion 컴파일러 버전 정보.

sizeOfCode는 코드 영역에 전체 크기를 말한단다.

sizeOfInitializedData 초기화 된 데이터 영역 전체 크기를 말한단다.

sizeOfUnInitializedData 초기화 되지 않은 데이터 영역 전체 크기를 말한다.

AddressOfEntryPoint는 Entry Point는 프로그램의 시작 위치이다. 그 시작 위치의 RVA값이란다. RVA(Relative Virtual Address)로 상대적 가상 주소를 말한다. 상대적 가상 주소.. 단어 어렵네… 가상의 주소 인데 상대적이란다. 뭐에 대한 상대적??? 요 밑에서 언급 될 ImageBase의 상대적 가상 주소 란다. 즉 ImageBase에서 AddressOfEntryPoint값 만큼 떨어진 위치에 Entry Point가 있다는 말이다. ImageBase에서 RVA만큼 떨어진.. 그럼 Offset 과 동일한 개념이라고 보면 되는건가? 음.

BaseOfCode 또한 RVA 값이란다. 이것도 ImageBase에서 RVA 값 만큼 위치에 Code의 시작 위치가 있다는 말인가 보다.

BaseOfData 위에 코드랑 똑같이 RVA 값.

ImageBase는 프로그램이 메모리에 적재될 위치를 가르킨단다. 보통 0x400000은 실행파일, 0x10000000은 DLL이란다. 물론 다른 값으로 지정될 수 있단다. 그러니까 메모리 로딩 시작 위치에서 RVA로 각 Entry Point, Code, Data위치를 기록하나 보다. (오프셋 개념인듯…). 아. DLL은 0x10000000인데 만약 여러 DLL들이 0x10000000로 ImageBase가 기록되어 있으면(미리 하나의 DLL이 메모리에 선점되어 있으면) 다른 메모리 주소로 PE 로더가 재배치 한단다.

SectionAlignment는 메모리에서 섹션(.TEXT, .DATA)의 최소 단위 크기란다. 이 크기보다 섹션의 크기가 커지면 이 섹션 크기 배수만큼 크기여야 한단다.

FileAlignment는 PE 파일 내에서 섹션의 최소 단위 크기.

뭐 기타 Version 정보는 건너 뛰고~~~ ^^

SizeOfImage는 PE 파일을 메모리에 올렸을 때 전체 크기를 말한단다. 파일 크기가 달라 질수 있는데 이건 SectionAlignment의 영향을 받는단다. (섹션 다음에 패딩이 붙는 다든지.. 등등)

SizeOfHeaders는 PE 포맷의 모든 헤더의 크기를 말한단다. 즉 파일 시작 위치에서 SizeOfHeaders 오프셋 뒤에 첫번째 섹션이 존재한단다. 이 SizeOfHeaders의 값은 FileAlignment값의 배수가 되어야 한단다.

Checksum 파일의 변경 유무 판단할 때 사용.

SubSystem은 파일이 드라이버 파일(1)인지 GUI 프로그램인지(2), CUI 프로그램인지를 나타내는 값.

SizeOfStackReserve 프로그램에서 사용할 스택 예약 크기.

SizeOfStackCommit 프로그램에서 사용할 스택 크기.

SizeOfHeapReserve 프로그램에서 사용할 힙 예약 크기.

SizeOfHeapCommit 프로그램에서 사용할 힙 크기.

LoaderFlags 사용하지 않음.

NumberOfRvaAndSizes 밑에 IMAGE_DATA_DIRECTORY구조체 배열 DataDirectory 크기.

DataDirectory 는…음.. IMAGE_DATA_DIRECTORY 구조체의 배열이다.

typedef struct _IMAGE_DATA_DIRECTORY {
    DWORD VirtualAddress;
    DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

IMAGE_DATA_DIRECTORY구조체에는 VirtualAddress변수와 Size가 있다. 이게 Export, Import, Resource, Exception 등등의 위치와 크기를 정의하는 것이라는데.. 별로 커널쪽에선 중요하진 않을 것 같아 패쓰~

휴~~~

IMAGE_NT_HEADER 파일 뒤에는 섹션에 관련된 IMAGE_SECTION_HEADER가 따라 온다. 뭐 섹션 테이블이라고도 불리운다고 한단다.

typedef struct _IMAGE_SECTION_HEADER {
    BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];
    union {
            DWORD   PhysicalAddress;
            DWORD   VirtualSize;
    } Misc;
    DWORD   VirtualAddress;
    DWORD   SizeOfRawData;
    DWORD   PointerToRawData;
    DWORD   PointerToRelocations;
    DWORD   PointerToLinenumbers;
    WORD    NumberOfRelocations;
    WORD    NumberOfLinenumbers;
    DWORD   Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

IMAGE_SECTION_HEADER 구조체 데이터가 IMAGE_FILE_HEADER구조체의 NumberOfSections값만큼 있단다..(섹션의 갯수만큼..)

Name은 섹셕의 이름인데 NULL도 채워도 되나 보통 .text, .data, .rdata, .bss, .idata, .edata 등이 보통 관례대로 사용된단다.

VirtualSize 섹션이 메모리에 올라갈 때 섹션 크기. (SectionAlignment 배수)

VirtualAddress 섹션이 메모리에 올라 갈 메모리 위치. RVA 값이다. 즉 ImageBase + VirtualAddress .

SizeOfRawData 메모리가 아닌 실제 PE 파일내에서 섹션의 크기. (FileAlignment 배수)

PointerToRawData PE 파일내에서 섹션이 시작하는 위치(파일 오프셋).

PointerToRelocation obj 파일에서만 사용되고 실제 실행파일에서는 0 이 된단다.

PointerToLinenumbers COFF 스타일의 라인번호를 위한 파일 오프셋(??) . 모르겠다 이건.

NumberOfRelocation 요것도 0.

PointerToLinenumbers COFF 라인번호가 PE 에 첨부되었을 때만 사용.(???) 진짜 모르겠다.

Characteristics 섹션의 속성. (CODE 인지 DATA, 실행, 읽기, 쓰기) OR 연산.

     0x00000020 코드 섹션
     0x00000040 초기화 된 데이터 섹션
     0x00000080 초기화 되지 않은 데이터 섹션
     0x20000000 실행 가능한 섹션
     0x40000000 읽기 가능한 섹션
     0x80000000 쓰기 가능한 섹션.


자… 이제 필요한건 다 했다. IAT, EAT 이런것들은 아직… 필요 없다고 판단되었다. 필요 할때 다시 살펴볼 생각… IMAGE_SECTION_HEADER 다음에 실제 섹션 데이터들이 온단다. 뭐 이제 PE 포맷 구조에 대충 훑어 봤다. 휴~~~ 시간이 많이 걸렸다..(노느니라~~~)

정리가 필요하다….정리…

실행파일은 PE 포맷으로 구성되어 있다. 뭐 이런 저런 잡다한 것 다 빼고…

코드, 데이터 섹션 이런식의 섹션별로 실행 코드, 데이터를 분류해 놓았다.

이걸, IMAGE_NT_HEADER 구조체 안에 있는 IMAGE_OPTIONAL_HEADER 구조체의 ImageBase 값. 메모리에 실행파일을 로딩해 놓는다.

이 때, 각 섹션은 ImageBase + VirtualAddress(IMAGE_SECTION_HEADER 구조체 내) 의 RVA 위치에 섹션을 배치한다.

다시… 정리하면..

실행파일에서 ImageBase 값을 구하고 섹션의 갯수를 구한 다음. 각 섹션을 ImageBase + RVA 위치에 메모리에 올린다.

라고 정리가 된다. 흠…

 

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

OS 만들기 #14 PE 재배치  (0) 2014.03.28
OS 만들기 #13 보호모드 진입  (0) 2014.03.27
OS 만들기 #11 - 리얼모드 & 보호모드  (0) 2013.08.14
OS 만들기 #10  (0) 2013.08.14
OS 만들기 #9 - 부트로더  (0) 2013.08.14



DLNA에 대해서 좀 알아 보려고 했더니만 뜬금없는 uPNP가 나오네??? DLNA가 uPNP에서 파생되었다고 하는데...어쩐지 miniDLNA소스를 봐도 uPNP 밖에 안 나온다… 그래서 uPNP에 대해서 먼저 학습을 하고 DLNA로 넘어가야 겠다.


uPNP

Universal Plug and Play 로 마이크로소프트사에 의해 제안된 기술이란다. PnP랑 이름이 같길래, 난 그건줄 알았더니 거의 유사하다. PC에 연결해서 주변기기를 알아서 등록, 재생 하는 그 기술의 확장판이랄까?? 요건 네트워크에 딱 등록되면 네트워크 연동해서 !!! 사용이 가능 하게 해주는 기술이랄까???

여튼, 요즘같이 스마트 시대에 아주 적당한 기술이라 할 수 있겠다. 프린트, 라디오, 비디오, 스마트폰이 네트워크로 연결되서 서로 사용이 가능 하다면 얼마나 편하겠나~~~ 좋은 세상이야..

uPNP는 TCP/IP, SSDP, SOAP, GENA, HTTP, XML같은 통신 표준과 기술을 바탕으로 한다고 한다.

TCP/IP는 알겠는데 SSDP, SOAP, GENA는 또 뭐냐??? 점점 꼬리에 꼬리를 물고 알아야 할게 늘어난다 ㅡㅡ;;;




SSDP
Simple Service Discovery Protocol로 네트워크상 장치들을 찾기 위한 프로토콜.

이라는데, 이게 뭔 소린지…흠..그냥 프로토콜 인데(?)

네트워크 상에서 1900 포트로 브로드캐스트 를 하면 같이 연결된 장치들이 그에 응답하여 서로 장치의 IP주소를 획득. 이렇게 해서 서로 같은 네트워크 상에서 장치를 인식하게 해주는 프로토콜(?) 인가 보다.



SOAP

Simple Object Access Protocol로 HTTP, HTTPS, SMTP등을 사용하여 XML 기반의 메시지를 컴퓨터 네트워크 상에서 교환하는 형태의 프로토콜이다.

요건 모.. 그냥 XML 데이터를 HTTP, HTTPS, SMTP 프로토콜로 서로 전송, 수신 한다는 말이고…



GENA

General Event Notification Architecture는 이벤트를 HTTP로 송.수신 한다.

뭔말인지… 그냥 이벤트를 HTTP를 통해서 전달한다는 말이겠지… 이벤트 전용 프로토콜인건가??



정리하믄 SSDP로 각 장치를 찾고, SOAP으로 데이터를 송수신 하고 GENA로 이벤트를 송수신 한다는 말인건가?

 



uPNP는 검색, 서술???(Description), 제어, 이벤트?(Eventing), 프리젠테이션  요런 단계로 나뉘어 진단다..




검색

Control Point 라는 놈(장치를 제어하는 놈)이 동일 네트워크 상에 장치(서비스 하는 기기)를 검색할 때 사용하나 보다. (사용할려면 어떤놈들이 있는지 부터 알아야 하니… ㅋㅋㅋ) 근데 Control Point 놈만 이걸 보내는 게 아니라, 장치 들도 네트워에 연결되면 알아서 ㅋ “나 여기 있어요~~” 라고 통지 메시지를 보낸다고 한다. 제법 기특해!~

Control Point는 M-SEARCH 메시지를 보내는데 “이 네트워크 있는 기기 들 있으면 모두 손!!!” 과 같다고 봐야 할 것 같다. 손 들라고 했으니 이 메시지를 수신 한 기기들은 Control Point로 응답 메시지(“저 여기 있어요~~~”)를 보낸다.

M-SEARCH * HTTP/1.1
HOST: 239.255.255.255:1900
MAN: “ssdp:discover”
MX: 30
ST: ssdp:all


M-SEARCH * HTTP/1.1 기기찾는 다는 M-SEARCH와 HTTP/1.1 버젼 정보
HOST: 239.255.255.255:1900 uPNP에서 약속된 브로드캐스팅 IP와 포트.
MAN: “ssdp:discover” 요건 필수!
MX: 30 Control Point라는 놈이 검색 한다고 하고 무한정 기다릴 수 없으니… 초 단위로 1에서 120 사이에 값.
ST: ssdp:all 네트워크에 있는 모든 장치 그리고 서비스를 찾는다. 문서를 보면 ssdp:all이 아닌 특정 장치, 특정 서비스(?)등을 찾는 세부적으로 나눠서 검색 할 수 있는 것 같은데.. 세부적인 내용에 대해선 뭔지 모르겠다…

M-SEARCH * HTTP/1.1 : HOST: 239.255.255.255:1900 uPNP에서 약속된 브로드캐스팅 IP와 포트 요건 안 바뀐다.

Control Point는 요런 형태로 네트워크 상에 있는 기기를 찾는다고 메시지를 보낸다.


HTTP/1.1 200 OK
CACHE-CONTROL: max-age=100
DATE: Tue, 18 Jun 2013 03:05:18 GMT
EXT:
LOCATION: http://192.168.10.100:8080/api/test.xml
SERVER: Linux/2.6.18 UPnP/1.0 Test Device/2.0
ST: ssdp:all
USN:



HTTP/1.1 200 OK HTTP 버전 및 응답
CACHE-CONTROL: max-age=100 장치가 Control Point 한테 본인이 유효한 시간(초 단위로 최대 1800)을 표기한다. 뭔말이냐 .ㅡㅡ;;; 이 시간 만큼 Control Point가 요청하는 내용에 대해서 응답을 할 수 있다는 말인가?? 흠.
DATE:Tue, 18 Jun 2013 03:05:18 GMT 응답 메시지가 만들어진 날짜/시간.
EXT: 요건 나도 몰라.
LOCATION: http://192.168.10.100:8080/api/test.xml 요 밑에서 언급될 서술? 설명? (Description)에 대한 장치의 정보를 담고 있는 위치.
SERVER:Linux/2.6.18 UPnP/1.0 Test Device/2.0 OS/버전, 장치/버전등의 정보.
ST: ssdp:all M-SEARCH의 ST 값에 의해 달라진다. 위에서 ssdp:all 이라고 했으니 ssdp:all 이라고 했다.
USN 서비스 이름. 식별자 인것 같기도 하고.. 몰겠어..

그러면 각 장치들은 요렇게 메시지를 만들어서 Control Point에 응답한다.

 

위에서 장치들이 네트워크에 연결이 되면 알.아.서 본인들이 통지 메시지를 보낼 수도 있다고 했다.

NOTIFY * HTTP/1.1
HOST: 239.255.255.250:1900
CACHE-CONTROL: max-age=100
LOCATION: http://192.168.10.100:8080/api/test/xml
NT: upnp:rootdevice
NTS: ssdp:alive
SERVER: Linux/2.6.18 UPnp/1.0 Test Device/2.0
USN: ~~~


NOTIFY * HTTP/1.1 장치가 알아서 응답한다.
HOST: 239.255.255.250:1900
NT: upnp:rootdevice upnp:rootdevice라는 놈이 보냈다 정도???
NTS: ssdp:alive 요렇게 명시해 줘야 한단다.

그래, “나 여기 있어요” 라고 보내는 참으로 기특하다. 근데 네트워크에서 제거 될 때도 알아서 통지 메시지를 보낸다.

NOTIFY * HTTP/1.1
HOST: 239.255.255.250:1900
NT: upnp:rootdevice
NTS: ssdp:byebye
USN: ~~~


NTS: ssdp:byebye 제거 될때 요렇게 해줘야 한단다.




서술? 설명? (Description)

Control Point 가 이제 네트워크 상에서 장치들이 어디(IP)에 있는지 알았다. 근데 이놈들이 정확히 어떤 놈들인지에 대한 확실한 정보는 없다. 그래서 검색 시에 장치들이 응답한 데이터 중 LOCATION 위치에 있는 정보(UTF-8 로 인코딩 되어 있단다)를 요청한다. 그냥 HTTP 프로토콜 GET 이다.

근데 이게 ㅡㅡ;;; XML 로 많은 내용이 있다.

<?xml version="1.0"?> 
 <root xmlns="urn:schemas-upnp-org:device-1-0"> 
 <specVersion> 
 <major>1</major> 
 <minor>0</minor> 
 </specVersion> 
 <URLBase>base URL for all relative URLs</URLBase> 
 <device> 
 <deviceType>urn:schemas-upnp-org:device:deviceType:v</deviceType> 
 <friendlyName>short user-friendly title</friendlyName> 
 <manufacturer>manufacturer name</manufacturer> 
 <manufacturerURL>URL to manufacturer site</manufacturerURL> 
 <modelDescription>long user-friendly title</modelDescription> 
 <modelName>model name</modelName> 
 <modelNumber>model number</modelNumber> 
 <modelURL>URL to model site</modelURL> 
 <serialNumber>manufacturer's serial number</serialNumber> 
 <UDN>uuid:UUID</UDN> 
 <UPC>Universal Product Code</UPC> 
 <iconList> 
 <icon> 
 <mimetype>image/format</mimetype> 
 <width>horizontal pixels</width> 
 <height>vertical pixels</height> 
 <depth>color depth</depth> 
 <url>URL to icon</url> 
 </icon> 
XML to declare other icons, if any, go here
 </iconList> 
 <serviceList> 
 <service> 
 <serviceType>urn:schemas-upnp-org:service:serviceType:v</serviceType> 
 <serviceId>urn:upnp-org:serviceId:serviceID</serviceId> 
 <SCPDURL>URL to service description</SCPDURL> 
 <controlURL>URL for control</controlURL> 
 <eventSubURL>URL for eventing</eventSubURL> 
 </service> 
Declarations for other services defined by a UPnP Forum working committee (if any)
go here
Declarations for other services added by UPnP vendor (if any) go here
 </serviceList> 
 <deviceList> 
Description of embedded devices defined by a UPnP Forum working committee (if any)
go here
Description of embedded devices added by UPnP vendor (if any) go here
 </deviceList> 
 <presentationURL>URL for presentation</presentationURL> 
 </device> 
 </root> 
 

복잡하다… ㅡㅡ;; 이제 각 속성들을 살펴 봐야 겠다.


root xmlns ”run:schemas-upnp-org:device-1-0 같이 xmlns이 반드시 있어야 한단다.
specVersion major, minor major , minor 버전. major 는 반드시 1이어야 한다고 하네?
URLBase   Optional.
device   장치
deviceType   장치 타입이라는데 “urn:schemas-upnp-org:device:” 로 시작해야 한다고 한다.  타입이 뭔지 모르겠다. devicetype:v 이렇게 있는데 v는 버전 정보란다.
friendlyName   그냥, 최종 사용자를 위한 짧은 이름?
manufacturer   제조사
manufacturerURL   제조사 URL
modelDescription   모델 설명
modelName   모델 이름
modelNumber   모델 넘버
modelURL   모델 웹 사이트.
serialNumber   시리얼 넘버
UDN   유일한 장치 이름. 장치 식별자.
UPC   제품 코드
iconList   장치에서 사용하는 아이콘 리스트
  icon Control point UI에서 표시될 아이콘
  mimetype 아이콘 MIME TYPE “image/png”.
  width 가로
  height 세로
  depth depth
  url 아이콘 이미지 파일일 있는 URL 주소
serviceList   장치에서 제공하는 서비스들(?)^^
  service 무슨 서비스를 할 것인가를 설명한 건가?? 참 어려다잉.
  serviceType 말 그대로 서비스 타입. “urn:schemas-upnp-org:service:”로 시작 되어야 한다고 한다. devicetype처럼 servicetype:버전 이렇게 설정한다.
  serviceId 서비스 식별자. “urn:upnp-org:serviceId:serviceID” 요렇게 설정.
  SPCDURL 서비스 설명을 위한 URL
  controlURL 컨트롤을 위한 URL
  eventSubURL 이벤팅을 위한 URL
devicelist   장치 안에 장치가 또 있나 보다. 또 디바이스 리스트.
presentationURL   presentation을 위한 URL

 

휴 많다… 이제까지 root device라는 용어가 나오는데 root device가 메인 장비?? 장치를 말하나 보다. 그 장치안에 또 장치가 있는 모양. ㅎㅎㅎ

이제, 장치에 대한 정보를 알았으니, 장치가 가지고 있는 서비스 정보에 대해서 알아야 한다. 요게 장치에서 제공하는 서비스의 액션, 변수, 데이터, 범위, 이벤트등의 정보를 가지고 있다고 한다. 그러니까 장치를 제어 하기 위해 필요한 정보 모음집으로 봐야 하나?? 하~~ 망할 영어…

Control Point는 장치 기술, 설명(description)을 HTTP GET으로 요청하고 다시 서비스 Description을 요청(HTTP GET)한단다.

 

<?xml version="1.0"?> 
 <scpd xmlns="urn:schemas-upnp-org:service-1-0"> 
 <specVersion> 
 <major>1</major> 
 <minor>0</minor> 
 </specVersion> 
 <actionList> 
 <action> 
 <name>actionName</name> 
 <argumentList> 
 <argument> 
 <name>formalParameterName</name> 
 <direction>in xor out</direction> 
 <retval /> 
 <relatedStateVariable>stateVariableName</relatedStateVariable> 
 </argument> 
Declarations for other arguments defined by UPnP Forum working committee (if any)
go here
 </argumentList> 
 </action> 
Declarations for other actions defined by UPnP Forum working committee (if any)
go here
Declarations for other actions added by UPnP vendor (if any) go here
 </actionList> 
 <serviceStateTable> 
 <stateVariable sendEvents="yes"> 
 <name>variableName</name> 
 <dataType>variable data type</dataType> 
 <defaultValue>default value</defaultValue> 
 <allowedValueList> 
 <allowedValue>enumerated value</allowedValue> 
Other allowed values defined by UPnP Forum working committee (if any) go here
 </allowedValueList> 
 </stateVariable> 
 <stateVariable sendEvents="yes"> 
 <name>variableName</name> 
 <dataType>variable data type</dataType> 
 <defaultValue>default value</defaultValue> 
 <allowedValueRange> 
 <minimum>minimum value</minimum> 
 <maximum>maximum value</maximum> 
 <step>increment value</step> 
 </allowedValueRange> 
 </stateVariable> 
Declarations for other state variables defined by UPnP Forum working committee
(if any) go here
Declarations for other state variables added by UPnP vendor (if any) go here
 </serviceStateTable> 
 </scpd> 

 


요놈도 장난이 아니다. 그냥 하나 하나 뜯어서 살펴 볼려니, 심신이 괴롭다. 아니 정확히 머리가 아프다..

 

scpd   반드시 urn:schemas-upnp-org:service-1-0으로 시작해야 한단다.
specVersion major, minor major, minor 버전 정보. major는 반드시 1이어야 한단다.
actionList   액션(?) 리스트?
action name 액션 이름. 동작 함수 인듯.
argumentList   액션이 함수라면 인자도 있겠지~~ 그 인자 리스트 정의.
argument name 인자 이름.
  direction 인자가 IN 타입인지 OUT 타입인지를 말하나 보다. IN, OUT 개념도 있네..흠.
  retval 액션 리턴 값.
  relatedStateVariable 액션 내부에서 사용할 StateVariable 이름??? 아직 모르겠다.
serviceStateTable   변수 테이블?
stateVariable name 변수 이름
  dateType 데이터 타입
  defaultValue 기본 값
allowedValueList allowedValue. 문자열에서 쓰나 본데 모르겠다.
allowValueRange minimum, maximum,step 변수값에 최소, 최대 값 정의.


휴~ 요렇게 해서 Description 마쳤다.

 

Control Point는 장치를 제어 하기 위한 정보를 Description을 통해서 다 얻었다. 이제 장치를 제어해 봐야 겠지~~




제어

장치의 제어는 서비스 Description에서 얻은 액션이름(함수명??)으로 가능하단다. 근데 위에서 언급했듯이 SOAP이라는 놈으로 구성되어 있다.(아~~ 망할…)

POST path of control URL HTTP/1.1 
 HOST: host of control URL:port of control URL
 CONTENT-LENGTH: bytes in body
 CONTENT-TYPE: text/xml; charset="utf-8" 
SOAPACTION: "urn:schemas-upnp-org:service:serviceType:v#actionName" 




---------body ----------
 <?xml version="1.0"?> 
 <s:Envelope
 xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" 
s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> 
 <s:Body> 
 <u:actionName xmlns:u="urn:schemas-upnp-org:service:serviceType:v"> 
 <argumentName>in arg value</argumentName> 
other in args and their values go here, if any
 </u:actionName> 
 </s:Body> 
 </s:Envelope>

장치 제어는 HTTP 헤더와 body 요렇게 두 부분으로 구성되어 있다.

POST control URL HTTP/1.1 장치 Description에서 설정된 control URL.
HOST IP 주소, 도메인.
CONTENT-LENGTH body 크기
CONTENT-TYPE text/xml; charset=”utf-8” 로 사용하란다.
SOAPACTION urn:schemas-upnp-org:service:serviceType:번호#액션이름 이렇게 사용하란다. 뭔말인건지.. 제어할 액션이름.

헤더는 이렇고 body는…

 

Envelope   이건 그대로 쓰면 되겠다.
body   제어 부분
actionName   제어할 액션 이름을 쓴다.
xmlns:u=”urn:schemas-upnp-org:service:serviceType:v” 요건 모르겠다. 아놔 환장하겠네..
  argumentName 인자 이름. 인자 이름을 쓰고 해당 인자에 값을 표기.

근데 이렇게 장치로 POST 했는데 “405 Method Not allowed” 라는 메시지를 리턴 받으면 다시 M-POST로 메시지를 보내야 한다고 하네?  뭐이리 복잡해.

 

M-POST path of control URL HTTP/1.1 
 HOST: host of control URL:port of control URL
 CONTENT-LENGTH: bytes in body
 CONTENT-TYPE: text/xml; charset="utf-8" 
 MAN: "http://schemas.xmlsoap.org/soap/envelope/"; ns=01
01-SOAPACTION: "urn:schemas-upnp-org:service:serviceType:v#actionName" 

 

자.. 이제 제어 명령을 보냈으니, 장치가 응답을 보내겠지. 주는게 있으니 받는것도 있는 법이니까..

 

 HTTP/1.1 200 OK 
 CONTENT-LENGTH: bytes in body
 CONTENT-TYPE: text/xml; charset="utf-8" 
 DATE: when response was generated
 EXT: 
 SERVER: OS/version UPnP/1.0 product/version


--------- body ----------

 <?xml version="1.0"?> 
 <s:Envelope
 xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" 
s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> 
 <s:Body> 
 <u:actionNameResponse xmlns:u="urn:schemas-upnp-org:service:serviceType:v"> 
 <argumentName>out arg value</argumentName> 
other out args and their values go here, if any
 </u:actionNameResponse> 
 </s:Body> 
 </s:Envelope> 

여기서 actionNameResponse는 actionName + Response 가 더해서 만들어진 단어인가 보다.  그렇다고 하니 그렇겠지.

 

제어는 대충 이런식인가 보다. 액션을 호출 하는 것만 알아 볼련다. 각 변수값들도 요청하는 게 있나 본데 솔직히 귀찮다. DLNA 수정해 보려다 uPNP때문에 미치겠다. 그래서 더 미치기 전에 간단하게 넘어 갈련다.

 



이벤트

Control Point가 장치의 서비스 변수의 상태가 변경이 되었을 때 통지 받고자 하면 이벤트를 요청한다고 한다. 장치 서비스는 변수의 값이 변경이 될 때마다 Control Point에게 이 값을 전달 하는게 이벤트 라고 한다.

Subscriber, publisher 뭐 이런놈들이 있는데 이건 그냥 무시하겠다… 깊게는 들어가지는 들어가지 말아야지^^

그럼 Control Point가 장치에게 이벤트 요청 메시지를 어떻게 보내냐면..

SUBSCRIBE publisher path HTTP/1.1 
 HOST: publisher host:publisher port
CALLBACK: <delivery URL> 
NT: upnp:event
 TIMEOUT: Second-requested subscription duration 

이렇게 보낸단다.

SUBSCRIBE publisher path HTTP/1.1 SUBSCRIBE 명령에 장비 Description에서 설정되었던 이벤트 URL 을 설정.
HOST 장치 IP, 포트
CALLBACK 이벤트를 전달받을 URL로 설정. 이때 이 URL은 여러개가 될 수 있다. 이벤트 메시지를 보낼곳들을 설정한다.
NT upnp:event
TIMEOUT 이벤트 발생시 한번 보내고 끝내는게 아닌가 보다. 이 timeout 값에 의해 보내는 시간이 결정된단다. Second-XX 이렇게 설정하던지 아니면 infinite 무한 설정 이렇게 한단다.

이벤트 설정? 메시지 수신 후 장치는 응답 메시지를 보낸다. 뭐~ 잘 받았다는 메시지 겠지..

HTTP/1.1 200 OK 
 DATE: when response was generated
 SERVER: OS/version UPnP/1.0 product/version
SID: uuid:subscription-UUID
 TIMEOUT: Second-actual subscription duration 

이 메시지중 SID 이 필드값 이건 아마도 이벤트 요청을 한 Control Point의 식별자를 장치가 하나 발급하나 보다. 이 SID 필드 값으로 관리하는듯… Control Point가 이 메시지를 수신 후 SID 값을 저장해 놔야 하나 보다.. 왜? 그건 renewal 메시지에서 사용하니까~~

SUBSCRIBE publisher path HTTP/1.1 
 HOST: publisher host:publisher port
SID: uuid:subscription UUID
 TIMEOUT: Second-requested subscription duration 

renewal 메시지는 이벤트 요청 메시지랑 명령은 같다. 다만 NT와 CALLBACK만 빠져 있다. 아~ SID 가 추가 되어 있다. 그럼 renewal은 뭐냐?? 단순히 메세지만 봐서는 TIMEOUT 값만 다시 변경 할 때 사용하나 보다. 추가된 SID는 장치에서 송신한 SID 필드값이겠지?? ㅋ 그러므로 Control Point도 SID값을 저장해 놔야 하는듯.

이벤트를 보내 달라고 했으니 보내지 말라는 메시지도 있겠지? 당연히~~ 있네…

UNSUBSCRIBE publisher path HTTP/1.1 
 HOST: publisher host:publisher port
SID: uuid:subscription UUID

이렇게 보내면 장치는 이벤트 메시지를 보내지 않는다고 한다.


이벤트를 요청하고 갱신?하고 이벤트 중지 하는 메시지 까지 했으니 이제 실제 이벤트 발생시 보내지는 메시지만 남았다.

NOTIFY delivery path HTTP/1.1 
 HOST: delivery host:delivery port
 CONTENT-TYPE: text/xml 
 CONTENT-LENGTH: Bytes in body
NT: upnp:event
NTS: upnp:propchange
SID: uuid:subscription-UUID
SEQ: event key


---------- body ----------------------

 <?xml version="1.0"?> 
 <e:propertyset xmlns:e="urn:schemas-upnp-org:event-1-0"> 
 <e:property> 
 <variableName>new value</variableName> 
 </e:property> 
</e:propertyset>

생김새는 제어 쪽에서 본 메시지랑 비슷하다.

NOTIFY delivery path HTTP/1.1   NOTIFY 요건 통지 관련 메시지다는 의미일테고 delivery path는 이벤트 요청시 설정한 CALLBACK url이란다.
CONTENT-TYPE   text/xml
CONTENT-LENGTH   body 크기
NT   upnp:event
NTS   upnp:propchange
SID   SID
SEQ   0에서 시작해서 1씩 증가한다고 한다.
propertyset   urn:schemas-upnp-org:event-1-0. 이렇게 써주고.
property variableName variableName변수의 변경된 값.

이벤트 메시지를 받았으니 메시지 잘 받았다고 응답 보내줘야지?

 HTTP/1.1 200 OK 

휴~~~

이 이벤트를 끝으로 uPNP는 그만 볼련다. DLNA를 시작으로 uPNP까지 머리도 터지겠고 보기도 싫다. 이거 뭐 되지도 않는 영어 문서 붙잡고.. 하나 하나 볼려니 오역80%에 개념 상실 20%.

그래도 uPNP 어느정도 개념은 잡았으니 miniDLNA 소스 코드는 어느정도 분석은 가능하겠군.. 근데 DLNA 프로토콜도 찾아서 봐야 하는데. 이 정도라면 그만 둘까 라는 생각이 든다.

 

참고문서:

http://upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.0.pdf

 

  

영상을 다루는 사람들은 FFMPEG 라이브러리를 모르는 사람을 없을 것이다. 그래.. 많다. 안 쓰이는 곳이 없으니 웬만한 동영상 플레이어도 FFMPEG 라이브러리를 사용해서 재생한다. 물론 Directshow 를 이용해서 하기도 하지만 Directshow의 ffdshow도 FFMPEG 라이브러를 사용하니…

이 라이브러리는 방대하다. 웬만한 코덱은 다 지원하다 보니 덩치가 겁나 크다. 그런데 한동안 libav 라고 해서 잠시 분리되어 프로젝트가 진행되기도 하다 보니 API 인터페이스가 다른(?) 상황도 있었다. 뭐 지금은 다시 통합되었지만… 구글에서 ffmpeg 관련에서 문의 하다 보면 관련 샘플 코드가 어떤건 libav , 어떤건 ffmpeg 으로 작성되어 있는 경우도 있었다. 뭐 그다지 크게 다르진 않고 조금 다르긴 하지만 헷갈렸던 건 사실이다.

FFMPEG 라이브러리를 이용한다는건 어떤 파일을 다른 파일 포맷으로 변환 한다던지(재인코딩), 영상 스트림을 디코딩, 인코딩을 사용하기 위해서 일것이다. 여기서 난 윈도우 기반에서 개발한다는 가정하에 디코딩,인코딩 API 만 짚을 예정이다.


개발환경

- Windows 32bit
- Visual Studio 2010

FFMPEG 라이브러리 구하기

FFMPEG라이브러리를 사용하기 위해선 파일을 다운 받아야 한다. FFMPEG 라이브러리는 리눅스 기반에서 개발되었으며, 거의 모든게 GCC 컴파일러에 맞춰 있다. 물론 윈도우에서 컴파일 하는 방법은 있지만 그 방법이 쉽지는 않다. 많은 사람들이 어려웠던지 윈도우용 DLL 만 따로 모아놓은 사이트가 있다.

http://ffmpeg.zeranoe.com/builds/ 

여기에 윈도우용 ffmpeg 라이브러리 DLL 파일이 있다. 32 비트 파일과 64비트 파일 둘 다 지원한다. 또한 static, share, dev 용으로 구분 되어져 있는데 share과 dev용을 다운 받는다.

dev 압축파일을 적당한 위치에 압축을 풀어준다. 그리고 share 압축 파일에서 BIN 디렉토리의 DLL만 선택 후 dev 압축이 풀린 폴더에서 lib 폴더에 복사한다. dev 압축 파일에 include 파일과 lib 파일만 공개하고 실행시 필요한 DLL 파일은 share에서 포함되어 있기 때문이다.

개발환경

Visual Studio에서 ffmpeg 라이브러릴 사용하기 위해서 프로젝트 속성설정에서 DEV 압축 풀린 폴더에서 include 및 DLL 파일을 설정한다.

프로젝트 속성->C/C++ –>일반 –> 추가 포함 디렉토리 –> DEV 압축 푼 폴더/include

그리고 관련 lib 연결 소스 코드 Stdafx.h에서 연결했다.

   1: #include "ffmpeg/include/libswscale/swscale.h"
   2: #include "ffmpeg/include/libavformat/avformat.h"
   3: #include "ffmpeg/include/libavformat/avio.h"
   4: #include "ffmpeg/include/libavcodec/avcodec.h"
   7:  
   8: #pragma comment( lib, "ffmpeg/lib/avcodec.lib")
   9: #pragma comment( lib, "ffmpeg/lib/avformat.lib")
  10: #pragma comment( lib, "ffmpeg/lib/avutil.lib")
  11: #pragma comment( lib, "ffmpeg/lib/swscale.lib")

영상 디코딩

FFMPEG을 사용하기 위해선 인코딩 이던 디코딩이던 라이브러리를 초기화가 필요하다. 전체 프로그램에서 한번만 호출 해 주면 된다.

avcodec_register_all();

예전 버전에는 avcodec_init(); 함수도 있었지만 버전업 되면서 이 함수가 없어졌다.

영상을 디코딩 하기 위해서

AVCodec 과 AVCodecContext 두 변수가 필요하다.

AVCodec은 어떤 코덱(MPEG, MJPEG, H264)인지를 나타내는 정보를 가지고 있으며

AVCodecContext는 스트림을 어떻게 디코딩 할 것인지 정보를 가지고 있다.

이 두 변수를 각각 전역이든 멤버 변수든 선언해 준다.

   1: AVCodec *                m_codec;
   2: AVCodecContext *        m_context;

그리고 각 변수를 초기화 해준다.

   1: m_codec = avcodec_find_decoder(CODEC_ID_H264);
   2: m_context = avcodec_alloc_context3(m_codec);
   3: avcodec_get_context_defaults(m_context);

avcodec_find_decoder(CodecID)

이 함수는 디코딩 코덱을 선택해서 m_codec을 초기화 해준다. CodecID는 avcodec.h에 정의되어 있다. 여기선 H264 코덱을 선택했다.

avcodec_alloc_context3(AVCodec*)

이 함수는 m_context 변수를 초기화 해주는 함수로 AVCodec 정보를 가지고 context를 메모리에 할당한다. 계속 FFMPEG이 버전업 되면서 끝에 숫자가 증가하는 것 같다. 이유? 나도 모른다. 검색해서 찾아 볼 여유가 없었다. ㅋㅋ

avcodec_get_context_defaults(AVCodecContext*)

context 변수를 초기화 하는 함수이다. 호출 안해도 상관 없다. 왜??? avcodec_alloc_context3에서 초기화가 이루어 졌으니까..

초기화가 되었으니까, 이제 어떤 영상 스트림을 디코딩 할것인지 직접 입력해 줘야 한다.

   1: m_context->width = 320;
   2: m_context->height = 240;
   3: m_context->codec_id = CODEC_ID_H264;
   4: m_context->codec_type = AVMEDIA_TYPE_VIDEO;
   5: m_context->flags2 |= CODEC_FLAG2_FAST;
   6: m_context->thread_count = 0;

 

width, height는 320, 240이고, 코덱은 H264, AVMEDIA_TYPE_VIDEO로 비디오를 디코딩 한다는걸 설정하고 CODEC_FLAG2_FAST는 그냥 샘플 코드에 있어서 넣어 봤다. 빼도 상관 없다. thread_count 몇개의 쓰레드로 디코딩 하는지 명시하는 부분인데, 나도 모르겠다. 저 값에 10을 넣으나 1을 넣으나 속도는 같은 것 같은데 왜 적용이 안 되는지 모르겠다. 0은 라이브러리가 알아서 쓰레드 만든다 라고 FFMPEG 문서에 있다. 이것도 제대로 되는지 모르겠다. 흠흠…

설정 정보 다 입력했으면 최종 FFMPEG 라이브러리를 설정해야 한다.

   1: avcodec_open2(m_context, m_codec);

요렇게만 하면 이제 디코딩 하기 위한 준비는 다 끝났다.

이제 스트림 데이터가 있는 변수를 ffmpeg 라이브러리에 넘겨 주고 디코딩 시키면 된다.

   1: AVPacket avpkt;
   2:  
   3:  
   4: av_init_packet(&avpkt);
   5:  
   6: avpkt.data = pEncodedData;
   7: avpkt.size = EncodedSize;

예전 버전에서는 스트림 데이터를 바로 이용했는데 어느 순간 버전업 되면서 AVPacket을 이용해야 했다. av_init_packet으로 초기화 해주고 data와 size에 스트림 데이터, 데이크 크기를 전달 한다.

   1: int got;
   2: int ret;
   3: AVFrame *m_picture;
   4:  
   5: m_picture = avcodec_alloc_frame();
   6:  
   7: ret = avcodec_decode_video2(m_context,m_picture, &got, &avpkt );
   8:  
   9: if( got > 0 )
  10: {
  11:   ...........
  12: }

 

깜박하고 디코딩시 디코딩 된 이미지 데이터를 저장할 변수를 초기화 하지 않았다. ^^ avcodec_alloc_frame()으로 이미지 변수를 할당 받는다.

avcodec_decode_video2(AVContext*, AVFrame*, int *, AVPacket *)

스트림 데이터를 이미지로 변환하는 함수다. 만약 이미지로 변환이 성공하면 got 변수에 0 보다 큰 값을 리턴한다.



영상 인코딩

초기화 기본은 디코딩과 같다. 다만 avcodec_find_decoder가 avcodec_find_encoder로 변경되었을 뿐이다.

   1: m_codec = avcodec_find_encoder(CODEC_ID_H264);
   2: m_context = avcodec_alloc_context3(m_codec);
   3: avcodec_get_context_defaults(m_context);

그리고 압축을 하기위한 설정을 context에 입력한다.

   1:  
   2: m_context->bit_rate = 512*1024;
   3: m_context->bit_rate_tolerance = 512*1024*2;
   4: m_context->width = 320;
   5: m_context->height = 240;
   6: m_context->time_base.num = 1;
   7: m_context->time_base.den = 24;
   8: m_context->gop_size = 24;
   9: m_context->profile = FF_PROFILE_H264_BASELINE;
  10: m_context->pix_fmt = PIX_FMT_YUV420P; 
  11:  
  12: av_opt_set(m_context->priv_data, "preset", "fast", 0);
  13: av_opt_set(m_context->priv_data, "profile", "baseline", 0);
  14:  
  15:  

압축될 영상은 320, 240 사이즈로 설정하고 비트레이트는 512kbps 로 설정했다. 또한 FPS 24, GOP는 24다. 그리고 압축을 H264로 하기 때문에 프로파일은 BASELINE. 예전 버전에서는 없었는데 버전업 되면서 생긴 av_opt_set 으로 preset, profile을 설정했다. 뭐 이렇게 써라고 하니 썼다… 정확하지는 않는다. 그냥 인터넷에서 긁어 모으다 보니~~ ㅋㅋㅋ

   1: AVFrame* m_picture;
   2:  
   3: m_picture = avcodec_alloc_frame();
   4:  

디코딩과 마찬가지로 인코딩도 AVFrame 으로 영상 데이터를 입력 받는다. 그래서 AVFrame을 할당 받는다.

인코딩은 보통 YUV420P 로만 입력 받는다. RGB24로 입력 해봤지만 계속 실패해서 성공해 본적이 없다. 버전업 되면서 지원 되는지 모르겠지만.. 예전에는 안 되었다. 그래서 영상 데이터를 YUV420P 로 변환 해 줘야 한다.

   1: struct SwsContext *    m_convert_ctx;
   2: AVPicture dstpic;
   3: AVPicture srcpic;
   4:  
   5: m_convert_ctx = sws_getContext( 320, 240, PIX_FMT_BGR24, 320, 240, PIX_FMT_YUV420P, SWS_FAST_BILINEAR | SWS_CPU_CAPS_MMX, NULL, NULL);
   6:  
   7: avpicture_fill(&dstpic, m_YUVImageData, PIX_FMT_YUV420P, 320, 240);
   8: avpicture_fill(&srcpic, m_RGBImageData, PIX_FMT_BGR24, 320, 240);
   9:  
  10: sws_scale( m_convert_ctx, srcpic.data, srcpic.linesize, 0, 240, dstpic.data, dstpic.linesize);
  11:  

변환을 위해서 sws_getContext() 함수를 이용한다. sws_getContext()함수로 변환 핸들을 얻은 후 이 핸들을 sws_scale()로 실질적인 변환 작업을 한다. sws_getContext는 소스의 가로, 세로, 파일포맷, 변환할 가로, 세로, 포맷을 지정한다. sws_scale 함수는 소스 AVPicture를 변환 AVPicture로 변환해 준다. ㅋㅋㅋ 뭔 소린지 ㅋㅋㅋ

BGR24를 YUV420P로 변환 하였다. 이제 인코딩 하면 된다.

   1: int encoded_size = 0;
   2: BYTE *ENCODED_DATA = 1024*1024;
   3: int ENCODED_SIZE = 1024*1024;
   4:  
   5: avpicture_fill((AVPicture*)m_picture, m_YUVImageData, 320, 240);
   6:  
   7: encoded_size = avcodec_encode_video(m_context, ENCODED_DATA, ENCODED_SIZE, m_picture);
   8:  

자 요렇게 인코딩 하면 된다.

 

 

'프로그래밍 > C | C++' 카테고리의 다른 글

UPNP 프로토콜  (0) 2013.08.21
클래스 private, protected, public 멤버 및 상속  (0) 2013.08.14
클래스 가상함수, 순수가상함수  (0) 2013.08.14
윈도우 malloc  (0) 2013.08.14
RTPLIB 1.0b 라이브러리 예제  (0) 2013.08.14

  많은 시간을 이것저것 미리 살펴 보느라 오래 걸렸다. 처음에 너무 대책 없이 시작하다 보니 이것저것 삐걱거리고 다시 학습해야 할 내용들이 많아서 당분간 쉬었다(라고 쓰고… 그냥 귀찮아서 놀았다…) 음.. 기존 내 BaramOS(바람OS)는  부트로더 에서 커널로더를 거친 후 커널로 진입하는 방식으로 구현되어 있다. VC2010에서 커널을 만들다 보니 부트로더에서 바로 호출 할 수 없었다. 이유는 http://manggong.org/blog/64 에 간략하게 적은 내용대로 VC2010컴파일러가 32비트 컴파일러 이기 때문이다. (NASM의 obj 파일을 VC에서 링크를 할 수 없다라는게 이유. MASM으로 하면 된다는데… 것도 문제.. 흠..) 또 VC 컴파일러다 보니 PE 파일 포맷이다. (요것도 따로 배워야 한다… 메모리 재배치라나 뭐래나~~). 뭐 인터넷에 검색해 보니 부트로더에서 바로 커널 불러들이는 OS들도 있는 것 같지만..그냥 이대로 갈 생각이다.




리얼모드


부트로더, OS_LDR.SYS는 현재 16비트에서 동작한다. 요걸 달리 리얼 모드 라고 한다고 한다. 이름도 참… 리얼이 뭐냐!~~ 그럼 가상모드 도 있단 말이냐?? ㅋㅋ 헉… 검색해보니 가상모드 도 있다. 흠흠흠…. 여튼. 리얼모드는 리얼 주소 모드 또는 호환 모드 라고 하며, 80286이후의 x86호환 CPU 운영 방식이라고 위키백과 에서는 설명하고 있다. 뭔 소리냐??? 좀더 더 쉽게 풀어 달란 말이다. 흠… 검색하자… 리얼모드는 호환 모드라고 한다고 했다. 즉, 80286 이전에 CPU를 호환하기 위한 모드 라고 한다. 왜 호환 하는 거야?? 기존 만들어 놨던 소프트웨어 지원할라고??? 몰겠다. 다시 리얼모드로 돌어와서 리얼 모드라는 이놈은 1M 이상의 메모리를 접근 할 수 없단다. 왜??? 만들 당시 1M도 큰 메모리였단다.. ㅎㅎㅎ  1M이기 때문에 메모리에 액세스 할 주소는 20비트면 충분 했나 보다…(에효 좀더 크게 좀 잡아놓지…. 20비트가 뭐냐?? 계산하기 빡 세게…) 20비트로 주소를 계산했단다… 어떻게??? 세그먼트:오프셋 요렇게 말이다. 근데 세그먼트 크기는 16비트이다. 20비트에서 4비트 부족하다. 그래서 오프셋 이란 놈을 더 뒀다. 그러면 오프셋이 나머지 4비트일까??? 아니다. 요놈도 16비트다. 그럼 32비트 쓴다는거?? ㅋㅋ 그랬으면 얼마나 좋았을 까? 흠. 졸라 복잡한 계산식으로 20비트 만든단다. 졸라 어렵다. 세그먼트를 왼쪽으로 4비트 시프트 시키고 거기에다가 오프셋을 더해 준단다.  예를 들어.. 세그먼트 = 0x0700와 오프셋 0x0C00 라면 세그먼트 4비트 왼쪽 시프트해서 0x7000 만들고 여기에 0x0C00 더해 주는 방식이다.


  0 7 0 0 0
+   0 C 0 0
    7 C 0 0


이렇게 졸라 복잡한 계산식(?)으로 1M까지 메모리를 접근했다.


근데 내가 초딩 때 봤던 도스가 리얼모드에서 동작했다고 한다. 여기서 급 궁금증이 생겼다. 도스라면 640KB밖에 여유 메모리가 없었던 기억이 있다. 게임 하나 실행 할라 치면, 메모리 상주 프로그램 다 지워줘야 했다. 1M라며????


1MB안에서 다시 요놈 저놈이 쓸 메모리 공간 다 주고 640KB가 남은 거란다. 640KB도 크게 느껴졌다고 하니~^^ ㅋㅋㅋ


0x00000000 - 0x0009FFFF   RAM
  0x00000000 - 0x000003FF 리얼모드용 인터럽트 벡터
  0x00000300 - 0x000003FF BIOS DATA 영역
  0x00000400 - 0x000004FF BIOS가 사용하는 영역
  0x00000500 - 0x00007BFF 임시영역? ( 자유 영역, 도스 파라미터 영역)
  0x00007C00 - 0x00007DFF 부트 영역
  0x00008000 – 0x0009FBFF 자유영역
  0x00080000 – 0x0009FBFF 자유영역
  0x0009FC00 - 0x0009FFFF 확장 BIOS DATA영역
0x000A0000 – 0x000BFFFF   비디오 카드 접근 영역
  0x000A0000 - 0x000AFFFF 그래픽 모드 비디오 메모리
  0x000B0000 – 0x000BFFFF 흑백 테스트 모드 비디오 메모리
  0x000B8000 –0x000BC000 컬러 텍스트 모드 비디오 메모리
0x000C0000 - 0x000C7FFF   비디오 BIOS
0x000C8000 – 0x000EFFFF   각종 카드의 ROM 영역
0x000F0000 – 0x000FFFFF   메인보드 BIOS

(출처 : http://wiki.osdev.org/Memory_Map_(x86))


요기 위 처럼 1M를 이넘 저넘 예약 되어 있다. 휴~ 리얼모드에서 플밍 하기 참~ 힘들었겠다는 생각이 단다. 뭐~ 힘들게 만든 프로그램 컴퓨터에서 실행시키기도 힘들었다. 무슨 설정을 그리 해야 했는지..




보호모드


80286 맹글면서 메모리 버스를 24bit로 늘렸단다. 뭔 말이냐면 메모리를 24bit 만큼 쓸 수 있다는 것이다. 16M인데?? (참~~~ 많기도 하다..ㅋ ) ㅎㅎ 80386에서는 요게 32bit로 늘렸단다. 그래 드뎌 4GB 메모리가 참조 가능해진다. 뭐, 요즘 요것도 적은 편이지만… 64비트가 나온판에 ㅋㅋㅋ 여튼. 보호모드에서는 세그먼트 레지스터를 세그먼트 셀렉터(selector)라는 이름으로 바꾸었단다. 왜 바꾸냐?? 머리 아프게 시리.. 세그먼트 레지스터는 물리 주소 자체를 가르키는 역활을 했으나 보호모드에서는 세그먼트 셀렉터 이놈은 주소 공간을 가르키는 게 아니고 디스크립터 테이블의 디스크립터 이라는 놈 자체를 가르킨다고 한다. 그럼 디스크립터 테이블 이놈이 무엇이냐?? 8바이트로 구성된 코드 세그먼트 디스크립터, 데이터 세그먼트 디스크립터 등등의 세그먼트 디스크립터를 모아 놓은게 디스크립터 테이블이란다. 이 중 한놈을 세그먼트 셀렉터 라는 놈이 가르킨단다. 딱 보니, 디스크립터 구조체 배열의 인덱스 번호를 세그먼트 셀렉터가 가르키는 꼴이다. 흠..디스크립터 구조는 다음과 같다.

 

겁나 복잡하게 생겼다. 저 Base Address 주소 가르키는 구조좀 봐라 … 아주 조각을 내놨다. 얼씨구 Segment Limit도 조각 냈네.. 요거 설계 하는 사람이 머리에 스팀 좀 받았겠다… 그러니 요거 배우는 나는 더 스팀 받을 수 밖에…ㅋㅋㅋㅋ 여튼 우습다. 흠.. 각 설명은 요렇다는 데..

  • Base Address : 세그먼트의 시작 주소를 가르킨다. 32비트 크기이므로 0~4GB까지 가능하다.
  • Segment Limit : 세그먼트의 크기를 나타낸다. 20비트의 크기로 0~1MB까지 가능하다. G 비트가 1로 설정되면 4KB를 여기에다 곱해준단다. 그럴 경우 0~4GB까지 설정 가능하단다.
  • Type : 4비트 크기로 세그먼트의 타입을 지정한단다. 코드 또는 데이터 로 말이다. 근데 여기에 읽기 쓰기 권한을 비트로 준단다. (엠병)
    0bit 1bit 2bit 3bit Type 설명
    0 0 0 0 0 데이터 데이터 세그먼트 읽기 전용
    1 0 0 0 1 데이터 데이터 세그먼트 읽고/ 접근 여부 (접근 여부 : 디스크립터가 참조 될 때 1로 설정함)
    2 0 0 1 0 데이터 데이터 세그먼트 읽고/쓰기
    3 0 0 1 1 데이터 데이터 세그먼트 읽고/쓰기/접근 여부
    4 0 1 0 0 데이터 데이터 세그먼트 읽기 전용/역방향 (역방향: 스택처럼 상위 주소에서 하위 주소로 자라남)
    5 0 1 0 1 데이터 데이터 세그먼트 읽기 전용/역방향/접근여부
    6 0 1 1 0 데이터 데이터 세그먼트 읽고/쓰기/역방향
    7 0 1 1 1 데이터 데이터 세그먼트 읽고/쓰기/역방향/접근 여부
    8 1 0 0 0 코드 코드 세그먼트 실행 전용
    9 1 0 0 1 코드 코드 세그먼트 실행전용/접근 여부
    A 1 0 1 0 코드 코드 세그먼트 실행/읽기
    B 1 0 1 1 코드 코드 세그먼트 실행/읽기/접근여부
    C 1 1 0 0 코드 코드 세그먼트 실행 전용/접근가능 (접근가능: (권한에 관계없이 모두 접근 가능)
    D 1 1 0 1 코드 코드 세그먼트 실행 전용/접근가능/접근 여부
    E 1 1 1 0 코드 코드 세그먼트 실행/읽기/접근 가능
    F 1 1 1 1 코드 코드 세그먼트 실행/읽기/접근 가능/접근여부
  • S : 디스크립터 타입을 가르키신단다. 0 일때는 시스템 디스크립터, 1일 때는 걍 디스크립터(코드, 데이터)
  • DPL : 2비트로 세그먼트 접근 권한(? 요건 또 뭐냐..) 을 주신단다. 주로 0은 커널, 3은 유저(?) 접근 가능 하단다.
  • P : 페이징과 관련 있단다.
  • AVL : 임의 용도란다.
  • L : 64비트 코드 세그먼트인지, 32비트인지를 의미한단다. 1: 64, 0:32비트 ..
  • D/B : 16비트용인지 32비트용인지를 의미한단다. 1: 32비트, 0: 16비트
  • G : Segment Limit에 4Kb를 곱할지 안 곱할지.


빡시다… 겁나 복잡(?) 하다. 뭐가 이리 복잡하게 해 놓은 건지 원… 여튼, 세그먼트 셀렉터는 이놈들 중 하나의 인덱스 번호를 가지고 있고 해당 디스크립터가 가리키는 메모리에 접근 한단다. 뭐, 메모리 조각조각 내서 사용하는 페이징은 사용하지 않으면 실제 물리 주소고 여기에 페이징을 사용한다면 그냥 가상 주소라고 한단다. 뭐 페이징은 아직까지 사용 할 생각 없으니, 물리 주소라고 봐도 무관하겠다.


흠 근데 이런 디스크립터 테이블이 3종류나 있단다. 아놔!~~~~ 보호모드 겁나 짜증나는 놈이네…


GLOBAL DESCRIPTOR TABLE 이라는 GDT 라는 놈하고

LOCAL DESCRIPTOR TABLE 이라는 LDT 라는 놈하고

INTERRUPT DESCRIPTOR TABLE 이라는 IDT 테이블


 요렇게 3개 있단다. 그러면 왜 3개씩이나 있는 이유가 뭐냐??? 물리주소를 가르키는 얘라며~!!!!!!!!!!!!!!! 이런게 3개 있을 필요가 있냐고!!!!~~~휴~~ 진정해야지…


GDT : 전역 디스크립터 테이블이라고 하며 커널에서 사용하기 위해서 구성된다고 한다.
LDT : 지역 디스크립터 테이블이라고 하며 보통 어플리케이션 에서 사용하기 위해 구성된다고 한다.
IDT :  각 인터럽트를 사용하기 위해 구성된다고 한다.


GDT와 LDT 디스크립터 구성은 동일 한단다. (그래~~~)  위에 표기한 디스크립터 구조다.

IDT 구조는 다음과 같다.


  • 세그먼트 셀렉터(2바이트) : GDT 던 LDT 던 이 놈들이 가지고 있는 디스크립터 인덱스로 설정한다.
  • 오프셋(4바이트) : 디스크립터에 설정된 Base Address에서 + 오프셋 한 값.
  • P(1비트) : 디스크립터가 물리 메모리 영역인지 아닌지 여부를 나타내는 것 같은데 1로 설정하란다. 0은 사용안하거나 Paging 처리 할때.
  • DPL(2비트) : 특권 레벨이라는데 디스크립터의 DPL 처럼 접근권한. 즉 0은 커널, 3은 유저. 요것도 0.
  • S(1비트) : 요건 모르겠다. 그냥 0 .
  • Type(4비트) : Gate 타입을 입력.
    0101 0x5 5 80386 32bit Task Gate
    0110 0x6 6 80286 16bit Interrupt Gate
    0111 0x7 7 80286 16bit Trap Gate
    1110 0xE 14 80386 32bit Interrupt Gate
    1111 0xF 15 80386 32bit Trap Gate

    Interrupt Gate : 하드웨어 인터럽트를 위해 만들어 진 넘.
    Trap Gate : 소프트웨어 인터럽트나 Exception 처리하기 위해 만들어 진 넘
    Task Gate : Task Switching을 위해 만들어 진 넘.

IDT는 Interrupt vector table을 구현해 놓은 놈인데
- 소트트웨어 인터럽트
- 하드웨어 인터럽트
- 프로세서 Exception

의해 사용 된단다.


정리하믄 IDT는 GDT나 LDT에 있는 디스크립터가 가리키는 메모리 세그먼트에서 offset 만큼 위치의 함수를 호출하나 보다.

아흐… 겁나 머리 아프다. 뭐 이리 알아야 할게 많아~~~!!!!


흠…위에서 IDT에서 언급한 특권 레벨, 디스크립터의 접근 권한 요거에 대해서 더 살펴 봤더니 0,1,2,3 요렇게 권한등급이 있는데 0은 코널 수준이고 3은 유저(어플리케이션)에서 접근 할 수 있는 없는지를 나타내는 거란다. 보통 0과 3만 쓴다고 한다. 현재 불리우는 놈의 권한 기록은 세그먼트 셀렉터에 있다고 한다. 엥?? CS, DS, ES, FS 에???

세그먼트 셀렉터의 구조다. 보호모드로 바뀌면서 저렇게 구조를 나눠서 사용하나 보다. Index는 디스크립터 테이블에서 인덱스 값 이고 TI  요놈은 GDT(0) 테이블을 참조 할 것인가 LDT(1) 테이블을 참조 할 것인가 구분자이고 RPL 요놈이 접근하는 특권 레벨 을 가지고 있는 놈이란다. 요넘이 3인데 권한이 0인 놈을 접근 하면 CPU는 에러를 뱉어 낸다는군.

자!!~~~ 이제 또 뭘 알아야 하냐???




GDTR, LDTR, IDTR


흠… 힘들다..귀찮고…

보호 모드 진입을 하기 위해선, GDT는 이미 구성되어 있어야 한다고 한다… 메모리에 그냥 만들어 놓으면 되나??? 아니다. GDT를 읽는 레지스터가 있다고 한다. 이름하야 GDTR 이 레지스터에 GDT 테이블을 메모리 위치를 넘겨 주면 된다고 한다. 그럼 그냥 mov GDTR, [메모리 위치] 냐… 것도 아니다. lgdt 라는 어셈 명령어를 써서 넘겨야 한다. 또한 lgdt 명령어에 테이블 메모리 위치가 아니고 GDT의 총크기와 메모리 위치를 넘겨야 한단다.

struct
{
    unsigned short GDT_Size;
    unsigned int GDT_Address;
} GDT_INFO

lgdt GDT_INFO

요런 방식으로 명령을 주면 GDTR 레지스터에 GDT가 들어간다고 한다. 흠… 그래 그리고 ? 또… GDT 구성에 있어서 첫번째 인덱스는 NULL 이여야 한다고 한다. NULL??? 아무값도 없이 0으로 채워주면 된다고 한다. 사용되지 않는다는데 뭘…흠… 왜?? 모르겠다. 그냥 그래 라고 하니 그래야지. 하지만 또 성실하게 검색 질  해봤더니 “초기화 과정에서 사용되지 않은 레지스터를 통해 메모리에 접근하려는 시도를 막기 위함이다.” 라고 하네…뭔 소리인지..흠.


LDT를 LDTR에 입력하려면, 우선 GDT에 LDT에 관한 디스크립터를 만들어야 한다고 한다. 왜??? LDT는 전역이 아니고 프로세스에 대응하는 테이블이기 때문에 GDT와 같은 속성이 아니란다. (뭐… 맥락은….) 그래서 LDT 위치를 GDT에 우선 등록해주고 GDT의 인덱스값을 LDTR에 입력해 주면 지가 알아서 세그먼트 위치, 크기, 속성까지 로딩한다고 한다. (편하네~~~)


mov ax, GDT내의 LDT인덱스값
lldt ax


IDTR에 IDT를 넣는 방법은 GDTR과 유사하다.

struct
{

   unsgined short IDT_SIZE; (256*8-1)
   unsigned int IDT_ADDRESS;
}


256에다가 8(IDT 디스크립터 크기)을 곱하고 빼기 1을 해준단다.
근데 256개의 디스크립터를 모두 생성해 줘야 하는지 모르겠다. 그러니까 256이 아닌 10개만 등록하면 안 되는지 말이다. 흠…뭐 해봐야 알겠지.




A20 GATE


여기까지 세그먼트 셀렉터로 시작해서 디스크립터 테이블, GDTR,LDTR,IDTR까지 꼬리를 물고 배워왔다. 뭐~~ 더 뭐가 필요한지 모르겠지만.. 완전 지친다. 왜 이리 처리해 줘야 하는 게 많은지..

아… 인터넷에서 보호 모드 검색하다 보니 A20 GATE 라는 놈이 등장했다.

앞에서 80286에서 20비트 에서 24비트로 메모리 버스가 늘었다고 했다. 16MB까지…. 헌데 리얼모드에서는 20비트 밖에 못쓴다. 세그먼트:오프셋 이 방식 때문에 말이다. 기존 8086/8088 요넘들은 0xFFFF:000F 까지 메모리를 액세스 할 수 없었다. 왜?? 20비트 넘어가면 이상한 주소가 참조해 버리니 원하는 위치를 벗어나 버리기 때문이다. 암만 0xFFFF:1234 주소로 입력한들 .. 0x1224 주소로 접근한다. 메모리 접근은 20비트니 20비트 이상은 다 없어지는 거지. 근데 80286은 24비트다. 뭔말이냐 하믄  0xFFFF:1234를 주면 0x101224로 접근이 되서 메모리를 읽고 쓸 수 있다는 말이다. 호~~~ 그럼 16M까지 접근 가능하겠네?? 라고 생각했다가 세그먼트:오프셋 주소 지정 방식을 생각해 보니, 웃음만 나왔다. ㅋㅋㅋ  0xFFFF:FFFF = 0x10FFEF 까지만 가능하다. ㅋㅋㅋㅋㅋ 메모리 버스를 24비트로 늘려도 쓸 수 없다 ㅋㅋㅋㅋ 기존 0xFFFFF에서 64kb 정도만 더 쓸 수 있다. ㅋㅋ 아놔 것도 많다면 많은 거지만.. 왜케 웃기지 ㅋㅋㅋㅋ

여튼 0x10FFEF까지 주소의 64kb 메모리라도 더 쓸라면 A20 GATE 요놈을 이용해야 한다. 요놈이 Keyboard 컨트롤러에 달려 있는 요놈을 ON 시켜야 쓸 수 있단다. 왜 키보드 컨트롤러 일까? 라고 의문이 들었지만 오늘 많은 검색질로 인해서 지쳤다….여튼 요놈을 ON 시켜주면 쓸 수 있다고 한다.. 어떻게??? 메모리 버스 21비트에 A20과 AND 연산기와 묶여 있단다. 그래서 A20 GATE에 ON(1)이 되어 있어야지만 21비트에 접근 가능하단다. 왜 굳이 A20 GATE를 달아서 AND 연산 시키는 괴상망측한 짓이 진행되었는지는…..모르겠다. 여튼, 요넘 때문에 보호모드에서 메모리 접근 할 때 문제가 생긴다. 왜??? A20 GATE가 ON이 안되어 있으면 21비트는 항상 0 이기 때문에…. 그래서 보호 모드 넘어 갈 때는 항상 A20 GATE를 켜고 진입해야 한다고 한다.

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

OS 만들기 #13 보호모드 진입  (0) 2014.03.27
OS 만들기 #12 - PE 파일 포맷  (0) 2013.09.10
OS 만들기 #10  (0) 2013.08.14
OS 만들기 #9 - 부트로더  (0) 2013.08.14
OS 만들기 #8 - 부트로더  (0) 2013.08.14


헥사 에디터로 부트로더 복사하는게 영.. 수동이라.. 자동으로 가상 파일에 복사하는 프로그램을 만들었다.

첫 섹터에 파티션 정보를 읽어서 해당 FAT을 찾고 거기에 기존 BPB를 건너뛰고 부트코드만 복사하는 코드이다. 생각 같아선 디스크 관리 에서 이미지를 마운트 시켜서 드라이브로 바로 입력하고 싶었으나, Windows 7 에서 볼륨이 있는 부트섹터 접근을 막았다고 하나.. 어쨌듯 하는 방법을 몰라서 그냥 파일을 읽어서 쓰는 방법으로 작성했다.


// 2013. 08. 02
// mangg@manggong.org
// 
// 
// 가상이미지 파일에 부트로더 복사.
// 할일 없어서 이거나 주물럭 주물럭.

#include "stdafx.h"
#include <Windows.h>

#define PARTITION_POS	0x1BE

#pragma pack(push, 1)
typedef struct _PARTITION                                                                          
{                                                                                                  
	BYTE ACTIVE;	// 부팅 가능 플래그 (0x80 : 부팅 가능, 0x00 : 부팅 불가)
	BYTE CHS_BEGIN[3];
	BYTE TYPE;		// 파티션 타입 ( 0x07:NTFS, 0x05:Extended Partition, 0x0B:FAT32, 0x0E:FAT16, 0x0F:Extended Partition, exp INT 13h
					//				0x1B: Hidden FAT32, 0x42:Secure FileSystem, 0x82:Linux Swap partition, 0x83:Linux native file systems)
	BYTE CHS_END[3];
	INT32 LBA_START;
	INT32 LENGTH;
} PARTITION, *PPARTITION;

typedef struct _FAT
{
	BYTE CODE[3];
	CHAR SYSTEMID[8];
	WORD SECTOR_SIZE;
	BYTE SECTOR_CLUSTER;
	WORD RESERVED_SECTOR;
	BYTE FAT_NUM;
	WORD ROOT_DIRECTORY_ENTRY_CNT;
	WORD TOTAL_SECTOR16;
	BYTE MEDIATYPE;
	WORD SECTOR_FAT16;
	WORD SECTOR_TRACK;
	WORD SECTOR_HEAD;
	DWORD HIDDEN_SECTOR;
	DWORD TOTAL_SECTOR32;
	DWORD SECTOR_FAT32;
	WORD EXT_FLAGS;
	WORD VER;
	DWORD ROOT_DIRECTORY_CLUSTER;
	WORD INFO;
	WORD BACKUP_BOOT_SECTOR;
	BYTE RESERVED_ID[12];
	BYTE PHYSICALDISK_TYPE;
	BYTE RESERVED;
	BYTE SIGNATURE;
	DWORD VOLUMEID;
	BYTE VOLUME_LABLE[11];
	BYTE FILESYSTEM_TYPE[8];
} FAT32, *PFAT32;


#pragma pack(pop)

bool LoadBootloader(BYTE *pDiskInfo,TCHAR *BootloaderFile);
bool CopyBootloader(HANDLE hFile, __int64 sector, BYTE *pDiskInfo,TCHAR *BootloaderFile);
bool GetFAT(HANDLE hFile,  __int64 sector, BYTE *pDiskInfo, PFAT32 FAT_INFO);
bool GetActivePartition(HANDLE hFile, BYTE *pDiskInfo, PPARTITION PartitionInfo);
bool DISK_WRITE(HANDLE hFile, __int64 sector, BYTE *pData, int sectorSize);
bool DISK_WRITE_PARTICLE(HANDLE hFile, __int64 sector, DWORD offset, BYTE *pData, int writeSize);
bool DISK_READ(HANDLE hFile, __int64 sector, BYTE *pData, int sectorSize);

void Print_PhysicalList()
{
	TCHAR physical[65536];
	TCHAR logical[65536];

    QueryDosDevice(NULL, physical, sizeof(physical));
	printf("Physical Drive Number : \n");

    for (char *pos = physical; *pos; pos+=strlen(pos)+1) 
	{
        QueryDosDevice(pos, logical, sizeof(logical));

		if( strstr(pos, "Physical") != NULL )
		{
			printf("%s : %s\n", pos, logical);
		}
    }
}

int _tmain(int argc, _TCHAR* argv[])
{
	HANDLE hFile;
	TCHAR szDrive[MAX_PATH];
	DWORD	FAT_SECTOR = 0;
	BYTE	*pDiskInfo;
	PARTITION PartitionInfo;
	FAT32 FAT_INFO;

	if( argc < 3 )
	{
		printf("Usage : bootcopy bootloader VirtualFile\n\n");
		//Print_PhysicalList();
		return -1;
	}

	_stprintf(szDrive, _T("%s"), argv[2]);

	hFile = ::CreateFile(
						szDrive, 
						GENERIC_READ | GENERIC_WRITE, 
						FILE_SHARE_READ | FILE_SHARE_WRITE, 
						NULL, 
						OPEN_EXISTING, 
						0, 
						NULL);

	if( hFile == INVALID_HANDLE_VALUE )
	{
		printf("Don't read drive\n");
		return -1;
	}


	pDiskInfo = (BYTE*)malloc(0x200);
	if(pDiskInfo == NULL )
	{
		CloseHandle(hFile);
		printf("Don't Allocate Memory\n");
		return -1;
	}

	DWORD junk;
	BOOL bResult;


	char bs[255];
	memset(bs,0,255);

	/////////////////////////////////////////
	// READ PARTITION INFO

	if( GetActivePartition(hFile, pDiskInfo, &PartitionInfo) == false )
	{
		CloseHandle(hFile);
		free(pDiskInfo);
		printf("Don't Read Partition Info\n");
		return -1;
	}

	/////////////////////////////////////////
	// READ FAT32 INFO
	if( GetFAT(hFile, PartitionInfo.LBA_START, pDiskInfo, &FAT_INFO) == false )
	{
		CloseHandle(hFile);
		free(pDiskInfo);
		printf("Don't Read FAT Info\n");
		return -1;
	}

	/////////////////////////////////////////
	// COPY BOOTLOADER
	if( CopyBootloader(hFile, PartitionInfo.LBA_START, pDiskInfo, argv[1]) == false )
	{
		CloseHandle(hFile);
		free(pDiskInfo);
		printf("Don't Copy Bootloader\n");
		return -1;
	}

	printf("Copy Success...\n");

	free(pDiskInfo);
	CloseHandle(hFile);

	return 0;
}

bool DISK_READ(HANDLE hFile, __int64 sector, BYTE *pData, int sectorSize)
{
	DWORD read = 0;

	LARGE_INTEGER li;

	li.QuadPart = (0x200 * sector) & ~(0x200-1);

	if (::SetFilePointer(hFile, li.LowPart, &li.HighPart, FILE_BEGIN) == INVALID_SET_FILE_POINTER)
	{
		::GetLastError();
		return false;
	}

	if (!::ReadFile(hFile, pData, sectorSize, &read, NULL))
		return false;

	return true;
}

bool DISK_WRITE(HANDLE hFile, __int64 sector, BYTE *pData, int sectorSize)
{
	DWORD written = 0;

	LARGE_INTEGER li;

	li.QuadPart = (0x200 * sector) & ~(0x200-1);

	if (::SetFilePointer(hFile, li.LowPart, &li.HighPart, FILE_BEGIN) == INVALID_SET_FILE_POINTER)
	{
		::GetLastError();
		return false;
	}

	if (!::WriteFile(hFile, pData, sectorSize, &written, NULL))
		return false;

	return true;
}

bool DISK_WRITE_PARTICLE(HANDLE hFile, __int64 sector, DWORD offset, BYTE *pData, int writeSize)
{
	DWORD written = 0;

	LARGE_INTEGER li;

	li.QuadPart = (0x200 * sector) & ~(0x200-1);
	li.LowPart += offset;

	if (::SetFilePointer(hFile, li.LowPart, &li.HighPart, FILE_BEGIN) == INVALID_SET_FILE_POINTER)
	{
		printf("SetFilePointer error : %d\n",::GetLastError());
		return false;
	}

	if (!::WriteFile(hFile, pData, writeSize, &written, NULL))
	{
		return false;
	}

	return true;
}

bool GetFAT(HANDLE hFile,  __int64 sector, BYTE *pDiskInfo, PFAT32 FAT_INFO)
{
	DWORD read = 0;

	if(DISK_READ(hFile, sector, pDiskInfo, 0x200) == false )
	{
		return false;
	}

	memcpy(FAT_INFO, pDiskInfo, sizeof(FAT32));

	return true;
}

bool GetActivePartition(HANDLE hFile, BYTE *pDiskInfo, PPARTITION PartitionInfo)
{
	PPARTITION PartitionTemp = NULL;

	if( DISK_READ(hFile, 0, pDiskInfo, 0x200) == false )
		return false;

	for(int i = 0; i < 4; i++ )
	{
		PartitionTemp = (PPARTITION) (pDiskInfo + PARTITION_POS + (sizeof(PARTITION) * i));

		if(PartitionTemp->ACTIVE == 0x80 )
		{
			memcpy(PartitionInfo, PartitionTemp, sizeof(PARTITION));
			return true;
		}
	}

	return false;
}

bool CopyBootloader(HANDLE hFile, __int64 sector, BYTE *pDiskInfo,TCHAR *BootloaderFile)
{
	if( LoadBootloader(pDiskInfo, BootloaderFile) == false)
		return false;

	if( DISK_WRITE_PARTICLE(hFile, sector, 0, pDiskInfo, 3) == false )
	{
		printf("ERROR : Copy \"BOOT JMP CODE\" to DISK\n");
		return false;
	}

	pDiskInfo += 0x5A;
	if( DISK_WRITE_PARTICLE(hFile, sector, 0x5A, pDiskInfo, 0x200-0x5A) == false)
	{
		printf("ERROR : Copy \"BOOT CODE\" to DISK\n");
		return false;
	}

	return true;
}

bool LoadBootloader(BYTE *pDiskInfo,TCHAR *BootloaderFile)
{
	HANDLE hBOOTFile;
	DWORD dwReadn;

	hBOOTFile = ::CreateFile(
						BootloaderFile, 
						GENERIC_READ | GENERIC_WRITE, 
						FILE_SHARE_READ | FILE_SHARE_WRITE, 
						NULL, 
						OPEN_EXISTING, 
						0, 
						NULL);

	if( hBOOTFile == INVALID_HANDLE_VALUE )
	{
		printf("Don't open bootloader\n");
		return false;
	}

	if(!::ReadFile(hBOOTFile, pDiskInfo, 0x200, &dwReadn, NULL))
	{
		printf("Don't read bootloader\n");

		CloseHandle(hBOOTFile);
		return false;
	}
	if(dwReadn != 0x200)
	{
		printf("Is not match bootloader size\n");
		CloseHandle(hBOOTFile);
		return false;
	}

	CloseHandle(hBOOTFile);
	return true;
}

개발 환경

Windows 7
Visual Studio 2010

잘되면 내탓, 잘 못되면 조상탓.

 

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

OS 만들기 #12 - PE 파일 포맷  (0) 2013.09.10
OS 만들기 #11 - 리얼모드 & 보호모드  (0) 2013.08.14
OS 만들기 #9 - 부트로더  (0) 2013.08.14
OS 만들기 #8 - 부트로더  (0) 2013.08.14
OS 만들기 #7 - FAT32  (1) 2013.08.14

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

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

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


	; 헤드, 트랙, 섹터로 재 계산해서 저장하자.
	; 트랙 = 총섹터 / (헤드 갯수 * 트랙당 섹터수)
	; 헤드 = (총섹터/트랙 당 섹터수) % 헤드 갯수
	; 섹터 = (총섹터 % 트랙 당 섹터) + 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

자!! FAT32 파일 시스템 분석해서 파일 찾아서 읽어 오는 것까지 다 했다.!! 이제 부트로더에서 파일 찾아서 메모리에 로딩 시키는 일만 남았다. 참 험난한 길이었다. 블로그에 글로 적는 것도 험난했고, 공부하는 것도 험난했다. 뭐 이제 코딩만 남았으니~~

부트로더는 또 어떻게 만드냐!~~~ 아~ 험난하다!!

흠… OS 하나 만들기 정말 힘들다.

우선, 부트로더는 NASM 으로 컴파일 하고 나머지 커널은 Visual Studio C++ 2010에서 컴파일 할 생각이다. 부트로더에서 바로 KERNEL.SYS 파일을 읽어 0x10000에 로딩하고 실행시킬 생각이다.

하지만, 안 된단다.. Visual studio 2010에서 커널 작성 컴파일 방법을 검색 하던 중 부트로더 => KERNEL.SYS (Visual Studio C++)로 실행 시킬 수 없다고 한다!!!!!!!!!


왜!!!!!!!!!! 왜 왜! ??? 왜 안되냐고!~~~~~~~~~~~~~~~~~~


OS 개발 안 해!!!!

ㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠ

에고고고고..


실은 부트로더 에서 KERNEL.SYS를 호출 해서 실행 시킬 수는 있단다. 다만 리얼 모드 에서 보호 모드로 전환 하고 실행 해야 한단다. 리얼 모드는 뭐고 보호 모드가 뭐길래? 안 된다는 것이냐??? 뭐 쏼라 쏼라 영어를 보니 16bit , 32bit가 언급된다. 아마 리얼 모드는 16비트이고 보호 모드는 32비트 인 모양이다. Visual Studio C++ 은 컴파일러가 32비트만 컴파일 된다고 한다. 16비트 컴파일 하려면 아~~~주 낮은 고대 유물과 비슷한 버전의 Visual Studio C++를 사용 하라고 한다.

아놔!~~~

OS만들려다 사람 잡겄다!!!

에고.. 보호모드 로 전환하고 KERNEL.SYS 호출하지 모… 근데 보호모드 예제 코드들을 보니..라인수가 장난이 아니다. 부트로더 512byte로는 어림도 없겠다. 아~~~~~ 미치겠다. VC++에서 인라인 어셈으로 처리해서 KERNEL.SYS 엔트리 포인터 위치에 보호모드 코드 작성 후 실제 커널 코드로 들어가는 부분 방법으로 작성해야 겠다.

아~~ 이 방법도 쉽지 않다. 쉽지 않아. 어렵다!!! 어렵다!!! 모르고 시작 할땐 의욕이 넘쳤는데 이젠 점점 하기가 싫어진다.

그냥 세 분류로 작성 하련다… 생각대로 하기에는 지친다..어렵고..

..

부트로더, 커널로더, 커널  이렇게 세 분류로 계획했다.


메모리 영역

구분

파일

역활

0x7C00

부트로더

BOOTLOADER

- 커널로더를 로드 하고 실행한다.

0x8000

커널로더

OS_LDR.SYS

- 커널 로드한다.
- 보호모드로 전환한다.
- 커널을 실행한다.

0x10000

커널

BARAM.SYS - 커널 실제 동작


꺼림직한 구조지만, 그냥 이렇게 타협?을 봤다. 커널로더에서 공부 할게 많지만 우선은 부트로더가 제일 문제다. ㅠㅠ 아직까지 부트로더에서 헤매고 있다니 ㅠㅠ




- FAT32 부트로더


음…기존 코드에서 BPB 영역(FAT32 정보) 부분만 더 붙여 주고 OS_LDR.SYS 파일 찾아서 로드하고 실행 하는 코드를 만들어야 한다. Baram 이미지 파일내의 0x10000 에서 각 값을 구해서 직접 입력했다.


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		; 2바이트 섹터당 바이트 크기.
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_FAT16_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


제길… 탭을 넣어서 보기 좋게 정리 했더니 여기에 붙여 넣기 하니 저리 되네.. 아 모르겠다.. 그냥 넘어가자.

스택 위치를 잡아야 하는데 0x8000에 커널 로더 OS_LDR.SYS를 로딩한다. 그러니까 0x8000으로 스택 주소를 잡는다. 뭐 스택은 아래로 감소하면서 포인터가 위치하니까 문제는 없을 것 같다. 그리고 부트로드 드라이버도 변수 하나 생성해서 저장해 주자!!


mov sp, 0x8000     ; 스택을 잡자
mov bp, sp           ; bp 도 0x8000으로 잡자.
mov [DRIVE], dl    ; 현재 드라이브 저장하자.


음…이제 FAT32 접근해야 하는데…..


1. 루트 디렉토리 엔트리 위치를 구하자.
2. FAT 테이블 위치를 구하자.
3. 클러스터당 몇 바이트 인지 구하자.
4. 현재 클러스터 위치를 저장하자.
5. 루트 디렉토리 엔트리 가서 파일명을 읽어서 OS_LDR.SYS를 찾는다.
6. OS_LDR.SYS의 데이터 클러스터로 이동해서 읽는다.
7. FAT 테이블 위치로 가서 OS_LDR.SYS 데이터가 더 있는지 확인한다.
8. 6,7번을 반복한다.
9. OS_LDR.SYS를 실행한다. (이동)


위와 같이 한번 설계해 봤다. 이 구조로 한번 코딩 해 봐야지~


	; 루트 디렉토리 엔트리 위치를 구하자.
	; 루트 디렉토리 엔트리 = (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 eax, [BPB_ROOT_ENTRY_CLUSTER]
	mov [CURRENT_CLUSTER], eax

; 변수를 선언하자.
DRIVE		db 0x00
ROOT_ENTRY	dd 0x00
FAT1		dd 0x00
CLUSTER_BYTES	dd 0x00
CURRENT_CLUSTER dd 0x00


1,2,3,4번을 구현했다. 여기까지는 순조롭다.

이제 루트 디렉토리 엔트리 위치를 로딩해서 OS_LDR.SYS를 찾아야 한다. 파일 로딩하는 함수를 만들어야 하는데~~


READ_SECTOR:
	pusha				; 모든 레지스터를 스택에 넣는다.
	mov ah, 0x02		; 디스크 읽기 모드
	mov al, 5			; 1 섹터만 읽는다.
	mov ch, 0			; 트랙은 0번 트랙
	mov cl, 2			; 읽을 섹터는 2번째 섹터
	mov dh, 0			; 헤드는 0번 헤드
	mov dl, [DRIVE_NUM] ; 읽을 드라이브 번호
	mov bx, 0x1000		; 0x9000 메모리에 커널 이미지 올린다.
	mov es, bx
	mov bx, 0x0000
	int 0x13			; 읽어라!

	popa
	ret	


루트 디렉토리 엔트리 위치는 섹터 번호로 기록되어 있다. 이걸 트랙 하고 헤드,섹터 이렇게 다시 재 계산 해야 한다.

트랙, 헤드를 구하는 식은 검색해 보니 이렇게 한다고 한다.


트랙 = 총섹터 / (헤드 갯수 * 트랙당 섹터수)
헤드 = (총섹터/트랙 당 섹터수) % 헤드 갯수
섹터 = (총섹터 % 트랙 당 섹터) + 1


식을 보면서 생각해 보니 대충 그림이 그려진다. 그렇다고 이걸 어셈으로 바꾸긴 힘들었다. 이해와 구현의 괴리감…인터넷에 있는 걸로 만들었다.^^


	; 헤드, 트랙, 섹터로 재 계산해서 저장하자.
	; 트랙 = 총섹터 / (헤드 갯수 * 트랙당 섹터수)
	; 헤드 = (총섹터/트랙 당 섹터수) % 헤드 갯수
	; 섹터 = (총섹터 % 트랙 당 섹터) + 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


위와 같이 만들었다. 이제 섹터 읽는 부분을 구현해야 겠다~


READ_SECTOR:
	pusha				; 모든 레지스터를 스택에 넣는다.
	mov ah, 0x02		; 디스크 읽기 모드
	mov al, [READ_SIZE]	; 읽을 섹터
	mov ch, [TRACK]		; 트랙
	mov cl, [SECTOR]	; 섹터
	mov dh, [HEAD]		; 헤드
	mov dl, [DRIVE_NUM] ; 읽을 드라이브 번호
	mov bx, [READ_MEMORY]	; 읽는 데이터 저장될 메모리
	mov es, bx
	mov bx, 0x0000
	int 0x13			; 읽어라!

	popa
	ret	


아주 쉽게 구현되었다. ㅎㅎ

이제 5번을 구현해 봐야지!~


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

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

	// 파일 이름을 비교한다.
	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


어셈 문법 찾아보면서 하려니 너무 진도가 더디다. 좀 쉬어야 겠다… 머리 쥐나겠다.

….

;; creator : cyj (mangg@manggong.org)
;; date : 2012. 4. 17
;; desc : BPB bootloader on FAT32
;;        1. FAT32를 분석한다.
;;        2. OS_LDR.sys파일을 FAT32 파일 시스템에서 찾아서 0x8000 번지로 로드한다.
;;        3. OS_LDR.sys데이터가 있는 0x8000으로 이동한다.

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 eax, 0x0800
	mov [LDR_MEMORY], eax
		
	; 현재 위치 클러스터를 저장하자.
	mov eax, [BPB_ROOT_ENTRY_CLUSTER]
	mov [CURRENT_CLUSTER], eax

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

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

	call CONVERT_CHS	; 헤드, 트랙, 섹터로 변환한다.
	call READ_SECTOR	; 데이터를 읽는다.

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

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

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

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

	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를 뺀다.
	add eax, ebx	; EAX = EAX-EBX. EAX는 루트디렉토리부터 현재 클러스터(-2) 더한값.

	call CONVERT_CHS	; 헤드, 트랙, 섹터로 변환한다.
	call READ_SECTOR	; 데이터를 읽는다.

	mov ax, [LDR_MEMORY]	; 데이터를 읽었으니까 데이터 주소를
	mov ebx, [CLUSTER_BYTES]	; 읽은 만큼 증가시켜준다.(클러스터의 바이트수)
	shr ebx, 4	; 세그먼트 계산법에 의해 4바이트 빼 버린다.
	add ax, bx	
	mov [LDR_MEMORY], ax	; 데이터 읽을만큼 LDR_MEMORY를 증가.

	ret

	; FAT 테이블에서 다음 클러스터를 찾자.
FAT_SEARCH:
	; FAT 리스트
	; 0x9000 에 임시로 FAT테이블을 읽어서 저장해 놓자.
	mov ax, 0x0900	; 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테이블 위치를 더한다.

	; FAT 테이블을 읽자.
	call CONVERT_CHS	; 헤드, 트랙, 섹터로 변환한다.
	call READ_SECTOR	; 데이터를 읽는다.

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

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

FAT_END:
	mov al, 1
	ret

	; 헤드, 트랙, 섹터로 재 계산해서 저장하자.
	; 트랙 = 총섹터 / (헤드 갯수 * 트랙당 섹터수)
	; 헤드 = (총섹터/트랙 당 섹터수) % 헤드 갯수
	; 섹터 = (총섹터 % 트랙 당 섹터) + 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
	
READ_SECTOR:
	pusha				; 모든 레지스터를 스택에 넣는다.
	mov ah, 0x02		; 디스크 읽기 모드
	mov al, [READ_SIZE]	; 읽을 섹터 수
	mov ch, [TRACK]		; 트랙
	mov cl, [SECTOR]	; 섹터
	mov dh, [HEAD]		; 헤드
	mov dl, [DRIVE] ; 읽을 드라이브 번호
	mov bx, [READ_MEMORY]	; 읽는 데이터 저장될 메모리
	mov es, bx
	mov bx, 0x0000
	int 0x13			; 읽어라!

	popa
	ret	

; 변수를 선언하자.
DRIVE		db 0x00
ROOT_ENTRY	dd 0x00
FAT1		dd 0x00
CLUSTER_BYTES	dd 0x00
CURRENT_CLUSTER dd 0x00
READ_MEMORY		dw 0x00
READ_SIZE		db 0x00
SECTOR			db 0x00
TRACK			db 0x00
HEAD			db 0x00
LDR_MEMORY		dw 0x00

OS_LDR_FILENAME db "OS_LDR  SYS",0

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


쉬는 동안 6,7,8,9 구현해 봤다. 하고 보니 완전 길다~  컴파일 될지도 모르겠고 동작 되는지도 모르겠다. 그냥 말 그대로 그냥 구현했다.

error : TIMES value –13 is negative


제길 … 컴파일 안 된다. 요건 왜 이러냐..


510-($-$$) 요거 때문에 생기는 에러란다. (현재위치 – 첫 위치) 가 510 바이트를 넘는다는 거지~~ 512… 아니…이것보다는 적은 공간에 작성하려니 너무 적다. 아 … 어떻게 해야 하나…


변수를 저렇게 잡지 않고 메모리 영역 하나 잡아서 선언하는 방법을 인터넷에서 검색하다가 찾았다.


DRIVE		equ 0x500
ROOT_ENTRY	equ 0x501
FAT1		equ 0x505
CLUSTER_BYTES	equ 0x509
CURRENT_CLUSTER	equ 0x50D
READ_MEMORY		equ 0x511
READ_SIZE		equ 0x513
SECTOR			equ 0x514
TRACK			equ 0x515
HEAD			equ 0x516
LDR_MEMORY		equ 0x517


요렇게 하고 컴파일 해보니 호~~~ 된다. 동작은 될련지는 미지수다…내가 만든 코드지만 솔직히 못 믿겠다. 문법도 맞는지도….그냥 막코딩!!!!

검증을 하기 위해서 전번에 만들어 두었던 코드로 테스트 해봐야 겠다.


ORG 0x8000			; 0x8000 메모리에서 실행되므로 0x8000 으로 주소정렬
BITS 16

	cli				; 인터럽트 발생안되게 설정
	xor ax,ax		; ax 0으로 초기화 한다. ax xor ax 어차피 0이다.
	mov ds, ax		; ds 세그먼트 레지스터를 0 으로 초기화 한다.
	mov es, ax		; es 세그먼트 레지스터를 0 으로 초기화 한다.
	mov ss,ax
	mov sp,0xffff
	sti				; 인터럽트 발생 설정

	lea si, [LOAD_MSG]	; LOAD_MSG 위치 주소
	call PRINT			; PRINT 함수 호출

	jmp $

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					;호출 한곳으로 리턴.

LOAD_MSG:
	db "LOAD MY KERNEL", 0x00

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


요걸 OS_LDR.SYS 이름으로 컴파일 한다.

nasm –fbin –o OS_LDR.SYS OS_LDR.ASM


파티션 부트로더에 컴파일 한 코드 붙여 넣고, 이걸 다시 [제어판][관리 도구][컴퓨터관리] 가서 VHD 연결 해서 드라이브로 연결해서 OS_LDR.SYS를 넣었다. 잘 될까?

 

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

OS 만들기 #10  (0) 2013.08.14
OS 만들기 #9 - 부트로더  (0) 2013.08.14
OS 만들기 #7 - FAT32  (1) 2013.08.14
OS 만들기 #6 - MBR  (0) 2013.08.14
OS 만들기 #5 - 부트로더  (0) 2013.08.14

- FAT32

MBR에 기록된 0x10000 위치를 헥사뷰어로 보니 뭔가 Windows 7에서 포맷하면서 써 놨다. 부트레코드 인가 보다. 음. 내가 알 수 없는 부분이라 불안하다. Windows 7 에서 만든 MBR 과 이 코드를 검색해 봐야 겠다. 있을진 모른겠다.


http://thestarman.pcministry.com/asm/mbr/W7MBR.htm

http://thestarman.pcministry.com/asm/mbr/W7VBR.htm


이 사이트에 MBR과 VBR(Volume Boot Record), BPB(Bios Parameter Block) 이라고 하는 0x10000 부트레코드 코드가 다 있다. 오! 역시 인터넷은 정보의 바다다!! 만쉐!!


에고.. 영어라 모르겠다. VBR 같은 경우 NTFS로 쏼라 쏼라 한다. 난 FAT32인데?? 음…이 사이트 기웃 거리다가 FAT32 찾았다!!

http://thestarman.pcministry.com/asm/mbr/ntFAT32BR.htm


내 부트레코드랑 틀리다. 참고만 해야 겠다. 대충 통빡으로 보면 앞 3자리는 jump 코드란다. 그 다음은 BPB 영역(http://thestarman.pcministry.com/asm/mbr/MSWIN41.htm#BPB)이라고 해서 클릭해 보니… 와우 이건 모냐???


FAT32 정보네~~ 아고 FAT32 정보가 어디 있나 했더니 부트로더랑 FAT32 정보가 포함되어 있네? 흠…공부해야겠다.

우선 복습하는 차원에서 MBR의 파티션 정보는 다음과 같다.



저번에 포스팅 했던 내용과 같다. 그리고 FAT32 구조는.



위와 같다. 위 그림에서 Volume ID 이 부분이 0x10000 위치의 부트코드가 있는 위치이고 그 다음이 Reserved Sector, FAT#1, FAT#2, Files & 디렉토리가 일련의 순서대로 구성되어 있다. 부트로더와 FAT#1 사이에는 Reserved Sector가 존재한다. 왜 들어가 있는지 모르겠다. 그리고 FAT#1 과 FAT#2가 있는데 파일이 하드디스크의 어느 부분에 저장되어 있는지 위치를 기록하는 공간(Data영역에서 어느 위치)이다. 두 개나? 있는데 하나는 백업용이란다. FAT#1이 지워졌을 때 복구 하기 위해 있다고 한다.  Data 영역(파일과 디렉토리) 영역에는 파일 목록과 실제 파일 데이터가 기록된다.

파일이 FAT32에서 어떻게 읽고 쓰는지는 요거 BPB 라는 놈 분석해 보고 알아보자. (아 ..하기 싫어..)



잊어 버릴 것 같아서 직접 표로 작성했다.

부트로더 0x03부터 0x59 까지 FAT 정보가 들어갔다. FAT12/16/32가 서로 유사하다고 한다. 뭐 난 FAT32 만 할꺼니 딴 건 신경 안 쓰기로 했다.


모르는 용어가 나온다. 나머지야 다 유추해 볼만 한데 클러스트 라는 용어는 생소한다.


PC의 저장기술 측면에서의 클러스터는 하드디스크 위에 파일을 저장하는 논리적 단위이며, 컴퓨터의 운영체계에 의해 관리된다.
파일이 하드디스크에 저장되면 적어도 하나 이상의 클러스터를 차지하게 되며, 아주 커다란 파일인 경우 여러 개의 클러스터에 걸쳐 저장되는 수도 있다. 그러나, 비록 하나의 파일이 여러 개의 클러스터에 나뉘어 있다고 해도, 이 클러스터들이 항상 연속되어 있어야만 하는 것은 아니며, 하드디스크의 이곳 저곳에 흩어져 있는 것도 가능하다. 이런 경우 하나의 파일에 연관된 여러 개의 클러스터들의 위치는 하드디스크의 파일 배치표(FAT)에 관리되므로, 사용자의 입장에서는 파일이 어떤 클러스터에 저장되어 있는지, 혹은 나뉘어 저장되어 있는지 아닌지 등에 대해 전혀 알 필요가 없으며, 그저 원하는 파일을 읽고자 하면 파일 내용 전체가 읽혀진다.


http://www.terms.co.kr/cluster.htm 에 이렇게 쓰여져 있다. 내용이 어렵고 부족하다. 좀더 검색 질 좀 해야겠다.

음.. 대충 검색해서 이해한 바로는..


파일을 디스크에 쓸 때 클러스터 단위로 파일을 쪼개고, 그걸 클러스터 단위로 저장하나 보다. 각 클러스터는 연속적으로 저장 될 수 있고 여기 저기 흩어질 수 있다고 한다. 클러스터는 섹터들이 모여서 구성된다고 한다. . 그럼 왜 저장 단위를 클러스터 단위로 묶는 것일 까??? 흐… 메모리 생성, 삭제를 생각해 보면 되겠다… 단.편.화! 아니면 용량이 큰 디스크 일 경우 섹터로 할 경우, 4바이트를 벗어날 경우가 있어서 그런가?? 흠. 뭐 다른 이유가 있을 지도 모르겠지만, 아무튼 클러스터가 이런 거란다.

BPB구조를 표로 다시 작성해봤다.


위치

구분

설명

0x00

Jump Boot Code 0x03부터 0x59까지 FAT32 정보가 들어가기 때문에 그 위치를 건너 코드를 호출한다.

0x03

OEM Name OEM Name이라는데 그냥 8바이트 문자열.

0x0B

섹터 당 바이트 1 섹터에 몇 바이트를 저장 하는지에 대한 정보. 보통 512 바이트이지만 뭐 다를 수도 있나 보다.

0x0D

섹터 당 클러스터 클러스터 하나에 섹터 몇 개가 묶이는지에 대한 정보.

0x0E

예약 영역 섹터 파티션 시작 위치부터 FAT#1 까지 위치까지의 섹터 수.

0x10

FAT 수 FAT 테이블 수.

0x11

최대 루트 엔트리 수 루트 엔트리 수. 그냥 0 이란다.

0x13

FAT16 총 섹터 FAT16 파일 시스템 일 경우, FAT16의 총 섹터 수.

0x15

장치 타입 플로피 디스크 인지, 하드디스크인지 정보 기록.
0xF0 : 기본 플로피 디스크
0xF8 : 하드 디스크

0x16

FAT16의 크기 FAT16 파일 시스템일 경우, FAT#1 테이블의 크기

0x18

트랙 당 섹터 수 한 트랙에 존재하는 섹터 총 수.

0x1A

헤드 수 헤드 수

0x1C

숨은 섹터 수 디스크의 0 부터 파티션 시작 위치까지의 섹터 수

0x20

FAT32 총 섹터 FAT32 파일 시스템 일 경우, 총 섹터 수. (총 크기)

0x24

FAT32 크기 FAT32 파일 시스템일 경우, FAT#1 테이블 크기

0x28

확장 FLAGS - 플래그

0x2A

File System Version 파일 시스템 버전

0x2C

루트 디렉토리 클러스터 루트 디렉토리 시작 클러스터 번호

0x30

File System Info -

0x32

백업 부트 섹터 백업 부트 섹터 위치

0x34

예약  

0x40

드라이브 번호 BIOS 콜 (INT 13h)드라이브 정보
0x00 : 플로피 디스크 A
0x01 : 플로피 디스크 B
0x80 : 드라이브 C
0x81 : 드라이브 D

0x41

예약  

0x42

부트 시그널 0x29 시그널

0x43

볼륨 ID 디스크 볼륨 ID

0x47

볼륨 레이블 디스크 볼륨 레이블

0x52

File System Type -


위치 별 각 항목을 적어봤다.

이 구조에 값을 대입해 보면서 파일을 찾고 파일 데이터 읽는 부분을 순차적으로 분석해 봐야 겠다.

우선 파일을 찾기 위해선 파일 목록 정보를 가지고 있는 루트 디렉토리 엔트리로 가야한단다. 무조건 시작은 루트 디렉토리 엔트리 영역에서 파일을 찾든 데이터를 읽든 시작한단다. 파일 목록이 너무 길면, 다음 파일 목록이 어디에 있는지 루트 디렉토리 영역에 기록되어 있단다. 그래?? 한번 찾아가봐야 겠네! 


(ㅋㅋㅋㅋ 실제로는 FAT 테이블에 다음 영역이 기록된다고 한다. ㅋ)

실제 디스크에서 루트 디렉토리는 위치는 위 표에서 나온대로

루트 디렉토리 = 숨은 영역 섹터 + 예약 영역 섹터 + (FAT#1 크기 * FAT 수)


위치에 있다. 이 영역에서 파일을 검색한다. 함 찾아 볼까??

내 BaramOS 이미지 BPB 영역에서 각 항목은



숨은 영역 섹터(0x1C) = 0x00000080
예약 영역 섹터(0x0E) = 0x186E
FAT32 크기(0x24) = 0x000003C9
FAT 수(0x10) = 0x02

루트 디렉토리 = 0x00000080 + 0x186E + (0x000003C9 * 0x02)


0x2080 섹터에 루트 디렉토리가 존재 한다. 그럼 이미지 파일에서 0x2080 섹터 위치를 찾아 볼까나? 아! 섹터 위치다 여기서 섹터당 바이트(0x0B) 위치에 보면 0x200 이므로 0x200을 곱한다. 그럼 0x410000위치에 루트 디렉토리가 존재한다. 가보잣!!



뭔가 적어져 있다. 포맷 하고 아무것도 파일 저장 안 했는데 뭔가 있다. 아! 루트 디렉토리(C:\) 정보란다. 그냥 기본으로 들어간다고 한다. 그래서 파일 하나를 저장해봤다.


오!! 파일이 기록되어 있다. KERNEL.SYS 파일명이 있고 쏼라 쏼라.. KERNEL.SYS 에서 “.” 이 빠진 게 특이하다. 그럼 이 파일목록 구조를 살펴봐야 겠다. (할게 겁나 많네..)


한 파일당 32바이트(0x20) 구성으로 이루어져 있다고 한다. 이미지 파일이 0x410000에서 시작하니 KERNEL.SYS 파일은 0x410020 에서 시작되는 것이다. 위 항목에 실제 이미지 파일 데이터를 대입해 보면


파일이름 : KERNEL
확장자 : SYS
속성 : 0x20 (Archive)

0000 0001 읽기 전용
0000 0010 숨은 파일
0000 0100 시스템 파일
0000 1000 볼륨 레이블
0000 1111 긴 파일
0001 0000 디렉토리
0010 0000 Archive

예약 : 0xAB18
파일 생성시간 : 0x7CA2

11~15비트 시간 0~23
5~10비트 분 0~59
0~4비트 초 0~59
파일 생성날짜 : 0x42EF
9~15비트 년도 (1980 + 비트값)
5~8비트 월(1~12)
0~4비트 일(1~31)
액세스 날짜 : 0x42EF
클러스터 위치: 0x0000 (high)
파일 Write 시간 : 0x0577
파일 Write 날짜 : 0x42EC (
클러스터 위치: 0x0003 (low) (3)
파일크기: 0x0000048F (1167 byte)


KERNEL.SYS 파일은 클러스터 HIGH + LOW 하면 0x00000003 위치에 있다. 그러면 데이터 영역 3번 클러스터 위치로 가보자! 1 클러스터는 2 섹터가 묶여 있다고 하니, 2 * 0x200(512) * 3.

현재 위치 0x410000 + (0x00000003 * 0x200 * 2) = 0x410C00 .. 으로 가보자!!!


없다…아무런 데이터도 없다.. 이게 웬일이냐??

흠…


그러니까 데이터 영역 시작과 현재 내가 보고 있는 루트 디렉토리 영역은 동일한 위치가 아니란다. 뭔말?? 즉, 데이터 영역에서 2 클러스터 이동한 위치가 루트 디렉토리 영역이다. 난, 데이터 영역 시작 위치가 루트 디렉토리 영역인지 알았다. 하지만 그게 아니고 데이터 영역 위치에서 2 클러스터 위치를 이동한 위치가 0x410000인것이다. 이 정보가 어디에 있냐면, BPB 의 루트 디렉토리 클러스터 (0x2C) 위치에 0x2 라고 적어 놨다… 아놔!!!~~~흠


현재 위치가 데이터 영역에서 2클러스터 위치 0x410000 이니까 3 클러스터 위치는 0x410000 영역에서 0x200(512) * 2 = 0x400 를 더한 0x410400 위치에 있다. 함 볼까?



데이터가 있다. 얼마나 있을까? 생각해 보니 1 클러스터 크기 만큼 있을테니 1 클러스터(2섹터)는 1024byte 만큼 있을 것이다. 그렇겠지!~~~? 그러면 내 KERNEL.SYS 파일 크기는 1167 byte 이므로 하나의 클러스터(1024byte)에 다 기록하기에는 힘들다. 그럼 다음 클러스터에 있겠지….라고 생각해서 이동해 봤더니 다음 데이터가 다음 클러스터 위치(0x410800)에 있다. 그런데 검색 질 해보니 이건 방금 포맷하고 파일 저장 한 거라 이렇게 되지만, 파일을 삭제,추가를 반복하게 되면 이렇게 순차적으로 저장되지 않는다고 한다. 어째 다음 데이터가 있길래 잘 풀린다 했다. 검색 내용으로는 파일 데이터가 있는 클러스터를 읽고 난 후 FAT 테이블로 이동해서 다음 데이터가 어디에 있는 지 클러스터 번호를 얻어야 한다고 한다.

그래 FAT 테이블이 어떤지를 또 알아야 된다는 거네!!!! 아놔~FAT 테이블로 이동해 보자.

FAT 테이블 = 숨은 영역 섹터 + 예약 영역 섹터
0x18EE = 0x00000080 + 0x186E
FAT 위치 = 0x18EE * 0x200 (512byte)

0x31DC00 위치에 FAT가 있다. 가보자!!


이상한 FF FF FF 만 있다. 이게 뭔지를 알아야 한다 ㅠㅠ 아이고!!!


FAT는 위 구조와 같이 구성되어 있다고 한다. 각 클러스터는 4바이트로 구성되고 다음 클러스터 위치를 가르킨 다고 한다. 0x0FFFFFFF 일 경우에는 다음 클러스터가 없다는 의미란다.


클러스터 0, 1 위치는 FAT32 에서는 쓰이지 않는다고 하니, 클러스터 2 는 루트 디렉토리 엔트리를 뜻한다. 현재 파일이 하나밖에 없고 디렉토리도 없으니까(클러스터 한 개에 파일 목록을 담을 수 있는 갯수는 32개이다. 왜??? 파일목록 데이터는 32바이트로 구성되고 한 개의 클러스터(1024) / 32 = 32이니까!~~즉 32개의 파일 목록 이상 가지고 있지 않으니 파일 목록 정보를 담은 클러스터는 클래스터2 로 충분하다는 말이다) 클러스터 2는 다음 클러스터를 0x0FFFFFFF 라고 기록되어 있다. 하지만 클러스터 3 은 KERNEL.SYS 데이터를 기록되어 있는 클러스터로 파일 크기가 1167 이니 클러스터 2개가 필요하다. 그래서 클러스터3 위치에 보면 다음 위치 0x00000004 위치를 가르킨다. 그리고 클러스터4 는 다음 클러스터를 0x0FFFFFFF 으로 더 이상 다음 클러스터가 없다고 기록되어 있다. 즉 KERNEL.SYS파일은 클러스터 3번과 4번을 이용해서 파일 데이터를 기록해 놓고 있다.


자 정리해 보면!


1. 루트 디렉토리 엔트리에서 원하는 파일을 찾는다.(KERNEL.SYS)
2. 파일 데이터가 기록되어 있는 클러스터3으로 이동해서 데이터를 읽는다.
3. FAT 테이블로 이동해 클러스터3번의 다음 클러스터가 있는지 체크한다. (클러스터 4가 있다)
4. 데이터 영역에서 다음 클러스터4 위치로 이동해서 데이터를 읽는다.
5. FAT 테이블로 이동해 클러스터4번의 다음 클러스터가 있는지 체크한다. (0x0FFFFFFF 이므로 더 이상 없다.)
6. 종료.


위와 같은 방법이 FAT32 동작 시나리오라 할 수 있겠다. 휴~~ 긴 FAT32 를 공부했다.

검색질을 몇번을 했는지 모른다. 글 쓰고 수정하고 수정하고… 글이 완전 산으로 간다.

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

OS 만들기 #9 - 부트로더  (0) 2013.08.14
OS 만들기 #8 - 부트로더  (0) 2013.08.14
OS 만들기 #6 - MBR  (0) 2013.08.14
OS 만들기 #5 - 부트로더  (0) 2013.08.14
OS 만들기 #4 - 부트로더  (0) 2013.08.14

부트로더 #4

BaramOS이미지의 첫번째 섹터의 데이터에 내가 입력하지 않는 데이터가 기록되어 있다. 이건 도대체 무엇일까?

또 검색질을 해본다. 아 MBR …


MBR이란?

마스터 부트 레코드 또는 파티션된 기억 장치의 첫 섹터 인 512 바이트 시동 섹터이다. MBR은 다음의 것들 가운데 하나 이상을 위해 사용된다.
디스크 프라이머리 파티션 테이블을 소유한다. 부트스트래핑 운영 체제 32비트 디스크 서명이 있는 각 디스크 매체의 구별 IBM PC 호환 컴퓨터의 대중화 때문에, 지원을 넓히고 다른 컴퓨터 통합하기 위해 이러한 종류의 MBR이 널리 사용된다.  - 위키백과


새로만든 BaramOS 이미지의 파일의 첫번째 섹터의 데이터는 윈도우즈가 디스크 초기화 메뉴에서 MBR 항목에서 선택했을 때 알아서 기록했던 데이터 인것이다. MBR은 파티션 정보를 담을 수 도 있고 , 없을 수도 있단다. 파티션 없는 저장 매체 일 경우 없겠지!~~ 음. http://www.terms.co.kr/MBR.htm 에서는 다음과 같이 설명한다.


MBR[엠비알]은 운영체계가 어디에, 어떻게 위치해 있는지를 식별하여 컴퓨터의 주기억장치에 적재될 수 있도록 하기 위한 정보로서 하드디스크디스켓의 첫 번째 섹터에 저장되어 있다. MBR은 또한 "파티션 섹터" 또는 "마스터 파티션 테이블"이라고도 불리는데, 그 이유는 하드디스크가 포맷될 때 나뉘어지는 각 파티션의 위치에 관한 정보를 가지고 있기 때문이다. 그외에도, MBR은 메모리에 적재될 운영체계가 저장되어 있는 파티션의 부트 섹터 레코드를 읽을 수 있는 프로그램을 포함하고 있는데, 부트 섹터 레코드에는 다시 운영체계의 나머지 부분들을 메모리에 적재시키는 프로그램을 담고 있다.


디스크 파티션을 나눴을 때, 그 정보를 디스크 첫번째 섹터에 담고 있나 보다.

자, 그러면 난 뭐해야 하냐??? 이 1섹터를 내 부트로더로 덮어써야 하는건가?? 검색질(^^)


아~


윈도우 기본 MBR은 활성화된 파티션을 찾아서 그 해당 파티션의 부트레코드를 0x7C00 메모리에 올리는 실행권을 넘긴다고 한다.


그냥 둬도 되겠네… 파티션 부트 레코드를 내 부트로더로 덮어 씌우면 되겠다. 그 전에

파티션.. 좀더 MBR 파티션 정보에 대해서 알아 봐야 겠다.


MBR은 446바이트의 부트코드 랑 16바이트 씩 4개의 파티션 정보를 지니고 있다고 한다. 뭐 내 관심은 446 부트코드는 아니니 넘어가고, 4개의 파티션 정보좀 살펴 봐야 겠다.


파티션 구조

오프셋

길이

내용

0x00

1

파티션 상태 (0x80 : 시동가능 ….)

0x01

3

파티션 첫번째 섹터의 트랙/헤더/섹터 주소

0x04

1

파티션 종류

0x05

3

파티션 마지막 트랙/헤더/섹터 주소

0x08

4

파티션 첫번째 섹터의 LBA

0x0C

4

파티션의 크기


4개의 파티션이 위와 같은 구조로 되어 있다고 한다. MBR은 이 4개의 파티션을 읽어서 파티션 상태 0x80 인 파티션을 찾아서 파티션 첫번째 섹터의 읽고 실행시킨다는 거지. 여튼 여긴 손댈곳이 없는 것 같다. 파티션의 첫번째 섹터만 찾아서 내 부트로더만 넣으면 되겠지.



MBR 의 부트코드가 446라니깐 그 뒤는 파티션 정보는 0x1BE 에 있다.



BaramOS 이미지 파일은 파티션이 하나이므로 뭐 볼 필요는 없겠지만, 첫번째 파티션 정보는 위 그림의 영역 까지다. 한번 나눠보자.

파티션 상태(1) : 0x80(0x80 = 시동 가능, 0x00 = 부팅불가, 기타 = 비정상)
파티션 첫번째 섹터 (3) : 0x000302
파티션 종류(1) : 0x0B(
http://www.win.tue.nl/~aeb/partitions/partition_types-1.html)
파티션 끝 셋터(3) : 0x0F21F0
파티션 첫번째 섹터 LBA : 0x00000080
파티션 크기(섹터갯수) : 0x0003E800

* 리틀 인디안


음. 부팅이 가능 0x80 상태고 FAT32 시스템이라고 파티션 정보에 나와 있다. LBA 는 뭐지???

오!!! int 13 으로 파일 읽을 때 트랙/헤드/섹터 요렇게 구분해 가지고 파일 읽었는데 LBA는 그냥 0부터 ~~쭉 섹터 번호를 읽는게 LBA 라는거네!~ 어쩐지 트랙/헤드/섹터 요거 읽는 거 불편했는데 직관적인 LBA 가 있었네.. 이제 LBA 방식으로 파일을 읽도록 해야 겠다. 트랙/헤드/섹터 요건 영~~~직관적이지 않아!!!!


자 그럼 내 부트로더를 덮어 씌울 파티션 부트섹터 영역으로 넘어가봐야 겠다. 파티션 정보에 LBA에 0x00000080 이라니깐 0x80 섹터 뒤에 있다는 거네!! 0x80 X 512 = 0x10000 이니깐 헥사뷰어 0x10000로 이동!!!



윈도우즈 7 에서 포맷 했더니 요놈은 포맷 하면서 윈도우 기본 부트로더 까지 넣었나 보다. 뭔가 쭉 채워져 있다.



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

OS 만들기 #8 - 부트로더  (0) 2013.08.14
OS 만들기 #7 - FAT32  (1) 2013.08.14
OS 만들기 #5 - 부트로더  (0) 2013.08.14
OS 만들기 #4 - 부트로더  (0) 2013.08.14
OS 만들기 #3 - 부트로더  (1) 2013.08.14

+ Recent posts