WIL - PintOS: Project1 / Alarm Clock

박상우·2024년 5월 20일
0

📝 TIL

목록 보기
33/45
post-thumbnail

⚽️ Main Goal

❗ Busy Waiting을 사용하고 있는 Alarm을 sleep/wakeup으로 개선
  • Sleep List를 활용하여 대기 스레드 저장
  • 스레드를 깨우기 위한 Tick을 저장
  • 특정 Tick이 되었을 때 Sleep List의 스레드를 Ready List로 이동

개략적인 매커니즘은 다음과 같이 진행된다.


⚙️ 사전 작업

먼저 대기 상태의 스레드가 들어갈 수 있는 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;
}

⚙️ timer_sleep

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_awake

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);
		}
	}
}
profile
나도 잘하고 싶다..!

0개의 댓글