Embedded Lab/linux, x86

[TSS(Task State Segment)]

cyj4369 2012. 4. 4. 02:36

우선 예전 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를 보겠습니다.

348 struct tss_struct {
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. /include/asm-i386/processor.h의 tss_struct >

 구 조체 내에서는 가장 먼저 선언된 멤버가 가장 이전 번지를 나타낸다는 것만 주의하면 <코드1>의 tss_struct는 <그림1>의 TSS와 생김새가 같습니다. 리눅스의 tss_struct는 기존 TSS 이후에 몇가지 관련 정보(io_bitmap, io_bitmap_max, io_bitmap_owner, __cacheline_filler, stack)를 덧붙여 놓았습니다. 이들에 대해서는 여기서 언급하지 않겠습니다.

그럼 GDT에 설정하는 TSS 디스크립터의 생김새를 보겠습니다.


사용자 삽입 이미지
< 그림 2. TSS 디스크립터 >
  • AVL     : 시스템 소프트웨어에서 사용됨
  • B         : Busy 플래그
  • BASE   : 세그먼트 시작 주소
  • DPL     : 권한 레벨
  • G        : 세그먼트의 크기 단위 지정(바이트, 4K) 플래그
  • LIMIT   : 세그먼트 크기
  • P         : 세그먼트의 메모리상에 존재 여부
  • TYPE    : 세그먼트 타입

 GDT에서 TSS 디스크립터를 참조하여 TSS 영역에 접근합니다. Task Register(TR)에 LTR 명령어를 써서 GDT의 TSS 디스크립터의 인덱스를 담아 둡니다. 이렇게 하면 현재 실행 중인 태스크가 스위칭이 될때 태스크의 상태를 이 TSS 영역에 저장 됩니다.

사용자 삽입 이미지
< 그림 3. TSS 영역 접근 >

  하지만 리눅스 커널에서는 x86에서 지원하는 하드웨어 태스크 스위칭을 사용하지 않습니다. 리눅스 커널에서는 CPU마다 하나의 TSS 영역을 가지고 있습니다. 하드웨어 태스크 스위칭에 사용하지는 않지만 다음과 같은 두가지 용도로 쓰기위함 입니다.

  1. x86 아키텍처에서 유저 모드에서 커널 모드로 전환할 때 TSS의 커널 모드 스택 주소를 가져옵니다.
  2. 유저 모드 프로세스가 in 이나 out 명령어를 사용하여 입출력 포트에 접근 할 때 CPU는 접근 권한 체크를 TSS에 들어 있는 I/O 허가 비트맵을 이용하려고 접근 할 수도 있습니다.
다음에는 64bit 롱 모드에서 TSS를 어떻게 관리 하는지 알아 보겠습니다.

< 참고자료 >
  • 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 커널의 구조와 원리(김범준 저)