많은 시간을 이것저것 미리 살펴 보느라 오래 걸렸다. 처음에 너무 대책 없이 시작하다 보니 이것저것 삐걱거리고 다시 학습해야 할 내용들이 많아서 당분간 쉬었다(라고 쓰고… 그냥 귀찮아서 놀았다…) 음.. 기존 내 BaramOS(바람OS)는  부트로더 에서 커널로더를 거친 후 커널로 진입하는 방식으로 구현되어 있다. VC2010에서 커널을 만들다 보니 부트로더에서 바로 호출 할 수 없었다. 이유는 http://manggong.org/blog/64 에 간략하게 적은 내용대로 VC2010컴파일러가 32비트 컴파일러 이기 때문이다. (NASM의 obj 파일을 VC에서 링크를 할 수 없다라는게 이유. MASM으로 하면 된다는데… 것도 문제.. 흠..) 또 VC 컴파일러다 보니 PE 파일 포맷이다. (요것도 따로 배워야 한다… 메모리 재배치라나 뭐래나~~). 뭐 인터넷에 검색해 보니 부트로더에서 바로 커널 불러들이는 OS들도 있는 것 같지만..그냥 이대로 갈 생각이다.




리얼모드


부트로더, OS_LDR.SYS는 현재 16비트에서 동작한다. 요걸 달리 리얼 모드 라고 한다고 한다. 이름도 참… 리얼이 뭐냐!~~ 그럼 가상모드 도 있단 말이냐?? ㅋㅋ 헉… 검색해보니 가상모드 도 있다. 흠흠흠…. 여튼. 리얼모드는 리얼 주소 모드 또는 호환 모드 라고 하며, 80286이후의 x86호환 CPU 운영 방식이라고 위키백과 에서는 설명하고 있다. 뭔 소리냐??? 좀더 더 쉽게 풀어 달란 말이다. 흠… 검색하자… 리얼모드는 호환 모드라고 한다고 했다. 즉, 80286 이전에 CPU를 호환하기 위한 모드 라고 한다. 왜 호환 하는 거야?? 기존 만들어 놨던 소프트웨어 지원할라고??? 몰겠다. 다시 리얼모드로 돌어와서 리얼 모드라는 이놈은 1M 이상의 메모리를 접근 할 수 없단다. 왜??? 만들 당시 1M도 큰 메모리였단다.. ㅎㅎㅎ  1M이기 때문에 메모리에 액세스 할 주소는 20비트면 충분 했나 보다…(에효 좀더 크게 좀 잡아놓지…. 20비트가 뭐냐?? 계산하기 빡 세게…) 20비트로 주소를 계산했단다… 어떻게??? 세그먼트:오프셋 요렇게 말이다. 근데 세그먼트 크기는 16비트이다. 20비트에서 4비트 부족하다. 그래서 오프셋 이란 놈을 더 뒀다. 그러면 오프셋이 나머지 4비트일까??? 아니다. 요놈도 16비트다. 그럼 32비트 쓴다는거?? ㅋㅋ 그랬으면 얼마나 좋았을 까? 흠. 졸라 복잡한 계산식으로 20비트 만든단다. 졸라 어렵다. 세그먼트를 왼쪽으로 4비트 시프트 시키고 거기에다가 오프셋을 더해 준단다.  예를 들어.. 세그먼트 = 0x0700와 오프셋 0x0C00 라면 세그먼트 4비트 왼쪽 시프트해서 0x7000 만들고 여기에 0x0C00 더해 주는 방식이다.


  0 7 0 0 0
+   0 C 0 0
    7 C 0 0


이렇게 졸라 복잡한 계산식(?)으로 1M까지 메모리를 접근했다.


근데 내가 초딩 때 봤던 도스가 리얼모드에서 동작했다고 한다. 여기서 급 궁금증이 생겼다. 도스라면 640KB밖에 여유 메모리가 없었던 기억이 있다. 게임 하나 실행 할라 치면, 메모리 상주 프로그램 다 지워줘야 했다. 1M라며????


1MB안에서 다시 요놈 저놈이 쓸 메모리 공간 다 주고 640KB가 남은 거란다. 640KB도 크게 느껴졌다고 하니~^^ ㅋㅋㅋ


