1. Task Switching TSS

 

Intel 80286 이상의 CPU Protected Mode에서 CPU  레벨에서 태스크 스위칭을 지원합니다.

태스크 스위칭이 일어나는 동안 프로그램에서 사용하는 레지스터의 값을 저장하고, 복원 시키기 위해서 레지스터를 보관하는 장소가 필요합니다. 이러한 영역을 TSS(Task State Segment)라고 합니다. GDT 처럼  TSS 지정하는 TSS 디스크립터가 GDT 지정되어야 합니다

31

15

I/O 맵의 Base Address

사용안함

T

사용안함

LDT 세그먼트 셀렉터

사용안함

GS

사용안함

FS

사용안함

DS

사용안함

SS

사용안함

CS

사용안함

ES

EDI

ESI

EBP

ESP

EBX

EDX

ECX

EAX

EFLAGS

EIP

CR3(PDBR)

사용안함

SS2

ESP2

사용안함

SS1

ESP1

사용안함

SS0

ESP0

사용안함

이전 태스크로의 링크

 

GDTR, IDTR, CR0, CR1레지스터와 같은 모든 태스크가 공유하는 레지스터를 제외하고 모든 레지스터가 포함되어 있습니다.

다음 코드는 tss 지정하는 코드입니다.

그림은 아래부터 위로 증가하며, 아래 코드는 위에서 아래로 증가합니다.

tss1:

                  dw 0, 0                     ; 이전 태스크로의 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 허가 비트맵

 

항목에 대해서 자세히 알아봅시다.

(1) 이전 태스크로의 back link

이전에 동작하던 프로그램의 TSS 영역의 세그먼트 셀렉터 값이 들어갑니다. 세그먼트 셀렉터를 이용하여 JMP, CALL 명령을 실행하여 태스크 스위칭을 하게 됩니다. CALL 명령어로 태스크 스위칭을 하게 되면, 다음 태스크는 자신의 TSS영역에 이전 태스크 TSS 디스크립터의 셀렉터 값을 저장 두었다가 IRET 명령어 , 이전 태스크로 스위칭을 합니다.

 

(2) ESP0, SS0

시스템이 사용하는 권한 레벨별로 사용할 스택이 따로 존재합니다.  ESP3, SS3 없고, 유저 레벨 스택은 TTS ESP SS 저장 됩니다.

 

(3) CR3

페이지 구현과 관련이 있는 레지스터 입니다.

 

(4) 디버그용 T비트

유저 레벨 태스크를 디버깅 , 태스크 스위칭이 일어나면 디버깅 주잉었다는 것을 표시하기 위해 T 비트에 설정을 합니다.

 

(5) I/O 허가 비트맵

유저레벨태스크는 주변 장치를 제멋대로 사용할 없어, I/O 허가 비트맵을 사용하여 사용할 있는 장비와, 사용할 없는 장비를 구분지어 줍니다.  RAM 영역에 표시하는데, 영역의 시작주소를 TSS I/O 허가 비트맵 칸에 넣어둡니다.

 

TSS 세그먼트 디스크립터

TSS 세그먼트 디스크립터도 GDT 들어가므로 형태가 비슷합니다.

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

Lmit 15 - 0비트

Base Address 15 - 0비트

P

DPL

0

Type

Base Address 23 – 16 비트

Base Address 31 – 24 비트

G

0

0

AVL

Lmit 19 - 16비트

63

62

61

60

59

58

57

56

55

54

53

52

51

50

49

48

 

다음 코드는 TSS 디스크립터를 기술 것이고, 아래 그림은 표로 나타낸 입니다.

descriptor4:

                  dw 104

                  dw 0

                  db 0

                  db 0x89

                  db 0

                  db 0

 

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

(1) 0x68

(3) 0

1

00

0

(2) 1001

(3) 0

0

0

0

0

0

0

63

62

61

60

59

58

57

56

55

54

53

52

51

50

49

48

 

(1) TSS 디스크립터에서는 항상 limit 값이 0x68 이상이어야 하며, 그렇지 않으면 TSS 예외(#TS) 발생합니다.

 

(2) Type 부분은 다음과 같습니다.

1

0

B

1

1 0 부분은 고정된 부분이며, B비트는 현재 태스크의 상태실행/대기를 나타냅니다. 처음에는 0으로 클리어 합니다.

 

(3) Base address에는 TSS 영역의 시작 주소를 넣습니다.

 

위의 코드에는TSS 세그먼트 디스크립터에  base address 0 기술하였습니다. 부분에는 원래 TSS영역의 시작 주소를 넣어 주어야 됩니다. 따라서 실행 코드에서는 Base Address 넣도록 합니다.

다음 코드가 TSS 세그먼트 디스크립터에 Base address 채워주는 코드 입니다.

tss1 주소(offset)+0x10000 값을 16비트와 8비트와 8비트로 나눠 각각 저장합니다.  3단계로 나눠서 살펴보겠습니다.

                  xor ebx, ebx

                  lea eax, [tss1]                         ; 1) tss1 주소를 구함

                  add eax, 0x10000 ; 2) base address 0x10000 더함

                  mov [descriptor4+2], ax     ; 3) 디스크립터에 알맞게 채워 넣음

                  shr eax, 16                               ;

                  mov [descriptor4+4], al      ;

                  mov [descriptor4+7], ah    ;

 

