관리 메뉴

밤색모자이크의 개발이야기

3일차. 32비트 모드와 C언어 도입 본문

Embedded/OS제작 with OS구조와원리

3일차. 32비트 모드와 C언어 도입

밤색모자이크 2017. 7. 7. 17:07

안녕하세요. 밤색모자이크입니다.

오늘은 3일차로 32비트 모드를 도입하고 C언어를 추가하는 작업을 하겠습니다.

먼저, 말씀드리면 C언어를 호출하는 부분은 책에도 설명되어 있지않습니다.

나중에 설명할 거 같습니다.


make 파일 작성하는 부분도 그냥 복사하시는게 정신 건강에 이롭습니다.

그리고 ipl10.nas에서 추가적으로 작성하는 부분을 쭉 했는데;;

오류가 1개 있는데 도무지 못 찾아서 그냥 전체 복사 붙여넣기 했습니다.




개발환경


운영체제 : Windows10

텍스트 편집기 : Atom

PC 에뮬레이터 : QEMU




소스코드


변경 파일 목록


파일 이름 변경 파일

ipl.nas    ->    ipl10.nas    :    디스크 읽고 로드


수정된 파일

ipl10.nas    :    디스크 읽고 로드

Makefile    :    메이크 파일


추가된 파일

asmhead.nas : 부트섹터 및 32비트 모드 준비

bootpack.c : OS부팅을 위한 여러 가지 파일 처리 준비

naskFunc.nas : CPU 공회전을 막기위한 간단한 테스트 함수



ipl10.nas

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
; haribote-ipl
; TAB=4

CYLS	EQU		10				; 어디까지 Read할까

		ORG		0x7c00			; 이 프로그램이 어디에 Read되는가

; 이하는 표준적인 FAT12 포맷 플로피 디스크를 위한 기술

		JMP		entry
		DB		0x90
		DB		"HARIBOTE"		; boot sector의 이름을 자유롭게 써도 좋다(8바이트)
		DW		512			; 1섹터 크기(512로 해야 함)
		DB		1			; 클러스터 크기(1섹터로 해야 함)
		DW		1			; FAT가 어디에서 시작될까(보통은 1 섹터째부터)
		DB		2			; FAT 개수(2로 해야 함)
		DW		224			; 루트 디렉토리 영역의 크기(보통은 224엔트리로 한다)
		DW		2880			; 드라이브 크기(2880섹터로 해야 함)
		DB		0xf0			; 미디어 타입(0xf0로 해야 함)
		DW		9			; FAT영역의 길이(9섹터로 해야 함)
		DW		18			; 1트럭에 몇개의 섹터가 있을까(18로 해야 함)
		DW		2			; 헤드 수(2로 해야 함)
		DD		0			; 파티션을 사용하지 않기 때문에 여기는 반드시 0
		DD		2880			; 드라이브 크기를 한번 더 write
		DB		0,0,0x29		; 잘 모르지만 이 값으로 해 두면 좋은 것 같다
		DD		0xffffffff		; 아마, 볼륨 시리얼 번호
		DB		"HARIBOTEOS "		; 디스크 이름(11바이트)
		DB		"FAT12   "		; 포맷 이름(8바이트)
		RESB	18				; 우선 18바이트를 비어 둔다

; 프로그램 본체

entry:
		MOV		AX, 0			; 레지스터 초기화
		MOV		SS,AX
		MOV		SP,0x7c00
		MOV		DS,AX

; 디스크를 읽는다

		MOV		AX,0x0820
		MOV		ES,AX
		MOV		CH, 0			; 실린더 0
		MOV		DH, 0			; 헤드 0
		MOV		CL, 2			; 섹터 2
readloop:
		MOV		SI, 0			; 실패 회수를 세는 레지스터
retry:
		MOV		AH, 0x02		; AH=0x02 : 디스크 read
		MOV		AL, 1			; 1섹터
		MOV		BX,0
		MOV		DL, 0x00		; A드라이브
		INT		0x13			; 디스크 BIOS 호출
		JNC		next			; 에러가 일어나지 않으면 next에
		ADD		SI, 1			; SI에 1을 더한다
		CMP		SI, 5			; SI와 5를 비교
		JAE		error			; SI >= 5 이면 error에
		MOV		AH,0x00
		MOV		DL, 0x00		; A드라이브
		INT		0x13			; 드라이브의 리셋트
		JMP		retry
