프로세스 스케줄링 알고리즘은 단순히 운영체제의 영역을 넘어서, 여러 자원을 필요로 하는 다양한 시스템과 응용 프로그램에서도 중요하게 활용되는 것처럼 보였다. 효율적인 자원 관리와 최적의 성능을 위해 다방면으로 쓰일 것으로 느껴졌다.
따라서 이전 3학년 OS과목의 과제였던 여러 개의 다중 처리 코어와 제한된 종류 및 수량의 공유 자원을 가지는 시스템에서의 스케줄링 알고리즘을 재구현해보려한다.
그때는 Python으로 진행했었지만, 전체 코드를 Java 리팩토링하고 재작성해보면서 OS 구조를 더 깊게 이해하고자 했다.

시스템의 주요 구성 요소들과 그 특성
이 구조는 프로세스가 자원을 요청하고, 자원 할당 관리자가 이를 처리하여 각 CPU 준비 큐에 프로세스를 배정한 후, 각 CPU가 프로세스를 실행하는 전체적인 과정을 나타낸다. 자원 할당 관리자는 교착 상태를 피하기 위해 자원을 관리하고, CPU 준비 큐는 특정 스케줄링 알고리즘에 따라 프로세스를 선택한다.

필드
currentTime: 현재 시뮬레이션 시간을 추적isSolvable: 시스템이 해결(스케줄링, 실행) 가능한 상태인지 여부processorList: 프로세서 리스트processWaitingList: 자원 할당을 기다리는 프로세스 리스트processDispatches: 프로세스 디스패치 리스트currentResources: 현재 가용 자원을 추적random: 랜덤 값을 생성 객체메서드
initializeProcessors(): 프로세서 리스트를 초기화generateRandomDispatchSchedule(): 랜덤한 프로세스 디스패치 스케줄을 생성simulate(): 시뮬레이션을 실행processTick(): 하나의 시간 단위를 처리하고, 프로세스 디스패치, 자원 할당, 상태 업데이트 등을 수행processResourceAssignments(): 자원 할당을 처리checkAndAssignProcesses(): 프로세서에 프로세스를 할당cleanupProcesses(): 완료된 프로세스를 정리하고 자원을 반환simulate(): 시뮬레이션을 시작하고 각 시간 단위에서 processTick()을 호출하여 프로세스를 처리processTick(): 프로세스 디스패치, 자원 할당, 프로세스 상태 업데이트 등을 수행public void simulate() {
System.out.println("#PROC #TICK : PROC ID | # CPU WAIT | # WAITING QUEUE | CURRENT RESOURCES");
System.out.println("-------------------------------------------------------------------------------");
while (isSolvable) {
if (processTick()) break;
}
System.out.println("Finish!");
}
private boolean processTick() {
boolean isDispatchedTasksExists = dispatchProcesses();
boolean isReadyTaskExists = isReadyTaskExists();
boolean isFinish = processResourceAssignments();
checkAndAssignProcesses();
printCurrentState();
cleanupProcesses();
advanceTime();
return checkSimulationCompletion(isDispatchedTasksExists, isReadyTaskExists, isFinish);
}
필드
available: 현재 가용 자원을 추적max: 각 프로세스가 요구하는 최대 자원을 추적.allocation: 각 프로세스에 할당된 자원을 추적finished: 각 프로세스가 완료되었는지 여부를 추적work: 현재 가용 자원의 복사본메서드
getProcessToAssignResources(): 현재 가용 자원 정보와 대기 중인 프로세스 정보를 기반으로 자원을 할당할 프로세스를 선택합simulateRequest(): 특정 프로세스의 자원 요청을 시뮬레이션하고, 안전 상태를 확인checkSafety(): 시스템의 현재 상태가 안전한지 확인합canGrantRequest(): 특정 프로세스의 자원 요청을 수락할 수 있는지 확인grantRequest(): 자원 요청을 수락하고 자원을 할당restoreState(): 이전 상태로 복원getProcessToAssignResources(): 현재 가용 자원 정보와 대기 중인 프로세스 정보를 기반으로 자원을 할당할 프로세스를 선택. public static Object[] getProcessToAssignResources(int[] currResourceInfo, int[] totalResourceInfo, List<Process> currProcessWaitingList) {
int[] available = currResourceInfo.clone();
int[][] max = extractMax(currProcessWaitingList);
int[][] allocation = extractAllocation(currProcessWaitingList);
for (int processIndex = 0; processIndex < currProcessWaitingList.size(); processIndex++) {
int[] request = currProcessWaitingList.get(processIndex).getRemainingResources();
if (anyGreaterThan(totalResourceInfo, request)) {
return new Object[]{-1, null};
}
ResourceManager resourceManager = new ResourceManager(available, max, allocation);
SafetyCheckResult safetyCheckResult = resourceManager.simulateRequest(processIndex, request);
if (safetyCheckResult.isSafe()) {
return new Object[]{1, getPickedProcesses(currProcessWaitingList, safetyCheckResult.safeSequence(), available)};
}
}
return new Object[]{0, null};
}
simulateRequest(): 특정 프로세스의 자원 요청을 시뮬레이션하고, 시스템의 안전 상태를 확인, 안전하지 않으면 이전 상태로 복원public SafetyCheckResult simulateRequest(int processIndex, int[] request) {
int[] availableBackup = Arrays.copyOf(available, available.length);
int[][] allocationBackup = copy2DArray(allocation);
if (!canGrantRequest(processIndex, request)) {
return new SafetyCheckResult(false, new ArrayList<>());
}
grantRequest(processIndex, request);
SafetyCheckResult safetyResult = checkSafety();
if (!safetyResult.isSafe()) {
restoreState(availableBackup, allocationBackup);
}
return safetyResult;
}
checkSafety(): 시스템의 현재 상태가 안전한지 확인하고, 안전한 경우 안전 순서를 반환private SafetyCheckResult checkSafety() {
List<Integer> safeSequence = new ArrayList<>();
boolean foundProcess;
do {
foundProcess = false;
for (int i = 0; i < max.length; i++) {
if (!finished[i] && canExecuteProcess(i)) {
allocateResources(i);
finished[i] = true;
safeSequence.add(i);
foundProcess = true;
}
}
} while (foundProcess);
return allProcessesFinished() ? new SafetyCheckResult(true, safeSequence) : new SafetyCheckResult(false, new ArrayList<>());
}
SchedulingStrategy 인터페이스를 구현하여 서로 다른 알고리즘을 쉽게 교체하고 확장할 수 있도록 함.TaskScheduler 클래스는 해당 전략을 선택하여 현재 상태에 맞는 스케줄링을 수행필드
strategies: EnumMap을 사용하여 스케줄링 알고리즘(Enum)을 해당 전략 객체와 매핑메서드
getProcessIdxFromQueue(): 현재 프로세스, 준비 큐, 스케줄링 알고리즘, 현재 프로세스 실행 시간, 타임 퀀텀을 기반으로 다음에 실행할 프로세스의 인덱스를 반환. 전략 객체를 사용하여 스케줄링 알고리즘에 따라 프로세스를 선택public static int getProcessIdxFromQueue(Process currentProcess, List<Process> readyQueue, SchedulingAlgorithm schedulingAlgorithm, int currentProcessRunTick, int timeQuantum) {
SchedulingStrategy strategy = strategies.get(schedulingAlgorithm);
if (strategy == null) {
throw new IllegalArgumentException("Unsupported scheduling algorithm: " + schedulingAlgorithm);
}
return strategy.selectNextProcessIndex(readyQueue, currentProcess, currentProcessRunTick, timeQuantum);
}
SchedulingStrategy 인터페이스는 다양한 스케줄링 알고리즘을 구현하기 위한 공통 인터페이스public interface SchedulingStrategy {
int selectNextProcessIndex(List<Process> readyQueue, Process currentProcess, int currentProcessRunTick, int timeQuantum);
}
public class FirstComeFirstServedStrategy implements SchedulingStrategy {
@Override
public int selectNextProcessIndex(List<Process> readyQueue, Process currentProcess, int currentProcessRunTick, int timeQuantum) {
if (currentProcess != null || readyQueue.isEmpty()) {
return -1;
}
return 0;
}
}
public class RoundRobinStrategy implements SchedulingStrategy {
@Override
public int selectNextProcessIndex(List<Process> readyQueue, Process currentProcess, int currentProcessRunTick, int timeQuantum) {
if ((currentProcess == null || currentProcessRunTick >= timeQuantum) && !readyQueue.isEmpty()) {
return 0;
}
return -1;
}
}
public class ShortestJobFirstStrategy implements SchedulingStrategy {
@Override
public int selectNextProcessIndex(List<Process> readyQueue, Process currentProcess, int currentProcessRunTick, int timeQuantum) {
if (currentProcess != null || readyQueue.isEmpty()) {
return -1;
}
return findShortestJobIndex(readyQueue);
}
private int findShortestJobIndex(List<Process> readyQueue) {
int minIndex = -1;
int minRemainingTime = Integer.MAX_VALUE;
for (int i = 0; i < readyQueue.size(); i++) {
int remainingTime = readyQueue.get(i).getRemainingTime();
if (remainingTime < minRemainingTime) {
minRemainingTime = remainingTime;
minIndex = i;
}
}
return minIndex;
}
}
public class ShortestRemainingJobFirstStrategy implements SchedulingStrategy {
@Override
public int selectNextProcessIndex(List<Process> readyQueue, Process currentProcess, int currentProcessRunTick, int timeQuantum) {
if (readyQueue.isEmpty()) {
return -1;
}
int minIndex = findShortestRemainingJobIndex(readyQueue);
if (currentProcess != null && !currentProcess.isFinished()
&& currentProcess.getRemainingTime() <= readyQueue.get(minIndex).getRemainingTime()) {
return -1;
}
return minIndex;
}
private int findShortestRemainingJobIndex(List<Process> readyQueue) {
int minIndex = -1;
int minRemainingTime = Integer.MAX_VALUE;
for (int i = 0; i < readyQueue.size(); i++) {
int remainingTime = readyQueue.get(i).getRemainingTime();
if (remainingTime < minRemainingTime) {
minRemainingTime = remainingTime;
minIndex = i;
}
}
return minIndex;
}
}
ex) 아래와 같은 출력 경우 (6이 7,8 보다 늦게 실행)
Generated tasks: [task number, dispatch time, expected run time, required resources]
[1, 0, 7, [0, 1, 2, 2, 1]]
[2, 0, 5, [2, 1, 2, 2, 2]]
[3, 0, 3, [1, 2, 2, 0, 2]]
[4, 1, 4, [0, 0, 1, 1, 2]]
[5, 1, 10, [2, 1, 1, 1, 0]]
[6, 1, 2, [1, 2, 1, 2, 1]]
[7, 3, 7, [1, 1, 0, 1, 0]]
[8, 5, 1, [1, 1, 1, 2, 2]]
#PROC #TICK : PROC ID | # CPU WAIT | # WAITING QUEUE | CURRENT RESOURCES
-------------------------------------------------------------------------------
P002 T000 : 1 2 | * * | 3 | [3, 3, 1, 1, 2]
P002 T001 : 1 2 | * 1 | 3, 5, 6 | [3, 3, 0, 0, 0]
P002 T002 : 1 2 | * 1 | 3, 5, 6 | [3, 3, 0, 0, 0]
P002 T003 : 1 2 | * 1 | 3, 5, 6, 7 | [3, 3, 0, 0, 0]
P002 T004 : 1 2 | * 1 | 3, 5, 6, 7 | [3, 3, 0, 0, 0]
P002 T005 : 1 4 | 1 1 | 5, 6, 8 | [3, 1, 0, 1, 0]
P002 T006 : 1 4 | 1 1 | 5, 6, 8 | [3, 1, 0, 1, 0]
P002 T007 : 3 4 | 1 1 | 6, 8 | [1, 1, 1, 2, 1]
P002 T008 : 3 4 | 1 1 | 6, 8 | [1, 1, 1, 2, 1]
P002 T009 : 3 7 | 1 1 | 6 | [0, 0, 1, 1, 1]
P002 T010 : 5 7 | * 1 | 6 | [1, 2, 3, 1, 3]
P002 T011 : 5 7 | * 1 | 6 | [1, 2, 3, 1, 3]
P002 T012 : 5 7 | * 1 | 6 | [1, 2, 3, 1, 3]
P002 T013 : 5 7 | * 1 | 6 | [1, 2, 3, 1, 3]
P002 T014 : 5 7 | * 1 | 6 | [1, 2, 3, 1, 3]
P002 T015 : 5 7 | * 1 | 6 | [1, 2, 3, 1, 3]
P002 T016 : 5 8 | * 1 | | [1, 1, 2, 0, 2]
P002 T017 : 5 6 | * * | | [2, 2, 3, 2, 4]
P002 T018 : 5 6 | * * | | [2, 2, 3, 2, 4]
P002 T019 : 5 -- | * * | | [3, 4, 4, 4, 5]
P002 T020 : -- -- | * * | | [5, 5, 5, 5, 5]
Finish!
Process finished with exit code 0
+) 프로세스 생명 주기와 자원 할당
