PintOS #2 : Alarm Clock

PintOS의 코드를 구현하는 것은 상당히 난이도 있다. 여러가지 고생했고, 어디서 어떻게 막혔는지 하나하나 논하기엔 여러분들은 나의 고생이 그다지 궁금하지 않다. 그냥 GPT 없이 접근해보길 바란다. ㅋㅋ ㅠㅠ

2025.05.12 - [구현하기] - PintOS #1 : 진행 흐름도 파악하기

 

PintOS #1 : 진행 흐름도 파악하기

정글의 커리큘럼으로 진행으로 진행 중인 PintOS의 테스트 데이터가 어떻게 입력되고 작동되는지에 대해 논해보겠다. 우리는 64bit의 KAIST PintOS에 대해 다루고 있다. 이 논함은 정규 커리큘럼은 아

hyeonistic.tistory.com

 

Alarm Clock은 정해진 시간 후에 쓰레드를 깨우고 재우는 내용을 구현하는 내용인데, 기존에 잘 작동하는 timer.c 파일에 위치한 timer_sleep() 함수의 최적화를 하는 것이 첫 번째 목표이다.

계속해서 반복문을 돌면서 현재 시간을 확인하고, 충분한 시간이 경과 할 때 까지 thread_yield() 를 호출하는데, 이것이 Busy Wait 방식으로, 앞으로 최적화 된 결과는, 상대적으로 자원을 덜 소모 한다.

카이스트 공식 문서에서 언급하는 구현 방향은 아래와 같다 :

  • 실행 대기 중인 목록을 ready_list로 정의한다. 재우는 목록을 sleep_list로 정의한다.
    • 우리는 sleep_list에서 나와야만 ready_list에 들어가서 실제 실행이 일어나도록 정의 하는 것이 목표이다.
  • thread는 특유의 sleep time을 부여 받은채로 이 sleep_list에 들어간다.
  • sleep_list에 들어가며 sleep_list 중 가장 빨리 닿을 수 있는 시간을 따로 저장해둔다. 즉 min(item.sleepTime of Lists) 인 것
    • 이것을 min_tick 이라 하자.
  • 이 min_tick이 넘는 경우에만 sleep_list의 갱신이 일어난다. 그러면서 min_tick은 다시 갱신된다.
  • 이렇게 함으로써 기존의 방식보다 CPU 사용률, 전기, 모든 것을 아끼는 코드를 완성 할 수 있다.

수정 대상 코드는 thread.* 와 timer.* 이다.

timer.*

/*************************************************************
 * timer_interrupt - 타이머 인터럽트 핸들러
 *
 * 요구사항:
 * - 시스템 틱 수를 증가시켜 전체 시간 흐름을 관리
 * - 현재 실행 중인 스레드의 time slice를 갱신해야 함
 * - sleep_list에 등록된 스레드 중 깨어날 시간이 도래한 경우 깨워야 함
 * 
 * 기능:
 * - 전역 변수 ticks를 1 증가시킴
 * - thread_tick()을 호출하여 현재 스레드의 time slice 소모 체크
 * - closest_tick(다음 깨어날 시각)과 비교하여 thread_awake() 호출 여부 결정
 *
 * 주의:
 * - 인터럽트 컨텍스트에서 실행되므로 thread_block()과 같은 동작은 금지
 * - closest_tick은 sleep_list에 있는 가장 이른 wakeup_tick 값을 나타냄
 * 
 * [AS-IS]
 * - 단순히 ticks 증가 및 thread_tick() 호출만 수행
 * → sleep 상태의 스레드를 깨우는 기능이 없음
 * 
 * [TO-BE]
 * - closest_tick <= ticks일 때만 thread_awake()를 호출하여,
 *   sleep_list에서 깨어날 시각이 지난 스레드를 READY 상태로 전환
 * → 정확하고 효율적인 알람 기능 제공
 *************************************************************/
static void
timer_interrupt (struct intr_frame *args UNUSED) 
{
	ticks++;						// 전체 시스템 tick 수 증가
	thread_tick ();					// 현재 running 중인 thread의 tick 처리 및 time slice 만료 검사
	if (closest_tick() <= ticks)  	// 가장 이른 wakeup_tick(closest_tick)이 현재 tick보다 작거나 같다면
		thread_awake(ticks);		// → 깨어날 시간이 도래한 스레드가 존재할 수 있으므로 thread_awake() 호출
}
/*************************************************************
 * timer_sleep - 지정된 tick 수만큼 현재 스레드를 재움
 *
 * 요구사항:
 * - busy-wait 없이 정확한 시간 동안 스레드를 BLOCKED 상태로 전환
 * - 인터럽트를 활성화한 상태에서 호출해야 함
 * 
 * 기능:
 * - 현재 tick을 기준으로, 깨어날 tick을 계산하여 thread_sleep() 호출
 * - sleep_list에 현재 스레드를 추가하고 BLOCKED 상태로 전환
 * 
 * 반환:
 * - 없음 (void)
 * 
 * 주의:
 * - ticks가 0 이하일 경우 sleep을 수행하지 않음
 * - intr_get_level() == INTR_ON 상태에서만 호출 가능
 * - sleep 중인 스레드는 timer_interrupt()에 의해 주기적으로 체크됨
 * 
 * [AS-IS]
 * - busy-wait 방식 사용
 * - while 루프에서 timer_elapsed()로 경과 시간 확인 후 thread_yield() 호출
 * → RUNNING 상태 유지하며 CPU 자원 낭비
 * 
 * [TO-BE]
 * - thread_sleep(start + ticks)를 통해 BLOCKED 상태로 진입
 * - thread_awake()가 타이머 인터럽트 시점마다 sleep_list를 확인하고 깨어남
 * → 정확하고 효율적인 sleep 가능 (CPU 절약)
 *************************************************************/
