들어가며
PintOS를 만지다 보면 인터럽트 함수를 자주 보게 됩니다.
이 인터럽트 함수들 중에서 자주 보는 것들을 정리해 보았습니다.
인터럽트란?
인터럽트는 CPU 외부(혹은 내부)에서 발생한 예상치 못한 사건을 알리는 신호입니다.
발생한 위치에 따라 내부 인터럽트, 외부 인터럽트로 나눕니다.
대표적인 인터럽트 예시들을 나열해 보면:
- 타이머 인터럽트(외부) : 정해진 시간마다 발생하여 커널이 CPU 점유권을 회수할 수 있게 해 줍니다.
- 키보드 입력, 디스크 I/O 인터럽트(외부) : 하드웨어 장치의 동작 완료 등을 알립니다.
- 소프트웨어 인터럽트(내부, 대표적인 것이 시스템 콜) : 사용자 프로그램이 커널에 도움을 요청할 때 사용됩니다.
PintOS에선 interrupt.c, interrupt.h, timer.c 등이 인터럽트 시스템을 구현하고 있습니다.
그래서 이 인터럽트가 PintOS 과제에서 왜 필요한가요??
프로젝트 1으로 제일 먼저 만나는 친구인 timer_sleep() 개선 문제부터 이 인터럽트 관련 처리를 해야 합니다.
enum intr_level old_level = intr_disable(); // 인터럽트 차단
list_insert_ordered(&sleep_list, &t->elem, wake_tick_less, NULL);
thread_block();
intr_set_level(old_level); // 인터럽트 부활
안 그러면 thread_block()이 정상적으로 작동하지 않습니다.
왜일까요?
결국 timer_sleep()에서 하는 일은 스레드를 재우는 일입니다. 그 뜻은 현재 CPU에 올라간 스레드들의 상태를 저장하고 다른 스레드로 yield 해야 하는 겁니다. 방금 말한 이 과정이 Thread Context Switching 이죠. 현재 스레드를 저장해야 하는데 인터럽트가 들어와 CPU가 스레드를 그냥 버리고 인터럽트를 처리하면 어떻게 될까요?? 인터럽트가 처리가 된 후에 다시 돌아왔을 땐, 이미 스레드를 사라지고 없을 겁니다. 저장을 안 했기 때문이죠. 그래서 Context Switching 하는 과정에선 반드시 인터럽트를 차단하고 실행해야 합니다.
인터럽트 차단 함수 intr_disable()
enum intr_level
intr_disable (void) {
enum intr_level old_level = intr_get_level ();
/* Disable interrupts by clearing the interrupt flag.
See [IA32-v2b] "CLI" and [IA32-v3a] 5.8.1 "Masking Maskable
Hardware Interrupts". */
asm volatile ("cli" : : : "memory");
return old_level;
}
// 사용 예시
enum intr_level old_level = intr_disable();
// 하고 싶은 일하기
// ...
intr_set_level(old_level);
이 차단을 해주는 함수가 intr_disable() 입니다. 여기서 주목해야 하는 건 return 입니다. 이전 인터럽트 상태를 반환합니다. 그래서 이 이전 인터럽트 상태를 따로 저장해 두고 disable() 하고 해야 할 일을 다 처리하고 나서 이 상태를 set 해주면 됩니다. 그러면 다시 인터럽트를 받을 수 있게 활성화해 주는 것이죠.
인터럽트 상태(Interrupt Level)에 대하여
사실 Pintos에선 두 가지 상태밖에 없습니다. INTR_ON 과 INTR_OFF가 그 주인공들인데 이름 보시면 유추하실 수 있듯이 INTR_ON 은 인터럽트 허용 상태, INTR_OFF은 인터럽트 비활성화 상태입니다.
참고) intr_enable() 함수도 존재합니다
인터럽트를 다시 허용하는 함수입니다.
인터럽트 상태 설정 함수 intr_set_level()
enum intr_level
intr_set_level (enum intr_level level) {
return level == INTR_ON ? intr_enable () : intr_disable ();
}
인터럽트 상태를 직접 설정하는 함수입니다.
일반적으로 intr_disable() 이후 이전 상태 복원용으로 사용됩니다.
인터럽트 상태 확인 함수 intr_get_level()
enum intr_level
intr_get_level (void) {
uint64_t flags;
/* Push the flags register on the processor stack, then pop the
value off the stack into `flags'. See [IA32-v2b] "PUSHF"
and "POP" and [IA32-v3a] 5.8.1 "Masking Maskable Hardware
Interrupts". */
asm volatile ("pushfq; popq %0" : "=g" (flags));
return flags & FLAG_IF ? INTR_ON : INTR_OFF;
}
인터럽트 상태를 반환하는 함수입니다.
인터럽트 핸들러 등록 함수 intr_register_ext()
외부 인터럽트 발생 시 실제로 실행될 함수(handler)를 등록합니다.
void
timer_init(void) {
/* 8254 input frequency divided by TIMER_FREQ, rounded to
nearest. */
uint16_t count = (1193180 + TIMER_FREQ / 2) / TIMER_FREQ;
outb(0x43, 0x34); /* CW: counter 0, LSB then MSB, mode 2, binary. */
outb(0x40, count & 0xff);
outb(0x40, count >> 8);
list_init(&sleep_list);
intr_register_ext(0x20, timer_interrupt, "8254 Timer");
}
타이머 초기값을 설정하는 함수 timer_init()에서 등장하는 함수입니다.
이 함수 때문에 실제로 타이머가 작동하게 되는 겁니다.
여기서 0x20은 타이머 인터럽트 번호입니다.
void
intr_register_ext (uint8_t vec_no, intr_handler_func *handler,
const char *name) {
ASSERT (vec_no >= 0x20 && vec_no <= 0x2f);
register_handler (vec_no, 0, INTR_OFF, handler, name);
}
내부적으로 보면 handler를 생성해서 작동되게 합니다.