32bit kernel - 1
32bit kernel
리얼모드에서 부트로더를 이용해서 32bit 커널을 로드해서 구동했다면 32bit에서는 64bit 커널을 로드해오고 몇가지 필요한 설정들을 바꿔야한다.
이전에 모드에 대한 설정을 할때 32bit 보호모드의 경우 아래와 같은 특성이 있다고 설명했다.
- 윈도우나 Linux가 구동되는 기본 모드이다.
- 멀티태스킹, 세그멘테이션, 페이징을 지원한다.
- 디바이스 드라이버를 써야 장치 제어가 가능하다.
- 4GB의 메모리 제한이 있다. (2^32)
- 민감한 메모리 영역에 대한 보호를 제공한다.
이러한 특성 중 2,3,4,5번에 대한 설정이 필요한데 멀티태스킹을 위한 테이블 구성 세그멘테이션과 페이징을 위한 테이블 구성과 레지스터 설정이 필수로 필요하고 5번에 대한 설정이 들어가면서 3번에 대한 제한이 추가되는 것이기에 사실상 3번과 5번은 같은 내용이라고 볼 수 있다. 그렇다면 2~5까지 차근차근 설명을 해보도록 하겠다.
Segment Descriptor
세그멘테이션에서 메모리의 각 영역을 정하는 세그먼트를 정의해둔 것을 세그먼트 디스크립터라고 한다. 크게는 코드영역과 데이터 영역으로 나뉘며 64bit OS를 위해서는 32bit에서 전체 메모리 영역이 잡혀있는 코드 세그먼트 디스크립터와 데이터 디스크립터만 있으면 된다.
이러한 디스크립터는 미리 정해진 포맷이 있는데 아래와 같다.
각 값의 명세는 다음과 같다.
- 기준 주소 : 세그먼트의 시작 주소이다.
- 세그먼트 크기 : 세그먼트의 크기이며 G비트가 1이면 4KB를 곱하고, 0이면 1MB까지만 지정 가능하다
- 타입 : 세그먼트의 타입으로 코드 또는 데이터이다.
- S : 디스크립터의 타입으로 1일때는 세그먼트, 0이면 시스템 디스크립터이다.
- DPL : 해당 디스크립터 사용에 필요한 권한을 말하며 가장 높은 0에서 가장 낮은 3까지 이다. x86에서는 커널모드인 0과, 응용프로그램 모드인 3만 사용한다.
- P : 현재 디스크립터가 유효한지 나타내며 1이면 유효하다
- AVL : OS 임의로 사용가능한 영역이다
- L : 1이면 64bit, 0이면 32bit 코드 세그먼트이다.
- D/B : 1이면 32bit용 세그먼트, 0이면 16bit용 세그먼트이다.
- G : 1이면 세그먼트 크기에 4KB를 곱하고, 0이면 곱하지 않는다.
DPL은 CPL(Current Privilege level)과 비교되어 해당 세그먼트 접근을 제한하는데 사용된다.
이러한 CPL은 CS라는 레지스터에 두 비트로 세팅되어 나타나며 해당 세그먼트를 실행하기 위해서는 이 CPL과 DPL이 같아야한다.
타입은 코드 또는 데이터를 나타내지만 용도에 대해서도 말하기도 한다. 타입 값에 대한 세부 명세는 아래와 같다.
11(코드/데이터) | 10(E) | 9(W) | 8(A) | 세그먼트 타입 | 설명 |
---|---|---|---|---|---|
0 | 0 | 0 | 0 | 데이터 | 읽기 |
0 | 0 | 0 | 1 | 데이터 | 읽기,접근됨 |
0 | 0 | 1 | 0 | 데이터 | 읽기/쓰기 |
0 | 0 | 1 | 1 | 데이터 | 읽기/쓰기,접근됨 |
0 | 1 | 0 | 0 | 데이터 | 읽기,역방향 확장 |
0 | 1 | 0 | 1 | 데이터 | 읽기,역방향 확장,접근됨 |
0 | 1 | 1 | 0 | 데이터 | 읽기/쓰기,역방향 확장 |
0 | 1 | 1 | 1 | 데이터 | 읽기/쓰기,역방향 확장,접근됨 |
1 | 0 | 0 | 0 | 코드 | 실행 |
1 | 0 | 0 | 1 | 코드 | 실행,접근 이력 유무 |
1 | 0 | 1 | 0 | 코드 | 실행/읽기 |
1 | 0 | 1 | 1 | 코드 | 실행/읽기,접근됨 |
1 | 1 | 0 | 0 | 코드 | 실행,접근 가능 |
1 | 1 | 0 | 1 | 코드 | 실행,접근 가능,접근됨 |
1 | 1 | 1 | 0 | 코드 | 실행/읽기,접근 가능 |
1 | 1 | 1 | 1 | 코드 | 실행/읽기,접근 가능,접근됨 |
여기서 역방향 확장은 스택을 위한 옵션이며 접근 가능은 권한에 상관없이 접근 가능함을 뜻하는 비트이다.
GDT
Global Descriptor Table의 약자로 위에서 언급한 세그먼트 디스크립터가 나열된 테이블이라고 할 수 있다. 제일 앞에서는 모든 영역이 Null로 초기화된 Null 디스크립터 이후에 필요한 디스크립터가 나열되는데 여기서는 코드 디스크립터와 데이터 디스크립터만 사용되게 된다. 이렇게 정의된 GDT를 쓰려면 CPU에서 해당 위치를 알고 있어야하는데 이럴때 필요한게 GDTR로 GDT의 위치를 갖고 있는 Register이다. 그래서 이러한 GDT를 사용하고자 할 때 GDTR에서 GDT의 어드레스를 가지고 몇 번째 GDT를 쓰는지 찾아서 쓰게 된다. 물론 본 GDT는 64bit로 점프하기 위한 교두보의 역할만 하므로 코드 세그먼트 또는 데이터 세그먼트만 쓰게된다.
32bit 용 코드 세그먼트는 32bit 아키텍처의 한계인 4GB 메모리 전체에 엑세스 할 수 있어야하므로 기준 주소 0번지에 크기는 4GB 전체로 잡은 세그먼트로 하나, 데이터 역시 전체에 엑세스 할 수 있어야하므로 기준 주소 0번지에 크기는 4GB 전체로 잡은 세그먼트로 만들어야한다.
다음 내용을 어셈블리어 코드로 만들면 아래와 같다. (코드는 책에서 가져왔다.)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
; 코드 세그먼트 디스크립터
CODEDESCRIPTOR:
dw 0xFFFF ; Limit [15:0]
dw 0x0000 ; Base [15:0]
db 0x00 ; Base [23:16]
db 0x9A ; P=1, DPL=0, Code Segment, Execute/Read
db 0xCF ; G=1, D=1, L=0, Limit[19:16]
db 0x00 ; Base [31:24]
; 데이터 세그먼트 디스크립터
DATADESCRIPTOR:
dw 0xFFFF ; Limit [15:0]
dw 0x0000 ; Base [15:0]
db 0x00 ; Base [23:16]
db 0x92 ; P=1, DPL=0, Data Segment, Read/Write
db 0xCF ; G=1, D=1, L=0, Limit[19:16]
db 0x00 ; Base [31:24]
참고 문헌
- 64Bit 멀티코어 OS의 구조 - 한승훈 저