자 이제 첫번째 과제 시작해보겠어요
Pintos 프로젝트를 처음 시작하면 timer_sleep()이라는 함수를 마주하게 됩니다
그게 뭐시냐
이 함수는 스레드가 일정 시간(tick 단위) 동안 “잠들어 있도록” 만들어주는 함수입니다
기본 구현
int64_t start = timer_ticks();
while (timer_elapsed(start) < ticks)
thread_yield();
언뜻보기엔 간단하고 직관적이지만 큰 문제점이 발생합니다
Busy Waiting!
위 코드에서 스레드는 sleep하는 동안 계속 thread_yield를 호출하면서 cpu를 차지한 채로 돌고 있습니다
실제로는 아무 일도 하지 않지만,
"계속 깨어나야하나?"를 확인하며 cpu를 소모하는 방식인데요
- cpu 자원 낭비
- 다른 스레드가 실행될 기회를 빼앗음
- 전력 소비 증가
등의 문제로 절대 비효율적인거죠
그래서 이 과제에서 원하는 것이 무엇이냐하면
스레드는 진짜로 자고 있을 땐 BLOCKED상태로 전환되어, 스케줄러가 cpu를 할당하지 않도록 만드는 것입니다

그럼 이제 대충 첫 과제(1-1) 내용은 파악했고 뭘 해야하느냐 라고 하면
일단 어떤 구조로 구현할지를 파악해봅시다
- 스레드가 timer_sleep()을 호출하면
- 지정한 시간이 될 때까지 BLOCKED 상태로 들어간다
- 타이머 인터럽트가 매 tick마다 확인해서,
- 깨어날 시간이 된 스레드를 다시 READY 상태로 전환시킨다
이렇게 하면 Busy Waiting 없이 진짜로 자고 있다가 깨어나는 구조가 됩니다
이제 좀 더 세부적으로 들어가볼까요
timer.c
일단 문제의 시발점이라 해야하나요..
암튼 timer_sleep를 수정해줍니다
void
timer_sleep (int64_t ticks) {
int64_t start = timer_ticks ();
ASSERT (intr_get_level () == INTR_ON);
if (timer_elapsed (start) < ticks)
thread_sleep(start + ticks); //현재 tick + 대기시간
}
timer_sleep()은 커널이 외부에서 호출하는 함수가 아니라,
현재 실행 중인 커널 스레드가 자기 자신을 지정된 시간 동안 BLOCK 상태로 전환하기 위해 직접 호출하는 함수입니다
실제 사용은 thread_create()를 통해 만들어진 스레드 내부에서 이루어지며,
이 스레드는 timer_sleep()을 통해 스스로 잠들고,
지정된 시간이 지나면 thread_awake()에 의해 다시 깨어나게 됩니다
주어진 tick 동안 스레드를 BLOCKED 상태로 전환시킵니다
내부에서는 현재 tick 기준으로 “언제 깨어나야 하는지” 계산하여
thread_sleep() 함수에 넘겨 실제로 BLOCK 처리하게 합니다
참고로 이 구조는 KAIST Pintos 슬라이드에서 제안한 구조를 따라 구현한 것입니다
static void
timer_interrupt (struct intr_frame *args UNUSED) {
ticks++;
thread_tick ();
thread_awake(ticks);
}
- 이 함수는 매 tick마다 자동으로 호출되는 인터럽트 핸들러입니다
- 여기서 thread_awake()를 호출해, “시간이 된 애들을 깨우는 일”을 맡겨요
thread.c
암튼 이제! 스레드가 언제 깨어나야하는지를 기억해야겠죠?
struct thread에 wake_up_tick 필드를 추가해줍니다
int64_t wake_up_tick;
- 스레드가 언제 깨어나야 하는지 기억하기 위해서
- thread_sleep() 시 이 값을 세팅하고,
- thread_awake() 시 현재 tick과 비교하여 깨움
sleep_list도 사용해야 합니다
static struct list sleep_list;
왜 WHY?
- BLOCKED 상태의 스레드들을 따로 관리할 슬립 큐가 필요함
- 단순히 스레드 상태만 BLOCK으로 바꾸면 안 되고,
- 정확한 깨울 시간을 기준으로 리스트를 정렬해 관리해야 함
기존 busy waiting을 제거하고 진짜 block 시키기위한 함수를 추가해줍니다
void thread_sleep(int64_t wakeup_tick) {
struct thread *cur = thread_current(); // 현재 실행 중인 스레드 가져오기
// idle_thread는 절대 재우면 안 됨
if (cur == idle_thread)
return;
enum intr_level old_level = intr_disable(); // 슬립 리스트 조작 전 인터럽트 비활성화 (race condition 방지)
cur->wake_up_tick = wakeup_tick; // 스레드가 언제 깨어나야 할지 저장
list_insert_ordered(&sleep_list, &cur->elem, compare_wake_up_tick, NULL); // 정렬된 채로 sleep_list에 삽입
thread_block(); // 스레드 상태를 BLOCKED로 바꾸고 CPU 양보
intr_set_level(old_level); // 인터럽트 상태 복원
}
- 현재 스레드의 wake_up_tick을 기록하고,
- sleep_list에 삽입 후 BLOCKED 상태로 전환
list_insert_ordered()함수를 사용하기 위해 비교 함수도 만들어줍니다
// 두 스레드의 wake_up_tick 값을 비교해서
// a가 b보다 먼저 깨어나야 하면 true를 반환한다.
// → list_insert_ordered()에 넘겨줄 비교 함수로 사용됨
static bool compare_wake_up_tick(const struct list_elem *a, const struct list_elem *b, void *aux UNUSED) {
// list_elem 포인터 a, b를 각각 thread 구조체로 캐스팅
struct thread *t1 = list_entry(a, struct thread, elem);
struct thread *t2 = list_entry(b, struct thread, elem);
// 깨어나야 할 시각이 더 빠른 쪽이 앞으로 오게 정렬
return t1->wake_up_tick < t2->wake_up_tick;
}
- 슬립 리스트에 들어가는 스레드들은 wake_up_tick 오름차순으로 정렬됨
- 그래야 thread_awake()에서 앞에서부터 효율적으로 검사 가능함
시간이 지난 애들을 대기 상태로 되돌려줄 함수도 필요하겠죠
// 현재 ticks 값을 기준으로
// sleep_list 안에 있는 스레드 중 wake_up_tick이 지난 애들을 READY 상태로 되돌린다.
void thread_awake(int64_t now_tick) {
// sleep_list의 맨 앞부터 순회 시작 (정렬되어 있기 때문)
struct list_elem *e = list_begin(&sleep_list);
// 리스트 끝까지 순회 (또는 중간에 break될 수도 있음)
while (e != list_end(&sleep_list)) {
// e가 가리키는 요소를 thread 구조체로 변환
struct thread *t = list_entry(e, struct thread, elem);
// 깨어날 시간이 된 경우
if (t->wake_up_tick <= now_tick) {
// 리스트에서 해당 요소를 제거하고 다음 요소로 e 갱신
e = list_remove(e);
// 해당 스레드를 READY 상태로 전환
thread_unblock(t);
} else {
// 리스트가 정렬되어 있기 때문에, 더 뒤에 있는 스레드는 아직 깰 시간이 아님
break;
}
}
}
- 매 tick마다 호출되어 sleep_list를 순회하며 깨어날 스레드들을 READY 상태로 돌림
- wake_up_tick <= 현재 tick 조건만 만족하면 thread_unblock()
- 그래서 timer_interrupt 함수에 thread_awake가 추가 되었던 것입니다
정리하자면
[1] 사용자 코드에서 호출
↓
timer_sleep(ticks)
↓
thread_sleep(current_tick + ticks)
↓
┌────────────────────────────────────────────┐
│ 현재 스레드의 wake_up_tick 설정 │
│ sleep_list에 정렬 삽입 │
│ thread_block() 호출 → BLOCKED 상태 전환 │
└────────────────────────────────────────────┘
↓ (시간 흐름, 틱 증가)
┌────────────────────────────────┐
│ 매 tick마다 호출됨 (인터럽트) │
│ timer_interrupt() │
│ └── ticks++ │
│ └── thread_awake(ticks) │
└────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ sleep_list에서 wake_up_tick ≤ ticks 인 스레드 │
│ → thread_unblock() 호출 │
│ → READY 상태로 전환, ready_list에 삽입 │
└─────────────────────────────────────────────┘
↓
스케줄러가 READY 중 우선순위에 따라
해당 스레드를 다시 RUNNING 상태로
| 단계 | 설명 |
| timer_sleep() | 외부 호출 인터페이스 (몇 틱 동안 자라) |
| thread_sleep() | 현재 스레드의 깨울 시점 기록 + BLOCK |
| sleep_list | BLOCK된 스레드 저장, 깨울 시간 기준 정렬 |
| thread_awake() | 매 tick마다 sleep_list 순회 → 깨어날 스레드 READY로 |
| thread_unblock() | BLOCK → READY 상태 전환 |
| thread_block() | READY → BLOCK 상태 전환 |
| timer_interrupt() | ticks++ & thread_awake() 호출 (핵심 루프) |
이런 흐름입니다
그래서 예시로 alram_single 테스트 하나를 돌려보면