1) tss 주소 구하기

18 00000009 6631DB                  xor ebx, ebx

    19 0000000C 668D06[3A01]                           lea eax, [tss1]

    20 00000011 660500000100                           add eax, 0x10000

    21 00000017 A3[2C01]                                mov [descriptor4+2], ax

    22 0000001A 66C1E810                              shr eax, 16

    23 0000001E A2[2E01]                                mov [descriptor4+4], al

    24 00000021 8826[3101]                               mov [descriptor4+7], ah

소스 코드를 기계어 코드로 바꿔본 결과, tss1 주소는 0x013A 것을 알아 내었습니다.

 

2) 0x10000 더함

구한 tss 주소에 base address 0x10000 더해줍니다.

(tss1 주소) + (base address) = 0x013A + 0x10000 = 0x1013A

 

3) 디스크립터에 알맞게 채워 넣음

Eax 레지스터에는 다음과 같이 저장이 되어 있습니다.

 

------- ax ---------

 

---ah---

---al----

0x00

0x01

0x01

0x3A

 

Descriptor4+2 주소에 ax, 0x013A 넣어줍니다.

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

0x68

0x013A

1

00

0

1001

0

0

0

0

0

0

0

63

62

61

60

59

58

57

56

55

54

53

52

51

50

49

48

 

 

다음 명령어는 Shr eax, 16으로 2바이트(16비트) 만큼 eax 레지스터의 값을 오른쪽으로 당겨줍니다 .

결과 eax 레지스터의 내용이 다음과 같이 변했습니다.

 

------- ax ---------

→→

---ah---

---al----

 

 

0x00

0x01


al 값을 Descriptor4+4, Base Address 23 – 16 비트 영역에 넣어줍니다.

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

0x68

0x013A

1

00

0

1001

0x01

0

0

0

0

0

0

63

62

61

60

59

58

57

56

55

54

53

52

51

50

49

48

 

마지막으로 ah 내용을 Descriptor4+7, Base Address 31-24비트 영역에 넣어 줍니다.

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

 

결과 Base address 영역에 값이 알맞게 나누어져 들어갔습니다.

Tss2 마찬가지로 base address 영역을 채워줄 있습니다.

 

(4) CPU Tss 세그먼트의 위치를 알립니다.

Tss 세그먼트 디스크립터의 위치를 CPU 알려야 사용할 있습니다. 그렇게 하기 위해 TR 레지스터에 TSS 디스크립터의 셀렉터를 넣어줍니다.

mov ax, TSS1Selector

ltr ax

 

Ltr 명령은 CPU TR 레지스터에 TSS 디스크립터 셀렉터 값을 넣는 명령입니다.

이로서 TSS 디스크립터의 설정이 끝났습니다.

신고

5. 예외(Exception)

예외는 프로그램이 프로그램 혹은 시스템에 문제를 일으킬만한 명령을 실행시킬 , CPU 문제 해결을 하기 위해 인터럽트를 일으키는 것입니다.

Protected mode에서 예외는 하드웨어, 인터럽트, 소프트웨어 인터럽트와 같은 IDT 사용합니다.

전체 예외의 종류는 다음과 같습니다.

# Exception Exception handler Signal

0 Divide error divide_error( ) SIGFPE

1 Debug debug( ) SIGTRAP

2 NMI nmi( ) None

3 Breakpoint int3( ) SIGTRAP

4 Overflow overflow( ) SIGSEGV

5 Bounds check bounds( ) SIGSEGV

6 Invalid opcode invalid_op( ) SIGILL

7 Device not available device_not_available( ) None

8 Double fault doublefault_fn( ) None

9 Coprocessor segment overrun coprocessor_segment_overrun( ) SIGFPE

10 Invalid TSS invalid_TSS( ) SIGSEGV

11 Segment not present segment_not_present( ) SIGBUS

12 Stack segment fault stack_segment( ) SIGBUS

13 General protection general_protection( ) SIGSEGV

14 Page Fault page_fault( ) SIGSEGV

15 Intel-reserved None None

16 Floating-point error coprocessor_error( ) SIGFPE

17 Alignment check alignment_check( ) SIGBUS

18 Machine check machine_check( ) None

19 SIMD floating point simd_coprocessor_error( ) SIGFPE

 