next:
		MOV		AX, ES			; 주소를 0x200 진행한다
		ADD		AX,0x0020
		MOV		ES, AX			; ADD ES, 0x020 라고 하는 명령이 없기 때문에 이렇게 하고 있다
		ADD		CL, 1			; CL에 1을 더한다
		CMP		CL, 18			; CL와 18을 비교
		JBE		readloop		; CL <= 18 이라면 readloop에
		MOV		CL,1
		ADD		DH,1
		CMP		DH,2
		JB		readloop		; DH < 2 라면 readloop에
		MOV		DH,0
		ADD		CH,1
		CMP		CH,CYLS
		JB		readloop		; CH < CYLS 라면 readloop에

; 다 읽었으므로 haribote.sys를 실행한다!

		MOV		[0x0ff0], CH		; IPL이 어디까지 읽었는지를 메모
		JMP		0xc200

error:
		MOV		AX,0
		MOV		ES,AX
		MOV		SI,msg
putloop:
		MOV		AL,[SI]
		ADD		SI, 1			; SI에 1을 더한다
		CMP		AL,0
		JE		fin
		MOV		AH, 0x0e		; 한 글자 표시 function
		MOV		BX, 15			; 칼라 코드
		INT		0x10			; 비디오 BIOS 호출
		JMP		putloop
fin:
		HLT					; 무엇인가 있을 때까지 CPU를 정지시킨다
		JMP		fin			; Endless Loop
msg:
		DB		0x0a, 0x0a		; 개행을 2개
		DB		"load error"
		DB		0x0a			; 개행
		DB		0

		RESB	0x7dfe-$			; 0x7dfe까지를 0x00로 채우는 명령

		DB		0x55, 0xaa


디스크의 실린더를 읽고 총 5회까지 반복하고 실패시 에러가 뜨게 구현되어있습니다.

실린더의 내용을 다 읽었으면 asmhead.nas를 실행하여 32모드와 C언어를 도입합니다.



asmhead.nas

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
; haribote-os boot asm
; TAB=4

BOTPAK	EQU		0x00280000		; bootpack의 로드 장소
DSKCAC	EQU		0x00100000		; 디스크 캐쉬 프로그램의 장소
DSKCAC0	EQU		0x00008000		; 디스크 캐쉬 프로그램의 장소(리얼모드)

; BOOT_INFO 관계
CYLS	EQU		0x0ff0			; boot sector가 설정한다
LEDS	EQU		0x0ff1
VMODE	EQU		0x0ff2			; 색 가지수에 관한 정보.어떤 비트 칼라인가?
SCRNX	EQU		0x0ff4			; 해상도의 X
SCRNY	EQU		0x0ff6			; 해상도의 Y
VRAM	EQU		0x0ff8			; 그래픽 버퍼의 개시 번지

		ORG		0xc200		; 이 프로그램이 어디에 Read되는가

; 화면 모드를 설정

		MOV		AL, 0x13	; VGA 그래픽스, 320 x200x8bit 칼라
		MOV		AH,0x00
		INT		0x10
		MOV		BYTE [VMODE], 8	; 화면 모드를 메모 한다(C언어가 참조한다)
		MOV		WORD [SCRNX],320
		MOV		WORD [SCRNY],200
		MOV		DWORD [VRAM],0x000a0000

; 키보드의 LED 상태를 BIOS가 가르쳐 준다

		MOV		AH,0x02
		INT		0x16 		; keyboard BIOS
		MOV		[LEDS],AL

; PIC가 일절의 세치기를 받아들이지 않게 한다
;	AT호환기의 사양에서는, PIC의 초기화를 한다면,
;	진한 개를 CLI앞에 해 두지 않으면 이따금 행업 한다
;	PIC의 초기화는 나중에 한다

		MOV		AL,0xff
		OUT		0x21,AL
		NOP				; OUT 명령을 연속하면 잘 되지 않는 기종이 있는 것 같기 때문에
		OUT		0xa1,AL

		CLI				; CPU 레벨에서도 인터럽트 금지

; CPU로부터 1MB이상의 메모리에 액세스 할 수 있도록, A20GATE를 설정

		CALL	waitkbdout
		MOV		AL,0xd1
		OUT		0x64,AL
		CALL	waitkbdout
		MOV		AL, 0xdf	; enable A20
		OUT		0x60,AL
		CALL	waitkbdout

; 프로텍트 모드 이행

[INSTRSET "i486p"]				; 486명령까지 사용하고 싶다고 하는 기술

		LGDT	[GDTR0]			; 잠정 GDT를 설정
		MOV		EAX,CR0
		AND		EAX, 0x7fffffff	; bit31를 0으로 한다(페이징 금지를 위해)
		OR		EAX, 0x00000001	; bit0를 1로 한다(프로텍트 모드 이행이므로)
		MOV		CR0,EAX
		JMP		pipelineflush
