64bit 멀티코어 OS 원리와 구조 - Day 2

이 글은 ‘64비트 멀티코어 OS 원리와 구조 - 한승훈 저자’을 공부하며 정리한 내용을 작성했습니다.

이미지 로딩 구현

책에서 구현하게 될 OS는 부트 로더, 보호 모드 커널, IA-32e 모드 커널로 구성되고 섹터 단위로 정렬되 하나의 부팅 이미지로 만들어지며 두 번째 섹터부터 읽어 메모리에 복사하면 이미지 로딩이 끝나게 된다. 책에서 구현하는 OS는 메모리 0x10000번지에 복사하게 되며, 이 주소는 정해진게 아니라 부트 로더 이후에 바로 복사해도 되지만, 책의 뒷장에서 0x10000 이하 번지를 다른 용도로 사용하기 위해서 비워둔다.

x86 프로세서는 스택과 관련된 레지스터가 3개 있는데 SS, SP, BP가 있다. 이때 SS를 어떻게 설정하느냐에 따라서 스택의 크기가 달라지는데 예를 들어 SS를 0x0000으로 설정한다면 0x00000~0x0FFFF SS를 0x1000으로 설정하면 0x010000~0x01FFFF까지 된다. 이렇게 SS를 통해서 스택의 범위를 지정할 순 있지만, 스택의 크기는 정하지 못하는데 32bit의 스택 한 칸의 크기는 4byte이다.

리얼 모드에서 보호 모드로의 전환

보호 모드로 전환하는 단계는 크게 6단계라고 할 수 있다. 2단계는 보호 모드 전환을 위한 자료구조 생성, 나머지 4단계는 생성된 자료구조를 프로세서에 설정을 한다. 2단계에서 생성할 필수 자료구조는 세그먼트 디스크립터GDT이다. 이 두 자료구조는 전환 즉시 프로세서에 의해 참조되므로 미리 생성해야 한다.

세그먼트 디스크립터 생성

세그먼트 디스크립터는 세그먼테이션 기법(메모리 관리 기법)에서 세그먼트의 정보를 나타내는 자료구조이다. 여기서 세그먼트는 메모리 공간을 임의의 크기로 나눈 영역을 의미한다. 세그먼트 디스크립터는 크게 코드 세그먼트, 데이터 세그먼트 디스크립터로 나뉜다.

  • 코드 세그먼트 : 실행가능한 코드가 포함된 세그먼트에 대한 정보 CS 세그먼트 셀렉터에 사용
  • 데이터 세그먼트 : 데이터가 포함된 세그먼트에 대한 정보를 나타냄, CS를 제외한 나머지 세그먼트에 사용할 수 있음.

세그먼트 디스크립트 구조는 책의 사진이 더욱 이해하기 쉬워 연관된 필드를 작성하는 것으로 대체합니다.

세그먼트 디스크립터 구조

  • S, 타입 : S 필드로 세그먼트 디스크립트임을 표시하고, 타입 필드로 읽기/쓰기, 실행/읽기 으로 설정한다.
  • 크기, G : 크기 필드는 총 20비트이며 최댓값은 1MB지만, 전체 4GB를 접근하기엔 무리가 있다. 따라서 G 필드를 활용해 1MB에 4KB가 곱해진 4GB에 접근할 수 있도록 한다.
  • D/B, L, 권한 : 기본 오퍼랜드 크기, 권한 설정
  • P, AVL : 기타 등등 ^^;;

GDT

GDT(Global Descriptor Table)은 연속된 디스크립터의 집합이다. 앞서 코드 세그먼트 디스크립터와 데이터 세그먼트 디스크립터를 어셈블리어로 생성하였는데 이를 연속된 영역이 GDT이다. 다만 한 가지 제약이 있는데 가장 앞부분에 NULL 디스크립터를 추가해야한다는 점이다. NULL 디스크립터는 프로세서에 의해 예약된 모든 필드가 NULL(=0)인 디스크립터이다. 모든 값이 NULL이기에 일반적으론 참조되지 않는다.

GDT는 디스크립터의 집합이므로 디스크립터가 필요한 프로세서에게 GDT의 시작 주소와 크기를 알려줘야 한다. 따라서 GDT의 시작주소, 크기 정보를 가지고 있는 자료구조(GDTR)가 필요하게 된다.

32bit의 기준 주소 필드는 데이터 세그먼트의 기준 주소와 상관없이 0번째 주소를 기준으로 하는 선형 주소이다. 따라서 GDT의 시작 어드레스를 실제 메모리 공간상의 주소로 변환할 필요가 없다.

보호 모드로 전환

보호 모드로 전환하려면 GDTR 레지스터 설정, CR0 컨트롤 레지스터 설정, jmp 명령 수행 3단계만 수행하면 된다.

먼저 프로세서에 GDT 정보를 설정하려면 lgdt(load GDT) 명령어를 사용한다. lgdt는 앞서 어셈블리로 작성했던 GDT의 시작 주소, GDT 크기를 오퍼랜드로 받는다. 단 한 줄로 GDT가 로드된 것이다.

다음은, CR0 컨트롤 레지스터를 설정하는데 CR0 컨트롤 레지스터는 보호 모드 전환에 관련된 필드 외에 캐시, 페이징, 실수 연산 장치 등과 관련된 필드가 포함되어 있다. CR0 컨트롤 레지스터 또한 세그먼테이션 기능외에는 사용하지 않기에 책을 통해 확인하자.

앞서 리얼 모드에서 세그먼트 레지스터(aka. 세그먼트 셀렉터)는 세그먼트의 시작 주소를 저장하는 레지스터였다. 보호 모드의 세그먼트(세그먼트 레지스터 아님)는 리얼 모드와 달리 다양한 정보를 포함하고 있어 세그먼트 정보는 디스크립터에 저장하고, 세그먼트 레지스터(aka. 세그먼트 셀렉터)는 그 디스크립터를 지시하는 용도로 사용한다. [세그먼트 레지스터] -> [디스크립터(세그먼트 정보가 포함되어 있음.)]와 같은 모양을 가지고 있다고 할 수 있다. 보호 모드 또한 세그먼트 셀렉터에 어드레스를 설정하여 GDT 내의 디스크립터를 참조한다. 다만, 세그먼트의 기준 주소 대신 GDT 내의 디스크립터의 주소를 사용한다. 예를 들어 NULL 디스크립터 다음의 커널 코드 세그먼트 디스크립터를 사용하고 싶다면, 디스크립터의 크기가 8byte임을 고려해 세그먼트 셀렉터에 8을 넣으면 된다. 요약하자면 주소가 아닌 오프셋으로 접근한다.

EntryPoint.s 파일의 내용은 책 또는 공식 Github를 참고!

Share