[TSS(Task State Segment)]
우선 예전 32bit x86 시스템에서의 TSS(Task State Segment)에 대해 알아 보겠습니다.
대부분의 최신
운영체제는 선점형 태스크 스위칭을 지원합니다. 선점형 태스크 스위칭은 태스크의 작업이 완료되지 않아도, CPU를 선점 할 수
있는것을 말합니다. 그러기 위해서는 태스트 스위칭이 일어날 때, 기존에 실행중인 태스크의 상태를 보존하고 있다가, 이후에 CPU를
다시 선점하게 되었을 때 보존된 태스크의 상태를 복귀 할 필요가 있습니다. 이때 RAM상에 태스크의 상태를 보존 할 영역, 즉
모든 레지스터 값들이 보존될 영역을 TSS(Task State Segment)라고 합니다.
그림 x86 아키텍처에서 TSS의 생김새를 보겠습니다.
< 그림1. x86 아키텍처 TSS >
x86 아키텍처 TSS는 <그림1>과 같이 거의 모든 레지스터들을 저장할 수 있습니다. 각 태스크가 사용하는 모든 레지스터를 포함하도록 되어있습니다.
- 범용 레지스터 필드들
EAX, EBX, ECX, EDX, ESP, EBP, ESI, EDI등 태스크 스위칭 이전의 범용 레지스터들의 값들이 저장되어 있습니다.
- 세그먼트 셀렉터 레지스터 필드들
ES, CS, SS, DS, FS, GS등 태스크 스위칭 이전의 세그먼트 셀렉터 레지스터의 값들이 저장되어 있습니다.
- EFLAGS 레지스터 필드
테스크 스위칭 이전의 EFLAGS 레지스터의 상태를 저장하고 있습니다.
- EIP 필드
태스크 스위칭 이전의 EIP가 들어있습니다. 즉, 다음에 이 태스크가 CPU를 다시 선점하였을때 수행 해야하는 코드의 주소가 들어있습니다.
- 이전 태스크 링크 필드
이전 태스크의
TSS 세그먼트 셀렉터 값이 들어있습니다. TSS 영역은 GDT에 있는 TSS 디스크립터에서 지정 합니다. 이 디스크립터를
셀렉터로 사용하여 JMP, CALL 명령어로 태스크 스위칭을 합니다. 이 필드는 CALL 명령어로 태스크 스위칭을 할때, 다음
태스크는 자신의 TSS 영역에 이전 태스크의 TSS 디스크립터의 셀럭터 값을 이 필드에 저장해 둡니다. 그리고 자신이 실행을
IRET로 마칠때, CPU는 현재 태스크 TSS 영역의 '이전 태스크 링크' 필드를 참조하여 이전 태스크로 되돌아 갑니다.
- LDT 세크먼드 셀렉터 필드
태스크의 LDT(Local Descriptor Table)의 세그먼트 셀렉터 값이 들어있습니다.
- CR3 컨트롤 레지스터 필드
3 단계 페이징을
할때, 첫번째 페이징 단계에서 참조하는 Page Directory의 물리 주소를 담고 있는 CR3 레지스터 값이 들어 있습니다.
CR3 레지스터는 Page Directory Base Register(PDBR)이라고도 합니다.
- 권한 레벨 0,1,2 스택 포인터(ESP0, SS0,...) 필드
CPU 에서 지원하는
4가지 권한 레벨(0,1,2,3)별로 스택을 담고 있습니다. 이렇게 권한 레벨별로 스택을 따로 둠으로서 상위 레벨의 자료를 보호
할 수 있습니다. 리눅스에서 유저모드는 권한 레벨 3을 사용하고, 커널모드는 권한 레벨 0을 사용합니다. 권한 레벨 3의 스택은
ESP, SS 필드에 저장 됩니다.
- T(Debug Trap) 플래그
태스크를 디버깅할 때
한 스탭씩 진행(Step Into)하며 프로그램의 동작을 확인해야 할 경우가 있습니다. 이때 사용되는 것이 플래그 레지스터의 T
플래그를 1로 셋합니다. 태스크 스위칭 이전의 태스크의 플래그 레지스터 T 플래그 값이 들어 있습니다.
- I/O 맵 기본 주소 필드
in, out 명령어를 사용하여 CPU의 port를 접근하는 것은 제한이 필요 합니다. 예를 들어, 권한 레벨3인 유저모드 태스크의 경우에는 많은 제약이 가해져야 합니다. 이런 port별 접근 권한을 비트맵으로 표시하게 됩니다. 이런 I/O 허가 비트맵이 있는 주소가 이 필드에 들어 있습니다.
그럼 리눅스 커널 소스상에서 구현된 x86 아키텍처 TSS를 보겠습니다.
349 unsigned short back_link,__blh;
350 unsigned long esp0;
351 unsigned short ss0,__ss0h;
352 unsigned long esp1;
353 unsigned short ss1,__ss1h; /* ss1 is used to cache MSR_IA32_SYSENTER_CS */
354 unsigned long esp2;
355 unsigned short ss2,__ss2h;
356 unsigned long __cr3;
357 unsigned long eip;
358 unsigned long eflags;
359 unsigned long eax,ecx,edx,ebx;
360 unsigned long esp;
361 unsigned long ebp;
362 unsigned long esi;
363 unsigned long edi;
364 unsigned short es, __esh;
365 unsigned short cs, __csh;
366 unsigned short ss, __ssh;
367 unsigned short ds, __dsh;
368 unsigned short fs, __fsh;
369 unsigned short gs, __gsh;
370 unsigned short ldt, __ldth;
371 unsigned short trace, io_bitmap_base;
372 /*
373 * The extra 1 is there because the CPU will access an
374 * additional byte beyond the end of the IO permission
375 * bitmap. The extra byte must be all 1 bits, and must
376 * be within the limit.
377 */
378 unsigned long io_bitmap[IO_BITMAP_LONGS + 1];
379 /*
380 * Cache the current maximum and the last task that used the bitmap:
381 */
382 unsigned long io_bitmap_max;
383 struct thread_struct *io_bitmap_owner;
384 /*
385 * pads the TSS to be cacheline-aligned (size is 0x100)
386 */
387 unsigned long __cacheline_filler[35];
388 /*
389 * .. and then another 0x100 bytes for emergency kernel stack
390 */
391 unsigned long stack[64];
392 } __attribute__((packed));
구
조체 내에서는 가장 먼저 선언된 멤버가 가장 이전 번지를 나타낸다는 것만 주의하면 <코드1>의 tss_struct는
<그림1>의 TSS와 생김새가 같습니다. 리눅스의 tss_struct는 기존 TSS 이후에 몇가지 관련
정보(io_bitmap, io_bitmap_max, io_bitmap_owner, __cacheline_filler,
stack)를 덧붙여 놓았습니다. 이들에 대해서는 여기서 언급하지 않겠습니다.
그럼 GDT에 설정하는 TSS 디스크립터의 생김새를 보겠습니다.
- AVL : 시스템 소프트웨어에서 사용됨
- B : Busy 플래그
- BASE : 세그먼트 시작 주소
- DPL : 권한 레벨
- G : 세그먼트의 크기 단위 지정(바이트, 4K) 플래그
- LIMIT : 세그먼트 크기
- P : 세그먼트의 메모리상에 존재 여부
- TYPE : 세그먼트 타입
GDT에서 TSS 디스크립터를 참조하여 TSS 영역에 접근합니다. Task Register(TR)에 LTR 명령어를 써서 GDT의 TSS 디스크립터의 인덱스를 담아 둡니다. 이렇게 하면 현재 실행 중인 태스크가 스위칭이 될때 태스크의 상태를 이 TSS 영역에 저장 됩니다.
- x86 아키텍처에서 유저 모드에서 커널 모드로 전환할 때 TSS의 커널 모드 스택 주소를 가져옵니다.
- 유저 모드 프로세스가 in 이나 out 명령어를 사용하여 입출력 포트에 접근 할 때 CPU는 접근 권한 체크를 TSS에 들어 있는 I/O 허가 비트맵을 이용하려고 접근 할 수도 있습니다.
< 참고자료 >
- Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 3A: System Programming Guide, Part 1
- AMD64 Architecture Programmer’s Manual Volume 2: System Programming
- 리눅스 커널의 이해 3판
- 만들면서 배우는 OS 커널의 구조와 원리(김범준 저)