pipelineflush:
		MOV		AX,1*8		; read, write 가능 세그먼트(segment) 32bit
		MOV		DS,AX
		MOV		ES,AX
		MOV		FS,AX
		MOV		GS,AX
		MOV		SS,AX

; bootpack의 전송

		MOV		ESI, bootpack	; 전송원
		MOV		EDI, BOTPAK	; 전송처
		MOV		ECX,512*1024/4
		CALL	memcpy

; 하는 김에 디스크 데이터도 본래의 위치에 전송

; 우선은 boot sector로부터

		MOV		ESI, 0x7c00	; 전송원
		MOV		EDI, DSKCAC	; 전송처
		MOV		ECX,512/4
		CALL	memcpy

; 나머지 전부

		MOV		ESI, DSKCAC0+512; 전송원
		MOV		EDI, DSKCAC+512	; 전송처
		MOV		ECX,0
		MOV		CL,BYTE [CYLS]
		IMUL	ECX,512*18*2/4		; 실린더수로부터 바이트수/4에 변환
		SUB		ECX,512/4	; IPL분만큼 공제한다
		CALL	memcpy

; asmhead에서 해야 하는 것은 전부 다 했으므로,
;	나머지는 bootpack에 맡긴다

; bootpack의 기동

		MOV		EBX,BOTPAK
		MOV		ECX,[EBX+16]
		ADD		ECX, 3		; ECX += 3;
		SHR		ECX, 2		; ECX /= 4;
		JZ		skip		; 전송 해야 할 것이 없다
		MOV		ESI,[EBX+20]	; 전송원
		ADD		ESI,EBX
		MOV		EDI,[EBX+12]	; 전송처
		CALL	memcpy
skip:
		MOV		ESP,[EBX+12]	; 스택 초기치
		JMP		DWORD 2*8:0x0000001b

waitkbdout:
		IN		 AL,0x64
		AND		 AL,0x02
		IN		 AL, 0x60	; 빈 데이터 READ(수신 버퍼가 나쁜짓을 못하게)
		JNZ		waitkbdout	; AND결과가 0이 아니면 waitkbdout에
		RET

memcpy:
		MOV		EAX,[ESI]
		ADD		ESI,4
		MOV		[EDI],EAX
		ADD		EDI,4
		SUB		ECX,1
		JNZ		memcpy		; 뺄셈 한 결과가 0이 아니면 memcpy에
		RET
; memcpy는 주소 사이즈 prefix 넣는 것을 잊지 않으면, string 명령에서도 쓸 수 있다

		ALIGNB	16
GDT0:
		RESB	8			; null selector
		DW		0xffff, 0x0000, 0x9200, 0x00cf	; read/write 가능 세그먼트(segment) 32bit
		DW		0xffff, 0x0000, 0x9a28, 0x0047	; 실행 가능 세그먼트(segment) 32 bit(bootpack용)

		DW		0
GDTR0:
		DW		8*3-1
		DD		GDT0

		ALIGNB	16
bootpack:


이 부분은 제데로 로드되고 실행이되면 NumberLock 키의 불이 들어오게 되어있습니다.

그 이후 행에 대해서는 책에서도 나중에 언급합니다.


bootpack.c

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
/*  함수 헤더 파일을 수동으로 연결 */

void io_hlt(void);

void HariMain(void)
{

fin:
	io_hlt(); /* 이것으로 naskfunc.nas의 _io_hlt가 실행됩니다 */
	goto fin;

}


진짜 OS가 시작되는 곳입니다. 여러가지 부팅에 관련된 세팅이 시작될 곳인데 지금은 허전합니다.

HarMain은 시작 함수로써 함수명을 변경하면 안됩니다.


naskFunc.nas

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
; naskfunc
; TAB=4

[FORMAT "WCOFF"]				; 오브젝트 파일을 만드는 모드
[BITS 32]					; 32 비트 모드용의 기계어를 만든다


; 오브젝트 파일을 위한 정보

[FILE "naskfunc.nas"]				; 원시 파일명 정보

		GLOBAL	_io_hlt			; 이 프로그램에 포함되는 함수명


; 이하는 실제의 함수

[SECTION .text]				; 오브젝트 파일에서는 이것을 쓰고 나서 프로그램을 쓴다

_io_hlt:	; void io_hlt(void);
		HLT
		RET


C언어에서는 HLT같은 어셈블리 명령어를 사용할 수 없습니다.

따라서, nask에서 필요한 어셈블리 명령어들은 앞으로 이 파일에 추가될 예정입니다.



Makefile

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
TOOLPATH = ../z_tools/
INCPATH  = ../z_tools/haribote/

