Search
moon
sun
✅

Multi-Level Feedback Queue Scheduler (MLFQS)

4BSD nice

MLFQ에 대한 설명

다단계 큐 스케줄링과 비슷하게 동작하지만, 프로세스들이 큐 사이를 이동할 수 있는 차이 존재
새롭게 진입하는 프로세스는 먼저 우선순위가 가장 높은 우선순위 큐에 삽입되고, 타임 슬라이스 동안 실행
•
해당 큐에서 프로세스의 실행이 끝나지 않으면 다음 우선순위 큐에 삽입되어 실행
•
결국 오래 CPU를 사용해야 하는 프로세스의 우선순위는 점차 낮아짐
◦
CPU 집중 프로세스들의 우선순위는 낮아짐(CPU 사용시간이 길기 때문)
◦
입출력 집중 프로세스들의 우선순위는 높아짐
•
아사 현상 예방을 위해 우선순위 큐에서 오래 기다리고 있는 프로세스들을 높은 우선순위 큐로 이동시키는 에이징 기법을 적용 가능
◦
Feedback : 스레드가 CPU를 얼마나 썼는지, 얼마나 I/O 대기를 했는지 등의 실행 패턴을 기반으로 우선순위를 동적으로 조정하는 것

4BSD 스케줄러

•
PintOS의 MLFQS는 4BSD 스케줄러(UNIX에서 쓰던 CPU 스케줄링 알고리즘)를 모델로 함
•
4BSD에서는 CPU 사용량(recent_cpu)과 프로세스의 nice 값(유저가 지정하는 힌트)을 기반으로 우선순위를 조정
◦
결과 : CPU를 너무 많이 쓰는 스레드는 priority가 점점 낮아지고, I/O 위주로 대기 시간이 많은 스레드는 priority가 높아져서 CPU를 더 빨리 얻을 수 있게됨
🥕
결론 : 4BSD 스케줄러를 기반으로 만들어진 MLFQS는, 피드백을 통해 우선순위를 동적으로 조정하며 이 결과에 대한 문제를 해소했다

nice 값

유닉스 계열 시스템에서 nice 값은, “내 우선순위를 좀 낮춰줄게”라는 힌트를 의미
•
값 범위 : -20 ~ +20
•
nice값이 클수록 우선순위가 낮아짐 → CPU 양보(yield)
•
nice값이 작을수록 우선순위가 높아짐 → CPU 선점
🥕
결론 : nice값과 CPU 사용량, 부하를 반영해서 priority를 계속 조정 → 최대한 시스템 전체가 공정하게 CPU를 나눠쓰도록 설계

예시 코드

// MLFQS(Multi-Level Feedback Queue Scheduler) #include <stdio.h> #include <stdlib.h> #define QUEUE_NUMS 4 #define TIME_SLICE 1 typedef struct thread_t { int id; int priority; // 0(최고) ~ 3(최저) int cpu_burst; // 남은 CPU 점유 시간 int nice; // 우선순위 결정에 영향을 미치는 값 struct thread_t* next; } thread_t; thread_t* queues[QUEUE_NUMS]; // 준비 큐 : 우선순위 별로 4개의 큐 // --------------------------------------------------------------- void enqueue(thread_t** queue, thread_t* thread); thread_t* dequeue(thread_t** queue); thread_t* schedule(); void recalculate_priority(thread_t* thread); // --------------------------------------------------------------- void enqueue(thread_t** queue, thread_t* thread) { thread->next = NULL; // 큐가 비어있는 경우 if(*queue == NULL) { *queue = thread; } // 큐가 비어있지 않은 경우 else { thread_t* temp = *queue; while(temp->next != NULL) { temp = temp->next; } // 큐의 마지막에 삽입 temp->next = thread; } } thread_t* dequeue(thread_t** queue) { if(*queue == NULL) { return NULL; } thread_t* t = *queue; // 제거할 스레드(헤드) *queue = (*queue)->next; // 다음 스레드를 앞으로 당기기(헤드로 설정) return t; } thread_t* schedule() { for(int i=0; i<QUEUE_NUMS; i++) { if(queues[i] != NULL) { // 가장 높은 우선순위 큐에서 스레드 선택 : 해당 큐에서 빼낸 스레드 반환 return dequeue(&queues[i]); } } // 모든 큐가 비어있는 경우 return NULL; } // 단순화된 버전의 "우선순위 재계산" 함수 void recalculate_priority(thread_t* thread) { /* <우선순위 재계산 공식 (예시)> : 공식은 자유롭게 변경 가능 1. nice값이 클수록 우선순위 낮아짐 2. cpu_burst가 클수록(CPU 집중 프로세스일수록 = CPU 사용량이 많을수록) 우선순위 낮아짐 */ int new_priority = 2 - (thread->nice) - (thread->cpu_burst / 4); if(new_priority < 0) { new_priority = 0; } else if(new_priority > 3) { new_priority = 3; } // 새로운 우선순위로 업데이트 thread->priority = new_priority; } // --------------------------------------------------------------- int main() { // 스레드 3개 생성 : id, priority, cpu_burst, nice thread_t t1 = {1, 0, 5, 0, NULL}; thread_t t2 = {2, 1, 3, 1, NULL}; thread_t t3 = {3, 2, 8, 0, NULL}; thread_t* threads[] = {&t1, &t2, &t3}; // 초기 우선순위 계산 & 준비 큐에 삽입 for(int i=0; i<3; i++) { recalculate_priority(threads[i]); enqueue(&queues[threads[i]->priority], threads[i]); // 우선순위에 맞는 큐에 삽입 } printf("MLFQS Scheduling 시작!\n"); thread_t* current_thread; while((current_thread = schedule())) { printf("스레드 %d 실행 (우선순위: %d, 남은 CPU 점유 시간: %d)\n", current_thread->id, current_thread->priority, current_thread->cpu_burst ); current_thread->cpu_burst -= TIME_SLICE; // CPU 점유 시간 감소 if(current_thread->cpu_burst > 0) { recalculate_priority(current_thread); // 우선순위 재계산 enqueue(&queues[current_thread->priority], current_thread); // 다시 준비 큐에 } } printf("모든 스레드가 완료되었습니다!\n"); return 0; }
C
복사