/////
Search

Project 1: Threads

Threads

대충 읽었을 때 느낀 점
1.
일단 스케쥴러가 뭔지 모른다
2.
ready, sleep까지는 이해를 하지만, thread 자료 구조와 그 함수에 대한 이해가 없다.
3.
alarm 구현을 위한 tik과 interrupt가 뭔지 모른다. 이를 위한 함수도...
4.
priority 스케쥴러의 방식
5.
synchronization이 뭐냐
6.
thread block이 뭐지
7.
priority inversion 개념이 뭐냐..?
4단원까지 해서 내가 할 수 있는건 alarm까지 밖에 없구나 빠른 시일 내에 alarm 까지 하도록 하자

읽어야하는 내용들

참고 블로그

Alarm Clock

쓰레드의 실행

thread_init()함수가 실행이 되고, thread_start()가 돈다
이러면 idle_thread가 실행이 되고, 이 idle_thread는 여러 쓰레드를 실행시킬 수 있는 최상위 쓰레드이다.
자, 이 때 하나의 쓰레드만 running 상태를 가지는데, 다른 쓰레드로 넘기려면 스케쥴링이 필요하다.
그냥 가만히 놔두면 한 쓰레드가 계속 CPU를 점유할텐데 이것을 방지하기 위해, tick을 이용해서 timer interrupt를 발생시켜준다. timer_interrupt()함수 안에 thread_tick()이란 함수가 있는데 여기서 time_slice보다 tick이 커지면, intr_yield_on_return이라는 함수가 실행되도록 되어있다. 어쨋든 이건 thread_yield를 호출함(이건 debug해보면 알겠지)
즉, 일정 시간마다 scheduling이 발생한다.
자세한 작동은
참고.

Busy Waiting

자 지금은 어떻게 되있냐
process A가 있고 process B가 있다. 이 때 process A가 실행중이고, B는 재워놨다.
그러면 현재 총 3개의 프로세스가 있음. idle, A, B
B를 sleep 시킨다면, A만 계속 실행되는게 맞다. 하지만 busy waiting은 sleep이 호출되는 즉시, ready queue에 들어간다. 왜냐면 thread_yield가 호출되니까. 하지만 조금 지나고 time_slice에 의해서 다시 A가 running 상태가 될꺼다. 그럼 또 시간 체크해서 자기를 ready 상태로 보낸다.
즉, A,B(시간 체크). A,B(시간 체크) 형태로 B가 제대로 못 쉬게 된다.
만약 A랑 idle만 있다고 한다면, 유휴 idle이 없게 될꺼다. 사실상 running queue에 A밖에 없다가, Ready queue로 빠진다면, running해야하는 프로세스가 존재해야하므로, A를 자동으로 running으로 만들것이기 때문. 따라서 A는 그냥 계속 시간 체크만 한다(CPU에서 내려오지 못하고, 시간 체크만 하는것)
즉, 우리는 코드를 조금 수정하여, idle 쓰레드가 running을 점유하게끔 하는 것이 목표임.
그렇다면 해야할 거
1.
일단 timer_sleep코드를 수정해야함
a.
while문을 통해서 시간 체크를 하지 않도록
b.
sleep 하면 wait queue에 추가시키자.
2.
그럴려면 wait queue를 만들어야함.
3.
wait queue를 관리하는 어떤 프로세스에서 각 element에 대한 시간 체크를 할 수 있도록
a.
즉 awake를 작성해야한다.
b.
만약에 시간 체크를 했을 때, 일어나야할 시간이라면 ready queue에 넣는다.

건드려야 할 부분

1.
struct thread
a.
sleep_list를 추가
2.
timer_sleep() 호출
a.
thread를 ready_list에서 제거, sleep queu에 추가
3.
wake up 수행
a.
timer interrupt가 발생시 tick 체크
b.
시간이 다된 thread는 sleep queue에서 삭제하고 ready_list에 추가

건드려야할 함수

1.
thread_init() → sleep_list를 init 시켜야함
2.
thread_sleep()

우선순위 스케쥴링