0x00000000 - 0x0009FFFF   RAM
  0x00000000 - 0x000003FF 리얼모드용 인터럽트 벡터
  0x00000300 - 0x000003FF BIOS DATA 영역
  0x00000400 - 0x000004FF BIOS가 사용하는 영역
  0x00000500 - 0x00007BFF 임시영역? ( 자유 영역, 도스 파라미터 영역)
  0x00007C00 - 0x00007DFF 부트 영역
  0x00008000 – 0x0009FBFF 자유영역
  0x00080000 – 0x0009FBFF 자유영역
  0x0009FC00 - 0x0009FFFF 확장 BIOS DATA영역
0x000A0000 – 0x000BFFFF   비디오 카드 접근 영역
  0x000A0000 - 0x000AFFFF 그래픽 모드 비디오 메모리
  0x000B0000 – 0x000BFFFF 흑백 테스트 모드 비디오 메모리
  0x000B8000 –0x000BC000 컬러 텍스트 모드 비디오 메모리
0x000C0000 - 0x000C7FFF   비디오 BIOS
0x000C8000 – 0x000EFFFF   각종 카드의 ROM 영역
0x000F0000 – 0x000FFFFF   메인보드 BIOS

(출처 : http://wiki.osdev.org/Memory_Map_(x86))


요기 위 처럼 1M를 이넘 저넘 예약 되어 있다. 휴~ 리얼모드에서 플밍 하기 참~ 힘들었겠다는 생각이 단다. 뭐~ 힘들게 만든 프로그램 컴퓨터에서 실행시키기도 힘들었다. 무슨 설정을 그리 해야 했는지..




보호모드


80286 맹글면서 메모리 버스를 24bit로 늘렸단다. 뭔 말이냐면 메모리를 24bit 만큼 쓸 수 있다는 것이다. 16M인데?? (참~~~ 많기도 하다..ㅋ ) ㅎㅎ 80386에서는 요게 32bit로 늘렸단다. 그래 드뎌 4GB 메모리가 참조 가능해진다. 뭐, 요즘 요것도 적은 편이지만… 64비트가 나온판에 ㅋㅋㅋ 여튼. 보호모드에서는 세그먼트 레지스터를 세그먼트 셀렉터(selector)라는 이름으로 바꾸었단다. 왜 바꾸냐?? 머리 아프게 시리.. 세그먼트 레지스터는 물리 주소 자체를 가르키는 역활을 했으나 보호모드에서는 세그먼트 셀렉터 이놈은 주소 공간을 가르키는 게 아니고 디스크립터 테이블의 디스크립터 이라는 놈 자체를 가르킨다고 한다. 그럼 디스크립터 테이블 이놈이 무엇이냐?? 8바이트로 구성된 코드 세그먼트 디스크립터, 데이터 세그먼트 디스크립터 등등의 세그먼트 디스크립터를 모아 놓은게 디스크립터 테이블이란다. 이 중 한놈을 세그먼트 셀렉터 라는 놈이 가르킨단다. 딱 보니, 디스크립터 구조체 배열의 인덱스 번호를 세그먼트 셀렉터가 가르키는 꼴이다. 흠..디스크립터 구조는 다음과 같다.

 

겁나 복잡하게 생겼다. 저 Base Address 주소 가르키는 구조좀 봐라 … 아주 조각을 내놨다. 얼씨구 Segment Limit도 조각 냈네.. 요거 설계 하는 사람이 머리에 스팀 좀 받았겠다… 그러니 요거 배우는 나는 더 스팀 받을 수 밖에…ㅋㅋㅋㅋ 여튼 우습다. 흠.. 각 설명은 요렇다는 데..

  • Base Address : 세그먼트의 시작 주소를 가르킨다. 32비트 크기이므로 0~4GB까지 가능하다.
  • Segment Limit : 세그먼트의 크기를 나타낸다. 20비트의 크기로 0~1MB까지 가능하다. G 비트가 1로 설정되면 4KB를 여기에다 곱해준단다. 그럴 경우 0~4GB까지 설정 가능하단다.
  • Type : 4비트 크기로 세그먼트의 타입을 지정한단다. 코드 또는 데이터 로 말이다. 근데 여기에 읽기 쓰기 권한을 비트로 준단다. (엠병)
    0bit 1bit 2bit 3bit Type 설명
    0 0 0 0 0 데이터 데이터 세그먼트 읽기 전용
    1 0 0 0 1 데이터 데이터 세그먼트 읽고/ 접근 여부 (접근 여부 : 디스크립터가 참조 될 때 1로 설정함)
    2 0 0 1 0 데이터 데이터 세그먼트 읽고/쓰기
    3 0 0 1 1 데이터 데이터 세그먼트 읽고/쓰기/접근 여부
    4 0 1 0 0 데이터 데이터 세그먼트 읽기 전용/역방향 (역방향: 스택처럼 상위 주소에서 하위 주소로 자라남)
    5 0 1 0 1 데이터 데이터 세그먼트 읽기 전용/역방향/접근여부
    6 0 1 1 0 데이터 데이터 세그먼트 읽고/쓰기/역방향
    7 0 1 1 1 데이터 데이터 세그먼트 읽고/쓰기/역방향/접근 여부
    8 1 0 0 0 코드 코드 세그먼트 실행 전용
    9 1 0 0 1 코드 코드 세그먼트 실행전용/접근 여부
    A 1 0 1 0 코드 코드 세그먼트 실행/읽기
    B 1 0 1 1 코드 코드 세그먼트 실행/읽기/접근여부
    C 1 1 0 0 코드 코드 세그먼트 실행 전용/접근가능 (접근가능: (권한에 관계없이 모두 접근 가능)
    D 1 1 0 1 코드 코드 세그먼트 실행 전용/접근가능/접근 여부
    E 1 1 1 0 코드 코드 세그먼트 실행/읽기/접근 가능
    F 1 1 1 1 코드 코드 세그먼트 실행/읽기/접근 가능/접근여부
  • S : 디스크립터 타입을 가르키신단다. 0 일때는 시스템 디스크립터, 1일 때는 걍 디스크립터(코드, 데이터)
  • DPL : 2비트로 세그먼트 접근 권한(? 요건 또 뭐냐..) 을 주신단다. 주로 0은 커널, 3은 유저(?) 접근 가능 하단다.
  • P : 페이징과 관련 있단다.
  • AVL : 임의 용도란다.
  • L : 64비트 코드 세그먼트인지, 32비트인지를 의미한단다. 1: 64, 0:32비트 ..
  • D/B : 16비트용인지 32비트용인지를 의미한단다. 1: 32비트, 0: 16비트
  • G : Segment Limit에 4Kb를 곱할지 안 곱할지.


빡시다… 겁나 복잡(?) 하다. 뭐가 이리 복잡하게 해 놓은 건지 원… 여튼, 세그먼트 셀렉터는 이놈들 중 하나의 인덱스 번호를 가지고 있고 해당 디스크립터가 가리키는 메모리에 접근 한단다. 뭐, 메모리 조각조각 내서 사용하는 페이징은 사용하지 않으면 실제 물리 주소고 여기에 페이징을 사용한다면 그냥 가상 주소라고 한단다. 뭐 페이징은 아직까지 사용 할 생각 없으니, 물리 주소라고 봐도 무관하겠다.


흠 근데 이런 디스크립터 테이블이 3종류나 있단다. 아놔!~~~~ 보호모드 겁나 짜증나는 놈이네…


GLOBAL DESCRIPTOR TABLE 이라는 GDT 라는 놈하고

LOCAL DESCRIPTOR TABLE 이라는 LDT 라는 놈하고

INTERRUPT DESCRIPTOR TABLE 이라는 IDT 테이블


 요렇게 3개 있단다. 그러면 왜 3개씩이나 있는 이유가 뭐냐??? 물리주소를 가르키는 얘라며~!!!!!!!!!!!!!!! 이런게 3개 있을 필요가 있냐고!!!!~~~휴~~ 진정해야지…


GDT : 전역 디스크립터 테이블이라고 하며 커널에서 사용하기 위해서 구성된다고 한다.
LDT : 지역 디스크립터 테이블이라고 하며 보통 어플리케이션 에서 사용하기 위해 구성된다고 한다.
IDT :  각 인터럽트를 사용하기 위해 구성된다고 한다.


GDT와 LDT 디스크립터 구성은 동일 한단다. (그래~~~)  위에 표기한 디스크립터 구조다.

IDT 구조는 다음과 같다.


  • 세그먼트 셀렉터(2바이트) : GDT 던 LDT 던 이 놈들이 가지고 있는 디스크립터 인덱스로 설정한다.
  • 오프셋(4바이트) : 디스크립터에 설정된 Base Address에서 + 오프셋 한 값.
  • P(1비트) : 디스크립터가 물리 메모리 영역인지 아닌지 여부를 나타내는 것 같은데 1로 설정하란다. 0은 사용안하거나 Paging 처리 할때.
  • DPL(2비트) : 특권 레벨이라는데 디스크립터의 DPL 처럼 접근권한. 즉 0은 커널, 3은 유저. 요것도 0.
  • S(1비트) : 요건 모르겠다. 그냥 0 .
  • Type(4비트) : Gate 타입을 입력.
    0101 0x5 5 80386 32bit Task Gate
    0110 0x6 6 80286 16bit Interrupt Gate
    0111 0x7 7 80286 16bit Trap Gate
    1110 0xE 14 80386 32bit Interrupt Gate
    1111 0xF 15 80386 32bit Trap Gate

    Interrupt Gate : 하드웨어 인터럽트를 위해 만들어 진 넘.
    Trap Gate : 소프트웨어 인터럽트나 Exception 처리하기 위해 만들어 진 넘
    Task Gate : Task Switching을 위해 만들어 진 넘.

IDT는 Interrupt vector table을 구현해 놓은 놈인데
- 소트트웨어 인터럽트
- 하드웨어 인터럽트
- 프로세서 Exception

의해 사용 된단다.


정리하믄 IDT는 GDT나 LDT에 있는 디스크립터가 가리키는 메모리 세그먼트에서 offset 만큼 위치의 함수를 호출하나 보다.

아흐… 겁나 머리 아프다. 뭐 이리 알아야 할게 많아~~~!!!!


흠…위에서 IDT에서 언급한 특권 레벨, 디스크립터의 접근 권한 요거에 대해서 더 살펴 봤더니 0,1,2,3 요렇게 권한등급이 있는데 0은 코널 수준이고 3은 유저(어플리케이션)에서 접근 할 수 있는 없는지를 나타내는 거란다. 보통 0과 3만 쓴다고 한다. 현재 불리우는 놈의 권한 기록은 세그먼트 셀렉터에 있다고 한다. 엥?? CS, DS, ES, FS 에???

세그먼트 셀렉터의 구조다. 보호모드로 바뀌면서 저렇게 구조를 나눠서 사용하나 보다. Index는 디스크립터 테이블에서 인덱스 값 이고 TI  요놈은 GDT(0) 테이블을 참조 할 것인가 LDT(1) 테이블을 참조 할 것인가 구분자이고 RPL 요놈이 접근하는 특권 레벨 을 가지고 있는 놈이란다. 요넘이 3인데 권한이 0인 놈을 접근 하면 CPU는 에러를 뱉어 낸다는군.

자!!~~~ 이제 또 뭘 알아야 하냐???




GDTR, LDTR, IDTR


흠… 힘들다..귀찮고…

보호 모드 진입을 하기 위해선, GDT는 이미 구성되어 있어야 한다고 한다… 메모리에 그냥 만들어 놓으면 되나??? 아니다. GDT를 읽는 레지스터가 있다고 한다. 이름하야 GDTR 이 레지스터에 GDT 테이블을 메모리 위치를 넘겨 주면 된다고 한다. 그럼 그냥 mov GDTR, [메모리 위치] 냐… 것도 아니다. lgdt 라는 어셈 명령어를 써서 넘겨야 한다. 또한 lgdt 명령어에 테이블 메모리 위치가 아니고 GDT의 총크기와 메모리 위치를 넘겨야 한단다.

struct
{
    unsigned short GDT_Size;
    unsigned int GDT_Address;
} GDT_INFO

lgdt GDT_INFO

요런 방식으로 명령을 주면 GDTR 레지스터에 GDT가 들어간다고 한다. 흠… 그래 그리고 ? 또… GDT 구성에 있어서 첫번째 인덱스는 NULL 이여야 한다고 한다. NULL??? 아무값도 없이 0으로 채워주면 된다고 한다. 사용되지 않는다는데 뭘…흠… 왜?? 모르겠다. 그냥 그래 라고 하니 그래야지. 하지만 또 성실하게 검색 질  해봤더니 “초기화 과정에서 사용되지 않은 레지스터를 통해 메모리에 접근하려는 시도를 막기 위함이다.” 라고 하네…뭔 소리인지..흠.


LDT를 LDTR에 입력하려면, 우선 GDT에 LDT에 관한 디스크립터를 만들어야 한다고 한다. 왜??? LDT는 전역이 아니고 프로세스에 대응하는 테이블이기 때문에 GDT와 같은 속성이 아니란다. (뭐… 맥락은….) 그래서 LDT 위치를 GDT에 우선 등록해주고 GDT의 인덱스값을 LDTR에 입력해 주면 지가 알아서 세그먼트 위치, 크기, 속성까지 로딩한다고 한다. (편하네~~~)


mov ax, GDT내의 LDT인덱스값
lldt ax


IDTR에 IDT를 넣는 방법은 GDTR과 유사하다.

struct
{

   unsgined short IDT_SIZE; (256*8-1)
   unsigned int IDT_ADDRESS;
}


256에다가 8(IDT 디스크립터 크기)을 곱하고 빼기 1을 해준단다.
근데 256개의 디스크립터를 모두 생성해 줘야 하는지 모르겠다. 그러니까 256이 아닌 10개만 등록하면 안 되는지 말이다. 흠…뭐 해봐야 알겠지.




A20 GATE


여기까지 세그먼트 셀렉터로 시작해서 디스크립터 테이블, GDTR,LDTR,IDTR까지 꼬리를 물고 배워왔다. 뭐~~ 더 뭐가 필요한지 모르겠지만.. 완전 지친다. 왜 이리 처리해 줘야 하는 게 많은지..

아… 인터넷에서 보호 모드 검색하다 보니 A20 GATE 라는 놈이 등장했다.

앞에서 80286에서 20비트 에서 24비트로 메모리 버스가 늘었다고 했다. 16MB까지…. 헌데 리얼모드에서는 20비트 밖에 못쓴다. 세그먼트:오프셋 이 방식 때문에 말이다. 기존 8086/8088 요넘들은 0xFFFF:000F 까지 메모리를 액세스 할 수 없었다. 왜?? 20비트 넘어가면 이상한 주소가 참조해 버리니 원하는 위치를 벗어나 버리기 때문이다. 암만 0xFFFF:1234 주소로 입력한들 .. 0x1224 주소로 접근한다. 메모리 접근은 20비트니 20비트 이상은 다 없어지는 거지. 근데 80286은 24비트다. 뭔말이냐 하믄  0xFFFF:1234를 주면 0x101224로 접근이 되서 메모리를 읽고 쓸 수 있다는 말이다. 호~~~ 그럼 16M까지 접근 가능하겠네?? 라고 생각했다가 세그먼트:오프셋 주소 지정 방식을 생각해 보니, 웃음만 나왔다. ㅋㅋㅋ  0xFFFF:FFFF = 0x10FFEF 까지만 가능하다. ㅋㅋㅋㅋㅋ 메모리 버스를 24비트로 늘려도 쓸 수 없다 ㅋㅋㅋㅋ 기존 0xFFFFF에서 64kb 정도만 더 쓸 수 있다. ㅋㅋ 아놔 것도 많다면 많은 거지만.. 왜케 웃기지 ㅋㅋㅋㅋ

여튼 0x10FFEF까지 주소의 64kb 메모리라도 더 쓸라면 A20 GATE 요놈을 이용해야 한다. 요놈이 Keyboard 컨트롤러에 달려 있는 요놈을 ON 시켜야 쓸 수 있단다. 왜 키보드 컨트롤러 일까? 라고 의문이 들었지만 오늘 많은 검색질로 인해서 지쳤다….여튼 요놈을 ON 시켜주면 쓸 수 있다고 한다.. 어떻게??? 메모리 버스 21비트에 A20과 AND 연산기와 묶여 있단다. 그래서 A20 GATE에 ON(1)이 되어 있어야지만 21비트에 접근 가능하단다. 왜 굳이 A20 GATE를 달아서 AND 연산 시키는 괴상망측한 짓이 진행되었는지는…..모르겠다. 여튼, 요넘 때문에 보호모드에서 메모리 접근 할 때 문제가 생긴다. 왜??? A20 GATE가 ON이 안되어 있으면 21비트는 항상 0 이기 때문에…. 그래서 보호 모드 넘어 갈 때는 항상 A20 GATE를 켜고 진입해야 한다고 한다.

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

OS 만들기 #13 보호모드 진입  (0) 2014.03.27
OS 만들기 #12 - PE 파일 포맷  (0) 2013.09.10
OS 만들기 #10  (0) 2013.08.14
OS 만들기 #9 - 부트로더  (0) 2013.08.14
OS 만들기 #8 - 부트로더  (0) 2013.08.14

+ Recent posts