[OS 개발 18] 태스크 스위칭과 보호 (3) - 보호
1. 보호의 개념
이번 포스팅에서는 보호에 대해 알아보도록 하겠습니다. 글을 쓰는 과정에서 나도 공부하면서 작성하는지라 처음에는 좀 내용이 빈약할 수 있습니다. 왜냐하면 이번 포스팅에서는 개념위주로 작성할 계획이기 때문입니다. 일단 포스팅을 마치고 나중에 다시 확인해서 부족한 점이 있으면 보완하도록 하겠습니다.
지난 포스팅까지는 jmp와 Call을 통해 각 태스크 간에 어떻게 스위칭이 일어나는지 살펴보았습니다. 그런데, 여기에서 한 가지 혹은 그 이상으로 문제가 발생할 개연성이 생깁니다. 특정 프로세스로 스위칭했는데 그 태스크 혹은 레벨(특권 레벨 간의 모드 스위칭은 다음에 알아볼 계획이다)에 민감한 코드, 혹은 데이터가 존재한다면 어떻게 될까요?
이러한 문제에서 기인하여 인텔 아키텍처에서는 보호모드에서 세그먼트 레벨과 페이지(Page)레벨(뒤에서 알아보도록 하자) 보호 메커니즘을 제공합니다. 보호 메커니즘이 사용되면, 메모리를 참조하는 모든 상황에서 그 참조가 합당한 경우인지 체크하게 됩니다. 이 모든 메모리 참조 체크는 메모리 사이클이 시작되기 전에 미리 구성되어 있고, 만약 잘못된 메모리 참조를 확인하면 곧바로 예외를 때려버립니다. 이 보호 체크는 다음과 같은 카테고리를 중심으로 이루어집니다.
- 리미트 체크
- 타입 체크
- 특권 레벨 체크
- 지정 가능한 도메인(주소 범위) 제한 체크
- 진입 절차의 제한 체크
- 명령 세트 체크
2. 세그먼트 레벨과 페이지 레벨에서 보호에 사용되는 필드와 플래그
세그먼트와 페이지로 접근을 통제하기 위해 시스템 데이터 구조체에서 다음과 같이 필드와 플래그에서는 프로세서의 보호 메커니즘이 사용됩니다. 단, 여기에서 페이지에서 사용되는 개념도 나오는데, 일단 봐두기만 하고 다음에 다룰때 참조하면 좋을 듯 합니다.
- 디스크립터 Type 필드: 코드, 데이터, 혹은 시스템 세그먼트 인지를 결정
- 디스크립터 Type 필드의 S 플래그: 세그먼트 디스크립터가 시스템 세그먼트인지 데이터 세그먼틘지 결정
- 리미트 필드, G플래그, E플래그: 세그먼트의 사이즈를 결정
- 디스크립터 특권 레벨(DPL)필드: 세그먼트의 특권 레벨 결정
- 요청된 특권 레벨(RPL)필드: 요청받은 세그먼트 셀렉터의 특권 레벨을 명시
- 유저/슈퍼바이저(U/S)플래그: 유저 또는 슈퍼바이즈 페이지 타입 결정
- 읽기/쓰기(R/W) 플래그: 접근 허가된 페이지가 read-only 인지 read-write인지 타입 결정
위와 같이 특정 필드와 플래그가 보호에 사용되는데, 인텔사에서 이에 대해 다음과 같이 한 눈에 정리해 놓은 그림이 있어서 이를 참고하면 되겠습니다.
3. 리미트 체크
앞서 포스팅에서 디스크립터에서 리미트 필드는 그 디스크립터의 주소 크기(영역)를 지정한다고 했습니다. 그 이유는 할당된 메모리 영역 내에서만 프로그램 또는 프로시저들이 메모리를 참조하도록 하기 위함인데, 이를 벗어나면 예외를 일으킵니다.
이 때 G(Granularity, 데이터 세분화 수준, 혹은 균질성)플래그의 설정을 검사합니다. G플래그 설정 내역은 다음과 같습니다.
- G플래그가 0일때(Byte Granularity): Limit 범위: 0 ~ 0xFFFFF (1MB)까지 선택 가능
- G플래그가 1일때(4KByte page Granularity): Limit 범위: 0xFFF(4KB) ~ 0xFFFFFFFF(4GB)까지 선택 가능
단, 위의 세그먼트 영역에서 유효 Limit는 전체 세그먼트 크기(영역)에서 1바이트를 뺀 것과 같으며, 다음과 같이 지정할 경우, 일반 보호 예외(#GP)를 발생시킵니다.
- 오프셋이 유효 Limit보다 큰 바이트
- 오프셋이 유효 Limit-1바이트보다 큰 워드
- 오프셋이 유효 Limit-3바이트보다 큰 더블워드
- 오프셋이 유효 Limit-7바이트보다 큰 쿼드워드
위와 같이, 오프셋 주소가 0xFFFC, 0xFFFD, 0x10000 처럼, 물리주소 내에 있더라도, 바이트가 1바이트를 넘어가면 예외처리를 해버리므로, 반드시 각 데이터 단위별로 1바이트를 빼주고 오프셋을 할당해줘야 합니다.
4. 타입 체크
세그먼트 디스크립터에서 S비트와 Type 필드로 타입을 체크합니다.
여기에서 S비트가 의미하는 바는 다음과 같습니다.
- S = 0 일경우: Type 필드 = 시스템 Type
- S = 1 일경우: Type 필드 = 코드 or 데이터 세그먼트
1. S가 0일 경우에는 다음과 같이 Type범위인 0x0~0xF 까지 어떤 값을 가지냐에 따라 그 의미하는 바가 달라집니다.
시스템 Type |
게이트 종류 |
0x0 |
예약됨 |
0x1 |
16비트 |
0x2 |
LDT |
0x3 |
Busy 16비트 TSS |
0x4 |
16비트 콜게이트 |
0x5 |
태스크 게이트 |
0x6 |
16비트 인터럽트 게이트 |
0x7 |
16비트 트랩 게이트 |
0x8 |
예약됨 |
0x9 |
32비트 TSS |
0xA |
예약됨 |
0xB |
Busy 32비트 TSS |
0xC |
32비트 콜게이트 |
0xD |
예약됨 |
0xE |
32비트 인터럽트 게이트 |
0xF |
32비트 트랩 게이트 |
2. S가 1일 경우에는 다음과 같이 디스크립터를 조작할 때 Type 정보를 검사합니다.
- 세그먼트 셀렉터가 세그먼트 레지스터로 로드될 때
- CS레지스터는 코드 세그먼트 셀렉터로만 로드된다.
- 코드 세그먼트를 위한 세그먼트 셀렉터는 읽을 수 없고, 시스템 세그먼트를 위한 세그먼트 셀렉터는 데이터 세그먼트
레지스터에 로드될 수 없다(DS, ES, FS, GS)
- 쓰기 가능한 데이터 세그먼트의 세그먼트 셀렉터만 SS레지스터로 로드될 수 있다.
- 세그먼트 셀렉터가 TR 또는 LDTR로 로도될 때
- LDTR은 오직 LDT를 위한 셀렉터로만 로드된다
- 태스크 레지스터는 오직 TSS를 위한 세그먼트만 로드된다.
- 명령어가 이미 세그먼트 레지스터로 로드된 디스크립터를 가진 세그먼트를 접근할 때
- 해당 명령은 실행가능한 세그먼트에 쓰기(Write) 작업을 하지 않을 것이다.
- 해당 명령은 만약 쓰기가 불가능하다면 데이터 세그먼트에 쓰기(Write) 작업을 하지 않을 것이다.
- 해당 명령은 읽어들일 수 있는 플래그가 셋 되어있음에도 불구하고, 실행 가능한 세그먼트를 읽어들이지 못할 것이다.
명령어 오퍼랜드가 세그먼트 셀렉터를 포함하고 있을 때
- far CALL이나 far JMP 명령어는 오직 conforming 코드 세그먼트, nonconforming 코드 세그먼트, 콜게이트, 태스크 게이트, 또는 TSS로만 접근이 가능하다
- LLDT명령은 반드시 LDT 세그먼트 디스크립터를 참조해야 한다.
- LTR 명령은 반드시 TSS 세그먼트 디스크립터를 잠조해야 한다.
- LAR 명령은 반드시 LDT, TSS, 콜게이트, 태스크 게이트, 코드 세그먼트나 데이터 세그먼트를 참조해야 한다.
- LSL 명령은 반드시 LDT, TSS, 코드 세그머트나 데이터 세그먼트를 참조해야 한다.
- IDT entry는 반드시 인터럽트, 트랩 또는 태스크 게이트가 되어야 한다.
- 내부 작동이 이루어지는 동안
- CALL이나 JMP 명령에서 오퍼랜드로 주어진 세그먼트 셀렉터에 의해 가리켜지는(혹은 포인터된) 세그먼트 디스크립터에 있는 타입 필드를 체크함으로써, 이를 실행하기 위한 통제 변환 타입을 프로세서가 far CALL이나 far jmp 상태에서 결정한다.
- 콜게이트에서 call이나 jmp 상태에서 프로세서는 자동적으로 코드세그 먼트를 위한 게이트에 의해 가리켜지고 있는 세그먼트 디스크립터를 체크한다.
- 태스크 게이트를 통해 새로운 태스크로 call이나 jmp 상태에서 프로세서는 자동적으로 TSS를 위한 태스크 게이트에 의해 가리켜지고 있는 세그먼트 디스크립터를 체크한다.
- TSS를 직접 참조함으로써 새로운 태스크로 call 이나 jmp 하는 상태에서 프로세서는 TSS를 위한 CALL이나 JMP명령에 의해 가리켜진 세그먼트 디스크립터를 자동적으로 체크한다.
- nested 태스크에서 되돌아갈때, 프로세서는 TSS를 가리키는 현재 TSS에서 previous task link field를 체크한다.
5. 특권 레벨
프로세서의 세그먼트 보호 메커니즘은 다음과 같이 0~3까지 4개의 특권 레벨 메커니즘을 인식합니다.
그리고 프로세서는 통제된 상황 하에서 더 높은 특권이 있는 세그먼트로에서 더 낮은 특권에서 작동하는 태스크나 프로그램을 막기위해 특권 레벨을 사용합니다. 그런데, 특권 레벨을 위반하는 상황을 감지하면, 일반 보호 예외(#GP)가 발생합니다. 데이터 세그먼트와 코드 세그먼트 사이에서 특권 레벨을 체크하기 위해, 아래와 같 3가지 특권 레벨 타입을 프로세서가 인지하고 있습니다.
- 현재 특권 레벨(CPL): 현재 실행중인 프로그램이나 태스크의 특권 레벨. CS와 SS 세그먼트 레지스터의 0과 1비트에 저장되어 있다. 프로그램이 다른 특권 레벨의 코드 세그먼트로 제어되어 이행되면 CPU가 CPL을 변경한다.
- 디스크립터 특권 레벨(DPL): DPL은 세그먼트나 게이트의 특권 레벨이다. 현재 실행중인 코드 세그만트가 세그먼트나 게이트로 접그을 시도할때, 세그먼트나 게이트의 DPL은 세그먼트 셀렉터나 게이트 셀렉터의 CPL과 DPL 값을 비교한다. DPL은 접근되는 게이터나 세그먼트의 타입에 따라 다르게 해석된다.
-데이터 세그먼트: DPL은 세그먼트로 접근이 허가된 가장 높은 특권 단계의 프로그램이나 태스크를 가리킨다. 만약 데이터 세그먼트의 DPL이 1이면 0이나 1의 CPL에서 작동중인 프로그램 프로그램만 세그먼트에 접근할 수 있다.
-Nonconforming 코드 세그먼트: DPL은 세그먼트를 접근해야 하는 프로그램이나 태스크의 특권 레벨을 가리킨다. 만약 nonconforming 코드 세그먼트의 DPL 값이 0이면, 0 CPL에서 동작중인 프로그램만 세그먼트에 접근할 수 있다.
- 콜게이트: DPL은 현재 실행중인 프로그램이나 태스크의 가장 높은 특권 레벨이 콜 게이트를 접근할 수 있다.
- Conforming 코드 세그먼트와 nonconforming 코드 세그먼트는 콜게이트를 통해 접근된다: DPL은 세그먼트 접근이 허가되어야 하는 태스크나 프로그램의 가장 낮은 레벨을 가리킨다. 만약 conforming 코드 세그먼트가 2라면,0이나 1의 CPL에서 작동중인 프로그램은 세그먼트에 접근할 수 없다.
- TSS: DPL은 TSS로 접근할 수 있는 현재 실행중인 태스크나 프로그램의 가장 높은 레벨을 가리킨다.
- 요청된 특권 레벨(RPL): 특권 레벨 3인 프로세스가 콜게이트를 통해 특권 레벨 0의 루틴을 실행할 때가 있는데, 0의 데이터 영역에 접근이 가능하다. 그런데 이 때 RPL 값을 사용하여 누가 요구했는가를 나타낸다. 즉, 불필요한 하위 특권 레벨 접근을 막기 위해 사용된다.
6. 콜게이트
낮은 특권 레벨에서 프로그램 실행 도중 높은 특권 레벨로 변경되는 수단에는 다음과 같은 방법이 있습니다.
인터럽트
예외
콜게이트
이 중에서 콜게이트는 낮은 레벨의 프로그램이 자신의 의지에 의해 높은 특권 레벨의 루틴을 잠시 사용하는 것입니다.
커널에서는 RAM, 하드웨어 장치, 데이터 등이 관리가 되어야 하는데, 프로그램들이 실행 중간에 이러한 것들을 잠시 사용해야하는 경우가 발생합니다. 이 때 콜게이트를 통해 특권 레벨이 이동되는데, 이 콜게이트 역시 GDT 테이블에 디스크립터로 포함되는 또 하나의 세그먼트 정의라고 보면 됩니다.
이 콜게이트는 jmp명령으로 특권 레벨 3에서 0으로 갈 때 사용되는데, CALL은 콜게이트 없이도 하위 특권 레벨로 이동이 가능합니다.
특권레벨1------------------CALL GATE-------------------->특권레벨 3 (JMP 시)
특권레벨1------------------------------------------------->특권레벨 3 (CALL 시)
특권레벨 0과 3에서 각각 0,1비트에는 다음과 같은 값이 들어가 있습니다.
특권레벨0 CS: 00
특권레벨3 CS: 11
그리고 특권레벨 3에서 다음과 같이 CALL을 호출하면 값이 CS셀렉터의 값이 00으로 바뀝니다.(DPL와 CPL값은 모두 같다)
특권레벨3: 11----------->CALL----------------->특권레벨 0: 00
특권레벨3: 11 <-------------IRET-----------------특권레벨 0: 00
단, 낮은 특권 레벨에서 높은 특권 레벨 데이터에 접근하면 폴트가 발생하는데, RPL을 조작하면 가능하기도 합니다.
7. 스택 변화
태스크 도중에 인터럽트나 예외가 발생, 혹은 콜게이트를 거칠 때 특권레벨에 변화가 발생합니다. 이 때 스택 스위칭이 일어나는데, 스택스위칭은 특권 레벨의 루틴이 스택 공간의 부족 때문에 충돌하지 않게 하기 위함이며, 또 낮은 특권 레벨의 루틴이 스택을 통해 높은 특권 레벨의 루틴에 간섭하지 못하도록 하기 위함입니다.
1. 먼저 CPU는 TSS의 SS와 Esp의 값을 참조한다.
2. CS, EIP를 차례대로 PUSH 한다.
3. SS, ESP에 각각 SS0, ESP0을 넣는다.
4. 콜게이트에 지정된 루틴으로 점프한다.
5. 루틴을 마치고 Ret에 의해 되돌아가면 저장된 SS,ESP,CS,EIP를 POP하여 레지스터를 복원한다.
단, CS를 확인하고 현재 커널보다 낮은 레벨이면 SS와 ESP를 POP한다.
'OperatingSystem > Creating OS' 카테고리의 다른 글
[OS 개발 17] 태스크 스위칭과 보호 (2) - CALL 스위칭 (0) | 2016.02.18 |
---|---|
[OS 개발 16] 태스크 스위칭과 보호 (1) (0) | 2016.02.16 |
[OS 개발 15] 무한 재부팅에 대한 개인적 견해 (0) | 2016.02.16 |
[OS 개발 14] 인터럽트와 예외처리(4) 예외처리 구현하기 (0) | 2016.02.12 |
[OS 개발 13] 인터럽트와 예외처리(3) PIC와 인터럽트 구현하기 (0) | 2016.02.12 |
[OS 개발 12] 인터럽트와 예외처리(2) PIC와 예외처리 (0) | 2016.02.12 |
[OS 개발 11] 인터럽트와 예외처리(1) IDT의 개념 (0) | 2016.02.11 |
[OS 개발 10] 32비트 커널 로더(4) - 커널 구현과 분석 (3) | 2016.02.10 |
[OS 개발 9] 32비트 커널 로더(3) - GDT의 개념과 적용 (0) | 2016.02.06 |
[OS 개발 8] 32비트 커널 로더(2) - 리얼 모드와 보호 모드 (0) | 2016.02.05 |