|
ncloader
0.1
|
NCLoader 는 커널 쓰레드만 지원한다. 더 자세히 ...
프로세스(쓰레드)에 대한 협력 다이어그램:열거형 타입 | |
| enum | thread_state { THREAD_STATE_CREATED = 0xbeefBEEF, THREAD_STATE_RUNNING = 0x01, THREAD_STATE_SLEEP = 0x02, THREAD_STATE_ZOMBIE = 0x04, THREAD_STATE_DESTROYED = 0xDEADdead, THREAD_STATE_DESTROYING = 0x00DEAD00 } |
| [Stage 2 Thread State] 더 자세히 ... | |
| enum | thread_type { THREAD_TYPE_KERNEL = 0x00, THREAD_TYPE_USER = 0x03, THREAD_TYPE_MAX = 0xFF } |
| [Stage 2 Thread State] 더 자세히 ... | |
| enum | thread_attribute { THREAD_ATTR_JOINABLE = 0x00, THREAD_ATTR_DETACHED = 0x01, THREAD_ATTR_MAX = 0xFF } |
| Thread 속성 더 자세히 ... | |
함수 | |
| struct thread * | thread_create (void *(*e)(void *), void *arg) |
| Thread 를 생성한다. 더 자세히 ... | |
| void | thread_destroy (struct thread *handle) |
| 주어진 Thread 를 소멸 시킬 Thread 목록에 추가시킨다. 더 자세히 ... | |
| void | thread_set_state (struct thread *handle, enum thread_state state) |
| 주어진 thread 의 상태를 변경한다. 더 자세히 ... | |
| enum thread_state | thread_state (struct thread *handle) |
| 주어진 thread 의 상태를 확인한다. 더 자세히 ... | |
| void | thread_set_exit_value (struct thread *thread, void *ret) |
| void * | thread_exit_value (struct thread *thread) |
| void | thread_set_attribute (struct thread *tcb, enum thread_attribute attribute) |
| enum thread_attribute | thread_attribute (struct thread *thread) |
| void * | thread_context (struct thread *thread) |
| void | thread_set_context (struct thread *thread, void *context) |
| struct thread * | thread_prev (struct thread *thread) |
| struct thread * | thread_next (struct thread *thread, int circle) |
NCLoader 는 커널 쓰레드만 지원한다.
쓰레드 구현체
지금까지 stage1 에서 stage2 로 이동하면서 ldscript 사용법을 간략히 살펴 보았고, 메모리 관리자를 구현하기 위해서, 시스템에 설치된 메모리의 크기에 대한 정보를 얻어 오는 방법에 대해서 알아 보았다. 또한 Interrupt Descriptor Table 에 대해서도 알아 보았다.
이번에는 Thread 에 대해서 알아보기로 하자.
여기서 Thread 는 두 가지 종류를 고려해야 한다.
물론, 둘은 어면히 다른 concept 으로 배우고 또 그렇게 쓰고 있다. 하지만 우리가 만드는 Kernel 에서는 Thread 와 Task 에 대해 큰 차이를 두지 않으려고 한다. 앞으로 얘기할 User mode 에서의 thread 는 task 와 다른 개념으로 구현해 볼 것이지만, 적어도 Kernel 에서는 이 둘을 구분하지 않으려고 한다. (자료 구조상으로 어떤 것이 thread 이고 어떤 것이 task 인지는 구분 한다.)
구분을 하지 않겠다고 하는 이유는 Thread 를 구현하는 방법 때문이다.
Interrupt Service Routine(ISR) 을 구현하면서 이미 눈치 챈 독자들도 있을거라고 생각한다.
ISR 을 구현할 때, 우리가 주의 깊게 봤던 것 중에 하나가, CPU Context 를 유지하는 방법이었다. CPU Context 를 유지하기 위해서 Stack 을 이용 했었다.
즉, Interrupt 가 발생하면, 현재의 CPU Context 를 stack 에 저장해두고 ISR 를 처리한 후 다시 원래 Context 로 돌아가고 있었다. 그렇다면, 우리가 돌아가야 할 CPU Context 를 다른 것으로 바꾸면 어떨까? 그것이 바로 Context Switching 이다. 뭐 우리가 읽어 왔던 각종 전공 서적에서도 Context Switching 에 대해서 빠짐없이 설명하고 있다.
현재 CPU 를 점유한 채 동작하고 있는 현재의 CPU Context 를, Thread 라고 정의하자.
이런 Thread 를 여러개 만들었다고 가정하고, Interrupt 가 발생해서 ISR 을 처리한 후 돌아갈 때, 앞서 말 했던 여러개의 Thread 중에 하나를 선택해서 그 Thread 의 CPU Context 로 복구 시켜주는 것이다.
여기서 한 가지 좀 더 생각해야 할 것이 있다면, User mode 와 Kernel mode 에 대한 것이다.
x86 에서는 RING0 부터 RING3 까지 다양한 권한으로 CPU 를 동작시킬 수 있게 지원하고 있다.
RING0 는 CPU 의 모든 기능에 접근하고 제어할 수 있고, RING3 에서는 일부 기능들에 대해서는 접근하지 못하도록 보호 되고 있다. 또한 GDT 를 이용해, Segment 들에 대해서는 RING0 ~ RING3 까지 접근 가능한 메모리 영역을 구분할 수 있도록 지원해주고 있다.
앞서 Interrupt Service Routine 을 구현할 때, 살짝 언급 했던 것 중 Privilege Level 변경이 있을 때의 Stack frame 에 대한 얘기가 있었다.
RING3 (일반적으로 User mode 라고 한다.) 에서 동작 중이다가 Interrupt 가 발생해서 RING0(일반적으로 Kernel mode 라고 한다.) 로 Privilege level 이 변경될 때, RING3 에서의 Context 는 RING3 의 memory 영역에 저장해 두고, RING0 로 넘어오면서 RING0 에서 접근가능한 일부 메모리 영역을 stack 으로 사용하여 context 를 저장하게 된다.
CPU Register 를 포함해 동작에 반드시 필요한 중요한 정보들은 RING0 에서 접근 가능한 메모리 영역에서 관리 된다.
Thread 를 위한 CPU Context 를 저장하고 관리하기 위해서 Thread Control Block 을 아래와 같이 정의했다.
Thread 는 여러가지 동작 상태를 가지고 있다.
NCLoader 에서는 커널과 유저레벨간의 전환이 없는 경우만을 처리한다. (하지만, user level 을 위한 field 들도 구현은 해 둔다.)
각각의 Thread 구조체 자체를 위해 할당한 N bytes 자체를 해당 Thread 의 Stack 으로 사용되게 한다.
각 Architecture 가 제공하는 Stack pointer register 에 Thread 구조체의 임의의 Offset 를 가르키게 하고 해당 offset 부터 Stack 으로 사용되게 하며 나머지 부분을 Thread 를 위한 데이터들로 채우는 것이다.
이 방법은 Linux Kernel 에서 사용하고 있는 것을 차용한다. ([From] Understand Linux Kernel 1st edition) 이렇게 했을 때 얻을 수 있는 장점은 멀티 Processor 로 갔을 때, 각각의 CPU 들이 현재 실행시키고 있는 Thread 의 목록을 별도로 관리 하지 않아도 된다는 것이다.
각각의 CPU 의 Stack pointer 가 가리키는 주소를 기준으로 Thread structure 를 찾을 수 있기 때문이다.
다음은 Thread structure 에 대한 Pseudo code 이다.
Thread Control Block 은 Stack Size 크기로 할당되며 Page size 에 맞춰 Align 되어 있어야 한다. 예를 들어 x86 에서 Page 가 4KB 이면, 현재 CPU 의 ESP 의 값과 0xFFFFE000 을 AND 시키면, 4KB 단위로 Align 된 주소가 나오며 그 주소를 struct thread *로 casting 하여 현재 실행중인 Thread 의 control block 에 접근하게 된다.
또한, Scheduler 가 다음 Thread 를 선택 해야 할 때 Current thread 를 Stack pointer 에서 얻어온 후 control block 의 link 정보를 이용해 다음 Thread 를 찾는 방법으로 Context switching 을 구현할 수 도 있다.
쓰레드 자료구조 연결 리스트는 인터럽트 핸들러 안에서만 관리 하도록 해야 한다. 특히, 쓰레드 컨텍스트 스위칭을 할 때, 리스트에 변경이 생긴 상태가 되면, 다음 쓰레드를 선택할 때 문제가 될 수 있으므로, 목록에서 삭제하는 것은, 다음 쓰레드를 선택하는 시점에서 삭제하도록 하자.
| enum thread_attribute |
| enum thread_state |
[Stage 2 Thread State]
Thread 상태
| enum thread_type |
| enum thread_attribute thread_attribute | ( | struct thread * | thread) |
| void* thread_context | ( | struct thread * | thread) |
이 함수를 호출하는 함수들에 대한 그래프입니다.:| struct thread* thread_create | ( | void *(*)(void *) | e, |
| void * | arg | ||
| ) |
Thread 를 생성한다.
| [in] | e | Thread 의 메인 함수 |
| [in] | arg | Thread 인자 |
| NULL | Thread 생성에 실패 |
| thread | Thread 포인터 |
이 함수 내부에서 호출하는 함수들에 대한 그래프입니다.:
이 함수를 호출하는 함수들에 대한 그래프입니다.:| void thread_destroy | ( | struct thread * | handle) |
주어진 Thread 를 소멸 시킬 Thread 목록에 추가시킨다.
| [in] | handle | Thread 핸들러 |
< Should be collected
이 함수 내부에서 호출하는 함수들에 대한 그래프입니다.:
이 함수를 호출하는 함수들에 대한 그래프입니다.:| void* thread_exit_value | ( | struct thread * | thread) |
이 함수를 호출하는 함수들에 대한 그래프입니다.:| void thread_set_attribute | ( | struct thread * | tcb, |
| enum thread_attribute | attribute | ||
| ) |
이 함수를 호출하는 함수들에 대한 그래프입니다.:| void thread_set_context | ( | struct thread * | thread, |
| void * | context | ||
| ) |
이 함수를 호출하는 함수들에 대한 그래프입니다.:| void thread_set_exit_value | ( | struct thread * | thread, |
| void * | ret | ||
| ) |
이 함수를 호출하는 함수들에 대한 그래프입니다.:| void thread_set_state | ( | struct thread * | handle, |
| enum thread_state | state | ||
| ) |
주어진 thread 의 상태를 변경한다.
| [in] | handle | Thread 핸들러 |
| [in] | state | Thread 상태 |
이 함수를 호출하는 함수들에 대한 그래프입니다.:| enum thread_state thread_state | ( | struct thread * | handle) |
주어진 thread 의 상태를 확인한다.
| [in] | handle | Thread 핸들러 |
| THREAD_STATE_CREATED | 쓰레드가 생성 되었음 |
| THREAD_STATE_RUNNING | 쓰레드가 실행 중임 |
| THREAD_STATE_SLEEP | 쓰레드가 실행 목록에서 제외된 상태 |
| THREAD_STATE_ZOMBIE | 쓰레드가 종료 단계 있음 |
| THREAD_STATE_DESTROYED | 쓰레드가 종료 되었음 |