티스토리 뷰


# CHAPTER 5 - 구조체, 문자 표시와 GDT / IDT 초기화



#1 부팅 정보 받기 (harib02a)


4일차까지의 bootpack.c 에서는 0xa0000 이라든가 320이나 200이라는 숫자를 직접 프로그램에 쓰고 있었는데, 본래 이 값들은 asmhead.nas 에서 가져와야 한다. 그렇지 않으면, 화면 모드를 바꿨을 때 실행이 잘 안 된다. 그렇다면 포인터를 사용해서 이 값을 가져와 보도록 해보자. 추가로, binfo는 'bootinfo'의 약어고 scm은 'screen'(화면)의 약어다.

/*************************************************
 ** HaribMain (in harib02a / bootpack.c)
*************************************************/

void HariMain(void)
{
	char *vram;
	int xsize, ysize;
	short *binfo_scrnx, *binfo_scrny;
	int *binfo_vram;

	init_palette();
	binfo_scrnx = (short *) 0x0ff4;
	binfo_scrny = (short *) 0x0ff6;
	binfo_vram = (int *) 0x0ff8;
	xsize = *binfo_scrnx;
	ysize = *binfo_scrny;
	vram = (char *) *binfo_vram;

	init_screen(vram, xsize, ysize);

	for (;;) {
		io_hlt();
	}
}

/*************************************************
 ** End Line
*************************************************/

0x0ff4 등의 번지는 asmhead.nas에서 설정한 값에 맞춘 것 뿐이다. 그리고 배경 화면을 그리는 부분은 다른 함수 init_screen으로 독립시키기로 했다. 이렇게 해서 기능적으로 독립된 함수를 만들고, 프로그램을 읽기 쉽게 만든다.



#2 구조체 사용하기 (harib02b)


앞의 소스는 그런대로 나쁘지 않지만, 앞서 설명했던 것처럼


xsize = *( (short *) 0x0ff4);


 과 같이 사용하면 짧아서 좋기는 하지만, 편법 같은 느낌이 든다. 따라서 좀 더 일반적인 방법을 써서 소스코드를 수정해보았다.

/*************************************************
 ** HaribMain과 구조체 파트 (in harib02b / bootpack.c)
*************************************************/

struct BOOTINFO {
	char cyls, leds, vmode, reserve;
	short scrnx, scrny;
	char *vram;
};

void HariMain(void)
{
	char *vram;
	int xsize, ysize;
	struct BOOTINFO *binfo;

	init_palette();
	binfo = (struct BOOTINFO *) 0x0ff0;
	xsize = (*binfo).scrnx;
	ysize = (*binfo).scrny;
	vram = (*binfo).vram;

	init_screen(vram, xsize, ysize);

	for (;;) {
		io_hlt();
	}
}

/*************************************************
 ** End Line
*************************************************/

첫 줄에 등장하는 struct 명령은 변수의 덩어리를 'struct BOOTINFO'라는 이름으로 선언한 것이다. 청므의 1바이트가 cyls라는 변수이며, 다음 1바이트가 leds라는 변수, 이하 순서대로 vram까지 늘어져 있다. (short의 경우는 2바이트, int *의 경우 4바이트라는 점을 잊지 말자.) 이 덩어리는 합계 12바이트다. 


 여기서는 *binfo가 12바이트의 변수들의 모음 덩어리인 strcut BOOTINFO의 주소를 가진다. binfo는 struct 구조체를 나타내는 포인터 변수일뿐이므로 *binfo의 자체 크기는 역시 4바이트다. 사용법의 경우 (*binfo).scrnx와 같이 되어 있는데 괄호를 써주지 않으면 C 컴파일러가 *(binfo.scrnx)와 같이 읽기 때문에 에러가 발생한다. binfo의 경우는 포인터 변수로 가리키기만 할 뿐, 그 자체가 struct BOOTINFO 를 가지고 있는 것이 아니므로 *(간접지정연산자)를 사용해서 접근해야만 한다.


