-
TSS프로젝트/운영체제 만들기 2023. 7. 6. 00:26
글의 참고
- 64-ia-32-architectures-software-developer-vol-3a-part-1-manual.pdf
글의 전제
- 밑줄로 작성된 글은 강조 표시를 의미한다.
- 그림 출처는 항시 그림 아래에 표시했다.
글의 내용
- 태스크 매니지먼트 개요 ( 7.1 )
A task is a unit of work that a processor can dispatch, execute, and suspend. It can be used to execute a program, a task or process, an operating-system service utility, an interrupt or exception handler, or a kernel or executive utility.
The IA-32 architecture provides a mechanism for saving the state of a task, for dispatching tasks for execution, and for switching from one task to another. When operating in protected mode, all processor execution takes place from within a task. Even simple systems must define at least one task. More complex systems can use the processor’s task management facilities to support multitasking applications.
...
- 참고 :
64-ia-32-architectures-software-developer-vol-3a-part-1-manual.pdf- 태스크 구조 ( 7.1.1 )
: x86 태스크라는 하드웨어적인 부분과 소프트웨어적인 부분이 있다. 여기서 다루는 태스크는 하드웨어적인 태스크를 의미한다. x86에서 태스크는 TSS로 구별된다. 예를 들어, 시스템에 서로 다른 3개의 TSS가 존재한다고 치자. 그렇면, 서로 다른 태스크가 3개 존재한다고 볼 수 있다. 태스크가 실행되기 위해 프로세서에 로드되면, `세그먼트 셀렉터, 베이스 어드레스, 리밋, 세그먼트 디스크립터 속성`등이 TR로 된다. 만약, 태스크가 페이징을 사용하고 있다면 태스크가 사용하는 페이지 디렉토리의 베이스 어드레스가 CR3에 된다.
A task is made up of two parts: a task execution space and a task-state segment (TSS). The task execution space consists of a code segment, a stack segment, and one or more data segments (see Figure 7-1). If an operating system or executive uses the processor’s privilege-level protection mechanism, the task execution space also provides a separate stack for each privilege level.
The TSS specifies the segments that make up the task execution space and provides a storage place for task state information. In multitasking systems, the TSS also provides a mechanism for linking tasks.
A task is identified by the segment selector for its TSS. When a task is loaded into the processor for execution, the segment selector, base address, limit, and segment descriptor attributes for the TSS are loaded into the task register (see Section 2.4.4, “Task Register (TR)”).
If paging is implemented for the task, the base address of the page directory used by the task is loaded into control register CR3.
...
- 참고 : 64-ia-32-architectures-software-developer-vol-3a-part-1-manual.pdf: 위에서 `When task is loaded into the processor ...` 에서 `task`는 무슨의미일까? `7.1.3 Executing a Task`에 자세히 나와있다. 프로세서에 태스크가 로드된다는 것은 새로운 `컨택스트`가 로드된다는 말과 같다. 즉, 새로운 레지스터, 스택, 페이지 테이블 등이 프로세서에 로드되는 것이다. 그러면, 프로세서는 새로운 태스크가 가리키는 EIP로 실행을 전환한다. 즉, `태스크`와 `컨택스트`는 완전히 동일한 의미로 보면 된다.
...
When a task is dispatched for execution, a task switch occurs between the currently running task and the dispatched task. During a task switch, the execution environment of the currently executing task (called the task’s state or context) is saved in its TSS and execution of the task is suspended. The context for the dispatched task is then loaded into the processor and execution of that task begins with the instruction pointed to by the newly loaded EIP register. If the task has not been run since the system was last initialized, the EIP will point to the first instruction of the task’s code; otherwise, it will point to the next instruction after the last instruction that the task executed when it was last active.
...
- 참고 : 64-ia-32-architectures-software-developer-vol-3a-part-1-manual.pdf- 태스크 상태 ( 7.1.2 )
: 새로운 태스크가 실행되기 전에, 이전 태스크의 아래 항목들이 TSS에 저장된다.
The following items define the state of the currently executing task:
• The task’s current execution space, defined by the segment selectors in the segment registers (CS, DS, SS, ES, FS, and GS).
• The state of the general-purpose registers.
• The state of the EFLAGS register.
• The state of the EIP register.
• The state of control register CR3.
• The state of the task register.
• The state of the LDTR register.
• The I/O map base address and I/O map (contained in the TSS).
• Stack pointers to the privilege 0, 1, and 2 stacks (contained in the TSS).
• Link to previously executed task (contained in the TSS).
Prior to dispatching a task, all of these items are contained in the task’s TSS, except the state of the task register. Also, the complete contents of the LDTR register are not contained in the TSS, only the segment selector for the LDT.
- 참고 : 64-ia-32-architectures-software-developer-vol-3a-part-1-manual.pdf- 태스크 매니지먼트 자료 구조 ( 7.2 )
: x86 기반 운영체제를 만드는데 있어서, TSS가 반드시 필요한 것은 아니다. 대개 권한 변경 시에, `스택 스위칭`용으로 사용되지만, 유저 레벨 프로그램을 구현하지 않는다면 구현하지 않아도 된다. TSS는 GDT에 저장되므로, LR에 로딩되는 값은 GDT에 작성한 TSS 인덱스값, 즉, 세그먼트 셀렉터를 로딩해야 한다. 참고로, TSS는 대개 커널 코드 및 데이터 디스크립터, 유저 코드 및 데이터 디스크립터 이후에 배치되기 때문에, GDT에서 최소 `0x28` 이후에 위치한다.
The processor defines five data structures for handling task-related activities:
• Task-state segment (TSS).
• Task-gate descriptor.
• TSS descriptor.
• Task register.
• NT flag in the EFLAGS register.
When operating in protected mode, a TSS and TSS descriptor must be created for at least one task, and the segment selector for the TSS must be loaded into the task register (using the `LTR instruction`).
- 참고 : 64-ia-32-architectures-software-developer-vol-3a-part-1-manual.pdf- 태스크-상태 세그먼트(TSS) ( 7.2.1 )
: 태스크 스위치가 발생할 때, 프로세서는 자동으로 현재 동작중이었던 태스크의 정보를 TSS의 동적 필드에 업데이트한다. 그리고 태스크가 새로 생성되었을 때(TSS가 새로 등록될 때), 정적 필드들을 읽는다. 정적 필드들은 읽기만 하고 바꾸지는 않는다. 그런데, 현대 운영체제에서 프로세스 컨택스트 스위칭을 하면 레지스터들만 바뀌는게 아니라, `페이지 테이블`, `스택`, `파일 디스크립터`등 많은 정보들이 새로운 프로세스의 것으로 대체된다.
The processor state information needed to restore a task is saved in a system segment called the task-state segment (TSS). Figure 7-2 shows the format of a TSS for tasks designed for 32-bit CPUs. The fields of a TSS are divided into two main categories: dynamic fields and static fields.
...
...
The processor updates dynamic fields when a task is suspended during a task switch. The following are dynamic fields:
• General-purpose register fields — State of the EAX, ECX, EDX, EBX, ESP, EBP, ESI, and EDI registers prior to the task switch.
• Segment selector fields — Segment selectors stored in the ES, CS, SS, DS, FS, and GS registers prior to the task switch.
• EFLAGS register field — State of the EFAGS register prior to the task switch.
• EIP (instruction pointer) field — State of the EIP register prior to the task switch.
• Previous task link field — Contains the segment selector for the TSS of the previous task (updated on a task switch that was initiated by a call, interrupt, or exception). This field (which is sometimes called the back link field) permits a task switch back to the previous task by using the IRET instruction
...
...
The processor reads the static fields, but does not normally change them. These fields are set up when a task is created. The following are static fields:
• LDT segment selector field — Contains the segment selector for the task's LDT.
• CR3 control register field — Contains the base physical address of the page directory to be used by the task. Control register CR3 is also known as the page-directory base register (PDBR).
• Privilege level-0, -1, and -2 stack pointer fields — These stack pointers consist of a logical address made up of the segment selector for the stack segment (SS0, SS1, and SS2) and an offset into the stack (ESP0, ESP1, and ESP2). Note that the values in these fields are static for a particular task; whereas, the SS and ESP values will change if stack switching occurs within the task.
• T (debug trap) flag (byte 100, bit 0) — When set, the T flag causes the processor to raise a debug exception when a task switch to this task occurs (see Section 17.3.1.5, “Task-Switch Exception Condition”).
• I/O map base address field — Contains a 16-bit offset from the base of the TSS to the I/O permission bit map and interrupt redirection bitmap. When present, these maps are stored in the TSS at higher addresses. The I/O map base address points to the beginning of the I/O permission bit map and the end of the interrupt redirection bit map. See Chapter 18, “Input/Output,” in the Intel® 64 and IA-32 Architectures Software Developer’s Manual, Volume 1, for more information about the I/O permission bit map. See Section 20.3, “Interrupt and Exception Handling in Virtual-8086 Mode,” for a detailed description of the interrupt redirection bit map.
...
...
- 참고 : 64-ia-32-architectures-software-developer-vol-3a-part-1-manual.pdf: 그런데, 현대 운영체제는 TSS를 사용하지 않는다. 그러나, x86 기반이라면 사용할 수 밖에 없는 환경이 있다. 바로 `스택 스위칭`시에 무조건 TSS가 사용된다. 그래서 한 개의 TSS는 선언하는 것이 좋다(유저 레벨을 구현한다면 필요). 그러면 선언만 하고 아무것도 안해도 될까? 안된다. 최소 SS0, ESP0은 설정해야 한다. 앞에서 언급했다시피, TSS는 `스택 스위칭`시에 사용되기 때문에, 반드시 스택 포인터(SS0, ESP0)를 설정해놔야 한다. 위의 인텔 문서에서 볼 수 있다시피 스택 포인터는 정적 필드다. 즉, TSS 생성 시에 한 번 설정되면 변경을 하지 않는다. 그런데, 인터럽트 및 익셉션이 발생했는데, 스택 스위칭이 발생했다고 치자. 이 때, TSS의 SS0, ESP0을 참고하게 되는데 계속 동일한 SS0, ESP0이면 어떻게 될까? 보안적으로 큰 이슈가 생긴다. 이 문제의 근본적인 이유는 TSS 하나를 모든 프로세스가 공유하기 때문이다. 그렇면, TSS를 여러 개 만들면 되는가? 그럴 것 같지만, 실제로 리눅스는 딱 한 개만 전역적으로 사용하고 있다. 왜, 한 개만 사용할까? 2가지 이유가 있다.
0" 퍼포먼스 - TSS는 아래에서 보다시피 많은 정보들을 저장할 수 있도록 구현되어 있다. 운영체제마다 컨택스트 스위칭시에 필요한 정보가 다를 수 있다. 그런데, TSS는 컨택스트 스위칭 발생 시, 동적 필드에 있는 모든 정보들을 현재 프로세스에서 가져와 저장한다. 이러한 부분이 소프트웨어적인 컨택스트 스위칭보다 퍼포먼스가 좋지 않다고 한다.
1" 호환성 - TSS는 x86 에만 존재하는 기능이다. 리눅스와 같은 범용 운영체제는 아키텍처 의존적인 코드를 좋아하지 않는다. 세그먼테이션 기법이 쓰이지 않는 가장 큰 이유가 외부화 문제이긴 하지만, 만만치 않는 이유로 호환성이 지켜지지 부분도 있다.: 위와 같은 이유로 TSS는 하나만 사용한다. 그런데, 아직 `동일한 SS0, ESP0를 모든 프로세스가 공유`에 대한 설명을 하지 않았다. 이 문제는 프로세스 컨택스트 스위칭이 발생하면, 기존(이전)의 TSS의 SS0, ESP0을 현재 프로세스의 SS0, ESP0으로 설정한다. 그리고 TR에 수정된 TSS를 새롭게 로드한다. 기존에 이미 로드된 TSS의 값을 수정하고 다시 TR에 로드한다? TSS는 이미 로딩이 되어있으니, 수정만 하면 안될까? 안된다. x86에서 시스템 자료구조들은 굉장히 비번하게 사용되기 때문에, 로딩되는 시점에 별도의 캐시에 저장된다. 그래서 TSS가 TR에 로드되면 캐시에 복사된다. 이미 로드된 TSS를 수정한다고 해서 캐시에 복사된 TSS의 내용이 수정되지는 않는다. 그러므로, TSS를 수정하면 반드시 TR에 재로딩해야 한다.
- TSS 디스크립터 ( 7.2.2 )
: `G` 플래그가 1일 경우, TSS의 길이는 67h 보다 크거나 같아야 한다고 한다. 이건 TSS의 길이를 의미한다. TSS의 길이는 68h이다. 그런데, 인텔은 시스템 자료 구조를 로딩할 때 길이를 명시하도록 하는데 이 때, 실제 길이가 아닌 마지막 바이트를 입력하라고 한다. GDT 같은 경우도 실제 길이에 `-1` 값을 명시하도록 한다. TSS도 마찬가지다.
The TSS, like all other segments, is defined by a segment descriptor. Figure 7-3 shows the format of a TSS descriptor. TSS descriptors may only be placed in the GDT; they cannot be placed in an LDT or the IDT.
...
...
An attempt to access a TSS using a segment selector with its TI flag set (which indicates the current LDT) causes a general-protection exception (#GP) to be generated during CALLs and JMPs;
...
The busy flag (B) in the type field indicates whether the task is busy. A busy task is currently running or suspended. A type field with a value of 1001B indicates an inactive task; a value of 1011B indicates a busy task. Tasks are not recursive. The processor uses the busy flag to detect an attempt to call a task whose execution has been interrupted. To insure that there is only one busy flag is associated with a task, each TSS should have only one TSS descriptor that points to it.
...
When the G flag is 0 in a TSS descriptor for a 32- bit TSS, the limit field must have a value equal to or greater than 67H,
...
The processor does not check for a limit greater than 67H on a task switch; however, it does check when accessing the I/O permission bit map or interrupt redirection bit map.
....
In most systems, the DPLs of TSS descriptors are set to values less than 3, so that only privileged software can perform task switching. However, in multitasking applications, DPLs for some TSS descriptors may be set to 3 to allow task switching at the application (or user) privilege level.- 태스크-게이트 디스크립터 ( 7.2.5 )
: 필수적인 부분은 아니다. 인터럽트 및 익셉션이 발생했을 때, 태스크 전환에 관한 내용이다. 태스크 게이트 디스크립터의 핵심은 `간접`이라는 부분에 있다. 결국 태스크 게이트 디스크립터는 다시 GDT의 TSS 디스크립터를 참조하게 된다.
A task-gate descriptor provides an indirect, protected reference to a task (see Figure 7-6). It can be placed in the GDT, an LDT, or the IDT. The TSS segment selector field in a task-gate descriptor points to a TSS descriptor in the GDT. The RPL in this segment selector is not used.
The DPL of a task-gate descriptor controls access to the TSS descriptor during a task switch. When a program or procedure makes a call or jump to a task through a task gate, the CPL and the RPL field of the gate selector pointing to the task gate must be less than or equal to the DPL of the task-gate descriptor. Note that when a task gate is used, the DPL of the destination TSS descriptor is not used.: 태스크 게이트 디스크립터와 TSS 디스크립터의 TSS 참조 과정은 아래와 같다. 아래를 보면, 알겠지만, `권한`과 `구조의 재사용`을 엿볼 수 있다.
1" 태스크 게이트 디스크립터 : 태스크 게이트 디스크립터 - TSS 디스크립터 - TSS
2" TSS 디스크립터 : TSS 디스크립터 - TSS: 먼저 `권한`에 관한 부분에서보면 `태스크 게이트` 또한 DPL 필드를 가지고 있다. 그렇기 때문에, 현재 접근해 오는 태스크의 RPL과 CPL이 반드시 해당 `태스크 게이트`의 DPL 필드값보다 작거나 같아야 한다. 이 과정을 통과하면 `TSS Segment Seclector` 값을 통해서 GDT에 있는 `TSS 디스크립터`에 접근할 수 있다. 주의할 건, `TSS 디스크립터`에 액세스할 때는 앞에서 이미 권한 검사를 했기 때문에 스킵한다.
: 위에서 말한 `구조의 재사용`을 아래 그림을 통해 확인할 수 있다. 잘 이해가 가지 않는다면, 위에서 `Figure 7-5 Task Register`를 보고오자.
- 태스크 레지스터(TR) (2.4.4)
The LTR and STR instructions load and store the segment selector part of the task register, respectively. When the LTR instruction loads a segment selector in the task register, the base address, limit, and descriptor attributes from the TSS descriptor are automatically loaded into the task register. On power up or reset of the processor, the base address is set to the default value of 0 and the limit is set to 0FFFFH.
...
...
When a task switch occurs, the task register is automatically loaded with the segment selector and descriptor for the TSS for the new task. The contents of the task register are not automatically saved prior to writing the new TSS information into the register.: LTR(Load Task Register)를 통해서 GDT안에 있는 태스크 디스크립터(세그먼트 디스크립터)의 주소를 태스크 레지스터에 로드해야 한다. 그러면, 태스크 레지스터에 로드된 태스크 디스크립터의 베이스 어드레스, 리밋, 속성 등의 값들이 자동으로 태스크 레지스터의 보이지 않는 영역(Invisible Part)에 자동으로 로드된다. 태스크 레지스터에 보이는 영역(Visible Part)에는 태스크 디스크립터의 인덱스가 들어간다.
: 아래 볼 수 있다시피, 태스크 레지스터는 `Visible Part`와 `Invisible Part`로 나뉜다.
The task register holds the 16-bit segment selector and the entire segment descriptor (32-bit base address (64 bits in IA-32e mode), 16-bit segment limit, and descriptor attributes) for the TSS of the current task (see Figure 2-6).
- 프로세서는 태스크 스위치를 어떻게 인식 ?
: 인텔 공식 문서 `7.3 TASK SWITCHING`에 그에 대한 부분이 나와있다. 4가지 경우에 프로세서는 태스크 스위치를 진행한다고 한다.
The processor transfers execution to another task in one of four cases:
• The current program, task, or procedure executes a JMP or CALL instruction to a TSS descriptor in the GDT.
• The current program, task, or procedure executes a JMP or CALL instruction to a task-gate descriptor in the GDT or the current LDT.
• An interrupt or exception vector points to a task-gate descriptor in the IDT.
• The current task executes an IRET when the NT flag in the EFLAGS register is set.
JMP, CALL, and IRET instructions, as well as interrupts and exceptions, are all mechanisms for redirecting a program. The referencing of a TSS descriptor or a task gate (when calling or jumping to a task) or the state of the NT flag (when executing an IRET instruction) determines whether a task switch occurs.
- 참고 : 64-ia-32-architectures-software-developer-vol-3a-part-1-manual.pdf: 현대 운영체제에서 위의 4개가 케이스가 발생하는 경우가 있을까? `1, 2, 3`번은 직접적으로 TASK 관련 자료 구조에 접근해야 한다. 이럴 경우는 없다고 보면 된다. 그러면, 4번은? 4번은 앞에 `1, 2, 3`번 중에 하나가 발생한 상황에서 IRET이 발생한 경우라고 생각하면 된다. `1, 2, 3`이 발생하지 않는다고 했는데, 과연 4번이 일어날까? 즉, x86 기반의 현대 운영체제에서는 하드웨어 수준에서 지원하는 `프로세서의 태스크 스위칭`이 발생하지 않는다. 태스크 스위칭에 대한 더 구체적인 내용은 `7.3 TASK SWITCHING`을 참고하면 된다.
: 여기서 `EFLAGS.NT` 플래그는 현재 태스크가 `중첩` 태스크인지를 나타낸다. 프로그래밍에서 `중첩`, 즉, `Nested`는 `함수내에서 또 다른 함수를 호출` 이라고 생각하면 좋을 거 같다. 다시 돌아와서 `EFLAGS.NT = 1`의 의미는 현재 태스크가 중첩 태스크임을 나타낸다.
The previous task link field of the TSS (sometimes called the “backlink”) and the NT flag in the EFLAGS register are used to return execution to the previous task. EFLAGS.NT = 1 indicates that the currently executing task is nested within the execution of another task.
- 참고 : 64-ia-32-architectures-software-developer-vol-3a-part-1-manual.pdf [ 7.4 TASK LINKING ]: `CALL`, `인터럽트 혹 익셉션`, `IRET` 은 `EFLAGS.NT`를 SET하고, 새롭게 로딩되는 TSS의 `previous task link`에 현재(이전) TSS의 세그먼테 셀렉터를 저장한다. 이전 태스크로 복귀하기 위해서는 `previous task link`의 값을 TR에 로드하면서 복귀하게 된다. 아래 그림에서 볼 수 있다시피, 어떻게 보면 `EFLAGS.NT = 0` 인 프로세스가 루트 태스크(?)가 될 것이다.
`JMP`는 명령어는 IP를 변경하기만 하고 복구 메커니즘이 존재하지 않는다. 그래서, `EFLAGS.NT` 플래그의 값을 SET 하지 않는다. NT 비트를 SET 하는 경우는 복귀하는 경우에만 SET 한다.
The previous task link field of the TSS (sometimes called the “backlink”) and the NT flag in the EFLAGS register are used to return execution to the previous task. `EFLAGS.NT = 1` indicates that the currently executing task is nested within the execution of another task.
When a CALL instruction, an interrupt, or an exception causes a task switch: the processor copies the segment selector for the current TSS to the previous task link field of the TSS for the new task; it then sets EFLAGS.NT = 1. If software uses an IRET instruction to suspend the new task, the processor checks for EFLAGS.NT = 1; it then uses the value in the previous task link field to return to the previous task. See Figures 7-8.
When a JMP instruction causes a task switch, the new task is not nested. The previous task link field is not used and EFLAGS.NT = 0. Use a JMP instruction to dispatch a new task when nesting is not desired.'프로젝트 > 운영체제 만들기' 카테고리의 다른 글
xv6 - Init process (0) 2023.07.12 [xv6] Page tables 상세 분석 1 (0) 2023.07.12 [x86] Unreal mode (0) 2023.06.28 [x86] 멀티 부트 (0) 2023.06.27 BOCHS (0) 2023.06.24