코드를 분석하다 보니 디렉토리가 너무 여기저기 흩어져 있고, 해당 함수의 정의가 어디에 있는지 한눈에 보기 힘들어서
이번 주 프로젝트의 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) {
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으로 정의되어 있다.
이는 초당 타이머 틱을 나타내며, 건들면 다른 테스트를 실패할 수 있으므로 건들지 않는 것이 좋음.
'Jungle' 카테고리의 다른 글
pintos 테스트 터미널 명령어 (0) | 2023.11.26 |
---|---|
[TIL] 타이머 인터럽트 정의 및 분석 (3) | 2023.11.26 |
[TIL] 인터럽트 / lock, monitor (3) | 2023.11.24 |
[TIL] pintos 운영체제 환경 세팅 + Synchronization(동기화) (0) | 2023.11.23 |
[TIL] 우분투 포트 kill 하는 법 (0) | 2023.11.22 |