binfo = (struct BOOTINFO *) 0x0ff0;


 위와 같이 사용된 이유는 단순히 컴파일러에게 알려주기 위함이다. 구조체 BOOTINFO의 주소를 가지는 binfo 라는 의미다. 구조체에 대해 잘 모르겠다면 여기서 잠시 중단하고 따로 구조체 공부를 하기 바란다.



#3 화살표 표기 사용하기 (harib02c)


사실 C언어에서는 (*binfo).scrnx와 같은 것을 써야 할 경우가 매우 많아서, 이것을 괄호를 쓰지 않고 편리하게 사용하는 방법이 있다. binfo->scrnx 이다. 이 표기를 화살표 표기라고 한다. 앞에서는 *(a + i)를 a[i]라고 쓰는 생략 표현을 봤었다. 결국 C언어는 포인터에 관한 생략 표현을 충실하게 구현해 놨다고 생각할 수 있다.


 이것을 사용하면 xsize = (*binfo).scrnx; 를 xsize = binfo->scrnx; 라고 쓸 수 있게 된다. 더 간단하게 사용하고 싶다면 굳이 xsize 변수를 따로 사용하지 말고 binfo->scrnx를 직접 사용하면 된다.

/*************************************************
 ** HaribMain (in harib02c / bootpack.c)
*************************************************/

void HariMain(void)
{
	struct BOOTINFO *binfo = (struct BOOTINFO *) 0x0ff0;

	init_palette();
	init_screen(binfo->vram, binfo->scrnx, binfo->scrny);

	for (;;) {
		io_hlt();
	}
}

/*************************************************
 ** End Line
*************************************************/

표기가 한결 보기 편해졌다. 지금껏 우리가 여러가지 조작을 했지만, 이것은 순수하게 C언어상의 표기문제일 뿐이다. 번역되는 기계어에서는 거의 차이가 없다. 차이가 없으니 코드를 작성할 때는 가독성을 우선적으로 고려하여 작성한다.



#4 문자 출력하기 (harib02d)


지금까지는 문자를 쓰려고 하면 BIOS에 부탁하면 됐지만, 지금은 32비트 모드이므로 BIOS에 의지할 수 없다. 문자를 쓰기 위해서는 어떤 방법을 쓰는가? 문자라는 것은 8 x 16의 사각형 화소의 집합으로 아래의 왼쪽 이미지를 오른쪽과 같이 0과 1로 바꾸면 된다. 그런 다음에 이 데이터를 읽어내어 화면에 점을 그려나가면 문자가 나오는 것이다. 8비트는 1바이트이므로, 1문자는 16바이트가 된다.

8 x 16 사이즈가 맘에 안들 수도 있지만 일단 이 크기로 가보도록 하자. 이러한 문자의 모양을 위한 데이터를 폰트(font) 데이터라고 한다. 이러한 폰트 데이터는 프로그램에서 어떻게 사용해야할까? 아래의 코드를 살펴보자.

/*************************************************
 ** 폰트를 위한 구조체
*************************************************/

struct char font_A[16] = {
	0x00, 0x18, 0x18, 0x18, 0x18, 0x24, 0x24, 0x24,
	0x24, 0x7e, 0x42, 0x42, 0x42, 0xe7, 0x00, 0x00
};

/*************************************************
 ** End Line
*************************************************/

이것은 바로 전에 나열한 0과 1의 데이터를 보고 16진수로 바꾼 것이다. C언어에서는 2진수 표기로 데이터를 쓰는 방법이 없으므로, 16진수나 8진수 표기로 써야 한다. 데이터가 갖추어졌다면, 다음에는 아래와 같은 함수를 사용한다. 8화소를 그리는 프로그램을 for문으로 16번 돌리면 1문자를 쓸 수 있다. (문자 크기: 8 x 16)

/*************************************************
 ** 폰트를 위한 함수
*************************************************/

