개략적인 매커니즘은 다음과 같이 진행된다.
먼저 대기 상태의 스레드가 들어갈 수 있는 Sleep List를 만들어 주었다. Sleep List의 경우 각 스레드에 국한된 데이터가 아닌 여러 스레드들이 들어갔다가 나올 수 있기 떄문에 전역적으로 선언해주고, thread_init
에서 초기화를 해주었다.
// thread.c
static struct list sleep_list;
...
void thread_init (void) {
...
list_init (&sleep_list);
...
}
timer_sleep
을 호출하게 되면 스레드가 대기 상태가 된다.
이때 현재 ticks의 인자로 넘겨주는 값이 0이거나 음수인지를 검사하기 위해서 start를 위해 현재 틱을 확인하고, timer_elapsed
내부에서 현재 틱을 다시 한 번 호출한다.
추측이긴하지만, 이렇게 두번의 timer_ticks
를 호출한 후, timer_elapsed
내부에서 빼기 연산을 한다. 그 결과 값은 양수가 아닌 아주 작은 값일 것이라 생각했고, 그 값을 통해서 0과 음수에 대한 조건 처리를 해주는 것이라고 생각했다.
void
timer_sleep (int64_t ticks) {
int64_t start = timer_ticks ();
ASSERT (intr_get_level () == INTR_ON);
// timer_elapsed()를 호출한 시점과 start를 호출한 시점이 0보다 큰 아주 작은 값이기 때문에
// 전달 받은 ticks가 0 or 음수인지 체크하기 위해 아래 조건문이 사용되었음.
if ( timer_elapsed (start) <= ticks ) {
thread_sleep(start + ticks);
}
}
int64_t timer_elapsed (int64_t then) {
return timer_ticks () - then;
}
tick이 유효하다면, 스레드는 인자로 전달받은 ticks 만큼 대기 상태에 들어간다.
thread_sleep
내부에는 스레드가 다시 깨어날 수 있게끔 thread 내부에 있는 sleep_ticks
를 설정하고, 해당 스레드를 sleep_list
에 넣는 동작을 수행한다.
이때 특정 tick에 깨어날 스레드가 있다는 것을 실행동안 알고 있기 위해 global_tick
이라는 전역 변수를 새롭게 선언해주었다.
뿐만 아니라 앞서 설명한대로 sleep_ticks
라는 새로운 데이터를 사용하기 위해 스레드 내부에 sleep_ticks
속성도 추가해주었다.
// timer.c
static int64_t global_ticks;
// trhead.c
int64_t get_global_ticks (void) {
return global_ticks;
}
/* ticks setter */
void set_global_ticks ( int64_t new_ticks ) {
global_ticks = new_ticks;
return;
}
// thread.h
struct thread {
...
int64_t sleep_ticks;
...
}
// thread.c
void thread_sleep(int64_t ticks) {
struct thread *curr = thread_current ();
enum intr_level old_level;
ASSERT(!intr_context());
old_level = intr_disable (); // 인터럽트 OFF
if (curr != idle_thread) { // 현재 스레드가 idle thread인지 확인
curr -> sleep_ticks = ticks; // tick 추가 for wake up
// list_push_back(&sleep_list, &(curr -> elem)); // 대기 큐로 변수 할당
list_push_back(&sleep_list, &curr -> elem); // 대기 큐로 변수 할당
set_global_ticks(ticks);
thread_block(); // block + 새로운 스케줄 생성 ( 내부에서 현재 스레드를 Running으로 바꿔줌 )
}
intr_set_level (old_level); // 인터럽트 ON
return;
}
실행 중인 스레드를 sleep_list에 넣고, global_tick을 변경한다. global_tick을 통해서 OS는 sleep_list에 깨울 스레드가 있음을 판단할 수 있다.
timer_intterupt의 동작을 보면 tick을 올리는 역할을 한다.
tick을 올린 직후, 현재 global_tick과 비교하여 sleep_list
의 스레드를 깨운다.
static void timer_interrupt (struct intr_frame *args UNUSED) {
ticks++;
thread_tick ();
if ( get_global_ticks() <= ticks ) {
thread_awake(ticks); // global tick과 sleep_list의 스레드를 비교, 깨울 스레드는 깨움
}
}
스레드를 깨우기 위해 thread_awake
를 만든다.
sleep_list
를 순회하면서 각 스레드의 sleep_ticks
가 현재 ticks보다 작다면, 스레드를 sleep_list
에서 제거하고, thread_unblock
을 통해 해당 스레드의 상태를 변경한다.
sleep_list
에서 조건에 해당하지 않는 스레드인 경우, 다음 awake를 위해서 global_ticks
와 비교하여 global_ticks
값을 갱신한다.
// thread.c
void thread_awake (int64_t ticks) { // 인자로 global tick 받을 에정
struct list_elem *e = list_begin(&sleep_list);
while ( e != list_end(&sleep_list) ) // 해당 리스트의 마지막 요소 ( 가드 노드 )까지 순회하며 탐색
{
// 현재 list_elem에서 원본 스레드 구조체로의 포인터를 얻음
struct thread *t = list_entry(e, struct thread, elem);
if ( t -> sleep_ticks <= ticks ) { // global ticks 보다 작으면
e = list_remove(e); // pop을 못해서 임의로 리스트에서 삭제 ( 삭제후 e를 next로 이동 )
thread_unblock(t); // 해당 스레드의 상태 변경
} else {
if ( t -> sleep_ticks < get_global_ticks() ) {
set_global_ticks(t -> sleep_ticks); // 전역 틱 갱신
}
e = list_next(e);
}
}
}