C#에서 16bit grayscale PNG 파일을 Bitmap.FromFile 또는 Image.FromFile로 읽을 시 32bit argb 4바이트로 변환되어 읽게 된다.

16bit gray 이미지를 얻기 위해서는 다음과 같이 처리한다.

 


 

Stream imageStreamSource = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);PngBitmapDecoder decoder = new PngBitmapDecoder(imageStreamSource, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
BitmapSource bitmapSource = decoder.Frames[0];
int stride = (int)bitmapSource.PixelWidth * (bitmapSource.Format.BitsPerPixel / 8);
byte[] data = new byte[(int)bitmapSource.PixelHeight * stride];


bitmapSource.CopyPixels(data, stride, 0);

PNG 파일을 FileStream으로 읽고 PngBitmapDecoder로 BitmapSource를 얻는다.

그후 CopyPixels로 raw데이터를 복사한다.

 

'프로그래밍 > C#' 카테고리의 다른 글

Window class name is not valid  (0) 2014.08.06
C# array char to String 변환  (0) 2013.08.14
쓰레드 종료 지연 문제  (0) 2013.08.14
변수 타입  (0) 2013.08.14

개인적인 일과 OS 만드는 과정에서 너무 지식이 부족함을 느끼고.. 한동안 공부만 했다.

이해 안되는 개념은 이해 될때까지 예제와 문서 읽으면서 공부를 하긴 했는데 역시나 OS 만드는 것에 대한 지식이 너무 방대함을 느끼고…좌절(?).

오래된 노트북 책장속에 꼽아 놓고, 가끔 켤 때 마다 보이는 바탕화면에 OS 만들기 폴더를 볼 때 마다 내 자신이 한심해지는 것 같아 더더욱 의도적으로 외면하게 되었다.

그러다가 죽은 노트북 살리면서 생각지도 않는 의욕이 불타 올라 , 다시 시작.


우선 페이징을 설명하기 전에 가상 메모리에 대해서 설명이 필요하다.

가상 메모리 단어에서 어느 정도 무엇을 내포된 의미를 알 수 있듯이 메모리를 가상화 한다는 거다.

?????? 메모리??? 내컴퓨터에 달려있는 램???

그렇다. 이 램을 가상화 한다는 거다. 가상화 라는 단어가 어려울진 모르겠는데 쉽게 내컴퓨터에 2G짜리 램 하나 달려 있는데 OS에서 4G 달려 있는 것처럼 속인다(?)라는 거다. 사기???? 맞다.. 사기치는 거다. 짜임새 있고 논리적으로…. 프로세스(프로그램)며 OS까지 전부 다 개별적으로 4G를 쓸 수 있게 해주는 황금 알을 낳게 해주는 기술이다. (배를 갈라보자…)

그럼 왜 사기 치는 걸까???
(당연하지 않나? 돈이 없어서.. ㅋㅋㅋㅋㅋ)

점점 소프트웨어들의(프로그램,OS) 기능이 커짐에 따라 메모리도 많이 소모하게 되었는데 이게 물리적인 메모리 램은 한정되어 있는 상황에서 서로 메모리 가져오기 전쟁을 벌어야 했기 때문이다.
MS-DOS 시절 한글 띄워 보겠다고 한글 램 상주 프로그램 실행 후 게임을 실행할 때를 생각해 보자… 메모리 부족으로 실행되지 않는다. 그러면 램 상주 프로그램 종료 시키고… 이것저것 최적화 해서 게임 실행 했었던 시절을 떠 올려 보니 이해가 쉬워 진다. 프로그램들은 메모리를 쓰고 싶은데 딴놈이 그놈의 메모리를 차지 하고 있으니 사용자가 간섭해서 종료 시키고 최적화 시켰던 그 시절…(추억이 새록새록하다.)

유한한 메모리 공간이 가상 메모리 기술을 사용한다고 해서 갑자기 없던 메모리가 3차원 우주공간에 생성되어 그 공간을 사용하는 그런 공상 과학적인 그런건 아니다. 앞에서 말했던 것처럼 속이는 거다. 어떻게????

우선 가상메모리는 개별 프로그램 및 OS에게 너에게 주어진 메모리 공간은 4G이다.(난 32bit OS를 목표로 하기 때문에 4G로 표기한다.) 라고 넉넉한(?) 메모리 공간을 제시한다. 그러면 좋아라 하겠지…. 마구마구 메모리 할당하고 쓸것이다. 지지고 볶고 . 어라 저놈도 메모리 크게 잡네?? 나도 잡자 … 마구 마구 할당 하면 그래 버블이 발생한다. 버블이 발생하면? 죽는거지 뭐… (갑자기 옆길로……샜다.)

가상 메모리는 개별 프로그램들에게 메모리 공간 4G와 독립적 메모리 공간을 약속한다. 4G공간은 물론 가상 공간인 것일 테고 독립적 메모리 공간이라 함은 각 프로그램들의 메모리 공간은 서로 접근할 수도 없고 독립적으로 메모리 0번지 ~0xFFFFFFFF 까지 나만의 공간을 만들어 준다는 것이다.
예를 들어 A프로그램이 메모리 0x100000에 어떤 데이터를 써 놨다고 하자. 그게 온라인 커뮤니티 아이디 , 패스워드라고 한다면 B라는 프로그램은 A프로그램이 0x100000 메모리 아이디, 패스워드를 저장해 놓은 걸 알고 있는 상황에서 메모리 0x100000 를 읽어 보면???? 아무것도 없다.  이는 각 프로그램마다 제공된 메모리 공간은 독립적으로 타 프로그램과 공유하지 되지 않는다는 의미인 것이다.

(독립된 가상 메모리는 실제 컴퓨터 메모리상의 주소와 일치 하지 않는다. A프로그램 0x100000 가상 메모리 주소는 실제 메모리 0x100000에 적재되지 않는다. 가상 메모리상의 주소는 실제 메모리 주소가 결정되지 않는 논리적 가상의 주소일 뿐이다. 이렇게 되지 않는다면 A프로그램과 B프로그램이 0x100000을 사용하게 된다면 메모리가 독립적 사용이 불가능 하게 된다. 이는 페이징에서 좀더 설명한다.)

그리고 가상 메모리를 프로그램 별 4G로 준다는 의미는 무엇인가? 분명히 앞서 설명한 바에 의하면 각 프로그램은 실제 램 2G가 설치된 컴퓨터에도 사기친(?) 4G를 제공한다고 했다. 그렇다면 위에서 예를 들었던 A,B 프로그램 두개가 독립적인 메모리 공간으로 4G를 가지고 있으므로 4G X 2 = 8G 라는 공간이 컴퓨터에 발생이 필요 할 텐데 가상 메모리는 물리적 메모리 2G로 어떻게 8G라는 공간을 만들어 놓을까?? (사기는 그럴듯 해야 사기빨이 받는 법이다…)

이런저런 아키텍처를 연구하는 사람들이 가만히 프로그램들을 살펴보니 4G가 공간을 주더라도 0x00000000 ~ 0xFFFFFFFF 메모리 공간에 차곡차곡 메모리를 쌓아 쓰지 않는 점을 밝혀 냈단다. 또한 프로그램 이라는게 4G메모리를 전체 할당해서 사용한다고 해도 메모리를 사용(CPU가 사용 시점)하는 시점에는 전체가 아닌 부분만 사용한다는 패턴을 찾아 냈다.  그러니까 메모리를 4G 전체 할당 한 상태에서 실제 CPU가 사용하는 메모리는 극히 작은 부분만 필요하다는 것이다.

   
image

즉, A,B 프로그램에서 8G를 사용할 경우, 실제 CPU가 사용하는 부분만 메모리에 올리고 나머지 부분은 우리가 흔히 OS사용하면서 보았던 페이징 파일로 디스크에 저장해 놓는다. 즉 실제 컴퓨터 메모리 2G는 A프로그램과 B프로그램 전체의 메모리를 저장하지 않고 페이징 파일로 저장해 놨다가 필요 할 때 실제 컴퓨터 메모리로 필요한 만큼 올리는 것이다. (필요할 때 필요한 만큼 꺼내 쓴다….역시나 실제 메모리에 올려 놓은 데이터가 더이상 쓰지 않거나 당분간 쓰지 않을 것 같을 때는 다시 파일로 보낸다.) 이렇게 실제 메모리와 디스크에 메모리 데이터를 서로 교환 작업함으로써 각 프로세스가 독립된 4G 공간의 메모리를 확보 할수 있게 되는 것이다.


페이징이란 앞서 설명한 가상 메모리를 구현 할 수 있게 만드는 메모리를 관리 기법을 말한다. 위 가상 메모리 설명에서 CPU가 필요로 할 때 페이징 파일에서 실제 메모리로 필요한 만큼 꺼내 쓴다라고 설명했다. 이때 실제 메모리로 페이징 파일에서 데이터를 옮기는 데이터량의 최소 단위를 페이지 말한다. 다시 말하면 메모리의 크기를 페이지 단위로 나눠서 데이터를 이동시킨다 라고 할 수 있다. 여기서 메모리를 페이지 단위로 나눠서 관리하는 기법을 페이징 이라고 말한다고 한다. (메모리를 페이지 단위 쪼개는 작업이라고 볼 수 있다.) 페이지는 2 제곱 값으로 지정 될 수 있으며 보통 4096byte(4kb) 으로 설정된다. (리눅스나 윈도우도 4KB)
메모리를 페이지 단위로 나누는 작업 자체가 페이징이라고 했지만 좀더 심화된 작업이 있다. 위에 가상 메모리 4G 를 구현하기 위해서는 페이징 이라는 작업을 해줘야 내 OS에 4G 가상 메모리 공간을 만들 수 있다. 조금 많이 난해하고 어렵다. 가상 메모리라는 용어하고 페이징이 결합하면서 실제 주소, 가상 주소 단어가 등장하게 된다. 이 단어가 글에서 왕복된 설명이 나오는 순간.. 머리가 글을 읽는 눈을 쫒아 가지 못하고 … 퍼진다. (그래도 진행해 보자….)

각 프로세스별 4G 가상 메모리 공간을 만들기 위해선 가상 메모리 4G 공간을 페이지 단위로 나눈다. 이 나눠진 페이지들을 표로 만들고 각 페이지 마다 번호를 붙여 보면 0부터 1048574 까지 번호가 부여 될 것이다. (4294967295(4G) ÷ 4096(페이지) = 1048575가 나온다. ) 1048575개의 페이지는 1MB 공간을 뜻하는 것이다. 즉 가상 메모리 4G를 구현하기 위해서는 실제 메모리 1MB의 할당이 필요해지며 여기에 가상 메모리 4G를 실제 메모리에 연결하기 위해서 각 페이지 마다 연결 참조 포인트 4Byte를 추가하면 4M 바이트의 공간이 각 프로세스 마다 고정적으로 생긴다는 결혼이다.

image
image

프로세스 마다 4MB 공간을 필요로 하다는 말은 프로세스 마다 고정적인 4MB의 메모리를 사용한다는 말인데 이건 정말 큰 메모리이다. 위 계산은 한 프로그램이 4G를 다 사용 할 때의 계산으로 작은 메모리(100kb 미만 메모리 할당)만 사용하는 프로그램도 4M의 고정적인 메모리를 할당 하고 실행된다.
이에 작은 메모리를 사용하는 프로그램과 많은 메모리를 사용하는 프로그램들의 독립적 메모리 공간과 4G의 가상 메모리를 제공하고 각각의 사용 메모리별 형평성(?), 계산 속도 최적화를 밤낮 연구하다가 방법을 만들어 냈다. (밤낮 연구했는지 그냥 알고 있는 내용을 구현했는지 난 모른다. 똥 싸다가 생각해 냈는지도 모른다. 또는 그냥 어렵게 만들려고 했을지도 모르겠고.. 그냥 이런 방식이 있다는걸 제안했다..)

우선 4바이트를 가진 1024개의 배열을 만든다. 총 크기는 4kb 이다. 이걸 프로그램 실행 때마다 할당 한다. 4M에서 4kb로 1024배로 줄었다. 오~~~~~~~~~~~. 다이어트를 제대로 해 버렸다. 이건 다이어트 수준이 아니라 병수준이다… 흠… 여튼… 4M를 4kb로 줄이는 대신 복잡해 졌다. 뭐든 복잡한게 간단해지면 그 반대 급부로 무언가가 발생한다…(절대량은 변하지 않는다??? ㅋ)

자 , 이 4kb가 어떻게 4G를 공간을 표현하는지 살펴 보자.

우선 4byte 크기로 1024 개의 배열을 만든다. 이 배열의 각 인덱스(요소) 들은 또다른 4byte크기의 배열의 시작 주소를 가르킨다. 어렵지~~~ ㅋㅋㅋㅋㅋ… 1024개의 배열이 있는데 배열의 각 인덱스는 4byte크기의 1024 배열을 가지고 있다 라고 표현해도 어려울 것이다. 이건 그림으로…

image
1024개의 배열의 각 인덱스는 1024개의 배열의 첫 시작 주소를 가지고 있다. 1024의 배열의 크기가 4096 이고 각 배열 인덱스가 4byte 1024 배열을 가지고 있으므로 4096 + (1024 x 4096) 은??? 약 4Mb이다. 기존 4MB 고정 보다 4G 전체 할당 했을 시 보다 4kb 더 많긴 하지만 이렇게 하면 1Mb의 작은 메모리를 할당하는 프로그램들은 4kb + 4kb 즉 8kb만 프로그램 할당 되므로 뭐… 나름 괜찮다고 볼수 있다. 
왜 8kb 냐고??? 음… 프로그램 실행때 기본 생성되는 4kb와 첫번째 배열 인덱스 하나는 총 4Mb까지의 메모리를 가르킨다. 주소를 가르키는 4byte가 1024개 있으므로 4MB이다.
즉 첫번째 배열의 각각의 인덱스는 메모리 주소 4MB 제곱으로 메모리 주소를 가르킨다라고 할 수 있다.


이제 4G 주소 공간 표현을 개념적으로 알아봤으니 실제로는 어떻게 계산되는지를 알아보자.

주소를 나타내는 포인터는 4바이트이다. (당연하지 않겠나… 4G까지 가르킬려면 4바이트가 필요하다) 이 주소를 32비트로 표현해서 비트 23번째 비트부터 32번째 비트를 첫번째 배열 인덱스값. 13번째 부터 22번째 비트까지를 첫번째 배열이 가르키는 배열의 인덱스값으로 구분한다. 그리고 나머지 1번째 부터 12번째 비트는 오프셋 값으로 구분하면 된다.

image
이해를 돕기 위해 그림을 그렸다. 위 그림은 가상 주소 0x1234가 어떻게 실제 메모리를 가르키게 되는지를 표현한 그림이다.
1. 23bit~32bit가 0 이므로 첫번째 배열(주황색) 인덱스 0을 가르킨다.
2. 첫번째 인덱스 0은 두번째 배열(연두색)의 주소를 가르키고 있다.
3. 13bit~22bit는 1 번째 인덱스를 가르킨다.
4. 그러면 첫번째 배열 인덱스0이 가르키는 두번째 배열 1번째 인덱스를 가르킨다고 볼수 있다.
5. 두번째 배열 1번째 인덱스는 실제 메모리 주소를 가르키고 있다. 어느 주소인지는 모른다. 할당 할 당시에 주소이기 때문에 임의의 주소일 것이다. 이 임의의 주소에서 564번째를 가르킨다.
실제 메모리도 4096 단위로 나눠져 있는 주소 단위이다. 두번째 배열 1이 가르키고 있는 주소는 4096 단위로 정렬된 주소이므로 이 주소에서 564 오프셋 만큼 떨어진 지점을 가르키고 있다.


이 가상 주소 변환 작업은 고맙게도 CPU가 해준다고 한다. 아니 CPU안에 있는 MMU 라는 놈이 해준단다. 그리고 위 첫번째 배열 이니 두번째 배열이니 하는 배열도 각자 용어가 따로 존재한다. 
난 이해를 돋기 위해 배열 이라고 사용했다. (뭐…..프로그램상으로 배열로 처리…) 이는 배열이 아닌 테이블이라고 불린다. 또한 첫번째 배열이라고 칭했던건 Page Directory Entry(PDE)라고 하며 첫번째 배열 PDE가 가르키는 배열(테이블)은 Page Table Entry(PTE) 라고 불리고 있다.
각 프로그램마다 첫번째 배열. 즉 PDE 4kb가 할당되어져 있다. 이를 CPU가 어느 프로그램을 선택해서 실행이라는 나쁜(?)짓을 할려고 할 때 이 PDE를 CPU에 등록한다. 즉 CPU가 작업 하는 프로그램을 변경하는 시점(컨텍스트 스위칭 이라고 하는데…..후에….)에 PDE 테이블 주소만 설정해 주면 알아서 다 해 준다.




이로써 개념 설명은 끝났다.

지금까지 바람 OS는 보호모드로 진입후 키보드 처리 하는 단계까지 구현되어 있는데 이제 가상 메모리 처리를 함으로써 명실상부한 32비트 OS로 거듭날 예정이다. 물론 구현에서 제대로 된 동작까지는 ……휴…갈길이 멀다… 어떤일이 펼쳐 질지는… 또 한 2년 잠수할지도….





'프로그래밍 > OS 만들기' 카테고리의 다른 글

OS 만들기 #19 키보드 처리 및 쉬어가기.  (0) 2015.08.16
OS 만들기 #18 IDT - 3  (0) 2015.08.12
OS 만들기 #17 IDT - 2  (0) 2015.08.10
OS 만들기 #16 IDT - 1  (0) 2015.07.30
OS 만들기 #15 커널-키보드 입력  (0) 2015.02.09

IDT까지 어찌어찌 해서 처리는 해 놓은 것 같다. 그런데 갈수록 함수가 늘고 이것저것 변수들도 늘고 이러니 메인 파일이 지저분 하다. “뭐… 언제 시간 나면 한번 정리 해야지..~

그런데… 다음에 할 내용이 조금 무겁고 어렵다.. 페이징. 많이 어렵다. 이걸 구현 하느리 기존 소스 정리나 하는 게 나을 것 같다. 쉴겸…..~~

우선.. StartKernel() 함수를 좀더 간단하게 정리했다.

int  StartKernel(int argc, char *argv[])
{
	char *hello = "Hello OS!";
	char *complete = "complete.";
	BYTE* vidmem = (BYTE*)0xB8000;
	BYTE data = 0;
	int b = 5;
	int c = 0;


	kprint(hello);

	InitPIC();
	Init_IDT();
	Init_kbd();
	
	while(1) ;
	return 0;
}

InitPIC() PIC 초기화 함수

Init_IDT() IDT 초기화 함수

Init_kbd() 키보드 관련 초기화 함수.

많이 간단해졌다. 

printf 함수도 다시 구현하였다… 커널에서 프린트 한다는 의미로 kprint (많은 OS 들도 kprint 라고 하더라 ^^ ㅋㅋㅋ. 그래서 이름을 차용했다.)로 함수명을 바구고.. 문자열만이 아닌 가변 인자를 받아 출력 할 수 있도록 수정하였다.

문자열 관련 함수들 . strlen, strcpy, strcmp, memcpy, memcmp, memset 이런 함수들은 그냥 기존 코드들을 가져왔다. 이런걸로 시간 뺏기기 싫어서..(라고 쓰고… 만들 줄 모르고… 다시 작성한다는게 귀찮아서~^^)..^^

그리고 IDT 최종 키보드 입력 부분까지 구현했었는데..

문제는 대문자와 특수문자( !~@#등) 출력이 되지 않았다… 그래서 키보드 테이블을 대문자와 특수문자를 출력 할 수 있도록 SHIFT키와 CAPS LOCK키를 지원하도록 수정했다.

#include "stdio.h"
#include "keyboard.h"
#include "8042.h"
#include "asm.h"

BOOL IsLShift = FALSE;
BOOL IsRShift = FALSE;
BOOL IsCapslock = FALSE;
BOOL IsAlt = FALSE;
BOOL IsCtrl = FALSE;
BOOL IsNumLock = FALSE;


KeyboardInfo kbd_scancode[] = 
{
	{0x00,	0x00, 0x00},	// ESC
	{'1','!',	0x00, 0x00},
	{'2','@',	0x00, 0x00},
	{'3','#',  	0x00, 0x00},
	{'4','$', 	0x00, 0x00},
	{'5','%', 	0x00, 0x00},
	{'6','^',	0x00, 0x00},
	{'7','&',	0x00, 0x00},
	{'8','*',	0x00, 0x00},
	{'9','(',	0x00, 0x00},
	{'0',')',	0x00, 0x00},
	{'-','_',	0x00, 0x00},
	{'=','+',	0x00, 0x00},
	{0x00,0x00, 	0x00, 0x00},	// BACKSPACE
	{0x00,0x00, 	0x00, 0x00},	// TAB
	{'q','Q', 	0x00, 0x00},
	{'w','W', 	0x00, 0x00},
	{'e','E', 	0x00, 0x00},
	{'r','R', 	0x00, 0x00},
	{'t','T', 	0x00, 0x00},
	{'y','Y', 	0x00, 0x00},
	{'u','U', 	0x00, 0x00},
	{'i','I', 	0x00, 0x00},
	{'o','O', 	0x00, 0x00},
	{'p','P', 	0x00, 0x00},
	{'[','{', 	0x00, 0x00},
	{']','}', 	0x00, 0x00},
	{0x00,0x00, 	0x00, 0x00},	// ENTER
	{0x00,0x00, 	0x00, 0x00},	// CTRL
	{'a','A', 	0x00, 0x00},
	{'s','S', 	0x00, 0x00},
	{'d','D', 	0x00, 0x00},
	{'f','F', 	0x00, 0x00},
	{'g','G', 	0x00, 0x00},
	{'h','H', 	0x00, 0x00},
	{'j','J', 	0x00, 0x00},
	{'k','K', 	0x00, 0x00},
	{'l','L', 	0x00, 0x00},
	{';',':', 	0x00, 0x00},
	{'\'','"', 	0x00, 0x00},
	{'`','~', 	0x00, 0x00},
	{0x00,0x00, 	0x00, 0x00},	// Left Shift
	{'\\','|', 	0x00, 0x00},
	{'z','Z', 	0x00, 0x00},
	{'x','X', 	0x00, 0x00},
	{'c','C', 	0x00, 0x00},
	{'v','V', 	0x00, 0x00},
	{'b','B', 	0x00, 0x00},
	{'n','N', 	0x00, 0x00},
	{'m','M', 	0x00, 0x00},
	{',','<', 	0x00, 0x00},
	{'.','>', 	0x00, 0x00},
	{'/','?', 	0x00, 0x00},
	{0x00,0x00,	0x00, 0x00},	// right shift
	{0x00,0x00, 	0x00, 0x00},	// PrtSc
	{0x00,0x00, 	0x00, 0x00},	// ALT
	{' ',' ', 	0x00, 0x00},	// space
	{0x00,0x00, 	0x00, 0x00},	// caps
	{0x00,0x00, 	0x00, 0x00},	// F1
	{0x00,0x00, 	0x00, 0x00},	// F2
	{0x00,0x00, 	0x00, 0x00},	// F3
	{0x00,0x00, 	0x00, 0x00},	// F4
	{0x00,0x00, 	0x00, 0x00},	// F5
	{0x00,0x00, 	0x00, 0x00},	// F6
	{0x00,0x00, 	0x00, 0x00},	// F7
	{0x00,0x00, 	0x00, 0x00},	// F8
	{0x00,0x00, 	0x00, 0x00},	// F9
	{0x00,0x00, 	0x00, 0x00},	// F10
	{0x00,0x00, 	0x00, 0x00},	// number lock
	{0x00,0x00, 	0x00, 0x00},	// scroll
	{0x00,0x00, 	0x00, 0x00},	// home { numlook = 7 }
	{0x00,0x00, 	0x00, 0x00},	// up	{ numlook = 8 }
	{0x00,0x00, 	0x00, 0x00},	// pgup	{ numlook = 9 }
	{'-','-', 	0x00, 0x00},	// -
	{0x00,0x00, 	0x00, 0x00},	// left	{ numlook = 4 }
	{0x00,0x00, 	0x00, 0x00},	// center	{ numlook = 5 }
	{0x00,0x00, 	0x00, 0x00},	// right	{ numlook = 6 }
	{'+','*', 	0x00, 0x00},	
	{0x00,0x00, 	0x00, 0x00},	// end	{ numlook = 1 }
	{0x00,0x00, 	0x00, 0x00},	// down	{ numlook = 2 }
	{0x00,0x00, 	0x00, 0x00},	// pgdn	{ numlook = 3 }
	{0x00,0x00, 	0x00, 0x00},	// ins	{ numlook = 0 }
	{0x00,0x00, 	0x00, 0x00},	// del	{ numlook = . }
	{'/','/', 	0x00, 0x00},	// /
	{0x00,0x00, 	0x00, 0x00},	// enter
	{0x00,0x00, 	0x57, 0xD7},	// F11
	{0x00,0x00, 	0x58, 0xD8},	// F12
	{0x00,0x00, 	0x52, 0xD2},	// ins
	{0x00,0x00, 	0x53, 0xD3},	// del
	{0x00,0x00, 	0x47, 0xC7},	// home
	{0x00,0x00, 	0x4F, 0xCF},	// end
	{0x00,0x00, 	0x49, 0xC9},	// pgup
	{0x00,0x00, 	0x51, 0xD1},	// pgdn
	{0x00,0x00, 	0x4B, 0xCB},	// left
	{0x00,0x00, 	0x4D, 0xCD},	// right
	{0x00,0x00, 	0x48, 0xC8},	// up
	{0x00,0x00, 	0x50, 0xD0},	// down
	{0x00,0x00, 	0x38, 0xB8},	// right alt
	{0x00,0x00, 	0x1D, 0x9D},	// right ctrl
	{0x11,0x00, 	0x00, 0x00}};	// pause	// 99 index

void Init_kbd()
{
	int i = 0;

	for(i = 0; i < 0x54; i++ )
	{
		kbd_scancode[i].Press = i+1;
		kbd_scancode[i].Release = 0x81 + i;
	}

	Write_port_byte(0x21, 0xFD);
}

int GetScanCodeInfo(BYTE code, KeyboardInfo *pkbdInfo)
{
	int i;

	if( code > 0xD9 )	// Unknown code
		return UNKNOWN_SCANCODE;

	for( i = 0; i < 0x64; i++ )
	{
		if( kbd_scancode[i].Press == code )
		{
			*pkbdInfo = kbd_scancode[i];
			return PRESS_SCANCODE;
		}

		if(kbd_scancode[i].Release == code)
		{
			*pkbdInfo = kbd_scancode[i];
			return RELEASE_SCANCODE;
		}
	}

	return UNKNOWN_SCANCODE;
}

void Kbd_ScanCode()
{
	BYTE scancode;
	int scancode_type;
	KeyboardInfo	Info;

	scancode = GetKeyboardScanCode();

	scancode_type = GetScanCodeInfo(scancode, &Info);
		
	if(scancode_type == UNKNOWN_SCANCODE )
	{
		return;
	}

	if( scancode_type == RELEASE_SCANCODE )
	{
		switch(Info.Press)
		{
		case KEY_LSHIFT:
			IsLShift = FALSE;
		case KEY_RSHIFT:
			IsRShift = FALSE;
			break;
		case KEY_ALT:
			IsAlt = FALSE;
			break;
		case KEY_CTRL:
			IsCtrl = FALSE;
			break;
		}
		return;
	}

	if( scancode_type == PRESS_SCANCODE )
	{
		switch(Info.Press)
		{
		case KEY_LSHIFT:
			IsLShift = TRUE;
			break;
		case KEY_RSHIFT:
			IsRShift = TRUE;
			break;
		case KEY_ALT:
			IsAlt = TRUE;
			break;
		case KEY_CTRL:
			IsCtrl = TRUE;
			break;
		case KEY_CAPSLOCK:
			IsCapslock = !IsCapslock;
			break;
		case KEY_NUMBERLOCK:
			IsNumLock = !IsNumLock;
			break;
		case KEY_ENTER:
			kprint("\n");
			return;
		}

		if( IsCtrl || IsAlt )
		{
			// function key process
		}

		if( IsNumLock )
		{
			switch( Info.Press )
			{
			case 0x47:
				putc('7');
				return;
			case 0x48:
				putc('8');
				return;
			case 0x49:
				putc('9');
				return;
			case 0x4B:
				putc('4');
				return;
			case 0x4C:
				putc('5');
				return;
			case 0x4D:
				putc('6');
				return;
			case 0x4F:
				putc('1');
				return;
			case 0x50:
				putc('2');
				return;
			case 0x51:
				putc('3');
				return;
			case 0x52:
				putc('0');
				return;
			case 0x53:
				putc('.');
				return;
			}
		}

		if( IsCapslock )
		{
			if( Info.Lowercase >= 'a' && Info.Lowercase <= 'z')
			{
				if( IsLShift || IsRShift )
					putc(Info.Lowercase);
				else
					putc(Info.Uppercase);

				return;
			}
		}
		if( IsLShift || IsRShift )
		{
			if(Info.Lowercase != 0x00 )
				putc(Info.Uppercase);
			else
			{
				// function key process

				// otherwise
			}

			return;
		}

		if( Info.Lowercase != 0x00 )
			putc(Info.Lowercase);
	}
}

잘 된다.

그후 이것저것 바꿨다.

 

전번에 IDT를 구성하고 커널까지 컴파일 하고 부팅까지 해 보았다. 물론 아무런 기능도(?) 넣지 않아 눈으로 보기에는 아무 변화도 없었다. 재부팅 안하는게 어디야~~~. 우선 인터럽트 동작이 잘 동작 하는지 확인 해 보는 코드를 넣어서 내가 만든게 잘 동작하는지 확인해 보잣!!!! 아자~


int  StartKernel(int argc, char *argv[])
{
	char *hello = "Hello OS!";
	char *complete = "complete.";
	unsigned char* vidmem = (unsigned char*)0xB8000;
	unsigned char data = 0;
	int b = 5;
	int c = 0;


	printf(hello);

	InitPIC();
	Init_IDT();

	b = b/0;
	
	while(1) ;
	return 0;
}


void _declspec(naked) interrupt_handler()
{
	_asm pushad;

	printf("interrupt handler");
	_asm {
		popad
		iretd
	}		
}

인터럽트 중에 0으로 나누면 예외상황 인터럽트가 발생하는 인터럽트가 있었다. 그래서 0으로 나누면 분명히 인터럽트가 발생 할것이고 interrupt_handler함수에서 인터럽트가 발생하면 interrupt handler라는 문구를 찍도록 해보았다. 자~~ 컴파일..

헉!!!!! interrupt handler!! 무한 출력 후 재부팅 된다.

결과야 어찌되었든 인터럽트가 발생하여 화면에 출력이 되었다는 거에 만족하자~~ ㅋㅋㅋㅋㅋ 대충 했는데도 된다… ㅋ 오늘은 확인 했으니 이만 잘까??? 음… 뭐 IDT를 구현하게 된데에는 키보드 입력 받아서 출력 하는데 목적을 두었으니 키보드로 입력하면 키출력 하는데까지 하고 자야 겠다.

우선 0으로 나누는 부분을 지우고!~ 

키가 눌려지면 interrupt_handler함수가 호출 될테고 전번 포스팅 에서 했었던 것 처럼 0x60 포트를 읽으면 될것이다. 그러면 기존 코드를 붙여서..


void _declspec(naked) interrupt_handler()
{
	char keycode;

	_asm pushad;


	keycode = Read_port_byte(0x60);
	if(keycode >= 0 )
	{
		putc((char)kbd_scancode[keycode]);
	}
	_asm {
		popad
		iretd
	}		
}

짠,,… 컴파일은 잘 된다… 자 그럼!~

5..4..3..2..1 ……. 음… 안돼! 키 입력해도 아무런 변화가 없어!~~

왜???????????????????????????????????????????????????????????????????????????

IDT가 잘못 구성 되었나?? ….

소스를 봐도 뭐가 잘못 된 건지 모르겠다.. 디버깅도 안되고…ㅠㅠ 키보드 관련 검색부터 해보자..흠..

아….INIT_PIC함수에서 잘못 되었나? 그러고 보니 하드웨어 인터럽트 즉.. 8259 PIC는 하드웨어 인터럽트와 충돌이 나서 주소를 옮겨야 된다고 했다. 안 옮겨줬나?? 아닌데… 키보드 입력 되었는데… 너무 인터넷에서 긁어서 복사만 했나~~~ 흠. 그러면 다시 INIT_PIC 함수를 분석해보자 ..

void InitPIC()
{
	 
	Write_port_byte(0x20, 0x11);       
	Write_port_byte(0x21, 0x20);       
	Write_port_byte(0x21, 0x04);       
	Write_port_byte(0x21, 0x01);       
	Write_port_byte(0x21, 0xFF);         

	
	Write_port_byte(0xA0, 0x11);       
	Write_port_byte(0xA1, 0x28);       
	Write_port_byte(0xA1, 0x02);       
	Write_port_byte(0xA1, 0x01);       
	Write_port_byte(0xA1, 0xFF);        
}
      

Write_port_byte 함수야.. 뭐..첫번째 인자는 포트이고 두번째는 데이터…라는 것이고

0x20 포트에 0x11 데이터를 쓴다…. 왜?? 음.. 그냥 첫번째 PIC 아니 마스터 PIC 너를 초기화 할것이다. 라는 의미인것 같다.

Write_port_byte(0x21, 0x20) 이건 너의 인터럽트 시작 주소는 0x20 이다 라고 지정해주는 것이고.
Write_port_byte(0x21, 0x04) 요것은 음.. 마스터 PIC 3번째하고 슬레이브 PIC가 연결되어 있다라는걸 설정하는 것이란다. 3번째 인데 왜 4냐고?? PIC는 8개 비트로 구성되어 있는데~~~ 3번재 연결 핀이..

8bit 7bit 6bit 5bit 4bit 3bit 2bit 1bit
          1    

4가 된다..
Write_port_byte(0x21, 0x01) 이것은 8086 모드라는 것인데… 모르겠다.. 1로 설정 할 경우는 8086. 0인경우 80/85 모드라는데 .. 모르겠다. ㅡㅡ;;;;

Write_port_byte(0x21, 0xFF) ?????? 마스터 PIC 에서 모든 인터럽트를 막는다????? 이것 때문에 키보드가 입력 안되었던 것인가

Write_port_byte(0xA0, 0x11)  슬레이브 PIC 너를 초기화 해주겠어
Write_port_byte(0xA1, 0x28)  인터럽트 시작주소는 28 이다…
Write_port_byte(0xA1, 0x02);  넌 마스터PIC와 두번째 핀과 연결되어 있다.
Write_port_byte(0xA1, 0x01)  ?? 8086 ㅡㅡ;;;;;
Write_port_byte(0xA1, 0xFF)   모든 인터럽트를 막는다!!!!

그러니까… 저기 뻘건색으로 줄쳐진 이놈들 때문에 키보드가 동작하지 않은 것인가??? 왜???? 왜 막아야 하는건가??? 모르겠다… 지랄같은 영어.. 여튼.. 키보드는 마스터 PIC 두번째 핀에 연결되어 있다고 하니 0xFF를 0xFD로 바꿔보자… 그리고 컴파일 . 확인.

오!!!!!!!!!!!!!!

보라… Hello OS! 옆에 작은 a 으흐흐흐 동작한다… IDT 구성은 문제 없고 키보드 인터럽트도 잘 받고…??? 어라 그런데 한번만 입력된다…. 검색해보니 이러더라 EOI 를 보내라고 …. 그냥 EOI를 보내서 PIC 를 리셋 시켜야 된다고 한다.  마스터 PIC에서 인터럽트가 발생했으면 0x20 포트에다가 보내고… 슬레이브 PIC에서 발생했으면 0xA0과 , 마스터 PIC 0x20 둘다 보내야 한다고 한다. 그러면 인터럽트가 발생하면 발생한 인터럽트 IRQ를 알아야 마스터인지 슬래이브 인지 아는것 아닌가…흠… CPU가 인자로 넘겨주나??? 그런가 보다.. 넘겨주는 듯… 귀찮으니 우선은 키보드만 입력 받고… EOI를 보내라고 했으니 마스터PIC에 0x20을 보내고 다시 테스트.

흠…

잘 된다..


 

 

8042 키보드 컨트롤러

모르는건 인터넷에서 검색하다가 그것마저 귀찮으면 소스 찾아서 긁어서 사용하다 보니, 진행하다 보면 이게 뭐지? 하는 내용들이 종종 생긴다. “이것까지 내가 알아야 하나?” 하고 어물쩡 넘어갔는데 그런게 너무 반복 되다 보니  “내가 지금 뭐하고 있지?” 라는 생각이 요즘 많이 든다. 단순히 OS 만들것이면 인터넷에 널려 있는 소스들 수정해서 만들면 된다. 그냥 내가 목표로 하는 OS만 결과로 던져 내면 되는 것이다.  자… 그러면 난 지금 무엇을 하고 있는 것인가? OS를 만드는 것인가… OS를 만들기 위해서 공부를 하고 있는 것인가… 흠 그러다가 OS 만들기의 대단원의 시작인 첫 포스팅 글을 방금 다시 한번 찾아봤다.

OS를 만들기 위해 부랴부랴 인터넷 검색을 해봤다. 우와! 책도 많고 관련 정보들이 상당하다. 수준급인 OS 부터 기초 정보 또한 대단히 많다. ‘그냥 이거 가져다 수정하면서 공부해 보면 되겠네…’ 라고 생각했다가 접었다.. 왜? 내 코드가 아니고 내 구조가 아니고… 내 영혼(?)이 아니다. 그냥 하나 하나 내가 부딪치며 만들어야 내것 같다는 생각이 들어서다. 또 뭔가 고비가 있어야 내 지식이지, 단순히 복사 & 붙이기는 그냥 영혼없는 결과물 만들기일 뿐이라고 생각해서 이다.

ㅎㅎㅎ 무척 단순한 생각으로 시작했다. 남의 코드는 내것이 아니므로….^^ 영혼까지 언급했다.

복사 & 붙이기 로 결과물을 만들지 말자고 했는데 지금은 복사 & 붙이기 로 만들고 있다….. 그래… 다 공부해서 만들기로 했었지..~~~ㅎㅎㅎ

내가 왜 이런 심각한 고민을 하게 된건 , 8042 키보드 컨트롤러가 키보드 관련해서 검색해 보다가 등장해서 이다. 키보드에서 인터럽트가 발생하게 될 경우, 8259 에서 인터럽트를 CPU에 알리고 다시 0x60 포트에서 키 값을 읽으면 된다. 라는 이 부분에서 0x60 이 포트가 궁금해서 찾아보다가 찾았다. 그냥 0x60에서 키 값을 읽으면 된다 라고 하고 넘어갈까 말까 망설였었다. 왜?? 귀찮으니까~~~

ㅎㅎㅎㅎ

여하튼.. 그냥 하자!!!~~~~~~

키보드와 마우스는 시리얼 통신을 한다고 배웠고 그렇게 되어 있다고 한다. 이 키보드와 마우스는 우리가 쉽게 얘기하는 PS/2 라고 불리우는 8042 컨트롤러와 연결 되어 있고 말이다. 키보드/마우스 제어 외에도 앞선 포스팅에서 언급한 주소 확장 관련해서 A20 Enable 기능도 8042 컨트롤러가 제어한다고 한다.

위 그림은 8042와 8259PIC와 CPU 연결을 도식화 한 것이다. 키보드나 마우스 입력 시 8042는 8259 PIC 마스터나 슬레이브에 IRQ 신호를 보내고 8259는 이를 CPU에 보낸다. 또 CPU는 8042를 0x60 또는 0x64 포트를 통해 데이터를 읽던지 제어를 한다고 한다.

IO 포트

접근 구분

용도

0x60 Read/Write 데이터 읽고 쓰기
0x64 Read Status 읽기
0x64 Write 명령 요청

전번 포스팅에서 키보드에 데이터 입력이 있는지 없는지 알기 위해서 0x64 포트의 Status를 읽어서 사용한 적이 있었다.

또 키보드 관련해서 알아야 할 내용이 생겼다. Set1, Set2, Set3… 이게 뭐냐면.. IBM 에서 콤퓨타를 만들어 낼 때 마다 새로운 키보드 인터페이스를 만들었었는데 XT 키보드, AT 키보드, PS/2 키보드 등으로 구분된다고 한다. 또한 이 키보드 타입 들은 같은 문자에 따라 다른 값들을 보낸다고 한단다… 그래서 각 값들을 Set1, Set2, Set3에 맞게 찾아서 문자를 대입해야 한다고 한다. Set1은 XT , Set2는 AT, Set3은 PS/2.
내가 코드에서 사용했던 건 Set1 이다. 8042를 통해 현재 사용하고 있는 키보드의 인터페이스를 얻어 낼 수 있고 지정 할 수 있다고 한다.

음.. 8042 명령어들과 기능들도 있는데 이건 필요할 때 마다 참조 하는 것으로 하고 8042에 대해서 여기서 마쳐야 겠다.

Tistory 태그: ,,,,

저번에 Interrupt Descriptor Table에 대해서 대충(?) 알아봤다. 그래서 이번에는 구현해 보기로 했다. 막상 구현하려고 하니 뭘 해야 할지 답답… ㅎㅎㅎ 우선 저번에 간단하게 작성해 놓은 IDT 구조체 다시 한번 작성해 놓고..

typedef struct _IDTDesc
{
	unsigned short	Offset_low;
	unsigned short	Selector;
	unsigned char	Unused;
	unsigned char	Access;
	unsigned short	Offset_high;
} IDTDesc;

음… 이제… 이 구조체가 여러개 존재 한다고 하니… 배열로… 구성해서…근데 몇개??? 몇개를 해야 하지??? 음… 그냥 1바이트 255개..ㅡㅡ;;;;; 대충해 보자.. 그래….

#define MAX_IDT	255

IDTDesc IDT[MAX_IDT];

음…그리고….에.. 그러니까.. 테이블을 만들었으니까 GDT 처럼 GDTR 이라는 레지스터에 이 테이블 주소를 넣어주는 …아.. 그래 IDTR에 이 테이블 주소를 넣어줘야지!!! ㅎㅎㅎ 

그러면 IDTR 구조.

32bit 16bit            0bit
IDT의 메모리상에서 위치 IDT의 크기

뭐… 별거 없다.. GDTR 구조와 같다…그러면..

typedef struct _IDTRegistor
{
	unsigned short	Size;
	unsigned int	Address;
} IDTRegistor;

IDTRegistor	IDTR;

음… 이렇게 만들었으면 예외상황 인터럽트, 하드웨어 인터럽트, 시스템콜 들을 등록 하라고 하는데 ….. ??? 어떻게 하지?? 검색해 보자…!!!!!!…………………………………………………………

저번에 포스팅한 예외상황 인터럽트 번호 0 부터 … 채울 수 있을 때까지 채우란다.. 시스템콜들은 뭐라뭐라 하는데 그냥 건너뛰고… 예외상황 하고 하드웨어 입력….!!! 그런데 말이다…. IDT 각각의 항목이 뭔지를 알아야 채우지~~~

Offset_low(2Byte)
Offset_high(2Byte)

요 두 놈들은 인터럽트 발생 시 인터럽트가 해야 할 (ISR이라고 저번에 적었지!~~^^ 잊어버렸었다.) 작업 위치 란다. 즉, 해당 함수의 메모리 상 위치.

Selector(2Byte)

요놈은 보호모드 진입시 GDT 테이블에 적어 놓은 CODE, DATA 셀렉터를 입력하는 놈이라는 데… 난 아직도 CODE와 DATA가 어떤 의미인지 모르겠다.. 그냥, CODE 셀렉터… 음… GDT 테이블에서 0x08 가 CODE 니까.. 저긴 0x08로 입력하는 걸로.

Unused(1Byte)

요넘은 사용 안하니 패쓰하고

Access(1Byte)

요놈은 그러니까… 전번에 한번 적었던 기억이 나는데…...http://manggong.org/41 여기…

P, DPL, S, TYPE 요렇게 되어 있고. P는 1로 하라고 하니 1. DPL 특권 레벨에 대한 것이라고 하고 레벨 0, 유저는 3이라고 하는데 0. S는 그냥 0.
TYPE은 여러 종류가 있는 것 같은데 보호 모드로 진입해서 32비트 라고 하고 Trap Gate는 소프트웨어 인터럽트나 예외 인터럽트, Interrupt Gate는 하드웨어 인터럽트. 그러면 0xE 아니면 0xF 인것 같다.

자, 그러면 Access 이놈한테 줄 수 있는 값은 10001110(0x8E : 소프트웨어 인터럽트, 예외인터럽트), 또는 10001111(0x8F : 하드웨어 인터럽트)

흠.. 이제 IDT를 채워보자.

아… 그러고 보니 Offset_low, high에 입력 해 줄 인터럽트 서비스 루틴(ISR) 함수가 없다.. 음… 더미 함수 하나 만들어 보고……근데 그냥 만들면 되나??? ……….이거 뭐 하나 쉽게 진행이 안되니…
http://wiki.osdev.org/Interrupt_Service_Routines에 의하면

void _declspec(naked) interrupt_handler()
{
    _asm pushad;
 
    /* do something */
 
    _asm{
        popad
        iretd
    }
}

이런식으로 만들면 된다고 한다. 그래 그냥 저 함수 저대로 쓰자~~ ^^

자 그러면.. 입력!

void Init_IDT()
{
	int i;
	unsigned int high, low;
	
	for(i = 0; i < MAX_IDT; i++ )
	{
		IDT[i].Selector = 0x08;
		if( i < 0x14 )
			IDT[i].Access = 0x8E;		// 예외 인터럽트
	    else
			IDT[i].Access = 0x8F;		// 하드웨어

		IDT[i].Unused = 0x00;

		low = (unsigned int)&interrupt_handler;
		low = low & 0xFFFF;

		high = (unsigned int)&interrupt_handler;
		high = (high >> 16);

		IDT[i].Offset_high = (unsigned short)high;
		IDT[i].Offset_low = (unsigned short)low;
	}
}

그리고 내친김에 IDTR도…

	IDTR.Address = (unsigned int)&IDT;
	IDTR.Size  = MAX_IDT * sizeof( IDTDesc );

다 입력했다… 마지막으로 이제 어셈블리어 LIDT 로 IDTR을 로딩시켜 주면…~~~~~~~ 된다고 하는데 … 어셈은 어려워 ㅠㅠ … 대충 해보자….

void LoadIDTR(unsigned int addr)
{
	_asm
	{
		mov	eax, [addr]
		lidt	[eax]
		sti
	}
}

ㅎㅎㅎㅎ lidt에 idtr 주소 넣어 주고 sti 명령어로 인터럽트를 활성화 시켜주면 된다고 한다. 자…. 한번 해볼까??

void InitPIC();
void Init_IDT();
void interrupt_handler();
void LoadIDTR(unsigned int addr);

unsigned char Read_port_byte(unsigned short port);
void Write_port_byte(unsigned short port, unsigned char data);


typedef struct _IDTDesc
{
	unsigned short	Offset_low;
	unsigned short	Selector;
	unsigned char	Unused;
	unsigned char	Access;
	unsigned short	Offset_high;
} IDTDesc;

typedef struct _IDTRegistor
{
	unsigned short	Size;
	unsigned int	Address;
} IDTRegistor;


#define MAX_IDT		255

IDTDesc	IDT[MAX_IDT];
IDTRegistor	IDTR;

unsigned char	*Screen = (unsigned char*)0xB8000;

#define	KEY_F1		0x80
#define	KEY_F2		(KEY_F1 + 1)
#define	KEY_F3		(KEY_F2 + 1)
#define	KEY_F4		(KEY_F3 + 1)
#define	KEY_F5		(KEY_F4 + 1)
#define	KEY_F6		(KEY_F5 + 1)
#define	KEY_F7		(KEY_F6 + 1)
#define	KEY_F8		(KEY_F7 + 1)
#define	KEY_F9		(KEY_F8 + 1)
#define	KEY_F10		(KEY_F9 + 1)
#define	KEY_F11		(KEY_F10 + 1)
#define	KEY_F12		(KEY_F11 + 1)

#define	KEY_INS		0x90
#define	KEY_DEL		(KEY_INS + 1)
#define	KEY_HOME	(KEY_DEL + 1)
#define	KEY_END		(KEY_HOME + 1)
#define	KEY_PGUP	(KEY_END + 1)
#define	KEY_PGDN	(KEY_PGUP + 1)
#define	KEY_LFT		(KEY_PGDN + 1)
#define	KEY_UP		(KEY_LFT + 1)
#define	KEY_DN		(KEY_UP + 1)
#define	KEY_RT		(KEY_DN + 1)
#define	KEY_PRNT	(KEY_RT + 1)
#define	KEY_PAUSE	(KEY_PRNT + 1)
#define	KEY_LWIN	(KEY_PAUSE + 1)
#define	KEY_RWIN	(KEY_LWIN + 1)
#define	KEY_MENU	(KEY_RWIN + 1)

	static const unsigned char kbd_scancode[] =
	{
0,	0x1B,	'1',	'2',	'3',	'4',	'5',	'6',
'7',	'8',	'9',	'0',	'-',	'=',	'\b',	'\t',
'q',	'w',	'e',	'r',	't',	'y',	'u',	'i',

'o',	'p',	'[',	']',	'\n',	0,	'a',	's',
'd',	'f',	'g',	'h',	'j',	'k',	'l',	';',

'\'',	'`',	0,	'\\',	'z',	'x',	'c',	'v',
'b',	'n',	'm',	',',	'.',	'/',	0,	0,
0,	' ',	0,	KEY_F1,	KEY_F2,	KEY_F3,	KEY_F4,	KEY_F5,
KEY_F6,	KEY_F7,	KEY_F8,	KEY_F9,	KEY_F10,0,	0,	KEY_HOME,
KEY_UP,	KEY_PGUP,'-',	KEY_LFT,'5',	KEY_RT,	'+',	KEY_END,
KEY_DN,	KEY_PGDN,KEY_INS,KEY_DEL,0,	0,	0,	KEY_F11,
KEY_F12
	};

void printf( char *msg )
{

	while(*msg != '\0')
	{
		*Screen++ = *msg++;
		*Screen++ = 7;
	}
}

void putc(char Code)
{
	*Screen++ = Code;
	*Screen++ = 7;
}



int  StartKernel(int argc, char *argv[])
{
	char *hello = "Hello OS!";
	char *complete = "complete.";
	unsigned char* vidmem = (unsigned char*)0xB8000;
	unsigned char data = 0;
	int b = 5;
	int c = 0;


	printf(hello);

	InitPIC();
	Init_IDT();


	while(1) ;
	return 0;
}

unsigned char Read_port_byte(unsigned short port)
{
	unsigned char KeyCode = 0;

	__asm
	{
		mov		dx, port
		xor		eax, eax
		in		ax, dx
		lea		ebx, KeyCode
		mov		[ebx], al
	}

	return KeyCode;
}

void Write_port_byte(unsigned short port, unsigned char data)
{
	__asm
	{
        mov		dx, [ebp+8]
        mov		al, [ebp+12]
        out		dx, al
	}
}

void InitPIC()
{
	 
	Write_port_byte(0x20, 0x11);       
	Write_port_byte(0x21, 0x20);       
	Write_port_byte(0x21, 0x04);       
	Write_port_byte(0x21, 0x01);       
	Write_port_byte(0x21, 0xFF);         

	
	Write_port_byte(0xA0, 0x11);       
	Write_port_byte(0xA1, 0x28);       
	Write_port_byte(0xA1, 0x02);       
	Write_port_byte(0xA1, 0x01);       
	Write_port_byte(0xA1, 0xFF);        
}


void _declspec(naked) interrupt_handler()
{
	_asm pushad;


	_asm {
		popad
		iretd
	}		
}

void Init_IDT()
{
	int i;
	unsigned int high, low;
	
	for(i = 0; i < MAX_IDT; i++ )
	{
		IDT[i].Selector = 0x08;
		if( i < 0x14 )
			IDT[i].Access = 0x8E;		// 예외 인터럽트
	    else
			IDT[i].Access = 0x8F;		// 하드웨어

		IDT[i].Unused = 0x00;

		low = (unsigned int)&interrupt_handler;
		low = low & 0xFFFF;

		high = (unsigned int)&interrupt_handler;
		high = (high >> 16);

		IDT[i].Offset_high = (unsigned short)high;
		IDT[i].Offset_low = (unsigned short)low;
	}

	IDTR.Address = (unsigned int)&IDT;
	IDTR.Size  = MAX_IDT * sizeof( IDTDesc );

	LoadIDTR((unsigned int)&IDTR);
}

void LoadIDTR(unsigned int addr)
{
	_asm
	{
		mov	eax, [addr]
		lidt	[eax]
		sti
	}
}


예전에 키보드 입력 받았던 코드에서 PIC를 초기화 해주고…!!! (하드웨어 인터럽트 발생해야 하니깐…) 컴파일 해서 확인!!!

아무 반응 없다…

뭐… 재부팅 안된것만 해도 다행이다.

키보드 입력을 받았으면 좋겠는데… 피곤해서 오늘은 그냥 여기까지…

다음시간에는 키보드 입력 루틴까지..

 

IDT.

http://manggong.org/41 여기에서 살짝 언급 했다.

Interrupt descriptor table라고 불리는 놈으로써 Interrupt Vector Table을 구현해 놓은 놈.

이라고 정리했었다. ㅎㅎㅎ 저 글 작성할때, 감정이 다시 돌아 상기 되는듯 하다. 자…

그러면, Interrupt Vector Table 이 무엇인지를 찾아 봐야 겠지??

검색해보자..

….

리얼모드에서는 Interrupt Vector Table(IVT) 이라고 불리우고… 보호모드에서는 Interrupt descriptor table(IDT)이라고 불리우고 .. 또…음???

Interrupt Service Routine(ISR)이 있는 곳을 가르키는 테이블? 이라는데??

흠… 그러면 ISR 은 또 무엇이냐???

이것저것 많은 것들이 검색 되던데 “어떤 인터럽트 발생 시, 발생된 인터럽트가 해야 할 일” 이라고 정리하고 끝내야 겠다. 즉 인터럽트 발생되면 실행되는 애라고 보면 될 것 같다. 그러면 여기서 짚고 넘어가야 할 인터럽트는 정확히 무엇인가??


인터럽트

뭔가 있어 보이는 말이다. ㅋㅋㅋㅋ

위키백과(https://ko.wikipedia.org/wiki/%EC%9D%B8%ED%84%B0%EB%9F%BD%ED%8A%B8)에서는 다음과 같은 정의를 내렸다.

“마이크로프로세서에서 인터럽트(Interrupt:중단, 세치기)란 마이크로프로세서(CPU)가 프로그램을 실행하고 있을 때, 입출력 하드웨어 등의 장치나 또는 예외 상황이 발생하여 처리가 필요할 경우에 마이크로프로세서에게 알려 처리할 수 있도록 하는 것을 말한다.”

인터럽트는 3가지 종류가 있다고 한다.

예외상황 인터럽트, 하드웨어 인터럽트, 시스템 콜.

예외상황 인터럽트는 시스템에 심각한 에러가 생겼을 때 발생되며 예로 어떤 수를 0으로 나누라는 명령어가 실행되었다던지, 페이지 폴트(?) 일어났을 때 발생된다.

인터럽트 번호 발생상황 설명
0

0 으로 나누기 예외

0으로 나누려고 할 경우.

1

디버그

EFLG의 T플래그 설정 및 디버그

2

NMI(Non Maskable Interrupt)

마스킹 불가능한 인터럽트용으로 예약

3

int 3

int 3 명령을 만날경우

4

오버플로우 발생

INTO 명령 실행 시 EFLAGS의 OF 플래그 설정.

5

유효주소 범위 초과

피연산자가 유효주소 범위를 벗어난 경우

6

알수 없는 명령어

잘못된 CPU 명령어 만날 경우.

7

디바이스 사용불가

CR0의 TS설정 상태에서 MMX 명령어를 실행.

8

연속 예외상황 처리 불가능

예외처리 과정중 예외가 또 발생한 경우.

9

Coprocessor 에러

Coprocess 에러 발생

10

무효 TSS 에러

잘못된 TSS를 가지고 태스크전환 할시

11

세그먼트 범위 초과

메모리에 존재하지 않는 세그먼트 참조

12

스택 세그먼트 에러

스택 제한을 벗어나거나 존재하지 않는 메모리 참조

13

일반 보호 에러

보호규약을 위반한 경우

14

페이지 폴트 에러

참조한 페이지가 메모리에 존재 하지 않은 경우

15

정의되지 않음

-

16

X87 부동소수점 에러

부동소수 오버플로우나 0으로 나눈 경우

17

정렬 체크 에러

피연산자의 주소가 정렬되지 않는 경우

18

머신 체크 에러

코드와 소스가 머신과 맞지 않는 경우

19

SIMD 부동소수점 에러

SSE와 SSE2 Floating-point instruction 의해 발생

하드웨어 인터럽트는 주변장치에 뭔가 일이 생겼을 때 발생되며 예로, 키보드가 눌렸다던지 마우스가 조작이 되었을 때 발생된다.

IRQ 설명
0

타이머

1

키보드

2

8259A 슬레이브 컨트롤러

3

두번째 시리얼 포트(COM2/COM4)

4

첫번째 시리얼 포트(COM1/COM3)

5

LPT2

6

플로피 디스크

7

LPT1

8

시스템 클록 RTC

9

할당되어 있지 않음

10

할당되어 있지 않음

11

할당되어 있지 않음

12

PS/2 마우스

13

수치 Coprocessor

14

EIDE Disk 컨트롤러 1

15

EIDE Disk 컨트롤러 2

  하드웨어 인터럽트의 발생을 CPU에 알리는 놈은 8259 고 이전 포스팅에서 적었다.
리얼모드에서는 0~F 까지 (15개 입력라인을 가질 수 있다고 했다.)  IRQ0~IRQ15까지 번호가 지정되었는데 보호모드에서는 IBM 초기 아키텍쳐 설계 실수로 충돌이 난다고 한다.(CPU 인터럽트와 충돌) 그래서 이 IRQ를 옮겨야 한다고 한다.(더 깊은 설명이 있는데… 번역이 대략 난감 ㅡㅡ…) (http://manggong.org/83)
 
또한 위 IRQ와 CPU 기본 인터럽트 번호가 충돌하여 옮겨줘야 한다고 언급 했었다. 영어로 된걸 번역하면서 이해하다 보니 긴글로 오는 피로감으로 넘어갔었는데… 간략하게 이해해 보자면
8259A는 인터럽트 발생 시 IRQ번호 + 내부 Base 로 계산하여 인터럽트 번호를 전달한다고 한다. 즉 내부 Base번호를 설정함으로써 인터럽트 번호 충돌을 회피 한다는 것이다.
 
 

시스템콜은 위 두개와 달리 시스템에서 자동으로 발생되는 것은 아니고 프로그램에서 인위적으로 조작하여 발생시키는 인터럽트이다. 예로 어떤 응용 프로그램이 커널 기능을 사용하기 위하여 커널에게 요청 할때 사용한다.( 지금은 조금 모호하다…^^ )


 

위 3개의 인터럽트들을 IDT 구조에 연속하여 입력하고 이 연속된 테이블 시작 주소를 IDTR 레지스터에 등록하면 인터럽트 처리에 대한 (IVT) 테이블에 정의해 놓고 인터럽트가 발생시 해당 인터럽트 기능을 실행하게 하는게 최종 목표인듯 싶다.

자.. 그러면… IDT 구조를 다시 한번 확인해 보자.

각 항목에 대한 설명은 넘어가고.. 이걸 구조체로 만들면 다음과 같다.

struct _IDT
{
	unsigned short Offset_low;
	unsigned short Selector;
	unsigned char Unused;
	unsigned char Access;
	unsgined short Offset_high;
};

셀렉터는 커널 셀렉터를 입력해 주면 될듯 싶고 인터럽트 번호에 매칭되는 ISR 위치를 Offset에 입력해주면 인터럽트 관련하여 IDT 구성은 끝나는 것 같다.

다음에는 실제 IDT 를 구현해 보는걸로…

 

'프로그래밍 > OS 만들기' 카테고리의 다른 글

OS 만들기 #18 IDT - 3  (0) 2015.08.12
OS 만들기 #17 IDT - 2  (0) 2015.08.10
OS 만들기 #15 커널-키보드 입력  (0) 2015.02.09
OS 만들기 #14 PE 재배치  (0) 2014.03.28
OS 만들기 #13 보호모드 진입  (0) 2014.03.27

요 근래 C# 으로 개발을 집중(?) 하는 탓에 MFC는 손을 놓고 있다가 간단한 윈도우 탐색기를 개발하려고 MFC를 끄집어 들었다.

그러던중, 굉장히 애먹은 작업이 있어서 해결방법이라기는 그렇고 대충 , 유사한 해결방법이 있어서 기록해 놓는다.

CCombobox를 DropDown 방식으로 할 경우, Edit 창이 하나 생기는데 그 창의 크기를 조절하고 싶었다. 그래서 GetWindow(GW_CHILD)로 Edit 창 윈도우 핸들을 얻거나, OnCtlColor함수를 오버로딩 하여 자식창 윈도우 핸들을 얻었다. 그리고 MoveWindow 나, SetWindowPos 함수를 사용하여 윈도우 크기를 조정하였으나, 반응이 없었다. (아… 사소한 작업이 귀찮아 지기 시작하는것이다…)

이리저리 검색해 보니 CCombobox의 폰트 자체를 크게 지정하면 가능하다 하여 지정해봤다.

	comboFont.CreateFont(20, 0, 0, 0, FW_BOLD, FALSE, FALSE, 0, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
        CLIP_DEFAULT_PRECIS, CLEARTYPE_NATURAL_QUALITY, DEFAULT_PITCH | FF_SWISS, _T("Arial"));

	SetFont(&comboFont);

에디트 창이 커지고 폰트도 커졌다… 그런데 실제로 원한건 에디트창 영역을 커지기를 원했는데….

그래서 다시 검색…

SetItemHeight(-1, 35);

간단하다 ,

int SetItemHeight(
   int nIndex,
   UINT cyItemHeight 
);

CCombobox 멤버 함수인 SetItemHeight 함수로 그 크기를 조정할 수 있었다. index 인자 부분에 –1, 두번째 인자에는 높이 지정…

원하던 결과는 아니지만, CCombobox 컨트롤 직접 만들지 않은 한은 이 결과에서 만족했다.(귀찮아..)

'프로그래밍 > MFC' 카테고리의 다른 글

WIN32 API프로그래밍에서 MDI에서 자식 생성.  (0) 2013.08.14
MFC 디렉토리 생성  (0) 2013.08.14
쓰레드 생성 (펌글)  (0) 2013.08.14
쓰레드 생성.  (0) 2013.08.14
유니코드  (0) 2013.08.14

보호모드로 진입하고 PE 재배치까지 한 후 메인 함수 호출을 마지막으로 고비는 넘겼다고 생각했다.

그래~ 이제부터 내 세상이닷

…..

화면에 HELLO 라는 문구 하나 출력 뒤에 뭘해야 할지 모르겠다…ㅠㅠ

아~~~휴~~~

뭘 해야 하지???

??? 그런데 왜 키보드 에 타이핑 하면 글자가 안 써지지??

그러고 보니 이제까지 키보드 입력에 관한 건 한번도 처리 안해 봤네???

그래… 키보드 입력 처리 해보자..

자료를 찾아보니 리얼모드에서는 int 21h 로 키보드 입력을 받는다고 한다. 당연히 난 보호모드에 진입되어 있으니 BIOS 함수들은 사용못하겠지.. 그러면 어떻게 해야 하냐??

검색!!!

헉…8259 Programmable Interrupt Controller 이건 모지??

뭐 이건… 뭐만 하려고 하면 굴비 엮듯이 줄줄이 모르는 단어들이 쏟아지는지 ㅡㅡ;;;

흠…

CPU로 인터럽트를 전달하기 위한 칩이란다. Master와 Slave 구조로 되어 있고 Master가 CPU와 연결되어 있단다. Master와 Slave는 각각 8개 입력 라인을 받을 수 있는데 Master 1개 입력 라인이 Slave와 연결되어 있으므로 총 15개 입력 라인을 사용 할수 있다고 한다. CPU가 여러개일 경우 각각 CPU에 연결 할 수 없기 때문에(?) Advanced PIC(APIC)가 나왔고 CPU 자체에 내장된 Local APIC 와 메인보드에 붙어 있는 I/O APIC로 발전 되었다고 하는데… CPU 여러개일 때는 모르겠고 …ㅡㅡ;;;

여튼 키보드에서 키가 입력 되었을 때, 8259 에서는 인터럽트 신호가 발생하고 CPU에서는 하던일을 멈추고 입력된 데이터를 읽는다 라는게 그 흐름 같아 보인다. (뭐 인터럽트에 대한 일반적인….내용).

리얼모드에서는 0~F 까지 (15개 입력라인을 가질 수 있다고 했다.)  IRQ0~IRQ15까지 번호가 지정되었는데 보호모드에서는 IBM 초기 아키텍쳐 설계 실수로 충돌이 난다고 한다.(CPU 인터럽트와 충돌) 그래서 이 IRQ를 옮겨야 한다고 한다.(더 깊은 설명이 있는데… 번역이 대략 난감 ㅡㅡ…)

그럼 어떻게 옮겨???
8259 칩은 0x20 은 Master, 0xA0라는 Slave 명령 IO 포트가 있고 0x21은 master Data IO 포트, 0xA1은 Slave Data IO 포트가 있다.  명령 IO 포트에 명령을 보내고 데이터 IO 포트에 데이터를 보내면 된다고 한다. 그냥 가져다 쓰자 ㅡㅡ;;;;

예제는..

 /* Init master interrupt controller */
outb(0x11, 0x20);       /* Start init sequence */
outb(0x20, 0x21);       /* Vector base */
outb(0x04, 0x21);       
outb(0x01, 0x21);       
outb(0xFF, 0x21);         

/* Init slave interrupt controller */
outb(0x11, 0xA0);       /* Start init sequence */
outb(0x28, 0xA1);       /* Vector base */
outb(0x02, 0xA1);       
outb(0x01, 0xA1);       
outb(0xFF, 0xA1);

이렇다는데… outb 함수 2번째 인자가 IO 포트 이고 1번째 인자가 명령 또는 데이터 값인가 보다. outb 함수가 IO포트에 쓰는(?) 함수인것 같은데 어셈블리어로 구성되어 있고 out 명령어를 사용한다. 이건 또 모지???

out : IO 포트에 데이터를 쓰는 명령어..

예제좀 바꿔야 겠다. outb 함수명 보다는 다른 이름으로~^^

흠… 그냥 인라인 어셈으로 해야 하나??

void Write_port_byte(unsigned short port, unsigned char data)
{
	__asm
	{
        mov		edx, [ebp+8]
        mov		al, [ebp+12]
        out		dx, al
	}
}

void InitPIC()
{
	 /* Init master interrupt controller */
	Write_port_byte(0x20, 0x11);       /* Start init sequence */
	Write_port_byte(0x21, 0x20);       /* Vector base */
	Write_port_byte(0x21, 0x04);       
	Write_port_byte(0x21, 0x01);       
	Write_port_byte(0x21, 0xFF);         

	/* Init slave interrupt controller */
	Write_port_byte(0xA0, 0x11);       /* Start init sequence */
	Write_port_byte(0xA1, 0x28);       /* Vector base */
	Write_port_byte(0xA1, 0x02);       
	Write_port_byte(0xA1, 0x01);       
	Write_port_byte(0xA1, 0xFF);        
}

되는지 안되는지는 모르겠다… 8259 PIC 초기화(?) 는 이정도로 끝마치고 이제.. 키보드 입력을 받는 부분을 만들자…

8259 PIC 의 0x60 포트를 읽으면 키 입력값이 들어온다고 한다.

0x60 포트에서 입력된 키 값은 아스키 코드값이 아니라고 한다.. 이를 다시 아스키 코드로 변환해야 한단다.. 흠.. 아.. 그러고 보니 이제까지 0xB8000 메모리 주소에 아스키 값 써서 화면에 글자 출력 하는 방법을 썼는데 아무래도 printf 함수도 하나 만들어야 겠다. 할게 많네 …갈길이 멀다..

우선 printf 함수 부터…

unsigned char	*Screen = (unsigned char*)0xB8000;

void printf( char *msg )
{

	while(*msg != '\0')
	{
		*Screen++ = *msg++;
		*Screen++ = 7;
	}
}

void putc(char Code)
{
	*Screen++ = Code;
	*Screen++ = 7;
}

전역변수로 Screen 변수를 선언하고 이제 printf() 함수를 호출해서 문자열을 출력하고 putc() 함수를 호출 해서 문자를 호출 하는 함수를 만들었다. 나중에 printf 함수를 손좀 봐서 C Standard library를 구현해야 겠다. 안돼면 가져오는 걸~^^로 하고 지금은 임시…

키보드 스캔코드를 아스키 코드로 변환할 변환 테이블 만들고(만든다긴 보다는 그냥 인터넷 검색.)….

0x60 포트에서 키보드 값 읽어서 화면에 출력… 이걸 무한 반복..

void InitPIC();
unsigned char Read_port_byte(unsigned short port);
void Write_port_byte(unsigned short port, unsigned char data);

unsigned char	*Screen = (unsigned char*)0xB8000;

#define	KEY_F1		0x80
#define	KEY_F2		(KEY_F1 + 1)
#define	KEY_F3		(KEY_F2 + 1)
#define	KEY_F4		(KEY_F3 + 1)
#define	KEY_F5		(KEY_F4 + 1)
#define	KEY_F6		(KEY_F5 + 1)
#define	KEY_F7		(KEY_F6 + 1)
#define	KEY_F8		(KEY_F7 + 1)
#define	KEY_F9		(KEY_F8 + 1)
#define	KEY_F10		(KEY_F9 + 1)
#define	KEY_F11		(KEY_F10 + 1)
#define	KEY_F12		(KEY_F11 + 1)

#define	KEY_INS		0x90
#define	KEY_DEL		(KEY_INS + 1)
#define	KEY_HOME	(KEY_DEL + 1)
#define	KEY_END		(KEY_HOME + 1)
#define	KEY_PGUP	(KEY_END + 1)
#define	KEY_PGDN	(KEY_PGUP + 1)
#define	KEY_LFT		(KEY_PGDN + 1)
#define	KEY_UP		(KEY_LFT + 1)
#define	KEY_DN		(KEY_UP + 1)
#define	KEY_RT		(KEY_DN + 1)
#define	KEY_PRNT	(KEY_RT + 1)
#define	KEY_PAUSE	(KEY_PRNT + 1)
#define	KEY_LWIN	(KEY_PAUSE + 1)
#define	KEY_RWIN	(KEY_LWIN + 1)
#define	KEY_MENU	(KEY_RWIN + 1)

	static const unsigned char kbd_scancode[] =
	{
0,	0x1B,	'1',	'2',	'3',	'4',	'5',	'6',
'7',	'8',	'9',	'0',	'-',	'=',	'\b',	'\t',
'q',	'w',	'e',	'r',	't',	'y',	'u',	'i',

'o',	'p',	'[',	']',	'\n',	0,	'a',	's',
'd',	'f',	'g',	'h',	'j',	'k',	'l',	';',

'\'',	'`',	0,	'\\',	'z',	'x',	'c',	'v',
'b',	'n',	'm',	',',	'.',	'/',	0,	0,
0,	' ',	0,	KEY_F1,	KEY_F2,	KEY_F3,	KEY_F4,	KEY_F5,
KEY_F6,	KEY_F7,	KEY_F8,	KEY_F9,	KEY_F10,0,	0,	KEY_HOME,
KEY_UP,	KEY_PGUP,'-',	KEY_LFT,'5',	KEY_RT,	'+',	KEY_END,
KEY_DN,	KEY_PGDN,KEY_INS,KEY_DEL,0,	0,	0,	KEY_F11,
KEY_F12
	};

void printf( char *msg )
{

	while(*msg != '\0')
	{
		*Screen++ = *msg++;
		*Screen++ = 7;
	}
}

void putc(char Code)
{
	*Screen++ = Code;
	*Screen++ = 7;
}



int  StartKernel(int argc, char *argv[])
{
	char *hello = "Hello OS!";
	unsigned char* vidmem = (unsigned char*)0xB8000;

	printf(hello);

	InitPIC();

	while(1)
	{
		data = Read_port_byte(0x60);
		if( data != 0 )
		{
			putc((char)kbd_scancode[data]);
		}
	}
	return 0;
}

unsigned char Read_port_byte(unsigned short port)
{
	unsigned char KeyCode = 0;

	__asm
	{
		mov		dx, port
		lea		ebx, KeyCode
		in		ax, dx
		mov		[ebx], ax
	}

	return KeyCode;
}

void Write_port_byte(unsigned short port, unsigned char data)
{
	__asm
	{
        mov		dx, [ebp+8]
        mov		al, [ebp+12]
        out		dx, al
	}
}

void InitPIC()
{
	 /* Init master interrupt controller */
	Write_port_byte(0x20, 0x11);       /* Start init sequence */
	Write_port_byte(0x21, 0x20);       /* Vector base */
	Write_port_byte(0x21, 0x04);       
	Write_port_byte(0x21, 0x01);       
	Write_port_byte(0x21, 0xFF);         

	/* Init slave interrupt controller */
	Write_port_byte(0xA0, 0x11);       /* Start init sequence */
	Write_port_byte(0xA1, 0x28);       /* Vector base */
	Write_port_byte(0xA1, 0x02);       
	Write_port_byte(0xA1, 0x01);       
	Write_port_byte(0xA1, 0xFF);        
}

자.. 잘되나 볼까???

뭐지… 그냥 이상한 값 출력 하다가 Virtual PC 창이 닫혀 버린다… 뭐가 잘못 된거지?? 음…계속 키 값이 입력 되는 것인가?? 다시 한번 확인…

음… 값은 키값이 반복되는 모습이다… 그런데 난 키보드 안 눌렀는데?? 키보드 버퍼에 쓰레기 값이 있는건가?? 우선 0x64 에서 현재 키보드 상태값을 읽는 부분이 있다. 출력 버퍼 상태라든지 입력 버퍼 상태 등이 기록된다는데 출력버퍼(출력인지 입력인지 잘 모르겠지만, 키가 입력되었으니 CPU 에게는 출력해야 한다고 가정…)의 상태가 체크 되어 있으면 화면에 출력 하는 루틴으로 수정해 보잣!!

int  StartKernel(int argc, char *argv[])
{
	char *hello = "Hello OS!";
	char *complete = "complete.";
	unsigned char* vidmem = (unsigned char*)0xB8000;
	unsigned char data = 0;

	printf(hello);

	InitPIC();

	while(1)
	{
		while((Read_port_byte(0x64) & 1) == 0 ) ;

		data = Read_port_byte(0x60);
		if( data != 0 )
		{
			putc((char)kbd_scancode[data]);
		}
	}
	return 0;
}

으흐흐흐 잘 된다..

키 하나 입력 될 때마다 하나 하나 화면에 찍힌다…

그런데 키 하나 입력 될때 마다 공백이 생기네??? 음…

아… Key down 과 Key up 이 때문에 key down 때는 글자가 찍히고 Key up은 다른 값으로 찍히나 보다.. 맞나?? 검색…

아.. 키보드에서 a 키를 입력(down)하면 1E 가 발생하고 a 키 UP은 1E | 0x80 = 9E 가 발생한다.. 0x80 비트가 Key down/up 을 나타내는 것 같다. 그러면 저기 코드에서 0x80 비트가 체크 되지 않을 때만 화면에 출력 하는 코드로 수정… 다시 확인.

void InitPIC();
unsigned char Read_port_byte(unsigned short port);
void Write_port_byte(unsigned short port, unsigned char data);

unsigned char	*Screen = (unsigned char*)0xB8000;

#define	KEY_F1		0x80
#define	KEY_F2		(KEY_F1 + 1)
#define	KEY_F3		(KEY_F2 + 1)
#define	KEY_F4		(KEY_F3 + 1)
#define	KEY_F5		(KEY_F4 + 1)
#define	KEY_F6		(KEY_F5 + 1)
#define	KEY_F7		(KEY_F6 + 1)
#define	KEY_F8		(KEY_F7 + 1)
#define	KEY_F9		(KEY_F8 + 1)
#define	KEY_F10		(KEY_F9 + 1)
#define	KEY_F11		(KEY_F10 + 1)
#define	KEY_F12		(KEY_F11 + 1)

#define	KEY_INS		0x90
#define	KEY_DEL		(KEY_INS + 1)
#define	KEY_HOME	(KEY_DEL + 1)
#define	KEY_END		(KEY_HOME + 1)
#define	KEY_PGUP	(KEY_END + 1)
#define	KEY_PGDN	(KEY_PGUP + 1)
#define	KEY_LFT		(KEY_PGDN + 1)
#define	KEY_UP		(KEY_LFT + 1)
#define	KEY_DN		(KEY_UP + 1)
#define	KEY_RT		(KEY_DN + 1)
#define	KEY_PRNT	(KEY_RT + 1)
#define	KEY_PAUSE	(KEY_PRNT + 1)
#define	KEY_LWIN	(KEY_PAUSE + 1)
#define	KEY_RWIN	(KEY_LWIN + 1)
#define	KEY_MENU	(KEY_RWIN + 1)

	static const unsigned char kbd_scancode[] =
	{
0,	0x1B,	'1',	'2',	'3',	'4',	'5',	'6',
'7',	'8',	'9',	'0',	'-',	'=',	'\b',	'\t',
'q',	'w',	'e',	'r',	't',	'y',	'u',	'i',

'o',	'p',	'[',	']',	'\n',	0,	'a',	's',
'd',	'f',	'g',	'h',	'j',	'k',	'l',	';',

'\'',	'`',	0,	'\\',	'z',	'x',	'c',	'v',
'b',	'n',	'm',	',',	'.',	'/',	0,	0,
0,	' ',	0,	KEY_F1,	KEY_F2,	KEY_F3,	KEY_F4,	KEY_F5,
KEY_F6,	KEY_F7,	KEY_F8,	KEY_F9,	KEY_F10,0,	0,	KEY_HOME,
KEY_UP,	KEY_PGUP,'-',	KEY_LFT,'5',	KEY_RT,	'+',	KEY_END,
KEY_DN,	KEY_PGDN,KEY_INS,KEY_DEL,0,	0,	0,	KEY_F11,
KEY_F12
	};

void printf( char *msg )
{

	while(*msg != '\0')
	{
		*Screen++ = *msg++;
		*Screen++ = 7;
	}
}

void putc(char Code)
{
	*Screen++ = Code;
	*Screen++ = 7;
}



int  StartKernel(int argc, char *argv[])
{
	char *hello = "Hello OS!";
	char *complete = "complete.";
	unsigned char* vidmem = (unsigned char*)0xB8000;
	unsigned char data = 0;

	printf(hello);

	InitPIC();

	while(1)
	{
		while((Read_port_byte(0x64) & 1) == 0 ) ;

		data = Read_port_byte(0x60);

		if(data & 0x80)
			continue;
		if( data != 0 )
		{
			putc((char)kbd_scancode[data]);
		}
	}
	return 0;
}

unsigned char Read_port_byte(unsigned short port)
{
	unsigned char KeyCode = 0;

	__asm
	{
		mov		dx, port
		xor		eax, eax
		lea		ebx, KeyCode
		in		ax, dx
		mov		[ebx], ax
	}

	return KeyCode;
}

void Write_port_byte(unsigned short port, unsigned char data)
{
	__asm
	{
                            mov	dx, [ebp+8]
                            mov	al, [ebp+12]
                            out	dx, al
	}
}

void InitPIC()
{
	 /* Init master interrupt controller */
	Write_port_byte(0x20, 0x11);       /* Start init sequence */
	Write_port_byte(0x21, 0x20);       /* Vector base */
	Write_port_byte(0x21, 0x04);       
	Write_port_byte(0x21, 0x01);       
	Write_port_byte(0x21, 0xFF);         

	/* Init slave interrupt controller */
	Write_port_byte(0xA0, 0x11);       /* Start init sequence */
	Write_port_byte(0xA1, 0x28);       /* Vector base */
	Write_port_byte(0xA1, 0x02);       
	Write_port_byte(0xA1, 0x01);       
	Write_port_byte(0xA1, 0xFF);        
}

오케이… 잘된다!!!!

너무 잘되니.. 손이 부들부들… ㅎㅎㅎㅎ

음.. 다 좋은데 키 입력을 이렇게 받을 순 없다.. ㅡㅡ;;;

키입력 관련해서 검색 해 보니 전에 잠깐 언급했던 IDT(Interrupt descriptor table)을 이용해서 키 입력 처리를 하네.. 흠…. 할게 너무 많어 ㅠㅠ

이젠 IDT에 대해서 알아보고 키보드 입력 처리를 해보잣..

'프로그래밍 > OS 만들기' 카테고리의 다른 글

OS 만들기 #17 IDT - 2  (0) 2015.08.10
OS 만들기 #16 IDT - 1  (0) 2015.07.30
OS 만들기 #14 PE 재배치  (0) 2014.03.28
OS 만들기 #13 보호모드 진입  (0) 2014.03.27
OS 만들기 #12 - PE 파일 포맷  (0) 2013.09.10

 

Window class name is not valid.

임베디드 상황에서 C# 으로 개발 했더니, 저런 오류를 토해낸다. 그것도 매번이 아닌가~~끔.

이 에러 메시지의 원인은 폼에서 자식 윈도우(컨트롤)을 생성할 때, 생성 할 수 없을 때 발생한다.

(comctl32.dll 과 관련이 있을지도..)

원인은 글쎄 못 찾겠다..

 

'프로그래밍 > C#' 카테고리의 다른 글

PNG 16bit grayscale 데이터 읽기  (0) 2022.03.15
C# array char to String 변환  (0) 2013.08.14
쓰레드 종료 지연 문제  (0) 2013.08.14
변수 타입  (0) 2013.08.14

 

하!~~~ 전 포스트에서 어찌어찌 해서 보호모드 까지 진입 했다. 이제 바람OS 커널을 읽어서 커널로 이동하면 되는데 Visual Studio 툴로 컴파일 하다 보니 PE 포맷이다. ㅠㅠ 아~~~메모리에 커널이미지를 재배치라는 걸 해줘야 커널로 이동 할 수 있다. 젠장 할일 정말 많네…

여튼, 보호모드 까지.. 진입 했으니 커널 이미지를 읽는 작업부터 해야 겠다.

그런데 보호모드에서는 BIOS 인터럽트를 이용 할 수 없으니 , 보호모드에서 파일 읽는 방법을 찾아야 한다.

어디 보자~~~~~~~

음… http://wiki.osdev.org/ATA_read/write_sectors#Read_in_LBA_mode 이 사이트에서 방법을 찾았다. 다만 기존의 만들어 놓은 걸 다시 고쳐서 만들어야 하니.. 그냥… 보호모드 들어가기 전에 커널 이미지를 읽자 ㅡㅡ;;;; 너~~~~~~~~~~~~~~~~~무 귀찮고… 힘들다 ….

그러면.. 기존의 부트로더에서 OS_LDR.SYS 읽는 방법을 그대로 사용…….

;; creator : cyj (mangg@manggong.org)
;; date : 2014. 3. 27
;; desc : 커널 로더.
;;        1. BARAMOS.SYS 파일을 찾아서 읽자.
;;        1. 보호모드로 진입한다.
;;        2. OK 문자 화면에 출력

[bits	16]
org 0x8000

OS_LDR_ENTRY:
	;; CS, DS, ES, & SS을 초기화 하자.
	cli
	xor eax, eax
	mov ds, ax
	mov ss, ax
	mov es, ax
	mov sp, 0xFFFF
	mov bp, sp
	sti

	mov dl,[0x0550]	; 부트 드라이브
	mov [BootDrive], dl

	lea si, [LOADING_MSG]
	call PRINT

	call FAT_INFO

	; RootDirectoryEntry 읽어 첫번째 파일을 읽자.
	mov eax, 0x9000
	mov [TEMP_MEMORY], eax

	mov eax, [RootDirectoryEntry]
	mov [READ_SECTOR], eax
	           
	mov eax, 0x10000000 ; 0x10000 위치
	mov ebx, eax
	mov [FILE_MEMORY], eax

	call READ

	mov ebx, [TEMP_MEMORY]
	add ebx, 0x200
	
	; find file name
	mov ax, [TEMP_MEMORY]
FIND_LOOP:

	cmp eax, ebx
	je NOTFOUND

	add ax, 0x20

	mov si, ax
	mov di, KERNEL_NAME
	mov cx, 11
	repe cmpsb
	jnz FIND_LOOP

	mov ax, [si+9]
	push ax
	mov ax, [si+15]
	push ax
	pop eax
	mov [CurrentCluster], eax

DATA_READ:

	lea si,[DOT]
	call PRINT

	sub eax, 2
	xor ebx, ebx
	mov bl, [BPB_SecPerClus]
	mul ebx
	mov ebx, [RootDirectoryEntry]
	add eax, ebx
	mov [READ_SECTOR], eax
	mov eax, [FILE_MEMORY]
	mov [TEMP_MEMORY], eax

	call READ

	mov eax, [BytsPerCluster]
	mov ebx, [FILE_MEMORY]
	add eax, ebx
	mov [FILE_MEMORY], eax
	xor ebx, ebx

	; FAT 리스트
	mov eax, 0x9000
	mov [TEMP_MEMORY], eax
	mov eax, [CurrentCluster]
	;xor ebx, ebx
	mov bx, [BPB_BytsPerSec]
	shr bx, 2
	xor edx, edx
	div ebx
	push edx
	add eax, [FATsector]
	mov [READ_SECTOR], eax

	call READ

	pop edx

	mov esi, 0x9000
	sal edx, 2
	add esi, edx
	mov eax, [esi]

	mov [CurrentCluster], eax
	cmp eax, [FATEoFMask]
	jz END

	jmp DATA_READ

END:
	lea si, [KERNEL_LOADING]
	call PRINT


    ; GDT 등록
    xor     eax, eax
    lgdt    [GDT_INFO]  

    cli
    mov     eax, cr0
    or      eax, 1
    mov     cr0, eax

    jmp     $+2
    nop
    nop

    jmp     0x08:Entry32

NOTFOUND:
	lea si,[FAIL_MSG]
	call PRINT
	jmp $


; 하드디스크 읽기
READ:
	; Disk Address Packet (DAP)
	xor ax,ax								;INT 13h AH=00h: Reset Disk Drive
	mov dl,BYTE [BootDrive]				;DL = Drive Number
	int 0x13
	jc READ
	mov	al, 0x10 ; 크기
	mov	[0x500], al

	mov al, [BPB_SecPerClus]	; 읽을 섹터 수
	mov	[0x502], ax
	mov eax, [TEMP_MEMORY] ; 데이터가 들어갈 주소
	mov [0x504], eax ; Segment:offset
	mov eax, [READ_SECTOR] ; 접근 할 LBA 번호
	mov	[0x508], eax

	mov ax, 0x500 ; DAP의 Segment:offset
	mov	si, ax
	mov	ax, 0
	mov ds, ax
	mov ah, 0x42 ; LBA 읽기 명령
	mov dl, [BootDrive] ; 장치

	int 0x13

	jc READ_ERROR
	ret

READ_ERROR:
	lea si,[READ_ERR]
	call PRINT

	jmp $

PRINT:
	pusha				; 모든 레지스터를 스택에 넣는다.
PRINT_LOOP:
	mov al, [si]		; al에 글자 하나 저장한다.
	inc si				; si 주소를 증가한다.
	or al, al			; or 연산. 
	jz PRINT_END		; 문자열 끝이 or 연산이 0 이라면. 종료
	mov ah,0x0E			; ah = 0xE
	int 0x10			; 인터럽트 0x10h
	jmp PRINT_LOOP		; 다음 문자 출력 루프	

PRINT_END:
	popa				;모든 레지스터 스택에서 뺀다.
	ret					;호출 한곳으로 리턴.

FAT_INFO:
	pusha

	xor ebx, ebx
	mov eax, 0x9000
	mov [TEMP_MEMORY], eax
	
	xor eax, eax
	mov eax, 0
	mov [READ_SECTOR], eax

	call READ

	xor ebx, ebx
	mov si, [TEMP_MEMORY]
	mov bl, [si+454]
	mov bl, 0x80

	mov eax, 0x9000
	mov [TEMP_MEMORY], eax
	
	mov eax, ebx
	mov [READ_SECTOR], eax

	call READ

	mov si, [TEMP_MEMORY]
	mov bx, [si]

	mov bx, [si + 11]		; BytePerSec
	mov [BPB_BytsPerSec], bx
	xor ebx, ebx
	mov bl, [si + 13]		; SecPerCluster
	mov [BPB_SecPerClus], bl

	xor ecx, ecx
	xor ebx, ebx
	xor eax, eax
	mov cl, [si + 16]    ;  FAT NUM
	mov ebx, [si + 28]	;  Hidden Sector
	mov eax, [si + 36]	;  FAT 32

	mul ecx
	mov cx, [si + 14]		; Reserved Sector

	add eax, ecx
	add eax, ebx
	mov [RootDirectoryEntry], eax

	xor eax, eax
	mov eax, ebx
	add eax, ecx
	mov [FATsector], eax

	xor eax, eax
	xor ebx, ebx
	mov bx, [si + 11]
	mov al, [si + 13]
	mul ebx
	mov [BytsPerCluster], eax

	lea si,[FAT32_OK]
	call PRINT

	popa
	ret


DOT:
	db ".", 0x0D, 0x0A,0
FAT32_OK:
	db "Loading FAT's Info...", 0x0D, 0x0A, 0
KERNEL_LOADING:
	db "Loading BARAMOS.SYS...", 0x0D, 0x0A, 0
READ_ERR:
	db "Can't Read Disk..", 0x0D, 0x0A, 0

KERNEL_NAME:
	db "BARAMOS SYS",0

LOADING_MSG			db 0x0D, 0x0A, "Searching for BARAMOS.SYS...", 0x0D, 0x0A, 0x00
FAIL_MSG			db 0x0D, 0x0A, "ERROR.... ", 0x0D, 0x0A, 0x0A, 0x00
 
BPB_BytsPerSec		dw 0
BPB_SecPerClus		db 1
BootDrive			db 0x00
RootDirectoryEntry	dd 0
FATsector			dd 0
BytsPerCluster		dd 0
FATClusterMask		dd 0
FATEoFMask			dd 0x0FFFFFFF
CurrentCluster		dd 2
TEMP_MEMORY			dd 0x9000
READ_SECTOR			dd 0
FILE_MEMORY			dd 0x10000

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 보호모드 GDT
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
GDT_INFO:
			dw     GDT_TABLE_END - GDT_TABLE - 1
		    dd     GDT_TABLE

GDT_TABLE:
			dw 0x0000 ; null
			dw 0x0000
			db 0x00
			db 0x00
			db 0x00
			db 0x00
            dw 0xFFFF    ; Code
            dw 0x0000
            db 0x00
            db 0x9A
            db 0xCF
            db 0x00
            dw 0xFFFF    ; Data
            dw 0x0000
            db 0x00
            db 0x92
            db 0xCF
            db 0x00
GDT_TABLE_END:

[bits 32]
Entry32:
	xor		eax, eax
    mov     ax, 0x10
	mov		ds, ax
	mov		es, ax
	mov		ss, ax
    xor     esp, esp
    mov     esp, 0xFFFF

    mov     BYTE [0xB8000], 'O'
    mov     BYTE [0xB8002], 'K'

 hang:
	jmp hang

위와 같이 작성..

이제 BARAMOS.SYS 파일을 만들어야 하는데~~~

음..

동작 까진 필요 없는 테스트이니깐....

Visual Studio 에서 콘솔에 프로그램 임의로 하나 작성..

// testKernel.cpp : 콘솔 응용 프로그램에 대한 진입점을 정의합니다.
//

#include "stdafx.h"


int _tmain(int argc, _TCHAR* argv[])
{
	return 0;
}

Visual Studio 에서 기본 으로 생성해주는 파일 그대로 컴파일 해서 BARAMOS.SYS 로 변경!!!

자 테스트 해보잣!!!

 

os14-1

흐흐흐흐 잘된다…

자~~~~~ 이제 읽은 이미지 데이터를 이제 메모리에 재배치 하잣!!!!

메모리 0x10000위치에 커널 이미지(BARAMOS.SYS) 데이터를 섹션별로 재배치~~~

..

……

…………..

PE포맷은 ImageBase + AddressOfEntryPoint 가 실제 주소란다.. 그렇겠지..ImageBase는 기본 메모리 시작점이라고 했고, AddressOfEntryPoint가 RVA 가상 위치 오프셋 이라고 하니..

근데 … 진짜 근데…보통 실행파일은 0x400000라는데….(?)

그래서 PCVIEW 프로그램으로 내가 예제로 만든 프로그램을 분석해 봤다.

os14-2

역쉬 4M 위치다. 리얼모드 모드에서는 1M 이상은 접근 안된다고 했는데????

아~~~~ 맞다… ㅡㅡ;;; 보호 모드로 넘어가서 재배치 해야 하는거군 … 에휴!!

음. 근데 초기 메모리 레이아웃에는 0x10000 위치에 커널 이미지 올린다고 했는데 … 0x400000으로 바꿔야 하나??? 흠…

구분

위치

부트로더 0x7C00
커널 로더 0x8000
커널 임시 0x10000
커널 재배치 0x400000

자… 이제 커널 재배치를 해보잣!!!!

0x10000 커널 데이터에서 ImageBase를 우선 구해야 하니, 요넘은 IMAGE_NT_HEADER 구조체 IMAGE_OPTIONAL_HEADER32 구조체 안에 있으니… 우선 IMAGE_OPTIONAL_HEADER32 구조체 위치를 찾아야겠다.

IMAGE_DOS_HEADER의 e_lfanew 에 IMAGE_NT_HEADER위치가 기록 되어 있고, IMAGE_OPTIONAL_HEADER32 위치는 e_lfanew + 0x18 에 있네.. 여기서 다시 ImageBase 위치는 아~~~ 그냥 e_lfanew + 0x34 구만..

ImageBase = e_lfanew(IMAGE_DOS_HEADER) + 0x34

PCVIEW 프로그램으로 찾아보니 쉽네 ㅋㅋㅋㅋ

ImageBase를 찾았으니, 섹션만 ImageBase 위치에 순차적으로 복사하면 되겠군..

섹션 첫 위치는 IMAGE_OPTIONAL_HEADER32 구조체의 SizeOfHeaders에 기록되니까 ImageBase 찾는거랑 마찬가지로…

Section = e_lfanew +  0x54

아.. 그런데 섹션위치는 알았는데 얼마만큼 복사하는지 섹션 크기가 알아야 하네… 그리고 섹션이 하나가 아니고 여러개 존재 하니깐…. Section 첫번째 위치는 별 의미가 없다… 흠… 그러면 섹션들 헤더를 열고.. 섹션 크기, 섹션 실제 위치, ImageBase + 섹션 RVA를 구한후 복사해줘야 할듯… 그러자면 섹션이 몇개 있는지에 대한 정보도 필요하다..섹션갯수는 elfanew 에서 …

SectionCount = e_lfanew + 0x06

그리고 각 섹션헤더 위치는 IMAGE_OPTION_HEADER 다음 위치에 있으니깐 IMAGE_FILE_HEADER구조체 SizeOfOptionalHeader가 IMAGE_OPTION_HEADER 크기니깐 ㅡㅡ;;;

SizeOfOptionHeader = e_lfanew + 0x14 구하고,

SectionHeader = e_lfanew + SizeOfOptionHeader + IMAGE_FILE_HEADER크기

IMAGE_FILE_HEADER크기는 0x14 크기니깐..

SectionHeader = e_lfanew + SizeOfOptionHeader + 0x14

에다가 다시 Signature 4바이트 더하면…

SectionHeader = e_lfanew + SizeOfOptionHeader + 0x14 + 0x04

만큼 있다.

아고 힘들어…

이제 섹션 갯수 만큼루프 돌면서 섹션 헤더를 보고 섹션의 실제 저장된 데이터를 ImageBase + 섹션 RVA만큼 위치에다가 복사해주면 된다!!!!

그럼 코딩해 보잣!!!!

;; creator : cyj (mangg@manggong.org)
;; date : 2014. 3. 27
;; desc : 커널 로더.
;;        1. BARAMOS.SYS 파일을 찾아서 읽자.
;;        1. 보호모드로 진입한다.
;;        2. OK 문자 화면에 출력

[bits	16]
org 0x8000

OS_LDR_ENTRY:
	;; CS, DS, ES, & SS을 초기화 하자.
	cli
	xor eax, eax
	mov ds, ax
	mov ss, ax
	mov es, ax
	mov sp, 0xFFFF
	mov bp, sp
	sti

	mov dl,[0x0550]	; 부트 드라이브
	mov [BootDrive], dl

	lea si, [LOADING_MSG]
	call PRINT

	call FAT_INFO

	; RootDirectoryEntry 읽어 첫번째 파일을 읽자.
	mov eax, 0x9000
	mov [TEMP_MEMORY], eax

	mov eax, [RootDirectoryEntry]
	mov [READ_SECTOR], eax
	           
	mov eax, 0x10000000
	mov ebx, eax
	mov [FILE_MEMORY], eax

	call READ

	mov ebx, [TEMP_MEMORY]
	add ebx, 0x200
	
	; find file name
	mov ax, [TEMP_MEMORY]
FIND_LOOP:

	cmp eax, ebx
	je NOTFOUND

	add ax, 0x20

	mov si, ax
	mov di, KERNEL_NAME
	mov cx, 11
	repe cmpsb
	jnz FIND_LOOP

	mov ax, [si+9]
	push ax
	mov ax, [si+15]
	push ax
	pop eax
	mov [CurrentCluster], eax

DATA_READ:

	lea si,[DOT]
	call PRINT

	sub eax, 2
	xor ebx, ebx
	mov bl, [BPB_SecPerClus]
	mul ebx
	mov ebx, [RootDirectoryEntry]
	add eax, ebx
	mov [READ_SECTOR], eax
	mov eax, [FILE_MEMORY]
	mov [TEMP_MEMORY], eax

	call READ

	mov eax, [BytsPerCluster]
	mov ebx, [FILE_MEMORY]
	add eax, ebx
	mov [FILE_MEMORY], eax
	xor ebx, ebx

	; FAT 리스트
	mov eax, 0x9000
	mov [TEMP_MEMORY], eax
	mov eax, [CurrentCluster]
	;xor ebx, ebx
	mov bx, [BPB_BytsPerSec]
	shr bx, 2
	xor edx, edx
	div ebx
	push edx
	add eax, [FATsector]
	mov [READ_SECTOR], eax

	call READ

	pop edx

	mov esi, 0x9000
	sal edx, 2
	add esi, edx
	mov eax, [esi]

	mov [CurrentCluster], eax
	cmp eax, [FATEoFMask]
	jz END

	jmp DATA_READ

END:
	lea si, [KERNEL_LOADING]
	call PRINT


    ; GDT 등록
    xor     eax, eax
    lgdt    [GDT_INFO]  

    cli
    mov     eax, cr0
    or      eax, 1
    mov     cr0, eax

    jmp     $+2
    nop
    nop

    jmp     0x08:Entry32

NOTFOUND:
	lea si,[FAIL_MSG]
	call PRINT
	jmp $


; 하드디스크 읽기
READ:
	; Disk Address Packet (DAP)
	xor ax,ax								;INT 13h AH=00h: Reset Disk Drive
	mov dl,BYTE [BootDrive]				;DL = Drive Number
	int 0x13
	jc READ
	mov	al, 0x10 ; 크기
	mov	[0x500], al

	mov al, [BPB_SecPerClus]	; 읽을 섹터 수
	mov	[0x502], ax
	mov eax, [TEMP_MEMORY] ; 데이터가 들어갈 주소
	mov [0x504], eax ; Segment:offset
	mov eax, [READ_SECTOR] ; 접근 할 LBA 번호
	mov	[0x508], eax

	mov ax, 0x500 ; DAP의 Segment:offset
	mov	si, ax
	mov	ax, 0
	mov ds, ax
	mov ah, 0x42 ; LBA 읽기 명령
	mov dl, [BootDrive] ; 장치

	int 0x13

	jc READ_ERROR
	ret

READ_ERROR:
	lea si,[READ_ERR]
	call PRINT

	jmp $

PRINT:
	pusha				; 모든 레지스터를 스택에 넣는다.
PRINT_LOOP:
	mov al, [si]		; al에 글자 하나 저장한다.
	inc si				; si 주소를 증가한다.
	or al, al			; or 연산. 
	jz PRINT_END		; 문자열 끝이 or 연산이 0 이라면. 종료
	mov ah,0x0E			; ah = 0xE
	int 0x10			; 인터럽트 0x10h
	jmp PRINT_LOOP		; 다음 문자 출력 루프	

PRINT_END:
	popa				;모든 레지스터 스택에서 뺀다.
	ret					;호출 한곳으로 리턴.

FAT_INFO:
	pusha

	xor ebx, ebx
	mov eax, 0x9000
	mov [TEMP_MEMORY], eax
	
	xor eax, eax
	mov eax, 0
	mov [READ_SECTOR], eax

	call READ

	xor ebx, ebx
	mov si, [TEMP_MEMORY]
	mov bl, [si+454]
	mov bl, 0x80

	mov eax, 0x9000
	mov [TEMP_MEMORY], eax
	
	mov eax, ebx
	mov [READ_SECTOR], eax

	call READ

	mov si, [TEMP_MEMORY]
	mov bx, [si]

	mov bx, [si + 11]		; BytePerSec
	mov [BPB_BytsPerSec], bx
	xor ebx, ebx
	mov bl, [si + 13]		; SecPerCluster
	mov [BPB_SecPerClus], bl

	xor ecx, ecx
	xor ebx, ebx
	xor eax, eax
	mov cl, [si + 16]    ;  FAT NUM
	mov ebx, [si + 28]	;  Hidden Sector
	mov eax, [si + 36]	;  FAT 32

	mul ecx
	mov cx, [si + 14]		; Reserved Sector

	add eax, ecx
	add eax, ebx
	mov [RootDirectoryEntry], eax

	xor eax, eax
	mov eax, ebx
	add eax, ecx
	mov [FATsector], eax

	xor eax, eax
	xor ebx, ebx
	mov bx, [si + 11]
	mov al, [si + 13]
	mul ebx
	mov [BytsPerCluster], eax

	lea si,[FAT32_OK]
	call PRINT

	popa
	ret


DOT:
	db ".", 0x0D, 0x0A,0
FAT32_OK:
	db "Loading FAT's Info...", 0x0D, 0x0A, 0
KERNEL_LOADING:
	db "Loading BARAMOS.SYS...", 0x0D, 0x0A, 0
READ_ERR:
	db "Can't Read Disk..", 0x0D, 0x0A, 0

KERNEL_NAME:
	db "BARAMOS SYS",0

LOADING_MSG			db 0x0D, 0x0A, "Searching for BARAMOS.SYS...", 0x0D, 0x0A, 0x00
FAIL_MSG			db 0x0D, 0x0A, "ERROR.... ", 0x0D, 0x0A, 0x0A, 0x00
 
BPB_BytsPerSec		dw 0
BPB_SecPerClus		db 1
BootDrive			db 0x00
RootDirectoryEntry	dd 0
FATsector			dd 0
BytsPerCluster		dd 0
FATClusterMask		dd 0
FATEoFMask			dd 0x0FFFFFFF
CurrentCluster		dd 2
TEMP_MEMORY			dd 0x9000
READ_SECTOR			dd 0
FILE_MEMORY			dd 0x10000

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 보호모드 GDT
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
GDT_INFO:
			dw     GDT_TABLE_END - GDT_TABLE - 1
		    dd     GDT_TABLE

GDT_TABLE:
			dw 0x0000 ; null
			dw 0x0000
			db 0x00
			db 0x00
			db 0x00
			db 0x00
            dw 0xFFFF    ; Code
            dw 0x0000
            db 0x00
            db 0x9A
            db 0xCF
            db 0x00
            dw 0xFFFF    ; Data
            dw 0x0000
            db 0x00
            db 0x92
            db 0xCF
            db 0x00
GDT_TABLE_END:

[bits 32]
Entry32:
	xor		eax, eax
    mov     ax, 0x10
	mov		ds, ax
	mov		es, ax
	mov		ss, ax
    xor     esp, esp
    mov     esp, 0xFFFF

	; 메모리 재배치 해보자
	mov		esi, 0x10000     ; 커널 이미지 저장된곳
	xor		edx, edx
	xor		eax, eax

	add		edx, 0x3C
	mov		eax, [esi+edx]
	mov		[NT_POINTER], eax ; e_lfanew 위치 구하기(IMAGE_NT_HEADER위치)
	mov		ebx, eax
	mov		edx, 0x34
	add		edx, ebx
	mov		eax, [esi+edx]
	mov		[IMAGE_BASE], eax	; IMAGE_BASE 위치 구하기
	mov		edx, 0x28
	add		edx, ebx
	mov		eax, [esi+edx]
	mov		[ENTRY_POINT], eax		; ENTRY_POINT 위치 구하기
	mov		edx, 0x06
	add		edx, ebx
	mov		ax, WORD [esi+edx]
	mov		[SECTION_COUNT], ax		; 섹션 갯수 구하기
	mov		edx, 0x14
	add		edx, ebx
	mov		ax, WORD [esi+edx]
	mov		[OPTIONHEADER_SIZE], ax		;IMAGE_OPTION_HEADER크기 구하기
	mov		edx, 0x18
	add		dx, WORD[OPTIONHEADER_SIZE]
	add		edx, ebx
	mov		eax, edx
	mov		[SECTION_HEADER], eax	;섹션 헤더 위치 구하기

	xor		ecx, ecx
	mov		cx, [SECTION_COUNT]		; 섹션 갯수 만큼 반복
SECTION_LOOP:
	mov		esi, 0x10000
	mov		ebx, [SECTION_HEADER]		; 섹션 헤더 위치

	mov		edx, 0x0C				; 섹션 RVA = 섹션 헤더 위치 + 0x0C
	add		edx, ebx
	mov		eax, [esi+edx]
	mov		[SECTION_RVA], eax
	mov		edx, 0x10				; 섹션 크기 = 섹션 헤더 위치 + 0x10
	add		edx, ebx
	mov		eax, [esi+edx]
	mov		[SECTION_SIZE], eax
	mov		edx, 0x14				; 섹션 데이터 위치 = 섹션 헤더 위치 + 14
	add		edx, ebx
	mov		eax, [esi+edx]
	mov		[SECTION_POINTER], eax

	; 복사할 원본 위치
	add		esi, [SECTION_POINTER]	; 섹션 데이터가 있는 위치 .

	mov		edi, [IMAGE_BASE]		; 복사될 메모리 위치
	add		edi, [SECTION_RVA]		; IMAGE_BASE 에서 섹션 RVA 만큼 위치

	push	ecx
	cld
	mov		ecx, [SECTION_SIZE]		; 섹션 사이즈 만큼
	repz	movsb					; 복사
	pop		ecx

	mov		eax, [SECTION_HEADER]	; 섹션 헤더 위치를 다음 섹션 헤더 위치로 변경
	add		eax, 40
	mov		[SECTION_HEADER], eax
	loop	SECTION_LOOP

    	mov     BYTE [0xB8000], 'O'
	mov     BYTE [0xB8002], 'K'


	mov		eax, [IMAGE_BASE]
	add		eax, [ENTRY_POINT]
	call	eax


 hang:
	jmp hang


IMAGE_BASE			dd	0
ENTRY_POINT			dd	0
NT_POINTER			dd	0
OPTIONHEADER_SIZE	dw	0
SECTION_COUNT		dw	0
SECTION_HEADER		dd	0
SECTION_RVA			dd	0
SECTION_SIZE		dd	0
SECTION_POINTER		dd	0

PE 메모리 재배치 하는 코드 찾다가 http://1228.tistory.com/entry/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EB%A7%8C%EB%93%A4%EA%B8%B0-%EC%96%B4%EC%85%88%EB%B8%94%EB%A6%AC%EC%BD%94%EB%93%9C%EC%97%90%EC%84%9C-C%EC%BD%94%EB%93%9C%EB%A1%9C-%EC%A0%90%ED%94%84%ED%95%98%EA%B8%B0-%EB%A1%9C%EB%8D%94-%EC%9E%91%EC%84%B1 블로그 꼬꼬넷님 코드를 훔쳤다(?) … 정말 감사드린다……똭!!!

이제 테스트…!!!

헐… 또 재부팅이다… 음…

코드를 몇번을 확인해도 문제는 없어 보인다.

커널이 메모리상에 재배치 되는건 문제가 없어 보인다. 그래서 Visual Studio에서 컴파일 할때 옵션이 잘못된듯 해 보여 또 부랴부랴 검색질.

http://wiki.osdev.org/Visual_Studio

요기 이 사이트에서 하라는대로 컴파일 옵션을 수정했다.

그리고 커널 코드도 바꿨다…

int StartKernel(int argc, char* argv[])
{
    char* hello = "Hello OS!";

    unsigned char* vidmem = (unsigned char*)0xB8000;

    while( *hello != '\0' )        
    {
        *vidmem++ = *hello++;
        *vidmem++ = 7;        // 문자 기본 속성
    }   

	return 0;
}

그리고 컴파일 옵션->고급 에서 진입점을 StartKernel 입력하고 컴파일…

아직까진 간단한 코드니 에러 없이 컴파일 잘 된다!!

자 이제!! 테스트

os14-3

ㅠㅠ

된다… 와 ! 된다 ㅠㅠ

부트로더, 커널로더 까지 많은 시행 착오 및 병행 학습을 많이 했는데 막상 이렇게 기능상 구현이 된 모습을 보니 간단하다… 누가 이걸 보고 많은 지식이 포함되어 있다는걸 알겠누.. 흠..

이제 어려운 과정(?)은 다 끝난것 같다. 물론 남은 일이 더 남아 있지만, 어셈블리어 메뉴얼 뒤져 보면서 한자 한자 출력 해보면서 테스트 하는 작업은 끝난 듯….

OS의 핵심인 커널은 이것보다 험난 할 듯 보이지만.. 적어도 문법적으로 검증해야 하는 난관이 없다는데 행복함을 느낀다.

커널은 부트로더, 커널 로더 처럼 단일 기능이 아니고 필요할 때마다 학습해서 될 문제는 아닌듯 해서 커널 까지는 다시 학습이 필요 할 것 같다…

학 ………………..습.

 

+ Recent posts