void putfont8(char *vram, int xsize, int x, int y, char c, char *font)
{
	int i;
	char *p, d /* data */;
	for (i = 0; i < 16; i++) {
		d = font[i];
		if ((d & 0x80) != 0) { vram[(y + i) * xsize + x + 0] = c; }
		if ((d & 0x40) != 0) { vram[(y + i) * xsize + x + 1] = c; }
		if ((d & 0x20) != 0) { vram[(y + i) * xsize + x + 2] = c; }
		if ((d & 0x10) != 0) { vram[(y + i) * xsize + x + 3] = c; }
		if ((d & 0x08) != 0) { vram[(y + i) * xsize + x + 4] = c; }
		if ((d & 0x04) != 0) { vram[(y + i) * xsize + x + 5] = c; }
		if ((d & 0x02) != 0) { vram[(y + i) * xsize + x + 6] = c; }
		if ((d & 0x01) != 0) { vram[(y + i) * xsize + x + 7] = c; }
	}
	return;
}

/*************************************************
 ** End Line
*************************************************/

 if문이 처음 등장했는데, 이것은( ) 안의 조건식을 조사하여 조건이 만족하면 { } 안을 실행하는 구문이다. 만약 조건이 성립되지 않는다면 아무것도 실행하지 않는다. (else를 써준다면 얘기는 달라진다.)


 &는 전에 나온 AND 연산이고, 0x80 은 2진수로 10000000을 의미하므로, 이것으로 AND한 결과가 0이 되면 d의 제일 왼쪽 비트는 0이었던 것이 된다. 역으로 결과가 0이 아니라면 d의 가장 왼쪽 비트는 1이었던 것이 된다. != '같지 않다'의 의미다. 다른 언어에서는 < >라고 쓸 수도 있다.


 위의 코드를 보면 vram 주소 연산 부분이 상당히 보기 불편하게 되어 있다. 따라서 기존 주소 정보를 하나의 변수로 지정해두고 하나 씩 증가한 값을 간접지정해주는 방법으로 코드를 더 깔끔하게 고쳐본다. 그 결과 아래의 코드가 나왔다.

/*************************************************
 ** 폰트를 위한 함수 (개선) in harib02d
*************************************************/

void putfont8(char *vram, int xsize, int x, int y, char c, char *font)
{
	int i;
	char *p, d /* data */;
	for (i = 0; i < 16; i++) {
		p = vram + (y + i) * xsize + x;
		d = font[i];
		if ((d & 0x80) != 0) { p[0] = c; }
		if ((d & 0x40) != 0) { p[1] = c; }
		if ((d & 0x20) != 0) { p[2] = c; }
		if ((d & 0x10) != 0) { p[3] = c; }
		if ((d & 0x08) != 0) { p[4] = c; }
		if ((d & 0x04) != 0) { p[5] = c; }
		if ((d & 0x02) != 0) { p[6] = c; }
		if ((d & 0x01) != 0) { p[7] = c; }
	}
	return;
}

/*************************************************
 ** End Line
*************************************************/

 지금까지 만들어진 것을 바탕으로 실행을 해 보면, 알파벳 'A'가 화면에 정상출력되는 것을 확인할 수 있다.



#5 여러 문자 출력하기 (harib02e)


A를 쓰는 것은 성공했지만, 다른 문자는 아직 사용할 수 없다. 다른 문자도 사용하려면 폰트 데이터가 더 많이 필요하다. 알파벳이 26문자나 되고 각각 대문자와 소문자가 존재한다. 또한 숫자와 기호까지 합치면 그 갯수가 확 늘어난다. 위와 같은 작업을 하려면 아직 한참이나 더 남았다. 따라서 OSASK에서 사용하고 있는 폰트 데이터를 사용해보자. // 한자나 한글, 히라가나 등은 지금 당장 사용할 수 없다.


 지금부터는 hankaku.txt 라는 텍스트 파일이 소스 프로그램에 들어간다. 이 파일의 내용은 다음과 같다.