핀토스에서 priority scheduling을 실행하는 것이다.
쓰레드가 ready list에 추가될 때 running thread보다 우선순위가 더 높다면, 현재 쓰레드는 즉시 프로세서를 양보해야한다. 비슷하게, 만약 쓰레드가 lock, semaphore, condition variable를 기다리고 있다면, 높은 순위의 waiting thread가 먼저 깨어나야한다.
쓰레드는 스스로의 순위를 올리거나 내릴 수 있지만, lowering priority는 즉시 CPU를 양보하는 효과를 가져온다.
쓰레드는 PRI_MIN에서 PRI_MAX까지의 범위를 가진다. 낮은 순서는 낮은 우선순위가 부여되므로 0이 제일 낮은 거고, 63이 제일 높은거다. initial thread priority는 thread_create()의 argument로 들어간다. 다른걸 선택할 이유가 없다면 기본적으로 PRI_DEFAULT(31)을 쓴다. PRI_ macros는 threads/thread.h에 정의되어있고, 바꿀 수 없다.
한 가지 이슈는 바로 ‘우선순위 역전'이다. 상위,중간,하위 우선순위를 가진 애들을 생각해보자. H가 L을 위해 기다리고 있고(예를 들어 L이 락을 들고 있다), M이 ready list에 있을 때, H는 절대 CPU를 얻지 못한다. 왜냐하면 low priority가 절대 CPU타임을 얻지 못하기 때문이다. 이 문제를 해결하는 방법은 L이 락을 들고 있는 동안, H가 L에게 우선순위를 donate하고, 그리고 L이 락을 놓아줄 때 donation을 회수하는 것이다.
priority donation을 구현해라. 우선순위 양보가 필요한 모든 상황을 고려해야할 거다. multiple donation도 고려해야하는데, 이거는 한 쓰레드에게 여러 priority가 양보되는 걸 의미한다. 너는 nested donation도 해결해야한다. (만약 H가 M이 들고 있는 락을 기다리고 있고, M이 L이 들고 있는 락을 기다린다면, M과 L모두 H의 우선순위로 boosted되야할 것이다. 필요하다면 nested priority donation에 대해 깊이를 limit을 걸 수 있을 것이다.)
락에 대한 priority donation을 구현해야한다. 다른 synchronization constrcts에 대해서는 구현할 필요가 없다. 마지막으로 아래의 함수를 구현해서 쓰레드가 그들의 순위를 평가하고 수정하도록 해라. 이 함수들의 skeleton은 threads/thread.c에 주어져있다.
void thread_set_priority(int new_priority)
current thread의 priority를 new_priority로 맞춘다. 현재 thread가 가장 높은 순위가 아니라면 양보한다.
int thread_get_priority(void)
현재 쓰레드의 우선순위를 뱉는다. priority donation이 있는 상태에서는, 높은 priority를 뱉는다.

FAQ

1.
기아 상태를 유발하지 않냐?
a.
맞다. advanced scheduler는 dynamically changing thread priorities를 제공한다.
b.
strict priority scheduling 는 real-time system에서 유용하다. it offers the programmer more control over which jobs get processing time
2.
락이 풀린 뒤에는 어떤 쓰레드가 run합니까?
a.
락이 해제되면, 해당 락에 대해 waiting에서 가장 높은 우선순위를 가지고 있는 쓰레드가 unblocked되고 ready threads에 추가되야한다. 스케쥴러는 그러면 가장 높은 우선순위 쓰레드를 ready list에서 꺼내 쓴다.
3.
만약 highest-priority가 양보된다면, 계속 running을 하냐?
a.
그렇다. 만약 single highest-priority thread가 있다면, 이것은 block되거나 finishes 되기 전까지 계속 running할거다. 그것이 thread_yield를 할지라도. 만약 multiple thread가 같은 highest priority를 가지고 있다면 thread_yield는 round robin 방식으로 스위칭할거다.
4.
priority를 양보한 쓰레드의 priority는 어떻게 되냐?
a.
priority donation은 오직 양보받은 쓰레드의 priority만 바꾼다. 양보한 쓰레드의 우선순위는 바뀌지 않는다. 그리고 priority donation은 additive 하지 않다. 만약 5짜리가 3한테 5를 양보한다해도, 5가 되지 3이 되진 않음.
5.
thread priority가 ready queue에 있는 와중에도 바뀔 수 있느냐.
a.
맞다. 만약 ready queue에 어떤 L이 락을 가지고 있을 때, 높은 우선순위의 H가 락을 획득하려고 할거고 블락된다면, 그것의 priority를 L에게 줄거다.
6.
thread’s priority가 블락된 상황에서도 바뀔 수 있느냐
a.
그렇다. 만약 락 L을 획득하려고 하는 쓰레드가 어떤 이유로 인해 블락됬다면, 이것의 우선순위는 priority donation을 통해 증가될 수 있다. 만약 높은 우선순위인 애들이 L을 획득하고자 한다면. 이것은 priority-donate-sema 테스트에 의해 체크 될거다.
7.
ready list에 있는 쓰레드가 프로세서를 선점할 수 있느냐?
a.
그렇다. 만약 ready list에 어떤 쓰레드가 추가됬고, 그것이 지금 쓰레드보다 우선순위가 높다면, 정확한 행동은 현재 쓰레드가 즉시 프로세서를 양보하는 게 맞다. 그 다음 timer interrupt까지 기다리는 건 허용되지 않는다. 높은 우선순위 쓰레드는 될 수 있는한 빨리 실행되어야하고, 어떤 쓰레드가 지금 실행되든 간에 프로세서를 선점해야한다.
8.
thread_set_priority()가 donation을 받는데 있어 어떤 영향을 주느냐
a.
그건 thread의 base priority를 설정한다. 그 쓰레드의 effective prirority는 새로 설정되는 priority가 되거나 가장 높게 donated 된 priority일 것이다. 만약 donation이 풀린다면, 쓰레드의 priority는 이 함수를 통해 set 되야할 것이다. priority-donate-lower 테스트에서 검증할 것.
9.
테스트가 이상해보이면 그냥 일단 이상한거임

