Task Switching(태스크 스위칭)

태스크 스위칭의 방법을 알아보기 위해 아래와 같이 소스를 짠다.


boot.asm


init.inc


kernel.asm

%include "init.inc"


[org 0x10000]

[bits 16]


start:

cld

mov ax, cs

mov ds, ax

xor ax, ax

mov ss, ax

; TSS 디스크립터에 Base Address를 넣는 부분이다.

xor ebx, ebx

lea eax, [tss1] ; EAX에 tss1의 물리 주소를 넣는다.

add eax, 0x10000    ; 더하는 이유는 디스크립터의 Base Address는 물리주소 값이 들어가야 하기 때문
                           이 소스부분이 16비트 코드이므로 오프셋을 나타내는 레지스터도 16비트
                           따라서 org[0x10000] 이라는 origin이 오프셋에 적용되지 않기 때문이다.

mov [descriptor4+2], ax

shr eax, 16

mov [descriptor4+4], al

mov [descriptor4+7], ah

lea eax, [tss2] ; EAX에 tss2의 물리 주소를 넣는다.

add eax, 0x10000

mov [descriptor5+2], ax

shr eax, 16

mov [descriptor5+4], al

mov [descriptor5+7], ah

cli

lgdt[gdtr]

mov eax, cr0

or eax, 0x00000001

mov cr0, eax

jmp $+2

nop

nop

jmp dword SysCodeSelector:PM_Start

[bits 32]


PM_Start:

mov bx, SysDataSelector

mov ds, bx

mov es, bx

mov fs, bx

mov gs, bx

mov ss, bx

lea esp, [PM_Start]


mov ax, TSS1Selector

ltr ax              ; LTR 명령은 CPU에 있는 TR 레지스터에 TSS 디스크립터의 셀렉터 값을 넣는 명령어

   ; 위와 같이 설정 시 현재 진행중인 태스크가 스위칭이 되어 저장되어야 할 때에는 이 TSS     영역을 저장하라 는 뜻이다. 또한 LTR 명령은 태스크 스위칭 구현에 이어 필수이다. 

lea eax, [process2]    ; Process2 라는 서브루틴이 존재하며, 이 루틴은 하나의 태스크로 다룰 것이다.      

mov [tss2_eip], eax   ; TSS2 영역의 EIP에 process2 루틴의 첫 번지를 넣어두어 태스크 스위칭이 일어났을
                            때 Process2: 번지부터 실행되도록 해둔다.

mov [tss2_esp], esp


jmp TSS2Selector:0 ; 다시 태스크 스위칭이 되면 이곳으로 돌아온다.
                           ; 세그먼트 셀렉터는 스위칭을 한 다음 TSS의 TSS 디스크립터 셀렉터 값을 사용한다.                            ; 오프셋은 아무 숫자나 사용 가능, CPU의 펌웨어 구조가 태스크 스위칭을 행할 때 오                             프셋은 아무 숫자나 사용해도 되도록 되어 있다. (오프셋은 별 의미 없음)                            ; 위 명령 실행 시 이 시점에서 tss2의 EIP에는 process2: 의 주소가 들어있기 때문에                              process2 루틴의 처음부터 실행하게 된다.

mov edi, 80*2*9

lea esi, [msg_process1]

call printf

jmp $


printf:

push eax

push es

mov ax, VideoSelector

mov es, ax

printf_loop:

mov al, byte [esi]

mov byte [es:edi], al

inc edi

mov byte [es:edi], 0x06

inc esi

inc edi

or al, al

jz printf_end

jmp printf_loop

printf_end:

pop es

pop eax

ret

process2:        ; Process2 루틴이다. 이곳에 들어왔다면 태스크 스위칭이 된 상태이다. 태스크 스위칭이 일어나
                  면 CPU의 TR 레지스터에는 새로운 태스크의 TSS 디스크립터 셀렉터의 값이 들어간다.
                  이 의미는 이제부터 태스크 스위칭이 일어나면 이곳으로 모든 레지스터 값을 저장하라 라는 뜻이
                 다.

mov edi, 80*2*7

lea esi, [msg_process2]    ; 문장 표시

call printf

