CSI3101-01 운영체제 차호정 교수님 2015123002 경영학과 김상현


1. 과제 개요

1-1. 과제 개요

이번 3차 과제는 스케줄러의 동작 원리와 메모리와 가상 메모리를 이해하는 데에 있다. 스케줄링과 메모리 관리는 운영체제의 대표적인 역할로 프로세스 간 자원을 효율적으로 분배하고 프로세스가 수행하는 일을 마칠 수 있도록 돕는다. 특히 CPU와 메인 메모리는 컴퓨터의 주요 자원으로, 이를 두고 경쟁하는 상황에서 운영체제는 이를 효율적으로 관리할 필요가 있다. 이를 알아볼 수 있는 시뮬레이터를 제작하여 스케줄링 기법과 버디 시스템, 그리고 페이징을 알아보는 데에 이번 과제의 목적이 있다.

1-2. 프로그램의 전반적 구현

프로그램은 초기에 필요한 정보를 세팅하는 구간과 이후의 무한 while 루프로 이루어진 구간으로 나눠져 있다. 초기에는 input 파일을 불러와, TOTAL_EVENT_NUM, VM_SIZE, PM_SIZE, PAGE/FRAME_SIZE 를 설정하는 역할을 하고 출력 파일을 생성한다. 이후 Priority의 수에 따른 각각의 Queue를 초기화하고, 실제 메모리를 의미하는 PM을 초기화하고 Page Replacement Algorithm을 지정한다.

(...)
Queues queues(NUM_QUEUES);
(...)
PM* pm = new PM(PM_SIZE/PAGE_SIZE, pra);
std::vector<Process*> processes;

이후에는 while 루프 내에서 모든 프로세스가 종료되고 모든 event가 끝이 날 때까지 반복되는 루틴이 존재한다. 한 번의 while 루프는 하나의 CPU 사이클을 의미한다. 과제 명세에 제시된 바와 같이 각 cycle은 아래의 순서대로 수행된다.

  1. Sleep Queue 체크 - 완료된 Sleep을 체크한다.

    for (Process* process : queues.run_queues[NUM_QUEUES].queue) {
        // Keep iterating
        process->sleep_one_cycle();
        queues.run_queues[NUM_QUEUES].queue.pop_front();
        if (process->getSleepTime() == 0) {
            // if sleep_time = 0, then move it back to original queue
            queues.push_to_queue(process->getPriority(), process);
        } else {
            // else, go back to the sleep queue
            queues.run_queues[NUM_QUEUES].queue.push_back(process);
        }
    }
    
  2. I/O 작업 수행 - 완료된 I/O 작업을 체크한다.

    if (events[current_line].time == cycle) {
    	  Event& current_event = events[current_line];
    		if (current_event.code == "INPUT")
    		            {
    				for (Process* process : queues.run_queues[NUM_QUEUES + 1].queue) {
    				    queues.run_queues[NUM_QUEUES + 1].queue.pop_front();
    				    if (process->getPID() == current_event.priority) {
    				        // for "INPUT", the third element (priority) means PID
    				        // if process_pid == input_pid, move it back to original queue
    				        queues.push_to_queue(process->getPriority(), process);
    				    } else {
    				        // else, go back to the IO WAIT queue
    				        queues.run_queues[NUM_QUEUES + 1].queue.push_back(process);
    				    }
    				}
    		(...)
    
  3. 새로운 프로세스 생성 - FCFS의 경우 time_quantum이 존재하지 않으며, RR의 경우는 기본값인 TIME_QUANTUM = 10을 할당한다.

    		else {
    				Process* new_process = new Process();
    				processes.push_back(new_process);
    				(...)
    				if (current_event.priority < NUM_QUEUES / 2) {
    				    // FCFS
    				    new_process->setTimeQuantum(NO_TIME_QUANTUM);
    				} else {
    				    // Round Robin
    				    new_process->setTimeQuantum(TIME_QUANTUM);
    				}
    				queues.push_to_queue(current_event.priority, new_process);
    		}
    		(...)
    }
    
  4. 프로세스 스케줄링 - 현재 실행 중인 프로세스가 없다면 높은 우선순위대로 프로세스를 선택하고, 현재 실행 중인 프로세스가 존재한다면 preemption이 가능한 (FCFS의 더 높은 우선순위 queue를 대상) 프로세스가 대기 중인지를 체크한다.

    if (!(queues.running_process)) {
        // if no process running
        for (int q = 0; q < NUM_QUEUES; q++) {
            // iterate over all queues (except sleep & io wait)
            if (queues.run_queues[q].queue.empty()) {
                continue;
            } else {
                Process* selected_process = queues.run_queues[q].queue.front();
                queues.run_queues[q].queue.pop_front();
                queues.running_process = selected_process;
                break;
            }
        }
    } else {
        // else if a process is running
        int current_priority = queues.running_process->getPriority();
        for (int q = 0; (q < current_priority) && (q < (NUM_QUEUES / 2)); q++) {
            if (queues.run_queues[q].queue.empty()) {
                continue;
            } else {
                // preempt the running process
                queues.push_to_queue(queues.running_process->getPriority(), queues.running_process);
                queues.running_process = queues.run_queues[q].queue.front();
                queues.run_queues[q].queue.pop_front();
                break;
            }
        }
    }
    
  5. 현재 실행 중인 프로세스의 코드를 실행한다. 이후에 프로세스의 종료 여부를 체크한다.

    if (current_process) {
        return_value = queues.running_process->execute();
    		(...)
    }
    (...)
    if ((current_process) && (current_process->current_line == current_process->num_codes)) {
        // terminate the process
        current_process->terminate();
    
        Process* terminated_process = queues.running_process;
        queues.running_process = nullptr;
    
        // release memory
        terminated_process->vm->releaseAll();
    }
    
  6. 마지막으로 모든 프로세스와 이벤트가 종료되었는지를 체크한다.

    // Check if all jobs are done
    if (queues.all_empty() && !queues.running_process) {
        operating = false;
    }
    // increase the cycle number
    cycle++;
    

물론 이외에도 중간 과정의 코드는 많지만, 주요 코드는 위와 같다. 이를 도식화해서 나타내면 아래 그림과 같다.

2. 스케줄러 구현

2-1. 스케줄러 개요