해야하는 것들

Priority Scheduling

ready_list를 우선순위로 정렬
thread_unblock() — implement
unblock 되서 ready_list에 삽입될 때, 우선순위로 정렬되도록.
thread_yield() — implement
양보될 때, 우선순위 순서로 정렬되서 ready_list에 삽입되도록
list_insert_ordered()함수에 사용되는 cmp_priority() — implement
ready_list에 insert시켜서 정렬시킬 때, 어떤 걸 기준으로 정렬할지
새로 생성되는 애들 선점
thread_create() - implement
새로 생길 때 현재 쓰레드와 비교했을 때 높으면 CPU를 양보하도록
thread_set_priority() - implement
ready_list에서 조정됬을 때 우선순위에 따라 선점이 생기도록
test_max_priority() - implement
새로 쓰레드가 생성되거나, ready_list의 쓰레드의 우선순위가 변경되었을 때, 가장 높은 우선순위의 쓰레드의 우선순위를 비교해서 선점할 수 있도록

Priority Scheduling and Synchronization

여러 스레드가 lock,semaphore, condition variable을 얻기 위해 기다릴 경우 우선순위가 가장 높은 thread가 CPU를 점유하도록 구현 → 현재는 FIFO로 구현되어있다.
waiters의 리스트를 우선순위로 정렬하도록 수정
sema_down() 함수 수정
waiter 리스트 삽입 시 우선순위대로 삽입되도록
sema_up() 함수 수정
스레드가 waiters list에 있는 동안 우선순위가 변경되었을 수 있다.
따라서 waiters list를 우선순위로 정렬해야한다,
priority preemption 코드를 추가해야한다..?
cond_wait() 함수 수정
condition variable 의 waiter list에 우선순위 순서로 삽입되도록 수정
cond_signal() 함수 수정
condition variable의 waiters list를 우선순위로 재 정렬
대기 중에 우선순위가 변경되었을 가능성이 있다.
cmp_sem_priority() → 세마포어에서 우선순위를 비교하도록
Cond안에 waiters
쓰레드 하나당 cond_wait를 하면 쓰레드만의 고유한 semaphore_elem이 생김
cond_wait를 하면 쓰레드만의 고유한 semaphore_elem에서 list_elem을 cond의 waiters에 밀어넣음
그리고 semaphore_elem의 semaphore를 sema_down을 시킴
이 semaphore_elem의 semaphore에도 waiters가 있는데, sema_down을 시키면, sema_down을 시킨 현재 실행중인 쓰레드가 해당 semaphore의 waiters로 들어간다. 따라서 쓰레드당 할당된 semaphore_elem에 자기 자신을 밀어넣는 식.
cond_signal을 하면 현재 cond에서 가지고 있는 waiters의 맨 앞에 있는 거를 빼서 semaphore_elem으로 변환시키고, 이 semaphore_elem의 semaphore를 sema_up 시킴
그러면 해당 semaphore에 걸려있는 쓰레드가 unblock된다.

