[OS개발] 3일차 - C언어 도입! 우워어어엉!
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언어로 코드를 쓰게 될테니 슬슬 재미가 붙지 않을까 싶다.