개발/공부 기록

Pintos 완주 후 다시 읽기 1: 시작점은 interrupt였다

cedis 2026. 5. 22. 21:53

Pintos 완주 후 다시 읽기

스레드, 알람, 시스템콜, page fault를 따로 외우기보다 커널로 들어오는 입구부터 다시 읽어본다.

이 글에서 다루는 것

Pintos를 처음 볼 때는 파일이 너무 많아서 위에서부터 읽기 쉽다. 하지만 완성하고 다시 보면 흐름은 함수 선언 순서가 아니라 이벤트가 커널로 들어오는 순서로 열린다.

  • timer interrupt가 시간과 스케줄링을 어떻게 움직이는지
  • syscallpage fault도 같은 관점에서 어떻게 읽히는지
  • 테스트 케이스가 왜 단순 채점표가 아니라 흐름을 확인하는 악보처럼 보였는지

1. 처음의 실수: 코드를 책처럼 읽으려 했다

처음 Pintos를 열면 이미 완성된 운영체제 일부처럼 보인다. 그래서 함수 이름을 위에서부터 읽고, 구조체를 외우고, 주석을 따라가려 했다. 그런데 그 방식으로는 알람, 우선순위, 시스템콜, VM이 서로 다른 섬처럼 보였다.

완성 후 다시 보니 더 중요한 질문은 이것이었다. 커널 코드는 언제 실행되는가? 답은 대부분 interrupt, trap, exception 같은 진입점에서 시작한다.

완성 후 다시 잡은 큰 그림

1
timer interrupt가 들어오면 tick이 증가하고, sleeping thread를 깨울지 판단한다.
2
syscall이 들어오면 user program의 요청을 커널 함수로 번역한다.
3
page fault가 들어오면 잘못된 접근인지, lazy page를 실제로 만들 시점인지 판단한다.
4
파일 시스템까지 가면 syscall 요청이 경로 해석, inode, FAT, disk 조작으로 이어진다.

2. timer interrupt는 단순한 시계가 아니었다

Project 1의 알람 파트에서 가장 먼저 잡아야 했던 것은 tick이었다. 여기서의 tick은 단순한 숫자가 아니라, sleep 상태인 thread가 다시 READY가 될 수 있는 기준이다.

static void
timer_interrupt (struct intr_frame *args UNUSED) {
    ticks++;
    thread_tick ();
    thread_wakeup(ticks);
}

이 짧은 함수에서 ticks++는 전역 시간 기준을 앞으로 밀고, thread_tick()은 현재 실행 중인 thread의 시간 사용을 기록하고, thread_wakeup(ticks)는 자야 할 시간이 끝난 thread를 다시 ready list로 보낸다.

void thread_wakeup(int64_t now_ticks) {
    struct thread *t;
    struct list_elem *e;
    bool woke_thread = false;

    for (e = list_begin(&sleep_list); e != list_end(&sleep_list); e = list_begin(&sleep_list)) {
        t = list_entry(e, struct thread, elem);

        if (t->wakeup_tick > now_ticks) {
            break;
        }

        list_remove(e);
        thread_unblock(t);
        woke_thread = true;
    }
}

여기서 헷갈리기 쉬운 점

timer.c의 ticks와 thread.c의 kernel_ticks는 같은 느낌으로 보이지만 역할이 다르다. ticks는 시스템 전체 시간이고, kernel_ticks는 그 시간 중 kernel thread가 CPU를 쓴 통계다.

알람 구현에서 필요한 것은 통계가 아니라 다시 깨어날 기준 시간이므로, sleep/wakeup 판단에는 전체 시간 tick이 쓰인다.

3. interrupt 관점으로 보면 테스트 이름도 다르게 보인다

테스트는 결과만 보는 도구가 아니었다. 어떤 interrupt나 상태 전이가 제대로 이어지는지를 묻는 질문에 가까웠다.

테스트 감각 실제로 묻는 흐름
alarm-single timer tick이 충분히 지난 뒤 blocked thread가 다시 READY가 되는가
alarm-priority 깨어난 thread가 ready list에 들어간 뒤 priority 기준으로 먼저 실행되는가
priority-preempt 더 높은 priority thread가 READY가 되었을 때 현재 실행 흐름이 양보하는가
page-* / mmap-* page fault가 단순 종료가 아니라 page materialization으로 이어지는가
dir-* / symlink-* syscall 요청이 경로 해석과 inode 조작까지 일관되게 이어지는가

4. 이 관점이 끝까지 유지된다

완주 전의 읽기

파일 이름과 함수 이름을 보고 기능별로 외웠다. 그래서 새로운 실패가 나오면 어디서 시작해야 할지 다시 헤맸다.

완주 후의 읽기

커널로 들어오는 사건을 먼저 찾는다. timer인지, syscall인지, page fault인지 잡으면 그다음 함수 호출 경로가 좁혀진다.

이번 글에서 기억할 것

  • Pintos는 파일 순서보다 event 진입점 순서로 읽는 편이 훨씬 선명하다.
  • Project 1의 첫 기준점은 timer_interrupt()였다.
  • 테스트 케이스는 단순 채점표가 아니라 상태 전이 흐름을 묻는 질문이다.

스스로 점검

  • sleep 중인 thread는 어떤 조건에서 다시 READY가 되는가?
  • timer tick과 CPU 사용량 tick을 구분해서 설명할 수 있는가?
  • 새 테스트가 실패했을 때 커널 진입점부터 찾을 수 있는가?

다음 글 예고

다음 글에서는 user program이 실행될 때 커널이 무엇을 만들어야 하는지 본다. argument passing, syscall, wait, fd table은 따로 떨어진 기능이 아니라 하나의 process를 구성하는 조각이었다.

한 줄 정리

Pintos를 다시 읽는 첫 기준은 함수 목록이 아니라, 커널로 들어오는 interrupt와 exception의 입구다.