티스토리 뷰
* CHAPETER 3 - 32비트 모드 돌입과 C언어 도입
#1 IPL 만들기 (initial program loader, 초기 프로그램 로더)
디스크의 맨 처음 512바이트는 부트섹터이므로 (이것은 2일째 OS 실습에서 정한 부분이다.), 그 다음의 512바이트를 읽어보자. projects / 03_day의 harib00a 안에 ipl.nas 는 기존의 ipl.nas에 추가된 부분이 존재한다.
/************************************************* ** day 03 ipl.nas, added part *************************************************/ // 기존의 ipl.nas에서 추가된 부분이다. ; added part MOV AX,0x0820 MOV ES,AX MOV CH, 0 ; cylinder 0 MOV DH, 0 ; header 0 MOV CL, 2 ; sector 2 MOV AH, 0x02 ; AH=0x02 : disk read MOV AL, 1 ; 1 sector MOV BX,0 MOV DL, 0x00 ; A drive INT 0x13 ; call disk BIOS JC error /************************************************* ** End Line *************************************************/
새로 나온 명령은 JC 뿐이다. JC라는 것은 'Jump if carry'의 약자로 캐리 플래그가 1이면 점프하라는 의미다. (뒤에 설명이 나온다.) INT 0x13 명령을 실행하면 어떤 일이 발생할까? disk BIOS의 구조를 살펴보자.
/* 디스크로부터 읽기, 디스크에 쓰기, 섹터의 검사(verify) 및 찾기 */
AH = 0x02 (읽을 때)
AH = 0X03 (쓸 때)
AH = 0X04 (검사를 할 때)
AH = 0X0C (찾기를 할 때)
AL = 처리할 섹터 수(연속된 섹터를 처리할 수 있음)
CH = 실린더 번호 & 0xFF
CL = 섹터 번호 (bit 0 ~ 5) | (실린더 번호 & 0x300) >> 2
DH = 헤드 번호
DL = 드라이브 번호
ES:BX = 버퍼 어드레스(검사 혹은 찾기를 할 때에는 이 값을 참조하지 않음)
리턴 값:
FLAGS.CF == 0 : 에러 없음, AH == 0
FLAGS.CF == 1 : 에러 있음, AH에 에러코드(리셋 기능과 같음)
위의 경우는 AH에는 0x02 값이 대입되므로, 읽기라는 걸 알 수 있다. '리턴 값'을 보면 FLAGS.CF라고 기술되어 있다. 이것이 캐리 플래그다. 즉, 이 함수를 호출하면 에러가 있을 경우에는 캐리 플래그가 0, 에러가 없을 경우에는 캐리 플래그가 1이 되는 것이다. 이것이 JC 명령의 존재이유다.
캐리 플래그라는 것은 1비트밖에 기억할 수 없는 레지스터로, CPU에는 그 밖에도 몇 가지 이러한 1비트밖에 기억 못하는 레지스터들이 존재한다. 이러한 레지스터를 플래그 라고 한다. flag의 의미는 깃발이다. 즉, 들고 내리기 이 두 가지 상태만 기억할 수 있다는 의미로 받아들이면 된다. 1bit는 두 가지의 경우의 수(0 과 1)를 나타낼 수 있으므로, 1bit면 충분하다.
캐리 플래그는 본래 캐리(carry)라는 상태를 나타내기 위해 사용되는 것이지만, CPU의 플래그 중에서 가장 다루기 쉬우므로 다른 용도에도 곧잘 사용된다. 이번에는 에러 유무의 보고에 사용한다.
나머지를 살펴보자. CH, CL, DH, DL에는 각각 실린더 번호, 섹터 번호, 헤드 번호, 드라이브 번호라는 것을 대입해야만 한다. 이번 프로그램에서는 위의 ipl.nas 소스코드에 나온 값들이 각각 사용된다.
*디스크, 드라이브
드라이브 번호라는 것은 플로피디스크 드라이브가 여러 개 연결 되었을 때, 어떤 드라이브의 디스크부터 읽어 들일까를 지정하기 위한 것이다. 요즘에는 PC에 플로피디스크 드라이브가 1개밖에 없지만, 예전에는 2개 정도 있었다. 지금은 한 개밖에 없으므로, 0번을 지정한다. 이것으로 어느 드라이브로부터 읽는지를 정했다. 다음은, 그 디스크의 어디부터 읽을까를 정할 차례다.
*버퍼 어드레스
버퍼 어드레스에 관해 알아보자. 버퍼 어드레스는 디스크에서 읽은 데이터를 메모리의 어디에 저장할 것인지를 나타내는 주소 값이다. 보통, 번지는 1개의 레지스터로 나타내면 좋을 것 같지만, BX (16비트 레지스터)만으로는 0 ~ 0XFFFF 까지의 값밖에 표현할 수 없다. 즉, 0부터 65,535번지 까지밖에 나타낼 수 없다는 의미다. PC의 메모리는 대부분 64MB 이상일 텐데, 단지 64KB까지밖에 사용할 수 없다는 건 말도 안된다.
따라서 이 점을 극복하기 위해, EBX 레지스터(32비트 레지스터)라는 것이 나중에 등장해 이것으로 4GB (2의 32승은 약 4GB)까지 다룰 수 있게 되었다. 이것은 CPU가 다룰 수 있는 최대 메모리 용량이므로, 문제 없다. 그러나 EBX가 사용될 수 있었던 건 훨씬 뒤의 이야기고, BIOS들이 설계된 시대의 CPU는 32비트 레지스터를 붙이는 것이 좀 어려웠기 때문에, 할 수 없이 보조적인 역할을 하는 세그먼트 레지스터라는 것을 만들었다. 그리고 메모리의 번지를 지정할 때 이 세그먼트 레지스터를 사용하게 된 것이다.
ES:BX라는 표현이 바로 그것이며, MOV AL, [ES:BX]와 같이 사용한다. 이 경우 메모리 번지는 ES x 16 + BX(ES는 16비트 세그먼트 레지스터)로 계산하게 되어 있다. ES 레지스터로 대략적인 번지를 정한 후에 BX로 세세하게 지정한다. 이것으로 ES에 0xffff, BX에도 0xffff 를 넣어서 1,114,095 바이트, 즉 약 1MB의 메모리 번지를 지정할 수 있게 됐다.
이 책에서 ES = 0x0820 이고, BX = 0이므로, 이 디스크의 데이터가 로드되는 곳은 0x8200(0x0820 * 16 + 0)번지부터 0x83ff번지까지가 된다. 0x8000 ~ 0x81ff 까지의 512바이트는 후에 부트섹터의 내용을 넣기 위해 남겨뒀다. OS개발 2일차에서 설명한 메모리 맵을 참고해 이 부분이 사용이 되지 않고 있다는 걸 알고 쓰는 거다. 0x7c00 ~ 0x7dff 는 부트섹터가 사용하고 있지만, 0x7e00 이후는 아무도 사용하지 않고 있고, 0x9fbff 까지는 OS가 마음대로 사용해도 된다.
지금까지 세그먼트 레지스터라는 것은 전혀 생각하지 않고 있었지만, 실은 어떤 메모리라도 세그먼트 레지스터와 함께 번지수를 지정해야만 한다는 규칙이 있다. 거의 대부분의 경우가 DS:를 지정한 것이 된다.
이제껏 MOV CX, [1234]라고 생각한 것은 MOV CX, [DS:1234]의 의미였던 것이다. MOV AL, [SI]도 MOV AL, [DS:SI]라는 의미다. 어셈블러에서는 매번 쓰는 것이 귀찮으므로 생략하게 되어 있다. 이러한 룰 때문에 DS를 0으로 해둬야만 한다. (DS x 16 + SI) 만약 DS가 0이 아니면, 그 16배수가 번지로 항상 더해지게 되므로, 어딘가 이상한 곳에 데이터를 읽고 쓰게 될지 모르기 때문이다.
#2 에러 방지를 위해 또 수정한 ipl.nas (실습 폴더 harib00b)
/************************************************* ** ipl.nas soure code in harib00b *************************************************/ ; read disk MOV AX,0x0820 MOV ES,AX MOV CH, 0 ; cylinder 0 MOV DH, 0 ; head 0 MOV CL, 2 ; sector 2 MOV SI, 0 ; count number of failure retry: MOV AH, 0x02 ; AH=0x02 : disk read MOV AL, 1 ; 1 sector MOV BX,0 MOV DL, 0x00 ; A drive INT 0x13 ; call disk BIOS JNC fin ; if(!error) goto fin ADD SI, 1 ; SI += 1 CMP SI, 5 ; SI == 5 ? JAE error ; SI >= 5 goto error MOV AH,0x00 MOV DL, 0x00 ; A drive INT 0x13 ; reset drive JMP retry /************************************************* ** End Line *************************************************/
#3 18섹터 까지 읽기 위한 코드 추가 (실습 폴더 harib00c), 시작 섹터는 2 (CL, 2)
/************************************************* ** ipl.nas soure code in harib00c *************************************************/ ; read disk MOV AX,0x0820 MOV ES,AX MOV CH, 0 ; cylinder 0 MOV DH, 0 ; head 0 MOV CL, 2 ; sector 2 readloop: MOV SI, 0 ; count number of failure retry: MOV AH, 0x02 ; AH=0x02 : disk read MOV AL, 1 ; 1 sector MOV BX,0 MOV DL, 0x00 ; A drive INT 0x13 ; call disk BIOS JNC fin ; if(!error) goto fin ADD SI, 1 ; SI += 1 CMP SI, 5 ; SI compare with 5 JAE error ; SI >= 5 goto error MOV AH,0x00 MOV DL, 0x00 ; A drive INT 0x13 ; reset drive JMP retry next: MOV AX, ES ; address += 0x200 (0x0020 * 16), ES = 16bit segment register ADD AX,0x0020 MOV ES, AX ; cannot use command like 'ADD ES, 0x020', so split to 3step ADD CL, 1 ; CL += 1 CMP CL, 18 ; CL compare with 18 JBE readloop ; CL <= 18 goto readloop /************************************************* ** End Line *************************************************/
- 처리하는 섹터 수는 0x01 ~ 0xff의 범위에서 지정(0x02 이상을 지정할 때는 연속 처리할 수 있는 조건이 있을지도 모르므로 주의 - FD의 경우에는 아마 복수의 트랙에 걸쳐 있지 않고 64KB 경계를 넘어도 안 된다고 생각합니다.)
/* 0xa3ff - 0x8200 + 1 = 8,704 가 나옵니다. 정확하게 8,704바이트 만큼 주소가 잡혀 있는 것이죠. */
/************************************************* ** ipl.nas soure code in harib00d *************************************************/ ; read disk MOV AX,0x0820 MOV ES,AX MOV CH, 0 ; cylinder 0 MOV DH, 0 ; head 0 MOV CL, 2 ; sector 2 readloop: MOV SI, 0 ; count number of failure retry: MOV AH, 0x02 ; AH=0x02 : disk read MOV AL, 1 ; 1 sector MOV BX,0 MOV DL, 0x00 ; A drive INT 0x13 ; call disk BIOS JNC fin ; if(!error) goto fin ADD SI, 1 ; SI += 1 CMP SI, 5 ; SI compare with 5 JAE error ; SI >= 5 goto error MOV AH,0x00 MOV DL, 0x00 ; A drive INT 0x13 ; reset drive JMP retry next: MOV AX, ES ; address += 0x200 (0x0020 * 16), ES = 16bit segment register ADD AX,0x0020 MOV ES, AX ; cannot use command like 'ADD ES, 0x020', so split to 3step ADD CL, 1 ; CL += 1 CMP CL, 18 ; CL compare with 18 JBE readloop ; CL <= 18 goto readloop MOV CL,1 ADD DH,1 CMP DH,2 JB readloop ; DH < 2 goto readloop MOV DH,0 ADD CH,1 CMP CH,CYLS JB readloop ; CH < CYLS goto readloop /************************************************* ** End Line *************************************************/
#5 OS 본체 작성하기
다음으로 haribote.sys 파일을 디스크 이미지 haribote.img로 저장할 차례다. 디스크 이미지로 저장하는 작업은 다음과 같다.
- 어떤 디스크 이미지 파일을 make install을 하여 디스크에 쓴다.
- 디스크를 Windows로 열어서 haribote.sys를 평소와 같이 저장한다.
- 디스크를 툴 등을 사용하여 디스크 이미지로 백업한다.
haribote.sys라는 파일명이 디스크에 어떻게 기록되는지를 직접 눈으로 볼 수 있다. 내용을 좀 더 살펴보면 0x004200에 'F4 EB FD'를 발견할 수 있다. 이것이 바로 haribote.sys의 내용이다.
어떻게 haribote.sys의 내용임을 알 수 있을까? 간단하다. haribote.sys를 바이너리 에디터로 열어보면 된다. 열어보면 이와 똑같이 3바이트로 되어 있다. 이상의 내용을 요약하자면 이렇다.
- 빈 상태의 디스크에 파일을 평소대로 저장하면
(1) 파일명은 0x002600 이후에 들어가는 듯하다.
(2) 파일의 내용은 0x004200 이후에 들어가는 듯하다.
#6 부트섹터에서 OS 본체 실행시키기
디스크 이미지 상에서 0x004200에 위치하는 haribote.sys의 코드를 실행시키기 위해서는 어떻게 하면 좋을까? 우리는 앞서 0x8000 ~ 0x81ff 에 해당하는 512바이트를 부트섹터의 내용을 넣기로 사전에 설계했으므로, (교재 79페이지, 현재 게시물의 #1 *버퍼어드레스 파트 참조) 현재 부트섹터의 맨 앞이 메모리의 0x8000번지에 오도록 디스크를 메모리에 읽어들인 상태다. 따라서 0x8000 + 0x4200 = 0xc200 으로 읽어들일 거라는 걸 알 수 있다.
/* ORG는 프로그램이 실행 시에 메모리 내 몇 번지에 로딩되는지를 알려주는 명령이다. origin의 의미다. */
#7 OS 본체의 동작 확인하기
haribote.nas를 다음과 같이 수정해본다. Windows와 같은 화면을 만들기 위해 화면 모드를 전환시키는 기능을 추가하는 작업이다.
/************************************************* ** day 03 haribote.nas in harib00g *************************************************/ // 바로 전에 작성했던 haribote.nas에 화면모드 전환 코드를 추가했다. ; haribote-os ; TAB=4 ORG 0xc200 ; where this program will be loaded MOV AL, 0x13 ; VGA graphics, 320x200x8bit color MOV AH, 0x00 INT 0x10 fin: HLT JMP fin /************************************************* ** End Line *************************************************/
- 비디오 모드
AH = 0x00
AL = 모드: (권장하지 않는 화면 모드는 생략되어 있다.)
0x03: 16색 텍스트, 80x25
0x12: VGA 그래픽스, 640x480x4bit 컬러, 독자 영역 액세스
0x13: VGA 그래픽스, 320x200x8bit 컬러, 패드 픽셀
0x6a: 확장 VGA 그래픽스, 800x600x4bit 컬러, 독자 영역 액세스 (비디오카드에 의해서는 서포트되지 않는다.) - 리턴 값: 없음
#8 32비트 모드 준비
/************************************************* ** day 03 haribote.nas in harib00h *************************************************/ // 바로 전에 작성했던 haribote.nas에 화면모드 정보를 추가했다. ; haribote-os ; TAB=4 ; BOOT_INFO 관계 CYLS EQU 0x0ff0 ; 부트섹터가 설정한다. LEDS EQU 0x0ff1 VMODE EQU 0x0ff2 ; 색 수에 관한 정보. 몇 비트 컬러인가? SCRNX EQU 0x0ff4 ; 해상도 X(screen X) SCRNY EQU 0x0ff6 ; 해상도 Y(screen Y) VRAM EQU 0x0ff8 ; 그래픽 버퍼의 개시 번지 ORG 0xc200 ; 이 프로그램이 어디에 로딩되는가? MOV AL, 0x13 ; VGA 그래픽스. 320x200x8bit 컬러 MOV AH,0x00 INT 0x10 MOV BYTE [VMODE], 8 ; 화면 모드를 메모한다. MOV WORD [SCRNX],320 MOV WORD [SCRNY],200 MOV DWORD [VRAM],0x000a0000 ; 키보드의 LED 상태를 BIOS가 알려준다. MOV AH,0x02 INT 0x16 ; keyboard BIOS MOV [LEDS],AL fin: HLT JMP fin /************************************************* ** End Line *************************************************/
#9 C언어 도입
- 먼저, cc1.exe를 사용해서 bootpack.c로부터 bootpack.gas를 만든다.
- 다음에, gas2nask.exe를 사용해서 bootpack.gas로부터 bootpack.nas를 만든다.
- 그리고, nask.exe를 사용하여 bootpack.nas로부터 bootpack.obj를 만든다.
- 또한, obj2bim.exe를 사용하여 bootpack.obj로부터 bootpack.bim을 만든다.
- 마지막으로, bim2hrb.exe를 사용하여 bootpack.bim으로부터 bootpack.hrb를 만든다.
- 이것으로 기계어가 되었으므로 copy 명령으로 asmhead.bin과 bootpack.hrb를 단순하게 붙여서
haribote.sys를 만든다.
#10 HLT 해 보기 (harib00j)
/************************************************* ** day 03 naskfunc.nas in harib00j *************************************************/ ; naskfunc ; TAB=4 [FORMAT "WCOFF"] ; 오브젝트 파일을 만드는 모드 [BITS 32] ; 32비트 모드용의 기계어를 만든다. ; 오브젝트 파일을 위한 정보 [FILE "naskfunc.nas"] ; 소스 파일명 정보 GLOBAL _io_hlt ; 이 프로그램에 포함된 함수명 ; 이하는 실제의 함수 [SECTION .text] ; 오브젝트 파일에서는 이것을 쓴 후에 프로그램을 쓴다. _io_hlt: ; void io_hlt(void); HLT RET /************************************************* ** End Line *************************************************/
/************************************************* ** day 03 bootpack.c in harib00j *************************************************/ /* 다른 파일로 만든 함수가 있으면 C 컴파일러에게 알려줌 */ void io_hlt(void); /* 함수 선언인데, { } 없이 갑자기 ;을 쓰면 다른 파일에 있다는 것을 나타낸다. */ void HariMain(void) { fin: io_hlt(); /* 이것으로 naskfunc.nas의 io_hlt가 실행된다. */ goto fin; } /************************************************* ** End Line *************************************************/
/************************************************* ** day 03 Makefile in harib00j *************************************************/ 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 /************************************************* ** End Line *************************************************/
#11 후기
포스팅을 시작할 때만 해도, 학습자들이 어려워 하거나 생소한 내용들을 위주로 다루고자 했다. 하지만 나 조차도 처음 접해보는 내용, 그리고 또 중요하다고 생각하는 내용들로만 가득 차 있었기에... 포스팅의 내용이 밑도 끝도 없이 길어져 버렸다. 포스팅의 내용을 보면, 교재 내용 + 나의 주석 (사람들이 어려워 할만한) 으로 되어 있다.
아마 앞으로도 포스팅의 방향성은 위에서 말한 것과 크게 다르진 않을 것이다. 공부 내용을 정리하고, 설명이 미흡하다고 생각되는 부분에 나만의 주석을 달아 추가하는 것. 으로 될 것이다. 30일을 채우려면 아직 멀었지만, 한 권을 떼고 어서 새로운 책을 접하고 싶다.
'Develop Story > OS' 카테고리의 다른 글
<OS 구조와 원리>: OS개발 30일 프로젝트 #5 (0) | 2017.08.09 |
---|---|
<OS 구조와 원리>: OS개발 30일 프로젝트 #4 (0) | 2017.07.21 |
<OS 구조와 원리> : OS개발 30일 프로젝트 #2 (7) | 2017.07.01 |
<OS 구조와 원리> : OS개발 30일 프로젝트 #1 (0) | 2017.06.29 |
<OS 구조와 원리> : OS개발 30일 프로젝트 #0 (0) | 2017.06.29 |
- Total
- Today
- Yesterday