OS 개발일지

[OS개발] 3일차 - C언어 도입! 우워어어엉!

RaiKuma 2015. 10. 30. 17:13

3일차

3일차는 32bit 모드로의 전환과 C언어를 활용하여 코드를 짜기 시작했다(감격...)


# 우선 IPL외에 좀더 많은 코드를 읽기 위해 더 많은 데이터를 읽기로 하였다.

플로피 디스크는 실린더, 헤드, 섹터로 영역이 나뉘어 있는데, 80 실린더(0~79), 2 헤드(0,1), 18섹터(1~18)로 이루어져 있다. 

출처: http://www.ifi.uio.no/~inf3151/disk_structure.jpg


우선 10실린더까지 메모리에 로드하는 어셈블리어 코드를 짰다.

; 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에

; 다 읽었지만 우선 할일이 없기 때문에 sleeve

fin:
		HLT					; 무엇인가 있을 때까지 CPU를 정지시킨다
		JMP		fin			; Endless Loop

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
msg:
		DB		0x0a, 0x0a		; 개행을 2개
		DB		"load error"
		DB		0x0a			; 개행
		DB		0

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

		DB		0x55, 0xaa

상당히 많이 추가된 모습인데, 우선 새로나온 명령어를 정리하보았다.

EQU(equal) : C언어의 define처럼 상수를 정의한다
JNC(jump if not carry) : carry 플래그가 서지 않았으면 점프
JAE(jump if above or equal) : CMP에서 왼쪽이 더 크거나 같으면 점프
JBE(jump if below or equal) : CMP에서 왼쪽이 더 작거나 같으면 점프
JB(jump if below) : CMP에서 왼쪽이 더 작으면 점프


우선 이번에 새로운 인터럽트(INT 0x13)가 쓰였다. 디스크를 읽는 바이오스 기능인데, 그 조건은 이러하다.

AH = 0x02 (읽기), 0x00(리셋)
AL = 처리할 섹터 수
CH = 실린더 번호
CL = 섹터 번호
DH = 헤드 번호
DL = 드라이브 번호(A드라이브이므로 0)
ES:BX = 버퍼 어드레스

리턴값:
FLAGS.CF == 0 : 에러 없음, AH == 0
FLAGS.CF == 1 : 에러 있음, AH에 에러코드

여기서 리턴값으로 넘겨주는 FLAGS.CF가 위에 명령어중 JNC에 쓰이는 carry 플래그이다.


그리고 ES:BX 라는 표현이 나오는데, 지금까지 써온 []기호는 사실[ES:]의 줄임말로써, 주소값을 넣게되면 [ES*16 + 주소값]으로 계산된다고 한다. 이는 BX만으로는 0xFFFF 까지 밖에 메모리 주소를 표현하지 못하는 한계 때문으로 이는 32bit 레지스터가 0xFFFFFFFF 까지 (약 4GB) 표현하게 되면서 해결되었다. 64bit는 0xFFFFFFFFFFFF 약 17179869GB 히익

섹터를 읽는 데 실패하면 5번까지 시도하도록 설계되어있다.(뭐, 에뮬이니 실패할 일은 없겠지만...)

무튼 그래서 이 인터럽트를 사용해서 retry에서 섹터를, next에서 헤드와 실린더를 하나씩 추가해가며 10실린더까지 읽을 수 있었다.



# 자 그러면 드디어 용량에 구애받지 않고 코드를 짤 수가 있게된것이다!(예헤이!)

IPL에 의해서 별도로 구동되는 프로그램은 haribote.nas가 맡기로 하였다. (하리보테는 가짜라는 뜻으로, 데모버전을 만드는 느낌으로 가자는 필자의 생각이 담겨있는 이름이다)

기계어화된 하리보테를 실행시키기 위해서는 프로그램이 메모리의 어느 공간에 로드 되는 지를 알아야 한다. 그래서 플로피안에서의 위치를 찾아보면, 0x002600 에 파일명이, 0x004200 에 본체가 기록된다고 한다. 따라서 IPL의 ORG인 0x7c00 + 0x004200 = 0xc200 이 본체의 ORG가 된다.

; haribote-os
; TAB=4

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

		MOV		AL, 0x13		; VGA 그래픽스, 320 x200x8bit 칼라
		MOV		AH,0x00
		INT		0x10
fin:
		HLT
		JMP		fin


본체에서 하는 일은 비디오 모드 전환(INT 0x10)으로써, 조건은 다음과 같다.

AH = 0x00
AL = 모드(이 경우 0x13이므로 VGA그래픽스 320x200x8bit 컬러, 팩드 픽셀)

리턴 값 : 없음


실행하면 예쁜 검은색 화면을 볼 수있다.



# 그리고 대망의 32bit 모드와 C언어.
여기서 부터 img파일의 빌드 과정이 상당히 복잡해지기 시작한다. 시망... 일단 소스 파일이 늘어나는데,

ipl.nas : 부트섹터
asmhead.nas : haribote.nas
bootpack.c : C언어 파트

이렇게 3가지 소스 파일을 아래의 하... 과정을 통해 img로 만든다. 이번엔 파워포인트다!

빌트 툴 및 자세한 설명은 생략한다

그래도 이정도 수준으로 OS를 만들 수 있는 이유는 빌드 툴을 안만드니까... 나중엔 빌드 과정도 확인해보는게 좋을 것 같다.


asmhead.nas는 32bit모드의 전환을 위해 설정코드가 약 100줄이상 새로 쓰였으나 아직은 때가 아니므로 들여다보지 않는다고 한다.

C언어는 그냥 무한 루프 코드로 작성되었다.

void HariMain(void)
{

fin:
	/* 여기에 HLT를 넣고 싶지만, C언어에서는 HLT를 사용할 수 없다!  */
	goto fin;

}

어어... goto문? 만악의 근원인데



# 필자가 최적화 덕후로 보이는 이유가 'C언어에서 HLT를 못 돌리니까 CPU가 불쌍해!'라는 이유로 어셈코드를 C언어로 불러오는마왕소환 방법을 소개하였다.

먼저 HLT 함수용 naskfunc.nas파일을 작성한다.

; 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언어에서 프로토타입 선언 후 사용하는게 가능하다!

/* 다른 파일로 만든 함수가 있으면 C컴파일러에 알려준다 */

void io_hlt(void);

/* 함수 선언인데 {}가 없고 갑자기 ;를 쓰면
	다른 파일에 있다는 의미입니다. */

void HariMain(void)
{

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

}


그래서 다시 총 4가지 소스를 아래와 같이 빌드한다. 하...



원래는 어제 포스팅을 하고 싶었으나 불목이기에(금공강!)...ㅋㅋㅋㅋ
이제 C언어로 코드를 쓰게 될테니 슬슬 재미가 붙지 않을까 싶다.