80386 chip 에서 현재 태스크에서 다른 태스크로 스위칭하는 방법은 4가지가 있습니다.

1. TSS descriptor를 참조하여 JMP 혹은 CALL 실행

2. task gate 를 참조하여 JMP 혹은 CALL 실행

3. IDT의 task gate로 인터럽트나 예외 벡터 사용

4. NT flag가 SET 되어 있을 때, IRET 실행

 

이 포스팅에서는 1번의 경우의 태스크 스위칭에 대해서 알아볼 것입니다.

 

1. jmp를 이용한 태스크 스위칭

태스크 스위칭이 일어나 Process2 루틴이 실행되게 해 봅시다.

Tss2의 영역에 있는 EIP에 process2 루틴의 첫 번지를 넣어 process2: 부터 실행 되도록 합니다.

(원래 tss의 EIP영역에는 유저 영역의 스택 주소가 저장되지만, 설명의 편의를 위해 커널 모드의 스택 주소를 넣어 설명하겠습니다.)

 

먼저 다음과 같이 tss 세그먼트 디스크립터가 준비가 되어 있다고 가정합니다.

descriptor 4(TSS 세그먼트 디스크립터) – tss1을 지정, TSS 세그먼트 셀렉터는 TSS1Selector(0x20)

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

0x68

0x013A

1

00

0

1001

0x01

0x00

0

0

0

0

0

63

62

61

60

59

58

57

56

55

54

53

52

51

50

49

48

 Descriptor 5 – tss2 를 지정, TSS 세그먼트 셀렉터는 TSS2Selector(0x28)

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

0x68

0x01A2

1

00

0

1001

0x01

0x00

0

0

0

0

0

63

62

61

60

59

58

57

56

55

54

53

52

51

50

49

48

 

그리고 LTR 명령으로 TSS1이 지정이 되었습니다.

Limit

0x68

Base Address

0x1013A

속성

 

이제 다음 명령을 통해 태스크 스위칭을 시도 합니다.

jmp TSS2Selector:0

 

TR 레지스터를 참조하여, GDT의 Tss!Selector(0x20)에 해당하는 디스크립터를 찾아, tss1영역을 찾은 뒤, 모든 레지스터 값을 저장합니다.

 

Offset은 아무 숫자나 사용해도 태스크 스위칭을 하여 저장되어 있던 EIP 레지스터를 CPU의 EIP 레지스터에 옮겨 전에 실행되던 주소부터 재개하게 때문에 아무 숫자나 사용해도 괜찮으며, 사실 의미가 없습니다.

 

다음과 같이 TSS의 셀렉터를 참조하여 다른 세그먼트 레지스터를 채운것과 같이 GDT에서 해당 세그먼트 디스크립터를 찾은 뒤 TS의 Hidden Part에 복사하여, TSS를 찾습니다.

 

Limit

0x68

Base Address

0x101A2

속성

 

찾은 tss2 영역에 저장되어 있는 레지스터의 값들을 현재 레지스터에 복원시킵니다.

복원 후, EIP 레지스터에 주소가 있다면, 여기서부터 프로그램이 시작되게 됩니다. 처음에 process2: 의 주소를 넣었으므로, process2가 실행 될 것입니다.

 

이 후에 태스크 스위칭이 일어나면 TR에 지정된 TSS 디스크립터에 현재 레지스터의 상태가 저장이 됩니다.

CPU는 TR 레지스터에서TSS 의 base address를 찾고, 여기에 레지스터의 상태를 저장합니다 (EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI, ES, CS, SS, DS, FS, GS, flag register). 이때 TSS의 EIP 필드는 다음에 실행될 명령어를 가리키고 있습니다(EIP 필드가 가리키는 명령어의 전에 실행된 명령어가 task switch를 일으켰다고 볼 수 있습니다).

 

2. CALL 명령에 의한 Task Switching

(1) Task Switching 시점

CALL 명령과 IRET 명령에서 Task Switching이 일어납니다.

(2) EFLAG의 NT 비트와 디스크립터의 B 비트 변화

0 

0 

0 

0 

0 

0 

0 

0 

0 

0 

ID 

VIP 

VIF 

AC 

VM 

RF 

0 

NT

IOPL 

OF 

DF 

IF

TF 

SF 

ZF 

0 

AF 

0 

PF 

1 

CF 

 

EFLAGS 레지스터의 14 비트는 NT 비트로 사용됩니다. Nt는 nested의 약자로, IRET 명령어가 인터럽트 핸들러에서 사용되었는지, 태스크 스위칭에서 사용되었는지 구별할 때 사용합니다.

 

LTR 명령은 해당 TSS 디스크립터의 B 비트를 1로 세트 하며, CPU는 B 비트가 1로 세트되어 있는 디스크립터를 현재 실행되고 있는 태스크로 인식합니다.

CALL 명령이 실행되면, 스위칭 될 태스크의 EFLAGS의 NT 비트와 디스크립터의 B비트가 1로 세트 됩니다.

CALL 명령이 실행될 시점에는 반드시 B 비트가 0이어야 합니다.

IRET명령이 실행될 때, 스위칭 될 태스크의 B 비트가 무조건 1이어야 IRET이 실행이 가능합니다.

IRET을 이용하여 태스크 스위칭 될 때, 현재 태스크의 NT 비트와 디스크립터의 B 비트가 0으로 클리어 됩니다.

 

이러한 B 비트 관리 규약을 사용하는 이유는 이전 태스크로 돌아가지 못하게 하기 위해서 입니다.

 

(3) 이전 주소 저장

CALL 명령이 실행 되면, 새로 태스크 스위칭 될 태스크의 TSS 영역 이전 태스크로의 백링크에 현재 태스크의 TSS 디스크립터 셀렉터를 저장합니다.

IRET 명령이 실행이 되면 현재 태스크의 TSS 영역의 이전 태스크로의 백링크를 사용하여, 이전 태스크를 찾습니다.

 

3. CALL과 JMP

CALL과 IRET 을 이용하여 태스크 스위칭을 하는 경우, 하나의 체인으로 묶여, 그 이전의 태스크는 새로운 태스크의 실행이 끝날 때 까지 다시 실행될 수 없어, 비선점 프로세스 관리에 해당됩니다.

JMP를 이용하여 태스크 스위칭을 하는 경우, 태스크 끼리의 구속성이 없어 자유롭습니다.

+ Recent posts