본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 12. 28. · 4 Views
프로세스 상태 전이 완벽 가이드
운영체제에서 프로세스가 어떤 상태를 거쳐 실행되는지, 그리고 스케줄러와 디스패처가 어떤 역할을 하는지 초급 개발자도 쉽게 이해할 수 있도록 설명합니다. 프로세스의 생애주기를 따라가며 운영체제의 핵심 개념을 마스터해보세요.
목차
1. 프로세스 5가지 상태
김개발 씨는 입사 후 처음으로 시스템 모니터링 업무를 맡게 되었습니다. 작업 관리자를 열어보니 수많은 프로세스들이 "실행 중", "대기 중" 등 다양한 상태로 표시되어 있었습니다.
"프로세스에 상태가 있다고요? 그냥 실행되거나 안 되거나 둘 중 하나 아닌가요?"
프로세스 상태는 운영체제가 프로세스를 효율적으로 관리하기 위해 부여하는 일종의 라벨입니다. 마치 병원 응급실에서 환자를 중증도에 따라 분류하는 것처럼, 운영체제도 프로세스를 상태별로 분류하여 관리합니다.
이를 이해하면 시스템이 왜 때로는 빠르고 때로는 느린지 파악할 수 있습니다.
다음 코드를 살펴봅시다.
// 프로세스 상태를 표현하는 열거형
public enum ProcessState {
NEW, // 생성 상태: 프로세스가 막 만들어진 상태
READY, // 준비 상태: CPU를 받으면 바로 실행 가능
RUNNING, // 실행 상태: 현재 CPU를 사용 중
WAITING, // 대기 상태: I/O 작업 완료를 기다리는 중
TERMINATED // 종료 상태: 실행이 완료됨
}
// 프로세스 제어 블록(PCB) 기본 구조
class ProcessControlBlock {
int processId;
ProcessState state;
int programCounter;
int[] registers;
}
김개발 씨는 입사 3개월 차 주니어 개발자입니다. 오늘 팀장님으로부터 서버 프로세스 모니터링 업무를 지시받았습니다.
작업 관리자를 열어보니 수백 개의 프로세스가 각기 다른 상태로 표시되어 있었습니다. "이게 다 뭐지?" 김개발 씨가 고개를 갸웃거리자, 옆자리 박시니어 씨가 다가왔습니다.
"프로세스 상태를 모르면 시스템을 제대로 이해할 수 없어요. 차근차근 설명해 드릴게요." 프로세스 상태를 이해하려면 먼저 비유를 들어보겠습니다.
프로세스는 마치 놀이공원의 방문객과 같습니다. 방문객이 놀이공원에 들어오면 입장 상태가 되고, 놀이기구 앞에서 줄을 서면 대기 상태, 놀이기구를 타면 탑승 상태가 됩니다.
프로세스도 이와 똑같이 여러 상태를 거칩니다. 첫 번째는 **생성 상태(NEW)**입니다.
사용자가 프로그램을 더블클릭하면 운영체제는 해당 프로그램을 위한 프로세스를 생성합니다. 이때 필요한 메모리 공간을 할당하고, 프로세스 제어 블록이라는 관리 자료구조를 만듭니다.
아직 실행 준비가 완료되지 않은 상태입니다. 두 번째는 **준비 상태(READY)**입니다.
프로세스가 실행에 필요한 모든 자원을 확보했지만, 아직 CPU를 할당받지 못한 상태입니다. 마치 놀이기구 앞에서 줄을 서서 기다리는 것과 같습니다.
언제든 차례가 오면 바로 탑승할 준비가 되어 있습니다. 세 번째는 **실행 상태(RUNNING)**입니다.
드디어 CPU를 할당받아 실제로 명령어를 수행하는 상태입니다. 단일 코어 CPU에서는 한 번에 하나의 프로세스만 실행 상태에 있을 수 있습니다.
놀이기구를 실제로 타고 있는 상황이라고 생각하면 됩니다. 네 번째는 **대기 상태(WAITING)**입니다.
프로세스가 I/O 작업이나 특정 이벤트를 기다리는 상태입니다. 예를 들어 파일을 읽거나 네트워크 응답을 기다릴 때 이 상태가 됩니다.
CPU는 다른 프로세스에게 양보하고, 자신은 필요한 작업이 완료되기를 기다립니다. 다섯 번째는 **종료 상태(TERMINATED)**입니다.
프로세스가 모든 실행을 마치고 자원을 반납하는 상태입니다. 운영체제는 이 프로세스가 사용하던 메모리와 자원을 회수하여 다른 프로세스가 사용할 수 있게 합니다.
실무에서 이 개념이 왜 중요할까요? 서버 애플리케이션을 운영하다 보면 특정 프로세스가 WAITING 상태에서 오래 머무르는 경우가 있습니다.
이는 I/O 병목이 발생했다는 신호입니다. 반면 READY 상태의 프로세스가 많다면 CPU가 부족하다는 의미입니다.
박시니어 씨의 설명을 들은 김개발 씨가 말했습니다. "아, 그래서 작업 관리자에서 프로세스 상태를 보여주는 거군요.
단순히 실행 중인지 아닌지만 보는 게 아니라, 어디서 병목이 생기는지 파악할 수 있네요." 프로세스 상태를 이해하면 시스템 성능 문제를 진단하는 첫 번째 단서를 얻을 수 있습니다. 다음 장에서는 이 상태들이 어떻게 서로 전이되는지 살펴보겠습니다.
실전 팁
💡 - 시스템이 느리다면 먼저 프로세스 상태 분포를 확인하세요
- WAITING 상태가 많으면 I/O 최적화를, READY가 많으면 CPU 증설을 고려하세요
2. 상태 전이 다이어그램
박시니어 씨의 설명을 들은 김개발 씨는 한 가지 의문이 생겼습니다. "그런데 프로세스가 어떻게 한 상태에서 다른 상태로 바뀌는 건가요?
마음대로 왔다 갔다 할 수 있나요?" 박시니어 씨가 화이트보드에 그림을 그리기 시작했습니다.
상태 전이 다이어그램은 프로세스가 어떤 조건에서 어떤 상태로 이동하는지 보여주는 지도와 같습니다. 마치 지하철 노선도처럼 갈 수 있는 경로가 정해져 있습니다.
프로세스는 아무 상태로나 점프할 수 없고, 반드시 정해진 경로를 따라 이동해야 합니다.
다음 코드를 살펴봅시다.
// 상태 전이를 시뮬레이션하는 코드
class ProcessTransition {
ProcessState currentState = ProcessState.NEW;
// 생성 -> 준비: admit (승인)
void admit() {
if (currentState == ProcessState.NEW) {
currentState = ProcessState.READY;
System.out.println("프로세스 승인됨: NEW -> READY");
}
}
// 준비 -> 실행: dispatch (디스패치)
void dispatch() {
if (currentState == ProcessState.READY) {
currentState = ProcessState.RUNNING;
System.out.println("CPU 할당됨: READY -> RUNNING");
}
}
// 실행 -> 준비: timeout (시간 초과)
void timeout() {
if (currentState == ProcessState.RUNNING) {
currentState = ProcessState.READY;
System.out.println("타임아웃: RUNNING -> READY");
}
}
// 실행 -> 대기: I/O 요청
void waitForIO() {
if (currentState == ProcessState.RUNNING) {
currentState = ProcessState.WAITING;
System.out.println("I/O 대기: RUNNING -> WAITING");
}
}
}
박시니어 씨가 화이트보드에 다섯 개의 동그라미를 그렸습니다. 그리고 동그라미 사이에 화살표를 그리기 시작했습니다.
"이게 바로 상태 전이 다이어그램이에요. 프로세스가 갈 수 있는 길을 보여주는 지도라고 생각하면 됩니다." 상태 전이를 이해하려면 지하철 노선도를 떠올려 보세요.
서울역에서 강남역으로 가려면 정해진 노선을 따라가야 합니다. 벽을 뚫고 직선으로 갈 수는 없습니다.
프로세스도 마찬가지입니다. NEW 상태에서 갑자기 RUNNING으로 점프할 수 없습니다.
첫 번째 전이는 **승인(Admit)**입니다. NEW에서 READY로 이동합니다.
운영체제가 새로 생성된 프로세스를 검토하고, 실행해도 괜찮다고 판단하면 준비 큐에 넣어줍니다. 장기 스케줄러가 이 역할을 담당합니다.
두 번째 전이는 **디스패치(Dispatch)**입니다. READY에서 RUNNING으로 이동합니다.
단기 스케줄러가 준비 큐에서 하나의 프로세스를 선택하여 CPU를 할당합니다. 드디어 프로세스가 실제로 실행되기 시작합니다.
세 번째 전이는 **타임아웃(Timeout)**입니다. RUNNING에서 READY로 돌아갑니다.
현대 운영체제는 하나의 프로세스가 CPU를 독점하지 못하게 합니다. 정해진 시간이 지나면 강제로 CPU를 빼앗고 다른 프로세스에게 기회를 줍니다.
네 번째 전이는 I/O 요청 또는 이벤트 대기입니다. RUNNING에서 WAITING으로 이동합니다.
프로세스가 파일을 읽거나 네트워크 응답을 기다려야 할 때 발생합니다. CPU를 잡고 기다리는 것은 낭비이므로, 자발적으로 CPU를 반납합니다.
다섯 번째 전이는 I/O 완료 또는 이벤트 발생입니다. WAITING에서 READY로 이동합니다.
기다리던 작업이 완료되면 다시 준비 큐로 들어가 CPU 할당을 기다립니다. 바로 RUNNING으로 가지 않는다는 점에 주의하세요.
마지막 전이는 **종료(Exit)**입니다. RUNNING에서 TERMINATED로 이동합니다.
프로세스가 모든 작업을 마치고 exit() 시스템 콜을 호출하면 종료 상태가 됩니다. 김개발 씨가 중요한 점을 발견했습니다.
"잠깐요, WAITING에서 바로 RUNNING으로 갈 수는 없네요?" 박시니어 씨가 고개를 끄덕였습니다. "정확해요.
반드시 READY를 거쳐야 해요. 이유는 간단합니다.
CPU 스케줄러가 READY 큐에서만 프로세스를 선택하기 때문이에요." 실무에서 이 다이어그램을 이해하면 디버깅에 큰 도움이 됩니다. 프로세스가 예상대로 동작하지 않을 때, 현재 어떤 상태에 있는지 파악하고 왜 다음 상태로 넘어가지 못하는지 분석할 수 있습니다.
주의할 점이 있습니다. 초보 개발자들이 흔히 하는 실수는 프로세스가 WAITING 상태에서 무한정 머무를 수 있다는 것을 간과하는 것입니다.
I/O 장치가 응답하지 않으면 프로세스는 영원히 대기 상태에 갇힐 수 있습니다. 이것이 바로 **교착 상태(Deadlock)**의 한 원인입니다.
실전 팁
💡 - 프로세스 디버깅 시 현재 상태와 전이 조건을 먼저 확인하세요
- WAITING 상태가 오래 지속되면 I/O 타임아웃 설정을 검토하세요
3. 장기 스케줄러 (Job Scheduler)
김개발 씨가 물었습니다. "그런데 NEW에서 READY로 가는 건 누가 결정하나요?
프로그램을 실행하면 무조건 READY로 가는 건가요?" 박시니어 씨가 웃으며 대답했습니다. "좋은 질문이에요.
바로 장기 스케줄러가 그 역할을 합니다. 하지만 요즘 운영체제에서는 조금 달라졌어요."
장기 스케줄러는 어떤 프로세스를 메모리에 적재할지 결정하는 문지기 역할을 합니다. 마치 인기 있는 클럽의 입구에서 손님을 선별하는 도어맨과 같습니다.
시스템의 멀티프로그래밍 정도를 조절하여 전체적인 성능 균형을 유지합니다.
다음 코드를 살펴봅시다.
// 장기 스케줄러 개념을 보여주는 의사 코드
class JobScheduler {
Queue<Process> jobQueue; // 디스크에서 대기 중인 작업들
Queue<Process> readyQueue; // 메모리에 적재된 프로세스들
int maxDegreeOfMultiprogramming = 10; // 최대 동시 실행 프로세스 수
void scheduleJob() {
// 메모리에 여유가 있는지 확인
if (readyQueue.size() < maxDegreeOfMultiprogramming) {
// I/O 바운드와 CPU 바운드 프로세스를 적절히 섞어서 선택
Process selected = selectOptimalProcess(jobQueue);
if (selected != null) {
loadIntoMemory(selected); // 메모리에 적재
readyQueue.add(selected); // 준비 큐에 추가
System.out.println("장기 스케줄러: " + selected.name + " 승인됨");
}
}
}
Process selectOptimalProcess(Queue<Process> jobs) {
// CPU 바운드와 I/O 바운드 프로세스를 균형 있게 선택
return jobs.poll(); // 단순화된 예시
}
}
박시니어 씨가 설명을 이어갔습니다. "예전에는 컴퓨터 메모리가 아주 작았어요.
모든 프로그램을 동시에 메모리에 올릴 수 없었죠. 그래서 누구를 먼저 올릴지 결정하는 역할이 필요했는데, 그게 바로 장기 스케줄러입니다." 장기 스케줄러를 이해하려면 대학교 수강 신청 시스템을 떠올려 보세요.
인기 있는 강의에는 수강 정원이 있습니다. 모든 학생이 동시에 수강할 수 없으므로, 시스템이 누구를 먼저 받아들일지 결정해야 합니다.
장기 스케줄러도 이와 비슷한 역할을 합니다. 장기 스케줄러의 가장 중요한 임무는 **멀티프로그래밍 정도(Degree of Multiprogramming)**를 조절하는 것입니다.
이는 메모리에 동시에 적재되어 있는 프로세스의 수를 의미합니다. 너무 많으면 메모리가 부족해지고, 너무 적으면 CPU가 놀게 됩니다.
또 하나의 중요한 역할은 프로세스의 성격을 고려하여 적절히 섞는 것입니다. 프로세스는 크게 두 종류로 나뉩니다.
CPU 바운드 프로세스는 계산 작업이 많아 CPU를 오래 사용합니다. 반면 I/O 바운드 프로세스는 파일 읽기, 네트워크 통신 등 I/O 작업이 많습니다.
만약 CPU 바운드 프로세스만 메모리에 올리면 어떻게 될까요? 모든 프로세스가 CPU를 차지하려고 경쟁하고, I/O 장치는 놀게 됩니다.
반대로 I/O 바운드 프로세스만 올리면 CPU가 놀게 됩니다. 장기 스케줄러는 이 둘을 적절히 섞어서 시스템 자원이 균형 있게 사용되도록 합니다.
김개발 씨가 고개를 갸웃거렸습니다. "그런데 요즘 컴퓨터는 메모리가 충분하잖아요.
그래도 장기 스케줄러가 필요한가요?" 박시니어 씨가 대답했습니다. "사실 현대 운영체제인 Windows, Linux, macOS에서는 전통적인 의미의 장기 스케줄러가 거의 없어요.
메모리가 충분하고 가상 메모리 기술이 발달했기 때문이죠. 프로그램을 실행하면 거의 즉시 메모리에 적재됩니다." 하지만 장기 스케줄러 개념이 완전히 사라진 것은 아닙니다.
배치 처리 시스템이나 대형 서버 환경에서는 여전히 비슷한 역할을 하는 메커니즘이 존재합니다. 또한 컨테이너 오케스트레이션 시스템인 Kubernetes에서 Pod를 노드에 배치하는 스케줄러도 장기 스케줄러와 비슷한 역할을 합니다.
실무에서 이 개념을 만나는 경우는 대용량 데이터 처리 시스템을 다룰 때입니다. Hadoop이나 Spark 같은 분산 처리 시스템에서는 어떤 작업을 먼저 클러스터에 제출할지 결정하는 스케줄러가 있습니다.
이것이 바로 현대판 장기 스케줄러라고 할 수 있습니다.
실전 팁
💡 - 현대 운영체제에서 장기 스케줄러는 거의 없지만, 개념은 분산 시스템에서 여전히 활용됩니다
- 시스템 자원 사용률이 불균형할 때 작업의 성격(CPU/IO 바운드)을 고려해보세요
4. 단기 스케줄러 (CPU Scheduler)
"장기 스케줄러는 이해했어요. 그런데 실제로 어떤 프로세스가 CPU를 받을지는 누가 정하나요?" 김개발 씨의 질문에 박시니어 씨가 본론으로 들어갔습니다.
"그게 바로 가장 중요한 단기 스케줄러예요. 운영체제의 심장이라고 할 수 있죠."
단기 스케줄러는 준비 큐에 있는 프로세스 중 다음에 CPU를 받을 프로세스를 선택합니다. 마치 은행 창구에서 다음 손님을 호출하는 번호표 시스템과 같습니다.
이 스케줄러는 매우 자주 실행되므로 빠르게 동작해야 합니다.
다음 코드를 살펴봅시다.
// 단기 스케줄러 - 다양한 알고리즘 예시
class CPUScheduler {
Queue<Process> readyQueue;
// FCFS (First Come First Served) - 선입선출
Process scheduleFCFS() {
return readyQueue.poll(); // 먼저 온 순서대로
}
// SJF (Shortest Job First) - 최단 작업 우선
Process scheduleSJF() {
return readyQueue.stream()
.min((p1, p2) -> p1.burstTime - p2.burstTime)
.orElse(null);
}
// Priority Scheduling - 우선순위 스케줄링
Process schedulePriority() {
return readyQueue.stream()
.max((p1, p2) -> p1.priority - p2.priority)
.orElse(null);
}
// Round Robin - 시간 할당량 방식
Process scheduleRoundRobin(int timeQuantum) {
Process current = readyQueue.poll();
// timeQuantum만큼 실행 후 다시 큐의 끝으로
return current;
}
}
박시니어 씨가 중요한 이야기를 꺼냈습니다. "장기 스케줄러는 가끔 실행되지만, 단기 스케줄러는 정말 자주 실행돼요.
초당 수십 번에서 수백 번까지 호출될 수 있어요. 그래서 정말 빨라야 합니다." 단기 스케줄러를 비유하자면 놀이공원의 안내원과 같습니다.
줄을 서 있는 사람들 중 누가 다음에 놀이기구를 탈지 결정합니다. 이 결정을 어떻게 하느냐에 따라 전체 대기 시간이 크게 달라집니다.
가장 단순한 방식은 **FCFS(First Come First Served)**입니다. 먼저 온 순서대로 처리합니다.
구현이 간단하지만, 긴 작업이 앞에 있으면 뒤의 짧은 작업들이 오래 기다려야 하는 **호위 효과(Convoy Effect)**가 발생합니다. **SJF(Shortest Job First)**는 실행 시간이 짧은 프로세스부터 처리합니다.
평균 대기 시간을 최소화할 수 있는 최적의 알고리즘이지만, 실행 시간을 미리 알기 어렵다는 치명적인 단점이 있습니다. 또한 긴 작업은 계속 밀려나는 **기아 현상(Starvation)**이 발생할 수 있습니다.
우선순위 스케줄링은 각 프로세스에 우선순위를 부여하고 높은 우선순위부터 실행합니다. 중요한 작업을 먼저 처리할 수 있지만, 역시 낮은 우선순위 프로세스의 기아 현상이 문제입니다.
이를 해결하기 위해 에이징(Aging) 기법을 사용합니다. 오래 기다린 프로세스의 우선순위를 점진적으로 높여주는 것입니다.
현대 운영체제에서 가장 많이 사용하는 방식은 **라운드 로빈(Round Robin)**입니다. 각 프로세스에게 동일한 시간 할당량(Time Quantum)을 줍니다.
할당량을 다 쓰면 큐의 맨 뒤로 가서 다시 차례를 기다립니다. 공정하고 응답 시간이 좋지만, 시간 할당량 설정이 중요합니다.
김개발 씨가 물었습니다. "시간 할당량은 어떻게 정하나요?" 박시니어 씨가 설명했습니다.
"너무 짧으면 컨텍스트 스위칭이 너무 자주 일어나서 오버헤드가 커져요. 너무 길면 FCFS와 다를 게 없고요.
보통 10~100밀리초 사이로 설정합니다." 실무에서 스케줄러 알고리즘을 직접 구현할 일은 거의 없습니다. 하지만 이 개념을 이해하면 시스템 성능을 튜닝할 때 큰 도움이 됩니다.
예를 들어 Linux에서는 nice 명령어로 프로세스 우선순위를 조절할 수 있습니다. 중요한 서버 프로세스의 우선순위를 높이면 응답 시간을 개선할 수 있습니다.
주의할 점이 있습니다. 스케줄러의 성능은 워크로드에 따라 크게 달라집니다.
대화형 시스템에서는 응답 시간이 중요하므로 라운드 로빈이 적합합니다. 배치 처리 시스템에서는 처리량이 중요하므로 SJF가 더 나을 수 있습니다.
만능 알고리즘은 없습니다.
실전 팁
💡 - Linux에서 nice, renice 명령으로 프로세스 우선순위를 조절할 수 있습니다
- 응답 시간이 중요하면 시간 할당량을 작게, 처리량이 중요하면 크게 설정하세요
5. 중기 스케줄러
김개발 씨가 시스템 모니터를 보다가 이상한 점을 발견했습니다. "메모리 사용량이 급격히 늘어났다가 줄어드네요.
프로세스를 종료하지 않았는데 어떻게 메모리가 확보되는 거죠?" 박시니어 씨가 미소를 지었습니다. "중기 스케줄러의 마법이에요."
중기 스케줄러는 메모리가 부족할 때 일부 프로세스를 일시적으로 디스크로 내보내는 역할을 합니다. 마치 도서관에서 자주 안 읽는 책을 창고로 옮기고, 필요할 때 다시 꺼내오는 것과 같습니다.
이를 **스와핑(Swapping)**이라고 합니다.
다음 코드를 살펴봅시다.
// 중기 스케줄러와 스와핑 개념
class MediumTermScheduler {
List<Process> inMemory;
List<Process> swappedOut; // 스왑 영역에 있는 프로세스
int memoryThreshold = 80; // 메모리 사용률 임계값
void checkMemoryPressure() {
int memoryUsage = getMemoryUsagePercent();
// 메모리 부족 시 스왑 아웃
if (memoryUsage > memoryThreshold) {
Process victim = selectVictimProcess();
swapOut(victim);
System.out.println("스왑 아웃: " + victim.name);
}
}
void swapOut(Process p) {
p.state = ProcessState.SUSPENDED_READY;
saveToSwapSpace(p); // 디스크에 저장
freeMemory(p); // 메모리 해제
inMemory.remove(p);
swappedOut.add(p);
}
void swapIn(Process p) {
loadFromSwapSpace(p); // 디스크에서 로드
p.state = ProcessState.READY;
swappedOut.remove(p);
inMemory.add(p);
System.out.println("스왑 인: " + p.name);
}
}
박시니어 씨가 설명을 시작했습니다. "장기 스케줄러가 입장을 담당하고, 단기 스케줄러가 CPU 배분을 담당한다면, 중기 스케줄러는 메모리 관리를 담당해요.
프로세스를 잠시 쉬게 하는 역할이죠." 중기 스케줄러를 이해하려면 호텔을 떠올려 보세요. 객실이 가득 찼는데 새 손님이 왔습니다.
현재 체크아웃할 손님은 없지만, 외출 중인 손님의 짐을 잠시 창고에 보관하고 그 방을 새 손님에게 줄 수 있습니다. 외출했던 손님이 돌아오면 짐을 다시 방으로 옮겨드리면 됩니다.
이 과정을 컴퓨터 용어로 **스왑 아웃(Swap Out)**과 **스왑 인(Swap In)**이라고 합니다. 메모리에서 디스크의 스왑 영역으로 프로세스를 내보내는 것이 스왑 아웃이고, 다시 메모리로 불러오는 것이 스왑 인입니다.
여기서 새로운 상태가 등장합니다. 바로 **중단 상태(Suspended)**입니다.
기존 5가지 상태에 추가되는 상태로, **중단 준비(Suspended Ready)**와 **중단 대기(Suspended Waiting)**가 있습니다. 스왑 아웃된 프로세스가 어떤 상태였느냐에 따라 달라집니다.
김개발 씨가 물었습니다. "아무 프로세스나 스왑 아웃하면 되나요?" 박시니어 씨가 대답했습니다.
"좋은 질문이에요. 피해자(Victim) 선정 기준이 있어요.
보통 오랫동안 대기 상태에 있는 프로세스, 우선순위가 낮은 프로세스, 또는 메모리를 많이 사용하는 프로세스를 선택합니다." 중기 스케줄러가 필요한 이유는 메모리가 유한하기 때문입니다. 사용자가 프로그램을 계속 실행하면 언젠가 메모리가 부족해집니다.
이때 시스템을 다운시키지 않고 계속 운영하려면 일부 프로세스를 임시로 디스크에 보관해야 합니다. 실무에서 스왑 현상을 만나면 주의해야 합니다.
스왑은 디스크 I/O를 발생시키므로 메모리 접근보다 수천 배 느립니다. 시스템이 스왑을 많이 사용하면 전체 성능이 급격히 저하됩니다.
이를 **스래싱(Thrashing)**이라고 합니다. Linux에서는 free 명령어로 스왑 사용량을 확인할 수 있습니다.
스왑 사용량이 지속적으로 높다면 메모리를 증설하거나 메모리를 많이 사용하는 프로세스를 찾아 최적화해야 합니다. 박시니어 씨가 마지막으로 조언했습니다.
"SSD가 보편화되면서 스왑 성능이 많이 좋아졌지만, 여전히 메모리보다는 느려요. 서버 환경에서는 스왑을 아예 비활성화하고 메모리를 충분히 확보하는 경우도 많습니다."
실전 팁
💡 - Linux에서 free -h 명령으로 스왑 사용량을 확인하세요
- 스왑 사용이 많으면 메모리 누수나 메모리 부족을 의심해보세요
6. 디스패처와 컨텍스트 스위칭
김개발 씨가 마지막 궁금증을 꺼냈습니다. "스케줄러가 다음 프로세스를 선택한다고 했는데, 실제로 CPU를 넘겨주는 건 누가 하나요?" 박시니어 씨가 마지막 주제를 설명하기 시작했습니다.
"바로 디스패처예요. 스케줄러가 감독이라면, 디스패처는 실제로 선수를 교체하는 코치라고 할 수 있죠."
디스패처는 스케줄러가 선택한 프로세스에게 실제로 CPU 제어권을 넘겨주는 모듈입니다. 이 과정에서 **컨텍스트 스위칭(Context Switching)**이 발생합니다.
마치 릴레이 경주에서 바통을 넘겨주는 것처럼, 현재 프로세스의 상태를 저장하고 다음 프로세스의 상태를 복원합니다.
다음 코드를 살펴봅시다.
// 디스패처와 컨텍스트 스위칭
class Dispatcher {
void dispatch(Process current, Process next) {
// 1. 현재 프로세스 상태 저장 (컨텍스트 스위칭 시작)
if (current != null) {
current.pcb.programCounter = CPU.getPC();
current.pcb.registers = CPU.getRegisters();
current.pcb.stackPointer = CPU.getSP();
System.out.println("상태 저장: " + current.name);
}
// 2. 다음 프로세스 상태 복원
CPU.setPC(next.pcb.programCounter);
CPU.setRegisters(next.pcb.registers);
CPU.setSP(next.pcb.stackPointer);
System.out.println("상태 복원: " + next.name);
// 3. 사용자 모드로 전환
CPU.switchToUserMode();
// 4. 다음 프로세스의 적절한 위치로 점프
CPU.jumpToLocation(next.pcb.programCounter);
}
// 디스패치 지연시간 측정
long measureDispatchLatency() {
long start = System.nanoTime();
// 컨텍스트 스위칭 수행
long end = System.nanoTime();
return end - start; // 디스패치 지연시간
}
}
박시니어 씨가 설명했습니다. "스케줄러는 누가 다음에 실행될지 결정만 해요.
실제로 CPU를 넘겨주는 작업은 디스패처가 담당합니다. 이 과정이 바로 컨텍스트 스위칭이에요." 컨텍스트 스위칭을 이해하려면 의사가 환자를 바꿔가며 진료하는 상황을 떠올려 보세요.
한 환자의 진료를 마치면 그 환자의 차트를 정리하고 보관합니다. 다음 환자를 부르면 그 환자의 차트를 꺼내 어디까지 진료했는지 확인합니다.
컨텍스트 스위칭도 이와 똑같습니다. 프로세스의 **컨텍스트(Context)**란 프로세스가 어디까지 실행했는지 기록한 모든 정보를 말합니다.
여기에는 프로그램 카운터(다음에 실행할 명령어 주소), CPU 레지스터 값들, 스택 포인터 등이 포함됩니다. 이 정보는 **프로세스 제어 블록(PCB)**에 저장됩니다.
컨텍스트 스위칭은 세 단계로 이루어집니다. 첫째, 현재 실행 중인 프로세스의 상태를 PCB에 저장합니다.
둘째, 다음에 실행할 프로세스의 PCB에서 상태를 읽어 CPU에 복원합니다. 셋째, 새 프로세스가 중단된 지점부터 다시 실행을 시작합니다.
김개발 씨가 중요한 점을 발견했습니다. "컨텍스트 스위칭하는 동안에는 아무 프로세스도 실행되지 않는 거네요?" 박시니어 씨가 고개를 끄덕였습니다.
"정확해요. 그래서 컨텍스트 스위칭은 순수 오버헤드예요.
이 시간 동안 CPU는 실제 작업을 하지 못합니다." **디스패치 지연시간(Dispatch Latency)**은 컨텍스트 스위칭에 걸리는 시간입니다. 현대 컴퓨터에서는 보통 몇 마이크로초 정도지만, 이것이 초당 수백 번 발생하면 무시할 수 없는 오버헤드가 됩니다.
실무에서 이 개념이 중요한 이유가 있습니다. 스레드가 너무 많거나, 동기화를 잘못 설계하면 컨텍스트 스위칭이 과도하게 발생합니다.
CPU 사용률은 높은데 실제 처리량은 낮은 이상한 현상이 나타납니다. 이를 확인하려면 Linux에서 vmstat 명령어를 사용하면 됩니다.
cs(context switch) 항목이 컨텍스트 스위칭 횟수입니다. 이 값이 비정상적으로 높다면 스레드 설계나 동기화 방식을 검토해야 합니다.
주의할 점이 있습니다. 프로세스 간 컨텍스트 스위칭보다 스레드 간 컨텍스트 스위칭이 빠릅니다.
스레드는 같은 프로세스 내에서 메모리 공간을 공유하므로, 메모리 관련 정보를 교체할 필요가 없기 때문입니다. 박시니어 씨가 마무리했습니다.
"김개발 씨, 오늘 배운 내용을 정리하면 이래요. 운영체제는 여러 스케줄러와 디스패처를 통해 프로세스를 관리합니다.
장기 스케줄러는 입장을, 단기 스케줄러는 CPU 할당을, 중기 스케줄러는 메모리 관리를 담당해요. 그리고 디스패처가 실제로 프로세스를 교체하며, 이 과정에서 컨텍스트 스위칭이 발생합니다." 김개발 씨가 고개를 끄덕였습니다.
"이제 시스템 모니터링을 할 때 뭘 봐야 할지 알겠어요. 감사합니다, 선배님!"
실전 팁
💡 - vmstat 명령으로 컨텍스트 스위칭 횟수를 모니터링하세요
- 컨텍스트 스위칭이 과도하면 스레드 수를 줄이거나 비동기 방식을 고려하세요
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
교착 상태 (Deadlock) 완벽 가이드
운영체제에서 발생하는 교착 상태의 개념부터 예방, 회피, 탐지까지 완벽하게 이해할 수 있는 가이드입니다. 실무에서 마주칠 수 있는 동시성 문제를 해결하는 핵심 지식을 담았습니다.
동기화 도구 완벽 가이드
멀티스레드 프로그래밍에서 핵심이 되는 동기화 도구들을 알아봅니다. 뮤텍스, 세마포어, 모니터 등 운영체제의 동기화 메커니즘을 실무 예제와 함께 쉽게 이해할 수 있습니다.
프로세스 동기화 완벽 가이드
멀티스레드 환경에서 여러 프로세스가 공유 자원에 안전하게 접근하는 방법을 알아봅니다. Race Condition부터 Peterson 알고리즘까지 동기화의 핵심 개념을 실무 예제와 함께 쉽게 설명합니다.
CPU 스케줄링 알고리즘 (2)
Round Robin부터 MLFQ까지, 현대 운영체제가 사용하는 핵심 CPU 스케줄링 알고리즘들을 실무 예제와 함께 알아봅니다. 초급 개발자도 쉽게 이해할 수 있도록 스토리텔링 방식으로 설명합니다.
CPU 스케줄링 알고리즘 완벽 가이드 (1)
운영체제의 핵심인 CPU 스케줄링 알고리즘을 초급 개발자도 쉽게 이해할 수 있도록 설명합니다. FCFS부터 HRRN까지, 각 알고리즘의 동작 원리와 장단점을 실무 스토리와 함께 배워봅니다.