/*************************************************
 ** hankaku.txt 내용 중 0x41 파트만 in harib02e
*************************************************/

char 0x41
........
...**...
...**...
...**...
...**...
..*..*..
..*..*..
..*..*..
..*..*..
.******.
.*....*.
.*....*.
.*....*.
***..***
........
........

/*************************************************
 ** End Line
*************************************************/

이것은 16진수보다 보기 쉽고, 또한 0과 1뿐인 2진수보다 훨씬 보기 쉽다. 당연히 이것은 C언어도 어셈블러도 아니기 때문에 전용 컴파일러가 필요하다. 여기서는 OSASK를 만들 때 사용했던 툴(makefont.exe)을 사용하자. 컴파일러라고 해서 크게 어려울 것이라 생각하지만, 단지 텍스트 파일(256문자 정도를 읽는다고 가정)을 읽어 16 x 256 = 4096바이트를 출력할 뿐이다.


 이렇게 해서 완성된 것이 hankaku.bin 이지만, 이것만으로는 bootpack.obj 와 링크할 수 없다. hankaku는 아직 obj 파일이 아니기 때문이다. 그래서 링크에 필요한 정보들을 붙여 obj로 변환한다. 이것이 bin2obj.exe다.


_hankaku:

DB 여러 가지 데이터 4096 바이트 가량


 이것은 지정된 파일에 붙어서 마치 소스 프로그램을 어셈블한 것처럼 obj 프로그램으로 자동 변환한다. 우리는 OS를 만드는 것이 목적이므로 폰트 파일 등은 외부 소스를 사용한다. 각각의 툴 사용법은 Makefile의 내용을 보고 판단하도록 한다.


 위 폰트 데이터를 C언어에서 취급 하려면,


exterun char hankaku[4096];


 라고 써 두면 된다. 이와 같이 소스 프로그램 외에 별도로 준비한 데이터에는 extern 이라는 속성을 붙여준다. 이로써 C컴파일러는 위의 파일을 처리할 수 있다.


 OSASK의 폰트 데이터는, 일반적인 문자 코드인 ASCll에 의해 256문자가 준비되어 있다. A의 문자 코드는 0x41 이므로, A의 폰트 데이터는 hankaku + 0x41 * 16에 16바이트로 저장된다. C언어에서는 문자 코드를 'A'라고 나타낼 수 있으므로 0x41 대신 A를 사용하여 hankaku + 'A' * 16으로 쓸 수도 있다. 이것을 사용해서 bootpack.c에 여러 가지를 추가해서 써 보았다.

/*************************************************
 ** 수정된 HariMain in harib02e
*************************************************/

void HariMain(void)
{
	struct BOOTINFO *binfo = (struct BOOTINFO *) 0x0ff0;
	extern char hankaku[4096];

	init_palette();
	init_screen(binfo->vram, binfo->scrnx, binfo->scrny);
	putfont8(binfo->vram, binfo->scrnx,  8, 8, COL8_FFFFFF, hankaku + 'A' * 16);
	putfont8(binfo->vram, binfo->scrnx, 16, 8, COL8_FFFFFF, hankaku + 'B' * 16);
	putfont8(binfo->vram, binfo->scrnx, 24, 8, COL8_FFFFFF, hankaku + 'C' * 16);
	putfont8(binfo->vram, binfo->scrnx, 40, 8, COL8_FFFFFF, hankaku + '1' * 16);
	putfont8(binfo->vram, binfo->scrnx, 48, 8, COL8_FFFFFF, hankaku + '2' * 16);
	putfont8(binfo->vram, binfo->scrnx, 56, 8, COL8_FFFFFF, hankaku + '3' * 16);

	for (;;) {
		io_hlt();
	}
}


/*************************************************
 ** End Line
*************************************************/



#6 문자열 출력하기 (harib02f) // 업데이트 예정



댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday