Jungle

[TIL] threads 코드 분석

손가든 2023. 11. 25. 00:01

코드를 분석하다 보니 디렉토리가 너무 여기저기 흩어져 있고, 해당 함수의 정의가 어디에 있는지 한눈에 보기 힘들어서

 

이번 주 프로젝트의 thread.c 코드 함수의 정의된 함수의 설명과 구조체의 정의 등을 정리하고, 이후 볼 수 있게 정리해 두겠다.

 

메모장에 적듯이 좀 불친절하게 정리 할 예정.

 


 

- Thread 구조체

위치 : include/threads/thread.h

 

struct thread {
	/* Owned by thread.c. */
	tid_t tid;                          /* Thread identifier. */
	enum thread_status status;          /* Thread state. */
	char name[16];                      /* Name (for debugging purposes). */
	int priority;                       /* Priority. */

 

tid는 쓰레드의 식별자로 보인다.

 

이후 나오는 thread_status는 enum 클래스로 다음과 같이 정의 되어있다.

 

enum thread_status {
	THREAD_RUNNING,     /* Running thread. */
	THREAD_READY,       /* Not running but ready to run. */
	THREAD_BLOCKED,     /* Waiting for an event to trigger. */
	THREAD_DYING        /* About to be destroyed. */
};

 

1. 실행 중인 상황  2. 실행 가능한 상황  3.실행 중이지만 블록된 상황  4.실행이 끝나고 폐기되어야 하는 상황 으로 보인다.

 

 

이후 name은 그저 디버깅 용으로 보이며 맨 마지막으로 스케줄링에서 사용할 것으로 보이는 priority 변수가 멤버로 구성되어 있다는 것을 알 수 있다.

 


Thread_create()

위치 : threads/thread.c

 

전체 declaration은 다음과 같다.

tid_t thread_create (const char *name, int priority, thread_func *function, void *aux)

 

tid_t thread_create (const char *name, int priority, thread_func *function, void *aux) {
	struct thread *t;
	tid_t tid;

	ASSERT (function != NULL);

	/* Allocate thread. */
	t = palloc_get_page (PAL_ZERO);
	if (t == NULL)
		return TID_ERROR;

	/* Initialize thread. */
	init_thread (t, name, priority);
	tid = t->tid = allocate_tid ();

	/* Call the kernel_thread if it scheduled.
	 * Note) rdi is 1st argument, and rsi is 2nd argument. */
	t->tf.rip = (uintptr_t) kernel_thread;
	t->tf.R.rdi = (uint64_t) function;
	t->tf.R.rsi = (uint64_t) aux;
	t->tf.ds = SEL_KDSEG;
	t->tf.es = SEL_KDSEG;
	t->tf.ss = SEL_KDSEG;
	t->tf.cs = SEL_KCSEG;
	t->tf.eflags = FLAG_IF;

	/* Add to run queue. */
	thread_unblock (t);

	return tid;
}

 

ASSERT () 는 괄호 안의 값이 False일 경우 kernel panic을 발생시켜 오류 메시지를 출력한다.

 

function 이 없다면 에러 메시지를 출력하게 하는 것 같다.

 

 

't = palloc_get_page(PAL_ZERO)' 코드는

 

page alloc으로 커널이 관리하는 커널 위치 혹은 사용자 풀 (USER POOL) 위치에 공간을 할당하고 포인터를 반환한다.

 

이 코드는 thread에 커널이 관리하여 자리를 배정해주는 코드이다.

 

palloc에 대한 코드는 threads/palloc.c 에 있다.

 

/* A memory pool. dir = "threads/palloc.c" */
struct pool {
	struct lock lock;               /* Mutual exclusion. */
	struct bitmap *used_map;        /* Bitmap of free pages. */
	uint8_t *base;                  /* Base of pool. */
};


void * palloc_get_page (enum palloc_flags flags) {
	return palloc_get_multiple (flags, 1);
}


static struct pool kernel_pool, user_pool;

void *palloc_get_multiple (enum palloc_flags flags, size_t page_cnt) {
	struct pool *pool = flags & PAL_USER ? &user_pool : &kernel_pool;

	lock_acquire (&pool->lock);
	size_t page_idx = bitmap_scan_and_flip (pool->used_map, 0, page_cnt, false);
	lock_release (&pool->lock);
	void *pages;

	if (page_idx != BITMAP_ERROR)
		pages = pool->base + PGSIZE * page_idx;
	else
		pages = NULL;

	if (pages) {
		if (flags & PAL_ZERO)
			memset (pages, 0, PGSIZE * page_cnt);
	} else {
		if (flags & PAL_ASSERT)
			PANIC ("palloc_get: out of pages");
	}

	return pages;
}



/* dir = "include/palloc.h" */
enum palloc_flags {
	PAL_ASSERT = 001,           /* Panic on failure. */
	PAL_ZERO = 002,             /* Zero page contents. */
	PAL_USER = 004              /* User page. */
};

 

해당 함수들은 thread_create() 함수 속에 존재하는 palloc()을 이해하기 위한 여러 디렉토리에 존재하는 함수들을 나열했다.

 

맨 밑에 enum 변수들을 유의하며 위의 코드를 따라 가보면 페이지를 어디 배치할 지를 정하고

 

비트맵 자료구조를 통해 페이지를 할당하는 과정이 나타나 있다.

 

더보기

비트맵(bitmap) 자료구조란 ?

 

비트맵(bitmap)은 이진수 형태의 자료 구조이다. n 자리 이진수로 이루어진 비트맵은 주로 자료 n개의 사용 가능성을 표시할 때 사용된다. 예를 들어 0은 사용 가능한 자료, 1은 사용 불가능한 자료임을 나타낼 때 쓰인다.

 

PAL_USER 플래그가 설정되어 있으면 user_pool에 , 아니면 kernel_pool에 페이지 공간을 할당하도록 하고,

 

PAL_ZERO 플래그가 설정되어 있으면 해당 페이지 공간을 0으로 초기화,

 

PAL_ASSERT 플래그가 설정되어 있으면 페이지 오류 메시지를 출력하게 설정되어 있다.

 

 

최종적으로 thread_create()의 마지막 코드 문단인 t 의 멤버 설정 코드는 아직 정확히 이해하지 못했다.

 

대략 thread의 정보들을 직접 레지스터에 삽입하는 것 같다.

 

이후 thread_unblock()을 통해 런타임 큐에 해당 쓰레드를 실행 대기 상태로 변환하면서 마무리하는 것 같다.

 

return으로는 해당 thread의 식별자를 반환한다.

 

 

 


 

idle 스레드

idle() 함수가 생기면서 idle 스레드라는 키워드가 튀어나왔다. 이에 대해 알아보자.

 

idle 스레드(유휴 스레드)는 시스템이 특별한 작업을 수행하지 않을 때 CPU가 스레드를 무한으로 탐색하는 작업을 수행하지 않고 대기할 수 있도록 하는 빈 스레드이다.

 

만약 idle 스레드가 없이 아무런 스레드가 실행되지 않는다면, 다음에 실행할 스레드가 있는지 무한으로 탐색하는 스케줄러가 수행된다.

 

하지만 idle 스레드가 현재 실행중인 스레드로 선정된 뒤 아무런 작업을 수행하지 않음으로서 시스템이 CPU를 절약할 수 있다.

 

 

idle 스레드는 초기에 한번만 스케줄되며, 이때 초기화 작업을 수행한다.

 

초기화 작업이 완료되면, idle 스레드는 세마포어 등을 통해 다른 스레드가 계속 진행할 수 있도록 신호를 열어놓는다.

 

이후에는 idle 스레드는 아무런 작업을 하지 않는 대기상태로 돌아가며, 다른 스레드가 CPU를 점유할 때까지 대기한다.

 


Thread_launch()

해당 함수는 문맥교환을 하는 함수이다.

 

이 함수에 대한 깊은 이해를 할 필요는 없다고 설명되어 있다.

 

문맥 교환을 하는 이 함수는 현재 실행중인 쓰레드의 상태를 저장하고, 스위칭을 진행할, 다음으로 실행할 쓰레드의 상태를 복원한다.

 

이전 스레드(PREV)에서 새로운 스레드로 전환시키며, 이때 인터럽트는 비활성화 상태로 유지된다.

 

이는 보통 커널 레벨에서 스레드를 전환할 때 인터럽트를 잠시 비활성화 하는 것이 일반적이다.(동기화를 위해)

 

 

 

 

 


그 외에 유의할 점

 

- TIMER_FREQ define 변수

 

TIMER_FREQ는 include/timer.h 에 100으로 정의되어 있다.

 

이는 초당 타이머 틱을 나타내며, 건들면 다른 테스트를 실패할 수 있으므로 건들지 않는 것이 좋음.