Priority Inversion Problem

1.
priority donation
a.
H,M,L이 있다.
b.
L이 락을 소유하고 있다.
c.
H가 락을 요청하고, 자버린다.
d.
스케쥴링이 발생한다.
e.
M이 실행된다. M이 엄청 많으면 엄청 오래 M이 실행된다.
f.
그러다가 L이 끝나고 락을 반납한다.
g.
H가 일어나서 실행된다.
h.
해결법
i.
L이 락을 소유하고 있고, H가 락을 요청하면, L에게 H의 우선순위를 donation한다.
ii.
그러면 M이 들어와도 L이 계속 실행될 것
iii.
그렇게 빨리 끝내고 락을 반납하면, H가 그 락을 받아서 쭉 실행
2.
Multiple donation
a.
L,M,H가 있다.
b.
L이 락 A,B를 소유하고 있다.
c.
M이 A를 요청한다. 그러면 L은 M의 우선순위가 되어서 일단 일을 한다.
d.
그러다가 갑자기 H가 B를 요청한다. 그러면 L은 H가 되어서 일단 일을 한다.
e.
그러다가 다 끝나면 L은 어디로 가야하는가? M의 우선순위로 가야한다.
i.
왜?
ii.
만약 그냥 L로 돌아가버리면 M이 요청한 락을 L이 들고 있는 상황 발생 → priority inversion
f.
자료 구조
g.
L
3.
Nested donation
a.
L,M,H가 있다.
b.
L이 락 A를 소유한다.
c.
M이 락 B를 소유한다.
d.
L이 실행되다가 M이 락 A를 요청한다.
e.
그러면 donation되어서 L이 M의 우선순위가 되어서 실행된다.
f.
그러다가 갑자기 H가 락 B를 요청한다.
g.
그러면 M,L은 어떻게 되야하는가?
i.
둘 다 H가 되야한다.
ii.
M만 H가 되버려봐야 아무 의미가 일단 없다. M은 A를 필요로 하는데 L은 현재 M이기 때문에 그 사이에 중간 우선순위가 들어와버리면 또 priority inversion
iii.
마찬가지로 L만 H가 되버려봐야 의미가 없다. L이 다 끝나면 락 A를 반환할텐데, 그러면 M이 락 B를 들고 H가 락 B를 요청하는 상황 → priority inversion. M에게 H를 donation 시켜야함
h.
따라서 M,L 둘다에게 H의 우선순위를 줘야한다.
i.
그리고 나서 L은 다 끝나면 L로, M은 다 끝나면 M의 우선순위로 간다.
현재 lock_acquire()를 하면 마지막에 sema_down을 해서 해당하는 락의 waiter 리스트에 자신을 추가하고, 자버림. 이런 행동이 한 묶음이어서, 할거면 이 행동 전에 뭔가를 해야한다.
따라서 sema_down()하기 전에, 내가 지금 기다리는 락의 홀더를 찾아서 내 우선순위로 높여줘야함.
다만, 여기서 nested donation을 구현하려면 H가 기다리는 락의 홀더(M)을 찾고, M이 기다리고 있는 락의 홀더(L)을 찾아서 모두 H 우선순위로 donate해야한다. M까지는 금방 찾을 수 있는데, M을 이용해서 모든 세마포어를 돌면서 현재 M이 어느 세마포어 waiters에 저장되었는지 알 방법이 없으모로 M이 기다리는 락이 뭔지를 나타내는 wait_on_lock 변수를 포함해야함.
그렇다면 어떻게 donate할 것이냐.
이전에 donate된 우선순위들의 목록을 가지고 있어야하기 때문에, 각 쓰레드는 donate 된 리스트를 일단 가지고 있어야한다.
M이 L에게 donate, L이 H에게 donate한다면 L의 donate 목록에 M과 H가 연결되어있어야함.
따라서 각 쓰레드는 d_elem이라는 donate list_elem과 donations 리스트를 들고 있어야함.
만약 L이 lock을 release 한다면 이 donations 리스트에서 삭제해야한다. release 할 lock을 기다리고 있는 쓰레드의 d_elem을 리스트에서 삭제.
그렇다면 donate를 하면 실제로 쓰레드의 우선순위가 높아져야하는데, 그건 어떻게 할거냐?
→ 일단 맨 처음에 세팅된 priority를 저장하는 변수가 있어야한다.
→ 그리고 현재 priority를 바꿔준다.
lock_acquire() → donate_priority() → readylist 정렬
lock_release() → remove_with_lock() & refresh_priority()
thread_set_priority()
init_thread()
donate_priority()
wait하는 락의 holder를 따라가면서 priority를 바꿔줌.
priority만 바꿔주는 거지 donation list에 추가하는 것은 아니다
remove_with_lock(struct lock * lock)
lock_release()할 때 호출
지금은 lock→holder를 null로 바꾸고, sema_up을 함.
donation_list에서 지워야함.
release 할 lock을 기다리고 있는 쓰레드의 d_elem을 리스트에서 삭제
refresh_priority()
donation_list를 업데이트했으니, 이제 그 다음 priority가 뭐가 되야할지를 결정
현재 가지고 있는 priority보다, donation list에 있는 게 크면 그걸로 하면 됨.
그렇다면 donation list에 추가할 때 우선순위대로 정렬해서 넣어야겠다.