이런 결과를 받아볼 수 있습니다
대충 설명드리자면
5개의 스레드를 각각 다른 시간 간격으로 한 번씩만 잠들게 하고, 각 스레드가 정확한 시간에 깨어나는지 확인하는 테스트입니다
실제로는 각 스레드가 timer_sleep()을 호출하고,
내부적으로는 thread_sleep() → BLOCKED 상태 → 시간이 되면 thread_awake()로 복귀하는 구조입니다
실행 흐름
(alarm-single) Creating 5 threads to sleep 1 times each.
(alarm-single) Thread 0 sleeps 10 ticks each time,
(alarm-single) thread 1 sleeps 20 ticks each time, and so on.
- Thread 0: 10 ticks 후 깸
- Thread 1: 20 ticks 후 깸
- …
- Thread 4: 50 ticks 후 깸
실행 결과
(alarm-single) thread 0: duration=10, iteration=1, product=10
(alarm-single) thread 1: duration=20, iteration=1, product=20
(alarm-single) thread 2: duration=30, iteration=1, product=30
(alarm-single) thread 3: duration=40, iteration=1, product=40
(alarm-single) thread 4: duration=50, iteration=1, product=50
- duration: 몇 tick 동안 잠들었는지
- iteration: 몇 번째 반복인지 (여기선 모두 1)
- product: duration × iteration → 순서 확인용 수치
🟢 결과가 오름차순으로 출력됨 → 정확한 시간에 깨어남을 의미함
시스템 정보
Timer: 291 ticks
Thread: 250 idle ticks, 42 kernel ticks, 0 user ticks
Console: 1026 characters output
- 전체 291 ticks 동안 테스트 실행
- 대부분의 시간 동안 idle 상태 (스레드가 sleep 중이었다는 뜻)
- kernel thread만 사용됨 (user space 없음)
'크래프톤정글 > Pintos' 카테고리의 다른 글
| PintOS_Priority_Scheduling_part_3_donation (1) | 2025.05.13 |
|---|---|
| 지금까지의 개념 정리_Alram Clock & Priority Scheduling (0) | 2025.05.12 |
| PintOS_Priority_Scheduling_part_2 (0) | 2025.05.11 |
| PintOS_Priority_Scheduling_part_1 (0) | 2025.05.10 |
| PintOS 첫시작! _ 반드시 알아야할 개념 (0) | 2025.05.09 |