문제 정의: Priority Inversion
- 상황: 낮은 우선순위의 스레드 A가 Lock을 잡고 있는 상태에서, 높은 우선순위의 스레드 B가 같은 Lock을 요청하면 B는 대기 상태가 된다. 그런데 A는 더 낮은 우선순위의 스레드 C에게 CPU를 빼앗기게 되어, 높은 우선순위인 B는 오히려 무한정 기다리는 상황이 발생할 수 있다.
- 문제: 우선순위 역전(Priority Inversion)

해결 방법: Priority Donation
- 높은 우선순위의 스레드가 낮은 우선순위의 스레드에게 자신의 우선순위를 일시적으로 기부(donate) 하는 방식
- donation은 단순한 경우뿐만 아니라 nested donation (chain) 도 처리해야 함

우선 먼저 원래의 우선순위를 저장해놓을 변수와 어떤 락을 기다리는 중인지, 현재 잡고 있는 락은 무엇인지 저장해놓을 것들을 구조체에 넣어줍니다
// thread.h
struct thread {
...
int init_priority; // 원래 우선순위 저장
struct lock *waiting_on_lock; // 어떤 락 기다리는 중인지
struct list hold_list; // 현재 잡고 있는 락 리스트
...
};
Lock 획득 시 Priority 기부 / Lock 획득 시 기록
lock_acquire()
void
lock_acquire (struct lock *lock) {
ASSERT (lock != NULL); // lock이 NULL이 아닌지 확인
ASSERT (!intr_context ()); // 인터럽트 핸들러 안에서 호출되지 않아야 함
ASSERT (!lock_held_by_current_thread (lock)); // 현재 스레드가 이미 이 락을 가지고 있어선 안 됨
struct thread *curr = thread_current(); // 현재 실행 중인 스레드
// [Priority Donation 처리]
if (lock->holder != NULL) { // 이미 누군가 이 락을 들고 있다면
curr->waiting_on_lock = lock; // 현재 스레드는 이 락을 기다리는 중이라고 표시
donate_priority(); // 우선순위 기부 발생 (nested donation도 처리됨)
}
sema_down(&lock->semaphore); // 락 획득을 위해 세마포어 down (0이면 BLOCK 대기)
// 세마포어 획득이 끝나고 나면 락을 얻은 상태
curr->waiting_on_lock = NULL; // 더 이상 락을 기다리지 않음
lock->holder = curr; // 이제 이 락의 소유자는 현재 스레드
list_push_back(&curr->hold_list, &lock->elem); // 현재 스레드가 가지고 있는 락 목록에 추가
}
- 누군가 락을 이미 들고 있으면, 나는 그 락을 기다리는 입장이 됨
- 내가 현재 기다리는 락 (waiting_on_lock)을 기록하고,
- 락을 들고 있는 애한테 내 우선순위를 기부힘
- 락을 성공적으로 획득했으면, 이제 더 이상 기다리는 상태가 아님 (waiting_on_lock = NULL)
- 내가 이 락을 소유함을 기록
- 나중에 refresh_priority()에서 hold_list를 기반으로 우선순위를 다시 계산하기 위해 기록함
Priority 기부 로직
donate_priority()
void donate_priority(void) {
struct thread *curr = thread_current(); // 현재 실행 중인 스레드
struct lock *lock = curr->waiting_on_lock; // 현재 스레드가 기다리고 있는 락
if (lock == NULL) return; // 기다리는 락이 없다면 기부할 상황이 아님
struct thread *holder = lock->holder; // 락을 소유하고 있는 스레드
if (holder == NULL) return; // 락의 소유자가 없다면 기부할 대상이 없음
// nested donation (우선순위 기부의 전이) 처리
while (lock != NULL &&
lock->holder != NULL &&
lock->holder->priority < curr->priority) {
// 현재 스레드의 우선순위가 더 높다면 holder에게 기부
lock->holder->priority = curr->priority;
// holder를 기준으로 다시 donation 확인 (chain donation을 위해)
curr = lock->holder;
lock = curr->waiting_on_lock; // holder가 또 다른 락을 기다리는 경우 다음 단계로 진행
}
}
- 내가 락을 기다리고 있다면, 그 락을 들고 있는 애에게 우선순위 기부
- 기부를 받은 애가 또 다른 락을 기다리고 있으면 → 그 락의 holder에게 또 기부 (nested donation)
- 최대 깊이 제한은 걸지 않았지만, 필요시 depth를 두어 제한 가능
Lock 해제 시 우선순위 복구
lock_release()
void
lock_release (struct lock *lock) {
ASSERT (lock != NULL);
ASSERT (lock_held_by_current_thread (lock)); // 현재 스레드가 해당 락의 소유자인지 확인
// 현재 스레드의 hold_list에서 해당 락 제거
list_remove(&lock->elem);
// donation이 있었다면, 원래 우선순위로 복구하거나, 남은 donation 기준으로 재계산
refresh_priority();
// 이제 더 이상 이 락의 소유자가 아님을 명시
lock->holder = NULL;
// 락을 기다리고 있는 스레드 중 하나를 깨워 READY 상태로 만듦
sema_up (&lock->semaphore);
}
- 내가 소유하고 있는 락 리스트에서 해당 락 제거
- 우선순위 복구 필요 → refresh_priority() 호출
- 내 우선순위는 더 이상 누군가의 기부가 반영되지 않게 바뀜
우선순위 복구 로직
void refresh_priority(void) {
struct thread *curr = thread_current(); // 현재 실행 중인 스레드를 가져옴
int max_priority = curr->init_priority; // 기본 우선순위(init_priority)를 기준으로 시작
struct list_elem *e;
for (e = list_begin(&curr->hold_list); e != list_end(&curr->hold_list); e = list_next(e)) {
// curr가 현재 보유하고 있는 모든 락에 대해 순회
struct lock *l = list_entry(e, struct lock, elem); // hold_list의 요소를 lock 구조체로 캐스팅
if (!list_empty(&l->semaphore.waiters)) {
// 해당 락을 기다리는(waiters) 스레드가 있다면
struct thread *t = list_entry(list_front(&l->semaphore.waiters), struct thread, elem);
// waiters 리스트에서 가장 우선순위가 높은(정렬되어 있으므로 맨 앞) 스레드 선택
if (t->priority > max_priority)
max_priority = t->priority; // 가장 높은 우선순위로 갱신
}
}
curr->priority = max_priority; // 갱신된 우선순위를 현재 스레드에 반영
}
- 내가 가지고 있는 모든 락의 waiters 중 가장 높은 우선순위를 찾아, 내 priority를 재설정한다
- 내가 직접 변경한 init_priority보다 높다면 → 우선순위는 기부 상태 유지
- 모든 락이 해제되면 → 다시 원래 우선순위 (init_priority)로 복귀
donation을 고려해 현재 우선순위를 갱신
void thread_set_priority(int new_priority)
{
struct thread *curr = thread_current();
curr->init_priority = new_priority;
if (list_empty(&curr->hold_list)) {
// 도네이션 받은 게 없다면 즉시 반영
curr->priority = new_priority;
} else {
// 도네이션 받은 게 있을 수 있으므로 우선순위 재계산
refresh_priority();
}
thread_test_preemption(); // priority가 변경되었으니까 우선순위 변경 확인
}
- 현재 스레드의 init_priority를 갱신
- init_priority는 donation 전 원래 스레드의 우선순위를 기억해두는 값
- 즉, 이 값은 항상 “본래 우선순위”를 뜻함
- 그리고 나서 현재 스레드가 어떤 락도 안 들고 있다면, 즉 donation을 받지 않은 상태라면:
- 그냥 priority = new_priority를 해도 괜찮음 (기부 받은 게 없으니 바로 반영 가능)
- 반대로 hold_list에 무언가 있다면, 누군가 나한테 donation을 줬을 수 있으니까:
- refresh_priority()를 호출해서 donation 받은 스레드들의 우선순위를 고려해서 현재 우선순위를 재계산함
- 마지막으로 thread_test_preemption()을 호출해서,
- 지금 ready_list에 나보다 높은 우선순위 스레드가 있다면, CPU를 양보할지 판단하게 함
자 이렇게 해서 Alram Clock부터해서 Donation까지 구현이 끝났습니다
여기까지 마친다면

이렇게 패스가 뜨는걸 볼 수 있습니다
다음엔 트러블슈팅으로 돌아오겠습니다
'크래프톤정글 > Pintos' 카테고리의 다른 글
| User Program _ Argument Passing (1) | 2025.05.17 |
|---|---|
| User Program _ main부터 (0) | 2025.05.17 |
| 지금까지의 개념 정리_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 |