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 threadthread_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 threadthread_prev (struct thread *thread)
 
struct threadthread_next (struct thread *thread, int circle)
 

상세한 설명

NCLoader 는 커널 쓰레드만 지원한다.

쓰레드 구현체

날짜
2011-8-22
작성자
Sung-jae Park nices.nosp@m.j@ni.nosp@m.cesj..nosp@m.com

지금까지 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 을 아래와 같이 정의했다.

struct thread {
struct thread *parent;
struct thread *next;
struct thread *prev;
void *argument;
void *return_value;
void *ctx;
char stack[];
};

Thread 는 여러가지 동작 상태를 가지고 있다.

작성자
Sung-jae Park nices.nosp@m.j@ni.nosp@m.cesj..nosp@m.com
날짜
2011-8-1

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 이다.

struct thread {
struct thread *parent;
struct thread *next;
struct thread *prev;
void *argument;
void *return_value;
void *ctx;
char stack[];
};

Thread Control Block 은 Stack Size 크기로 할당되며 Page size 에 맞춰 Align 되어 있어야 한다. 예를 들어 x86 에서 Page 가 4KB 이면, 현재 CPU 의 ESP 의 값과 0xFFFFE000 을 AND 시키면, 4KB 단위로 Align 된 주소가 나오며 그 주소를 struct thread *로 casting 하여 현재 실행중인 Thread 의 control block 에 접근하게 된다.

주의
여기서 항상 염두에 두고 있어야 할 것은, Thread Control Block 이 저장되어 있는 Stack 은 Kernel mode 에서의 Stack 이라는 점이다. 필자는 간혹 User mode stack 과 Kernel mode stack 을 혼돈한 적이 있었다.

또한, Scheduler 가 다음 Thread 를 선택 해야 할 때 Current thread 를 Stack pointer 에서 얻어온 후 control block 의 link 정보를 이용해 다음 Thread 를 찾는 방법으로 Context switching 을 구현할 수 도 있다.

static void __NORET cleanup_thread(void)
{
struct thread *thread;
void *ret;
register unsigned long eax __asm__("eax");
ret = (void *)eax;
thread = arch_current_thread();
ASSERT(thread && "Invalid thread for cleanup_thread");
thread_set_exit_value(thread, ret);
while (1);
}

쓰레드 자료구조 연결 리스트는 인터럽트 핸들러 안에서만 관리 하도록 해야 한다. 특히, 쓰레드 컨텍스트 스위칭을 할 때, 리스트에 변경이 생긴 상태가 되면, 다음 쓰레드를 선택할 때 문제가 될 수 있으므로, 목록에서 삭제하는 것은, 다음 쓰레드를 선택하는 시점에서 삭제하도록 하자.

열거형 타입 문서화

Thread 속성

열거형 멤버
THREAD_ATTR_JOINABLE 

Parent Thread 가 현재 Thread 의 Resource 를 관리해 줘야 하는 경우

THREAD_ATTR_DETACHED 

System 이 현재 Thread 의 Resource 를 관리해 줘야 하는 경우

THREAD_ATTR_MAX 

thread_attribute 의 최대 값, 1 Byte 크기로 한정 될 수 있게 한다.

[Stage 2 Thread State]

Thread 상태

열거형 멤버
THREAD_STATE_CREATED 

방금 생성된 Thread, Running List 에 들어있지 않은 상태

THREAD_STATE_RUNNING 

Running List 에서 실행 중인 상태

THREAD_STATE_SLEEP 

Running List 에서 빠져 Sleep 중인 상태

THREAD_STATE_ZOMBIE 

Running List 에서 빠져 삭제 준비 중인 상태

THREAD_STATE_DESTROYED 

삭제 된 상태

THREAD_STATE_DESTROYING 

삭제 중

[Stage 2 Thread State]

Thread 종류

열거형 멤버
THREAD_TYPE_KERNEL 

커널 쓰레드

THREAD_TYPE_USER 

사용자 쓰레드

THREAD_TYPE_MAX 

thread_type 의 최대 값, 1 Byte 크기로 한정 될 수 있게 한다.

함수 문서화

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]eThread 의 메인 함수
[in]argThread 인자
반환값
struct thread * Thread Control Block
반환값
NULLThread 생성에 실패
threadThread 포인터
참고
thread_destroy
주의
순서가 중요하다. 쓰레드 목록에 넣기 전에 상태를 수정해 두어야 한다. 그렇지 않으면, 스케쥴러에 의해 선택될 때 상태가 유효하지 않은 (의도하지 않은) 값이 있게 된다

+ 이 함수 내부에서 호출하는 함수들에 대한 그래프입니다.:

+ 이 함수를 호출하는 함수들에 대한 그래프입니다.:

void thread_destroy ( struct thread handle)

주어진 Thread 를 소멸 시킬 Thread 목록에 추가시킨다.

매개변수
[in]handleThread 핸들러
반환값
void 없음
참고
thread_create

< Should be collected

+ 이 함수 내부에서 호출하는 함수들에 대한 그래프입니다.:

+ 이 함수를 호출하는 함수들에 대한 그래프입니다.:

void* thread_exit_value ( struct thread thread)

+ 이 함수를 호출하는 함수들에 대한 그래프입니다.:

struct thread* thread_next ( struct thread thread,
int  circle 
)
struct thread* thread_prev ( 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]handleThread 핸들러
[in]stateThread 상태
반환값
void 없음
참고
thread_state

+ 이 함수를 호출하는 함수들에 대한 그래프입니다.:

enum thread_state thread_state ( struct thread handle)

주어진 thread 의 상태를 확인한다.

매개변수
[in]handleThread 핸들러
반환값
enum thread_state
반환값
THREAD_STATE_CREATED쓰레드가 생성 되었음
THREAD_STATE_RUNNING쓰레드가 실행 중임
THREAD_STATE_SLEEP쓰레드가 실행 목록에서 제외된 상태
THREAD_STATE_ZOMBIE쓰레드가 종료 단계 있음
THREAD_STATE_DESTROYED쓰레드가 종료 되었음
참고
thread_set_state