void
timer_sleep (int64_t ticks) 
{
	if (ticks <= 0) return;
	int64_t wakeup_tick = timer_ticks() + ticks;
	thread_sleep(wakeup_tick);
}

 

thread.*

/*************************************************************
 * thread_awake - 슬립 리스트에서 깨울 시간이 지난 스레드들을 깨움
 *
 * 기능:
 * - sleep_list에 있는 스레드 중, current_tick ≤ 현재 tick인 스레드를 READY 상태로 전환
 * - list_remove() 및 thread_unblock()을 통해 스레드 깨움
 * - 남아 있는 스레드들의 current_tick 중 가장 이른 값으로 awake_closest_tick 갱신
 *
 * 동기화:
 * - 인터럽트 컨텍스트에서 실행되므로 별도 락 불필요
 * - list_remove() 시 반복자 갱신에 주의 필요
 *
 * 호출 위치:
 * - timer_interrupt() 내부에서, ticks ≥ awake_closest_tick일 때 호출
 *
 * 제약 조건:
 * - BLOCKED 상태가 아닌 스레드를 깨우면 안 됨 (thread_unblock 제약)
 * - thread_block() 호출 금지 (인터럽트 컨텍스트이므로)
 *
 * 요구사항:
 * - busy-wait 없이 정확한 tick 기반 sleep/wakeup 동작 보장
 * - awake_closest_tick 값을 매 tick마다 갱신하여 불필요한 검사 최소화
 *************************************************************/
void
thread_awake (int64_t current_tick)
{
    awake_closest_tick = INT64_MAX;  // 슬립 리스트를 순회하며 가장 빠른 wakeup_tick으로 초기화

    struct list_elem *sleeping = list_begin(&sleep_list);  // sleep_list 순회 시작

    while (sleeping != list_end(&sleep_list)) {
        struct thread *th = list_entry(sleeping, struct thread, elem);  // 요소를 thread 구조체로 변환

        if (current_tick >= th->wakeup_ticks && th->status == THREAD_BLOCKED) {
            struct list_elem *next = list_remove(sleeping);  // 리스트에서 제거 후 다음 요소 저장
            thread_unblock(th);  // BLOCKED 상태 → READY 상태로 전환
            sleeping = next;  // 다음 요소로 이동
        } else {
            update_closest_tick(th->wakeup_ticks);  // 남은 thread 중 가장 가까운 wakeup_tick 갱신
            break;  // 오름차순 정렬이므로 더 이상 확인할 필요 없음
        }
    }
}
/**********************************************************
 * thread_sleep - 현재 실행 중인 스레드를 지정한 틱 수만큼 재움
 *
 * 기능:
 * - idle_thread는 재우지 않음
 * - 현재 스레드를 sleep_list에 추가하고 BLOCKED 상태로 전환
 * - wakeup_ticks을 현재 시간 + ticks로 설정
 * - (global) awake_closest_tick 갱신
 * - thread_block() 호출로 스케줄러 대상에서 제외
 *
 * 동기화:
 * - 인터럽트를 비활성화한 상태에서 sleep_list에 접근
 *
 * 호출:
 * - timer_sleep() 함수에서 호출됨
 **********************************************************/
void
thread_sleep (int64_t wakeup_tick) 
{
    struct thread *cur = thread_current(); // 현재 실행중인 스리드 해석

    if (cur == idle_thread) return;       // idle 스리드는 잠을 할 필요 없음

    enum intr_level old_level = intr_disable(); // 동기화 복잡화 목적으로 인터럽트 비활성화

    cur->wakeup_ticks = wakeup_tick; // 지정된 잠을 기간의 종료 tick을 저장
    update_closest_tick(wakeup_tick); // 가장 보기 가까운 tick 갱신
    list_insert_ordered(&sleep_list, &cur->elem, cmp_wakeup_tick, NULL); // wakeup_tick 까지 정렬된 순서로 삽입
    thread_block(); // 현재 스리드를 BLOCKED 상태로 변경 후 스케줄러 대상\uc5d에서 제제

    intr_set_level(old_level); // 이전 인터럽트 상태로 복원

    /* ❌ 기존 busy-wait 구조에서 지정 (AS-IS)
    cur->wakeup_ticks = timer_ticks() + ticks;      // 현재 시간 + ticks 값 계산해서 wakeup_ticks 설정 */

    // // 중복 삽입 방지를 위해 무적가로써 이미 sleep_list에 포함되어 있는지 확인
    // if (list_contains(&sleep_list, &cur->elem))
    // 	list_remove(&cur->elem); // 포함되었다면 제거
}
static bool 
cmp_wakeup_tick(const struct list_elem *a, const struct list_elem *b, void *aux UNUSED) {
    struct thread *ta = list_entry(a, struct thread, elem);
    struct thread *tb = list_entry(b, struct thread, elem);
    return ta->wakeup_ticks < tb->wakeup_ticks;
}

배경 지식을 다 설명하는게 마냥 쉽지가 않다. 신경써보려고 한다..

'구현하기' 카테고리의 다른 글

PintOS P2 #1 : User Program 서론  (0) 2025.05.20
PintOS #3 : Priority Scheduling  (0) 2025.05.16
PintOS #1 : 진행 흐름도 파악하기  (0) 2025.05.12
PintOS #0 : 서론  (0) 2025.05.10
Web Proxy #6 : Caching Proxy  (0) 2025.05.08