Advanced Scheduler

BSD scheduler와 비슷한 multilevel feedback queue scheduler 구현해보자.
Appendix B의 91페이지를 읽어봐라. 더 자세한 requirements를 원한다면.
priority scheduler와 같이 advanced scheduler 또한 우선순위를 기반으로 쓰레드를 읽는다. 그렇지만 priority donation은 안 한다. 그러힉에, 우리는 priority donation을 거놓은 priority scheduler를 준비하기를 바란ㄷ.
너는 핀토스가 시작할 때 scheduling algorithm을 선택할 수 있도록 코드를 짜야한다.
디폴트로, priority scheduler가 기본으로 돌아가지만, 너는 4.4BSD Scheduler를 ‘-mlfqs’ 커널 옵션을 줘서 선택할 수 있도록 해야한다. 이 옵션은 parse_option()을 실행할 때, thread.h에 있는 thread_mlfqs를 true로 설정한다.
만약 4.4BSD scheduler가 가능해진다면, 쓰레드는 더 이상 그들의 own priority를 직접 조작하지 않는다. thread_create()에 있는 priority argument는 무시되고, thread_set_priority()와 thread_get_priority()로 호출되는 것들은 전부다 스케쥴러에 의해 설정된 current priority를 반환한다.
advanced scheduler는 이 이후의 프로젝트에서 쓰지 않는다.

공통 FAQ

timer interrupts 간의 interval은 무엇이냐
timer interrupt는 1초동안 TIMER_FREQ 만큼 이 값을 devices/timer.h에서 수정할 수 있고, 디폴트는 100HZ이다.
이 값을 바꾸는걸 별로 추천하지 않는다. 이 값을 바꾸는 건 대체로 테스트 fail을 야기한다.
time slice는 얼마나 기냐
한 time_slice 당 TIME_SLICE가 존재한다. 이 macro는 thread.c 에 정해져있는데, 기본으로 4틱으로 되어있다. 이 값도 안 바꾸면 좋겠다...

Advanced Scheduler FAQ

우선순위 도네가 advanced scheduler와 어떻게 상호작용하냐
신경쓸 필요 없다. 우선순위 도네와 advance scheduler를 한꺼번에 테스트 안할꺼다.
64개 queue들 대신에 큐 한 개만 써도 되냐?
그렇다. 일반적으로 너의 구현은, 동작이 같은 한, description이랑 많이 다를 거다.
스케쥴러가 테스트를 실패하는데, 왜 그런지 도와달라 Help!
advanced scheduler test에서 이상하게 implement하다면 다음처럼 해봐라.
니가 실패하는 테스트의 소스 파일을 읽어봐고, 어떤 일이 일어나는지 이해해봐라. 각 테스트마다 코멘트가 달려서, 각 테스트의 목적과 예상 결과를 설명해줄 거다.
정해진 만큼 도는 루틴과 스케쥴러에서 그것들을 사용하는 부분을 한 번 더 체크해봐라.
timer interrupt에서 니가 얼만큼 구현했는지를 고려해봐라. 만약 타이머 인터럽트 핸들러가 너무 길다면, 그러면 그 타이머 인터럽트가 선점한 쓰레드로부터 지정된 timer tick을 뺏어버릴거다. 그것이 쓰레드에게 다시 제어권을 돌려준다면, 그 다음 타이머 인터럽트가 도착하기 전에 많은 일을 못해낼거다. 그러면 CPU 시간을 예상보다 많이 사용하게 될 거고, 이건 interrupt 된 쓰레드의 현재 CPU 카운트를 올려서 priority를 낮춰버릴거다. 이게 스케쥴링 decision을 바꿀 수 있다.