MAKE     = $(TOOLPATH)make.exe -r
NASK     = $(TOOLPATH)nask.exe
CC1      = $(TOOLPATH)cc1.exe -I$(INCPATH) -Os -Wall -quiet
GAS2NASK = $(TOOLPATH)gas2nask.exe -a
OBJ2BIM  = $(TOOLPATH)obj2bim.exe
BIM2HRB  = $(TOOLPATH)bim2hrb.exe
RULEFILE = $(TOOLPATH)haribote/haribote.rul
EDIMG    = $(TOOLPATH)edimg.exe
IMGTOL   = $(TOOLPATH)imgtol.com
COPY     = copy
DEL      = del

# 디폴트 동작

default :
	$(MAKE) img

# 파일 생성 규칙

ipl10.bin : ipl10.nas Makefile
	$(NASK) ipl10.nas ipl10.bin ipl10.lst

asmhead.bin : asmhead.nas Makefile
	$(NASK) asmhead.nas asmhead.bin asmhead.lst

bootpack.gas : bootpack.c Makefile
	$(CC1) -o bootpack.gas bootpack.c

bootpack.nas : bootpack.gas Makefile
	$(GAS2NASK) bootpack.gas bootpack.nas

bootpack.obj : bootpack.nas Makefile
	$(NASK) bootpack.nas bootpack.obj bootpack.lst

naskfunc.obj : naskfunc.nas Makefile
	$(NASK) naskfunc.nas naskfunc.obj naskfunc.lst

bootpack.bim : bootpack.obj naskfunc.obj Makefile
	$(OBJ2BIM) @$(RULEFILE) out:bootpack.bim stack:3136k map:bootpack.map \
		bootpack.obj naskfunc.obj
# 3MB+64KB=3136KB

bootpack.hrb : bootpack.bim Makefile
	$(BIM2HRB) bootpack.bim bootpack.hrb 0

haribote.sys : asmhead.bin bootpack.hrb Makefile
	copy /B asmhead.bin+bootpack.hrb haribote.sys

haribote.img : ipl10.bin haribote.sys Makefile
	$(EDIMG)   imgin:../z_tools/fdimg0at.tek \
		wbinimg src:ipl10.bin len:512 from:0 to:0 \
		copy from:haribote.sys to:@: \
		imgout:haribote.img

# 커맨드

img :
	$(MAKE) haribote.img

run :
	$(MAKE) img
	$(COPY) haribote.img ..\z_tools\qemu\fdimage0.bin
	$(MAKE) -C ../z_tools/qemu

install :
	$(MAKE) img
	$(IMGTOL) w a: haribote.img

clean :
	-$(DEL) *.bin
	-$(DEL) *.lst
	-$(DEL) *.gas
	-$(DEL) *.obj
	-$(DEL) bootpack.nas
	-$(DEL) bootpack.map
	-$(DEL) bootpack.bim
	-$(DEL) bootpack.hrb
	-$(DEL) haribote.sys

src_only :
	$(MAKE) clean
	-$(DEL) haribote.img


Make 파일인데 복잡합니다.

여러가지 명령어를 추가하였고, 주소라던가 일관되게 자주 쓰이는 것들은 변수로 빼내서 작동합니다.

천천히 읽어보면 알 수 있긴 하는데 그냥 복붙을 추천드려요.


이 장에서 실린더 읽기에 대해서 자세히 나갑니다.

그 밖에 어셈블리 명령어에 대해서 나가죠.

책으로 읽으면 쉽게 읽힙니다.

따로 정리는 하지 않겠습니다. 기본적인 내용이라 넘어가도록 하겠습니다.





실행



위 소스코드를 다 완성하셨으면 이렇게 파일 목록이 생성됩니다.

!cons_nt.bat 파일이 뭔지 모르시는 분은 2일차를 읽어보시길 바랍니다.


http://godrjsmgl.tistory.com/94


!cons_nt.bat을 열고 실행시키기 make run을 수행하면 됩니다.



수행 결과입니다. 미친듯이 파일이 많이 생성됩니다.

이미지 파일을 생성하는 일련의 작업이므로 이미지 파일 생성 후에는 make clean으로 생성된 파일 삭제 하시면 깔끔하게 유지됩니다.



이렇게 많은 파일이 생성됩니다...




결과


아무것도 없는 까만 화면이 나오면 정상입니다.



참고 자료

OS 구조와 원리, 카와이 히데미 저, 한빛미디어 출판

링크 : http://www.hanbit.co.kr/store/books/look.php?p_code=B9833754652


기존 글 바로가기 링크

링크 : http://godrjsmgl.tistory.com/66


Comments