멀티스레드 사용 시 여러개의 스레드들은 실행 순서에 따라 실행되거나 대기하거나 sleep(block)될 수 있다.

레드는 크게 위와 같은 4가지 상태로 존재할 수 있다.
기존 timer_sleep은 특정 시간이 될 때까지 while 문을 반복하며 thread_yield()를 호출하여 CPU를 낭비했습니다. 이를 해결하기 위해, 스레드를 block 상태로 전환하여, 스케줄링에서 제외시키는 방식으로 개선합니다.
Block 상태로 변경: 스레드를 block 상태로 전환하여 CPU 사용을 줄입니다.
Sleep 리스트 추가: 깨어날 시간이 있는 스레드를 sleep_list에 저장하여, 각 스레드의 깨어날 시간을 관리합니다.
Timer Interrupt 활용: timer_interrupt에서 1 tick마다 깨어날 시간이 된 스레드를 ready state로 전환합니다.
기존 "Busy waiting" 방식을 -> "Blocking (sleeping)" 방식으로 변경하는 것이 핵심
자원이 준비될 때까지 프로세스나 스레드가 block 상태로 들어가며, 이 상태에서는 CPU를 전혀 사용하지 않습니다. 자원이 준비되면 OS가 자동으로 wake-up 시켜 실행 가능 상태로 전환합니다.
주로 스레드나 프로세스를 지정된 시간 동안 대기시키며, 이 시간 동안 CPU를 사용하지 않는 상태로 둡니다. 지정된 시간이 지나면 스레드는 자동으로 깨어납니다.
Project1의 알람 기능 개선을 위해서
기본 소스 코드 상에서
thread.h
thread.c
timer.c
를 수정할 필요가 있다.
스레드가 깨어날 시간을 저장하는 wakeup 필드를 추가합니다.
/* thread/thread.h */
struct thread {
...
int64_t wakeup; // 깨어나야 할 tick 시간
...
};
새로운 sleep_list를 추가하여, block 상태인 스레드를 관리합니다.
/* thread/thread.c */
static struct list sleep_list;
void thread_init(void) {
...
list_init(&sleep_list); // sleep_list 초기화
...
}
thread_sleep 함수는 현재 스레드를 sleep_list에 추가하고, block 상태로 변경합니다. 이 함수는 timer_sleep에서 호출됩니다.
/* thread/thread.c */
void thread_sleep(int64_t ticks) {
struct thread *cur = thread_current();
enum intr_level old_level;
old_level = intr_disable(); // 인터럽트 비활성화
ASSERT(cur != idle_thread); // idle 스레드는 sleep 되지 않도록 예외 처리
cur->wakeup = ticks; // 깨어날 시간 설정
list_push_back(&sleep_list, &cur->elem); // sleep_list에 추가
thread_block(); // 스레드를 block 상태로 전환
intr_set_level(old_level); // 인터럽트 활성화
}
timer_sleep은 thread_sleep을 호출하여 block 상태로 스레드를 전환하고, 깨어날 시간을 설정합니다.
/* devices/timer.c */
void timer_sleep(int64_t ticks) {
int64_t start = timer_ticks();
thread_sleep(start + ticks); // 깨어날 시간 지정
}
thread_awake 함수는 sleep_list의 모든 스레드를 확인하여 깨어날 시간이 된 스레드를 ready 상태로 전환합니다.
/* thread/thread.c */
void thread_awake(int64_t ticks) {
struct list_elem *e = list_begin(&sleep_list);
while (e != list_end(&sleep_list)) {
struct thread *t = list_entry(e, struct thread, elem);
if (t->wakeup <= ticks) { // 깨어날 시간이 되면
e = list_remove(e); // sleep_list에서 제거
thread_unblock(t); // 스레드를 unblock 상태로 전환
} else {
e = list_next(e);
}
}
}
매 tick마다 thread_awake 함수를 호출하여 깨어날 시간이 된 스레드를 찾아 깨웁니다.
/* devices/timer.c */
static void timer_interrupt(struct intr_frame *args UNUSED) {
ticks++;
thread_tick();
thread_awake(ticks); // 매 tick마다 awake 작업 수행
}
수정 이유:
스레드 상태 관리에서 interrupt를 비활성화하고 활성화하는 작업이 빈번히 필요합니다. 특히, sleep 상태의 스레드를 깨어나게 하는 timer_interrupt와 관련이 깊습니다.
수정 방법:
인터럽트 비활성화 및 활성화가 자주 호출되는 코드에서 성능 최적화가 가능한지 확인하고, 인터럽트 핸들링 중 타이머 관련 기능을 더 정밀하게 관리할 수 있도록 수정할 수 있습니다.
수정 이유:
스레드 간 동기화 문제를 해결하기 위한 파일로, 세마포어, 락, 조건 변수 등의 구조체와 관련 함수들이 정의되어 있습니다. 특히, 스레드가 잠드는 동안 공유 자원을 안전하게 관리하려면 동기화가 필요합니다.
수정 방법:
스레드가 sleep 상태에서 ready 상태로 전환될 때 사용될 락이나 조건 변수를 최적화하여 데드락 방지 및 성능 개선을 도모할 수 있습니다. 또한, 스레드가 깨울 때 발생할 수 있는 경쟁 상태를 해결하기 위한 동기화 로직을 추가할 수 있습니다.
수정 이유:
Pintos의 리스트 자료구조는 스레드 관리에 중요한 역할을 합니다. 예를 들어, sleep_list, ready_list 등이 모두 리스트로 관리되므로 효율적인 리스트 조작은 성능에 큰 영향을 미칩니다.
수정 방법:
sleep_list에서 깨워야 하는 스레드를 효율적으로 찾기 위해 리스트를 정렬된 상태로 유지하거나, 이진 탐색과 같은 알고리즘을 적용하여 성능을 개선할 수 있습니다.
수정 이유:
스레드를 어떤 순서로 실행할지를 결정하는 스케줄링 정책을 다룹니다. 스레드가 sleep 상태에서 깨어났을 때의 우선순위 관리가 중요합니다.
수정 방법:
스레드를 깨울 때 우선순위를 기반으로 ready_list에 삽입하거나, 우선순위 역전을 방지하기 위해 Priority Inheritance와 같은 기능을 추가하여 우선순위 기반의 효율적인 스케줄링을 구현할 수 있습니다.
수정 이유:
Pintos 시스템 초기화와 관련된 설정 파일로, 스레드나 타이머의 초기 설정이 이루어집니다. 특히 main 함수에서 스레드 관련 모듈 초기화가 수행됩니다.
수정 방법:
시스템이 부팅될 때, sleep_list와 같은 추가 리스트를 초기화하거나, 타이머 주기를 조정하여 시스템 전체 성능에 맞게 최적화할 수 있습니다.
수정 이유:
Pintos에서 페이지 단위로 메모리를 할당 및 해제하는 코드가 포함되어 있습니다. 스레드가 새로운 메모리를 필요로 할 때, 이 할당기를 통해 메모리를 요청하게 됩니다.
수정 방법:
스레드 관리 시 필요할 수 있는 메모리 할당을 최적화하거나, 메모리 누수가 발생하지 않도록 메모리 해제 루틴을 개선할 수 있습니다.
수정 이유:
타이머 주기, 인터럽트 관련 변수들이 선언되어 있으며, timer_sleep의 인터페이스를 제공합니다.
수정 방법:
timer.h에 새로운 타이머 주기 상수나 매크로를 추가하여 시스템 설정을 보다 세밀하게 조정할 수 있습니다. 필요 시 thread.h에 정의된 타이머 관련 변수와 연동하여 효율성을 높일 수 있습니다.