나의 FAQ

time slice와 time_freq는 다른건가?
생각해보니 time자체를 세는 애들이 있을거고, 실제로 time slice가 다 찼는지를 체크하는 애들이 있을 텐데, 각각 함수가 뭐지
후자가 timer_interrupt()같고.... 그러면 time_freq는 그냥 틱을 올려주는 애들인가.
답변
timer_freq = 100, 그니까 100분의 1초마다 timer_interrupt가 발생한다.
timer_interrupt를 처리하는 핸들러의 마지막에서 thread_yield가 호출될 수 있다.
timer_interrupt에서 thread_tick을 호출하는데, 이 때 time slice보다 현재 쓰레드가 소비한 시간이 크면 intr_yield_on_return을 true로 함.
intr_yield_on_return이 true이면 thread_yield가 발생함.

해야할 것

1.
mlfqs 옵션을 읽을 수 있도록
a.
thread_mlfqs가 true이면................
b.
false이면 그대로...?
c.
뭐 어떻게 해야하지
2.
쓰레드에 nice 필드 붙이기
3.
4틱마다 쓰레드의 우선순위 업데이트하기
a.
all_list 돌면서 시행
b.
4틱마다 시행되는 함수 → timer_interrupt(), 수정해야함
4.
매 틱마다 recent_cpu 1만큼 늘리기.
5.
1초마다 load_avg, recent_cpu 업데이트하기
6.
4틱마다 all_list 돌면서 뭘 하지?
a.
priority 다시 계산
i.
priority = PRI_MAX - recent_cpu/4 - nice *2
7.
고정 소수점 계산할 수 있는 식

구현해야하는 함수

자료 구조 추가
nice, recent_cpu
thread_init에 추가
전역 변수로 load_avg 추가
thread_start에서 load_avg를 default 값으로
소수점 연산 구현
recent_cpu, load_avg 계산을 위한 소수점 연산
17.14 fixed-point number representation
오른쪽 14비트를 소숫점
그 다음 17비트를 정수
제일 왼쪽 1비트 → sign
int thread_get_nice(void)
현재 스레드의 nice 값 반환
해당 작업 중에 인터럽트 비활성화
void thread_set_nice(int);
작업 중의 인터럽트 비활성화
현재 스레드의 nice 값 변경
nice 값 변경 후에는 우선순위를 재계산, 우선순위에 의해 스케줄링
int thread_get_recent_cpu(void)
인터럽트 비활성화
현재 recent_cpu의 100배 (rounded to the nearest integer) 반환
int thread_get_load_avg(void)
인터럽트 비활성화
현재 system load average의 100배를 반환
timer_ticks() % TIMER_FREQ == 0 이어야 실행되도록
추가로 구현할 함수들
void mlfqs_priority(struct thread *t);
해당 스레드가 idle_thread가 아닌지 검사
priority 계산식을 구현, recent_cpu와 nice 값을 이용하여 계산
void mlfqs_recent_cpu(struct thread *t);
idle thread인지 검사
recent_cpu 계산식 구현
void mlfqs_load_avg(void);
load_avg 계산식 구현
0보다 작아질 수 없다.
void mlfqs_increment(void);
해당 스레드가 idle_thread인지 검사
현재 스레드의 recent_cpu 값을 1 증가
void mlfqs_recalc(void);
모든 스레드의 recent_cpu와 priority 값 재계산
thread_set_priority()
mlfqs 스케줄러일 때 임의로 변경할 수 없도록
timer_interrupt()
mlfqs 스케줄러 인 경우
timer interrupt가 발생할 때마다 recent_cpu 1 증가
1초마다 load_avg, recent_cpu 계산
매 4tick마다 현재 쓰레드의 priority 재계산
lock_acquire()
mlfqs 스케줄러 활성화 시 priority donation 관련 코드 비활성화
lock_release()
mlfqs 스케줄러 활성화시 priority donation 관련 코드 비활성화