주어진 시간(ticks) 이후에 프로세스를 깨우는 시스템콜
PintOS는 바쁜 대기 알람을 사용한다.
devices/timer.c에 정의된 timer_sleep()을 다시 구현하기
PintOS에서는 제공된 코드는 Busy-waiting(바쁜대기)를 수행하는 코드다. 즉, 현재 실행중인 스레드를 주어진 조건이 맞을 때 까지 반복적으로 다른 스레드에게 CPU를 양보하여 주어진 틱 동안 실행을 일시 중단하는 방식으로 구현되어있다.
비효율적인 Busy-waiting이 아닌 sleep-awake방식으로 바꿔서 구현하는게 alarm-clock의 목표인데 여기서 Busy-waiting과 sleep-awake방식에 대해 알아보자
프로세스가 다른 프로세스에게 CPU를 양보하지 않고 계속 조건이 맞을 때 까지 확인하는 방식을 말한다.
이러한 방식은 CPU 자원을 낭비하고, 다른 스레드가 실행되는 기회를 줄여 성능 저하가 발생할 수 있다.
그렇다고 무조건 Busy-waiting이 사용되지 않는건 아니다.
1. 대기하는 시간이 매우 짧고, 컨텍스트 스위칭으로 인한 오버에드가 대기 시간 보다 큰 경우
2. 빠른 반응이 필요한 실시간 시스템에서, 조건이 빠르게 충족될 것으로 예상되는 경우에 사용된다.
각 방식의 장단점을 따져 적절한 상황에 쓰는 것이 중요하다.
busy-waiting이 아닌 방식은 non-busy-waiting이라고 하는데 이 방식은 대기 시간이 길어 CPU를 양보하는게 효율적이거나, 대기 시간을 예측할 수 없는 경우에 사용한다. 위에 sleep-Awake방식이 non-busy-waiting 방식이다.
공부하면서 이슈
내가 busy-waiting은 CPU를 반납하지 않고 조건을 계속 확인하여 컨텍스트 스위칭이 일어나지 않는건데 PintOS에서는 현재 스레드의 조건이 맞을 때까지 계속 CPU를 양보해서 내가 아는 것과 괴리가 있어 받아 들이기 까지 꽤 많은 시간이 걸렸다.
나중에 찾아보니 PintOS에서의 busy-waiting 구현 방식은 전통적인 바쁜대기와는 차이점이 있다고한다.
void timer_sleep (int64_t ticks)
{
int64_t start = timer_ticks ();
ASSERT (intr_get_level () == INTR_ON);
while (timer_elapsed (start) < ticks)
thread_yield ();
}
static struct list sleep_list;
thread_init (void) {
...
list_init (&sleep_list);
...
}
struct thread {
...
int64_t wake_up_tick;
...
}
3) 스레드 재우기 로직 변경
timer_sleep
void
timer_sleep (int64_t ticks) {
int64_t start = timer_ticks ();
ASSERT (intr_get_level () == INTR_ON);
// while (timer_elapsed (start) < ticks)
// thread_yield ();
thread_sleep (start + ticks);
// 현재 시각 (start) + 잠들 시간 (ticks)
}
thread_sleep
void thread_sleep(int64_t ticks)
{
struct thread *curr = thread_current();
enum intr_level old_level;
ASSERT(curr != idle_thread);
old_level = intr_disable();
curr->wakeup_ticks = ticks;
list_insert_ordered(&sleep_list, &curr->elem, cmp_thread_ticks, NULL);
thread_block();
intr_set_level(old_level);
}
thread_block (void)
void thread_block (void) {
ASSERT (!intr_context ());
ASSERT (intr_get_level () == INTR_OFF);
thread_current ()->status = THREAD_BLOCKED;
schedule ();
}
cmp_thread_ticks
// 두 스레드의 wakeup_ticks를 비교해서 작으면 true를 반환하는 함수
bool cmp_thread_ticks(const struct list_elem *a, const struct list_elem *b, void *aux UNUSED)
{
struct thread *st_a = list_entry(a, struct thread, elem);
struct thread *st_b = list_entry(b, struct thread, elem);
return st_a->wakeup_ticks < st_b->wakeup_ticks;
}
4) 재운 스레드 깨우기 로직 추가
timer_interrupt
static void
timer_interrupt (struct intr_frame *args UNUSED) {
ticks++;
thread_tick ();
thread_wake_up (ticks);
}
thread_wakeup
void thread_wakeup(int64_t os_ticks)
{
if (list_empty(&sleep_list))
return;
struct thread *t;
enum intr_level old_level;
old_level = intr_disable();
while (!list_empty(&sleep_list))
{
t = list_entry(list_front(&sleep_list), struct thread, elem);
if (t->wakeup_ticks > os_ticks)
break;
list_pop_front(&sleep_list);
list_insert_ordered(&ready_list, &t->elem, cmp_priority, NULL);
t->status = THREAD_READY;
}
intr_set_level(old_level);
}
여기서 깨우고 준비리스트에 삽입할 때 우선순위 별로 준비 리스트에 오름차순으로 정렬해주면 우선순위 순서대로 실행된다.
cmp_priority
bool cmp_priority(const struct list_elem *a, const struct list_elem *b, void *aux UNUSED)
{
struct thread *st_a = list_entry(a, struct thread, elem);
struct thread *st_b = list_entry(b, struct thread, elem);
return st_a->priority > st_b->priority;
}
구현 목록알람 시계 구현 결과
위의 코드에서 우선 순위별로 readylist에 삽입해주면 alarm관련 테스트는 모두 통과할 수 있다!!
static struct list sleep_list; 선언
list_init (&sleep_list); 초기화
각각의 스레드에 깨어나는 시간저장
thread_sleep
thread-wakeup
스레드가 가진 틱의 최소값을 저장하는 함수
틱의 최소값을 반환하는 함수