jmp TSS1Selector:0        ; tss1 태스크로 스위칭 한다. 이 명령 직후 현재 TR 레지스터에 있는 TSS 디스크
                                립터 셀렉터
와 관련된 TSS 영역(여기에서는 tss2)에 모든 레지스터를 저장하고,
                                TSS1Selector와 관련된 TSS 영역에서 모든 레지스터 값을 꺼내서 CPU에 복원
                                시키고, TR 레지스터에 TSS1Selector 값을 저장한다.


msg_process1 db "This is System Process 1", 0

msg_process2 db "This is System Process 2", 0


gdtr:

dw gdt_end-gdt-1

dd gdt

gdt:

dd 0, 0

dd 0x0000FFFF, 0x00CF9A00

dd 0x0000FFFF, 0x00CF9200

dd 0x8000FFFF, 0x0040920B

descriptor4:

dw 104    ; TSS 의 Limit 가 104(0x68)로 되어있음. TSS 디스크립터에서는 Limit 값이 항상 0x67이상의 값
                가지고 있어야 한다. 그렇지 않을 시 태스크 스위칭 시 무효 TSS 예외(#TS)가 발생한다.

dw 0

db 0

db 0x89    ; P 비트가 1, DPL이 00, 고정된 0, Type이 1001이라는 것을 알 수 있다. 태스크 스위칭은 항상
                  커널 레벨에서 이루어 지도록 하는 것이 보통이므로 DPL은 00으로 해둔다.

db 0        ; 소스상에는 Base Address에 TSS 영역의 시작주소를 넣는데, 0으로 되어 있지만 프로그램으로
                   Base Address를 넣도록 하였다. 그 부분이 상단의 Start의 xor ebx, ebx 부분이다.

db 0

descriptor5:

dw 104

dw 0

db 0

db 0x89

db 0

db 0

gdt_end:


tss1:

dw 0, 0 ; 이전 태스크로의 back link, 이전에 동작하던 프로그램의 TSS 영역의 세그먼트 셀렉
                            터 값이 들어간다.
TSS영역은 GDT에 있는 TSS 세그먼트 디스크립트와 한쌍을 이룸
                            2개의 태스크를 관리하기 위해 2개의 TSS, TSS 세그먼트 디스크립트 쌍을 지정함.
                            이 디스크립터를 셀렉터로 지정하여 JMP, CALL 명령으로 태스크 스위칭을 한다.
                            JMP 명령은 해당되지 않으나, CALL 명령을 통해 태스크 스위칭다음 태스크는 자
                           신의 TSS 영역에 이전 태스크의 TSS 디스크립터의 셀렉터 값을 저장
해 두었다가 자신
                            은 IRET 명령으로 프로그램을 마치고 CPU는 현재 태스크의 TSS영역에서 이전 태스
                            크로의 back link 값을 사용하여 이전 
태스크로의 스위칭을 행하게 된다.

dd 0 ; ESP0

dw 0, 0 ; SS0, 사용안함

dd 0 ; ESP1

dw 0, 0 ; SS1, 사용안함

dd 0 ; ESP2

dw 0,0 ; SS2, 사용안함 

dd 0, 0, 0 ; CR3, EIP, EFLAGS

dd 0, 0, 0, 0 ; EAX, ECX, EDX, EBX

dd 0, 0, 0, 0 ; ESP, EBP, ESI, EDI

dw 0, 0 ; ES, 사용안함

dw 0, 0 ; CS, 사용안함

dw 0, 0 ; SS, 사용안함

dw 0, 0 ; DS, 사용안함

dw 0, 0 ; FS, 사용안함 dw 0, 0 ; GS, 사용안함

dw 0, 0 ; LDT, 사용안함

dw 0, 0 ; 디버그용 T비트, IO 허가 비트맵

tss2:

dw 0, 0 ; 이전 태스크로의 ack link

dd 0 ; ESP0

dw 0, 0 ; SS0, 사용안함

dd 0 ; ESP1

dw 0, 0 ; SS1, 사용안함

dd 0 ; ESP2

dw 0, 0 ; SS2, 사용안함

dd 0


; 아래 소스 부분은 tss2에 지정된 tss2_eip 번지와 tss2_esp 번지를 나타낸다.

tss2_eip:

dd 0, 0 ; EIP, EFLAGS (EFLAGS=0x200 for ints)

dd 0, 0, 0, 0

tss2_esp:    ; tss2_esp에 원래 유저영역의 스택주소가 저장되어야 하나, 커널모드끼리의 태스크 스위칭인 이유도
             있으며, 소스의 설명을 위한 편의상 mov [tss2_esp], esp 명령이 실행될 때의 esp 스택 포인터의 값을
             넣는다.

dd 0, 0, 0, 0 ; ESP, EBP, ESI, EDI

dw SysDataSelector, 0 ; ES, 사용안함

dw SysCodeSelector, 0 ; CS, 사용안함

dw SysDataSelector, 0 ; SS, 사용안함

dw SysDataSelector, 0 ; DS, 사용안함

dw SysDataSelector, 0 ; FS, 사용안함

dw SysDataSelector, 0 ; GS, 사용안함

dw 0, 0 ; LDT, 사용안함

dw 0, 0 ; 디버그용 T 비트, IO 허가 비트맵

times 1024-($-$$) db 0


컴파일 시 아래 그림과 같이 프로세스가 태스크 스위칭 되어 출력되는 것을 확인할 수 있다.

두개의 tss영역을 사용하여 두 개의 프로그램을 태스크 스위칭한다. 진행되던 루틴을 태스크 스위칭 시켜 새로운 프로그램이 CPU에서 동작하게 하다가 다시 본래 진행되던 루틴으로 태스크 스위칭 한다. Process 2 부분은 새로운 프로그램이 화면에 표시한것, Process 1 부분은 새로운 프로그램에서 태스크 스위칭을 하여 본래 진행되던 프로그램을 다시 CPU에서 동작하게 하여 화면에 나타낸 문자열이다. 문자열을 나타내고 프로그램은 멈추게 된다.


중요 사항 정리

1. Protected Mode의 태스크 스위칭은 CPU의 비효율적인 시간 낭비를 보완하기 위해 CPU 타임을 FULL로 사용할 수 있게 해준다.
2. 우리가 사용하는 Intel 80286 이상의 CPU는 Protected Mode에서의 태스크 스위칭을 CPU 레벨에서 지원해준다.
3. 선점형 방식 태스크 스위칭은 프로그램이 실행되는 동안 어떤 상황에 있든지 관계 업이 그 프로그램을 일단 정지시키고 다른 프로그램이 이전에 실행했던 곳부터 다시 실행되도록 하는 방식이다.
4. 위 구현을 위해 CPU에서 수행되는 프로그램의 모든 레지시터 값을 일단 보존시키고, 이전에 수행되었다 저장되었던 프로그램의 모든 레지스터 값들을 CPU에 옮겨 놓아, 멈추었던 부분부터 다시 재개하도록 해야한다.
5. 프로그램이 재개되면 보존되어 있던 CS에 있는 코드 세그먼트EIP가 복원되어 그자리부터 프로그램이 실행될 것이고, SS:ESP도 복원되어 스택 영역을 참조하고, EAX, EBX, ECX, EDX 등 범용 레지스터들도 값이 복원된다.
6. 태스크 스위칭을 구현하기 위해서는 먼저 RAM상에 모든 레지스터 값들이 보존될 영역을 만들어 놓아야 한다. 이 영역을 TSS(Task State Segment)라고 한다.
7. TSS를 지정하는 TSS 디스크립터가 GDT에 지정되어 있어야 한다. TSS 생김새는 아래와 같다.

TSS는 CPU의 거의 모든 레지스터 값들을 저장할 수 있게 되어있다. GDTR, IDTR, CR0~CR2 등의 모든 태스크가 공통으로 사용하는 레지스터를 제외하고, 각 태스크가 사용하는 모든 레지스터를 포함하도록 되어 있다. RAM 에서의 번지가 그림처럼 아래에서 위로 증가하고, 소스에서는 위에서부터 아래로 증가한다. (혼동 주의)

ESP0, SS0

유저 모드(레벨 3) 태스크가 실행 중 커널모드(레벨 0)로 태스크 스위칭이 행해졌을 때 스택 값이 바뀌어야 한다. 그 이유는 유저모드와 커널모드에서 스택을 같이 사용한다면 프로그램의 실행이 엉키기도 할 뿐더러 유저모드에서 커널모드의 데이터를 읽고 쓰는 행위가 가능해지므로 커널의 보안 기능이 나빠지게 된다. 그래서 TSS 영역에 ESP0, SS0, ESP1, SS1, ESP2, SS2와 같이 CPU가 사용하는 시스템 레벨별로 스택이 따로 존재한다. 이중 우리는 레벨 0과 레벨 3망늘 사용한다. ESP3, SS3은 없다. 유저 레벨 스택은 TSS의 ESP 칸과 SS칸에 저장한다.

CR3
페이징 구현과 관련 있다. 차 후 설명함

디버그용 T 비트
유저 레벨 태스크를 디버깅 할 때, 브레이크 포인트를 걸어두고 한 스텝씩 진행시켜가며 프로그램 동작을 확인해야 할 경우가 있다. 물론 디버그 도중 다른 태스크가 존재하고 태스크 스위칭이 빈번히 행해지기 때문에 디버깅하고 있는 태스크가 태스크 스위칭 되기 전에 이 태스크는 디버깅 중이었다는 표시를 이곳 T 비트에 해둔다.

I/O 허가 비트맵
유저 레벨 태스크는 주변장치를 마음대로 사용 불가능이다. I/O 허가비트맵이라는 것으로 사용할 수 있는 I/O 장치와 사용할 수 없는 I/O 장치를 구분해야 한다. 이 또한 RAM의 한 영역에 표시해야 한다. 이 표시해 둔 영역의 시작 주소를 TSS의 I/O 허가 비트맵 칸에 넣어둔다.

TSS와 한쌍을 이루는 TSS 세그먼트 디스크립터는 아래와 같다.

위 디스크립터도 GDT에 들어가며, init.inc에서 보듯이 TSS 디스크립터도 8바이트이므로 셀렉터 값이 8씩 건너뛰며 지정되고 있다.
Type은 1 0 B 1 4비트로 나누어져 있으며 1과 0으로 고정된 부분과 B 비트가 있다. B 비트는 이 태스크가 실행 중 혹은 실행을 기다리고 있는 중이라는 표시이다. CPU는 이 비트를 사용하여 실행 중 인터럽트가 걸린 태스크를 다시 CALL 했는지 아닌지를 검사한다. 처음에는 0으로 클리어 해둔다.

태스크 스위칭이 일어나는 순서(가장 중요)
1. 프로그램이 Protected Mode로 넘어오고, LTR 명령으로 TSS 영역을 지정한 후 jmp TSS2Selector:0 명령이 내려진다.
2. CPU 내부의 TR 레지스터를 참조하고 GDTR 레지스터를 참조하여 GDT에 있는 TSS1Selector(0x20) - 5번째의 디스크립터를 찾는다. (그 이유는 위 ltr 명령어를 통해 TSS1Selector 값(0x20)이 TR 레지스터에 들어가 있기 때문)
3. Tss1Selector 디스크립터의 Base Address를 참조하여 tss1 영역을 찾는다.
4. tss1 영역에 현재 CPU가 가지고 있는 모든 레지스터 값을 각각의 자리에 저장한다.
5. GDT에서 TSS2Selector(0x28) - 6번째 디스크립터를 찾는다. (그 이유는 순서 1번의 jmp TSS2Selector:0의 명령으로 TSS2 태스크 명령어의 실행을 위해서다.) 이때 TR 레지스터에는 TSS2Selector(0x28)이 들어가게 된다.
6. TSS2Selector 디스크립터에 있는 Base Address를 참조하여 tss2 영역을 찾는다.
7. tss2에 있는 모든 레지스터 값을 CPU에 복원한다. (이미 ESP에는 process2의 루틴 주소가 들어가 있음)
8. 복원이 되었을 때 ESP 레지스터에는 process2: 루틴의 주소가 있으므로 여기서부터 프로그램이 시작된다. EIP에 다른 값이 있다면 그 주소부터 프로그램이 시작될 것이다.


태스크 스위칭에 대해 반드시 이해하고 넘어가자..

+ Recent posts