다음 코드는 첫번째 인터럽트 디스크립터에 divide error 처리하는 인터럽트 루틴에 관련된 디스크립터를 등록하는 것입니다.

mov edi, 0

lea esi, [idt_zero_divide]

mov cx, 8

rep movsb

 

다음 코드는 인터럽트 디스크립터를 작성한 코드입니다. Isr_zero_divide 인터럽트 서비스 루틴으로 설정하였습니다.

Idt_zero_divide:

dw isr_zero_divide

dw 0x08

db 0

db 0x8E

dw 0x0001

 

isr_zero_divide:

push gs

push fs

push es

push ds

pusad

pushfd

 

mov al, 0x20

out 0x20, al

 

mov ax, VideoSelector

move s, ax

move di, (80*6*2)

lea esi, [msg_isr_zero+divide]

call printf

 

jmp $

 

popfd

popad

pop ds

pop es

pop fs

pop gs

 

신고

3. 타이머 인터럽트 핸들러

타이머는 PIC의 IRQ 0번입니다. 그러나 PIC을 사용하기 위해서 그 전의 포스트에서 초기화 하는 과정에서 시작 번호를 0x20으로 주었으므로, 타이머 인터럽트가 발생하였을 경우 PIC은 CPU에게 0x20을 알릴 것입니다. 따라서 IRQ 0번에 연결된 타이머의 인터럽트를 처리하는 인터럽트 디스크립터는 0x20 번째 디스크립터에 기술 되어야 합니다.

Protected Mode로 넘어갈 때, 모든 인터럽트를 막는 것이 작업에 효율적이었습니다. 따라서 모든 인터럽트를 막았고, 타이머 인터럽트를 받아 들이기 위해서 0x20째 인터럽트를 다시 허용 해야 합니다.

 

lidt[idtr]

 

mov al, 0xFA

out 0x21, al

sti

jmp $

 

Idt를 등록한 뒤에, 타이머에 해당하는 IRQ 0번, 0번 비트를 0으로 클리어 한 뒤, 이것을 0x21 번 포트에 out 명령어로 내주면 해당 인터럽트가 유효하게 됩니다. 마스터 PIC의 데이터 부분의 주소인 0x21에 써줍니다.

7

6

5

4

3

2

1

0

1

리얼타임 클록

1

플로피디스크

컨트롤러

1

프린터

포트1

1

COM1

1

COM2

0

슬레이브

PC

1

키보드

0

타이머

 

Cpu가 PIC으로부터 인터럽트를 받아들이고 /INTA 신호를 돌려주기 위해 sti를 실행 시켜 줍니다.

 

idt_timer:

dw isr_32_timer ;

dw 0x08

db 0

db 0x8E

dw 0x0001

 

     

15

  

  

  

  

  

  

  

  

  

  

  

  

  

  

0

(1)핸들러의 오프셋 15 – 0 (isr_32_timer)

(2)핸들러의 코드 세그먼트 셀렉터 (0x08)

(3)P(1)

(4) DPL(00)

(5) 0

(6)D

(7)1

(8) 1

(9)0

사용안함 ( 0 )

(10) 핸들러의 오프셋 31 – 16(0x0001)

63

  

  

  

  

  

  

  

  

  

  

  

  

  

  

48

 

인터럽트 디스크립터를 보면 0x10000+isr_32_timer 물리 주소로 지정하고 있습니다. 인터럽트 서비스 루틴의 예는 다음과 같습니다.

 

isr_32_timer:

push gs

push fs

push es

push ds

pushad

pushfd

 

mov al, 0x20

out 0x20, al

mov ax, VideoSelector

mov es, ax

mov edi, (80*2*2)

lea esi, [msg_isr_32_timer]

call printf

inc byte [msg_isr_32_timer]

 

popfd

popad

pop ds

pop es

pop fs

pop gs

 

iret

 

핸들러로 들어오면 먼저 모든 레지스터와 EFLAGS를 스택에 저장해 놨다가, 인터럽트 루틴이 종료될 때, 다시 복귀시킵니다.

PIC을 리셋하고 iret 명령어를 실행시켜, 다시 인터럽트가 걸릴 수 있도록 합니다.

 

4. 키보드 인터럽트 핸들러

isr_33_keyboard:

pushad

push gs

push fs

push es

push ds

pushfd

 

in al, 0x60

 

mov al, 0x20

out 0x20, al

 

mov ax, videoSelector

mov es, ax

mov edi, (80*4*2)

lea esi, [msg_isr_33_keyboard]

call printf

inc byte [msg_isr_33_keyboard]

 

popfd

pop ds

pop es

pop fs

pop gs

popad

iret

 

키보드에서 어느 키가 눌렸는지 확인 하기 위해, in al, 0x60 명령어로, 키보드 버퍼에 있는 문자 스캔코드를 가져온 후, PIC을 리셋합니다.

 

신고


 

티스토리 툴바