운영체제(Operating System, OS)는 컴퓨터 하드웨어와 소프트웨어 사이에서 자원을 관리하고 프로그램 실행을 돕는 핵심 소프트웨어이다.
✅ 운영체제의 핵심 구조: 커널과 시스템 콜
✅ 프로세스 및 스레드 관리: 프로그램 실행과 동기화
✅ 자원 관리: CPU, 메모리, 파일 시스템 관리
커널은 운영체제의 핵심 부분으로, 하드웨어를 직접 제어하며 CPU, 메모리, 입출력 장치 등을 관리하는 역할을 한다.
운영체제는 크게 두 가지 영역으로 나뉜다.
🔹 사용자 영역(User Space): 응용 프로그램(브라우저, 게임 등)이 실행되는 공간
🔹 커널 영역(Kernel Space): 운영체제 핵심 기능이 실행되는 공간
운영체제는 보안과 안정성을 위해 사용자 영역과 커널 영역을 분리하며, 일반 프로그램이 커널 영역에 직접 접근하는 것을 막는다.

사용자 프로그램이 운영체제의 기능을 사용하려면 시스템 콜(System Call)을 호출해야 한다.
즉, 프로그램이 파일을 열거나, 새로운 프로세스를 생성하는 등 중요한 작업을 수행할 때 운영체제의 도움을 요청하는 방식이다.
✅ 시스템 콜 과정
1️⃣ 응용 프로그램이 시스템 콜을 호출 🛑 (ex. open()으로 파일 열기)
2️⃣ 사용자 모드 → 커널 모드 전환 🛠️ (운영체제가 개입)
3️⃣ 운영체제 코드 실행 🎯 (파일을 읽고 메모리에 로드)
4️⃣ 시스템 콜 리턴 후 사용자 모드로 복귀 🔁
✅ 주요 시스템 콜 종류
📂 파일 관리: open(), close(), read(), write()
📂 디렉터리 관리: mkdir(), rmdir(), chdir()
🔄 프로세스 관리: fork(), execve(), exit(), waitpid()
💡 운영체제는 사용자 프로그램이 직접 커널 영역을 조작하지 못하도록 시스템 콜을 통해 제어한다.

프로세스는 실행 중인 프로그램을 의미한다.
단순한 프로그램 파일(.exe, .out)은 실행되지 않으면 프로세스가 아니며, 실행될 때 메모리에 적재되고 CPU 자원을 할당받아 실행되는 것이 프로세스이다.
스레드는 프로세스 내에서 실행되는 실행 단위이다.
즉, 프로세스가 하나 이상의 스레드를 가질 수 있으며, 여러 스레드가 동시에 실행될 수 있다.
CPU 스케줄링이란 여러 프로세스를 효율적으로 실행하기 위해 CPU를 할당하는 방법을 의미한다.
💡 CPU 스케줄링은 멀티태스킹 환경에서 필수적인 개념이며, 성능 최적화에 중요한 역할을 한다.
가상 메모리는 실제 물리 메모리보다 큰 공간을 사용할 수 있도록 도와주는 기술이다.
운영체제는 프로그램이 필요할 때만 데이터를 메모리에 로드하며, 이를 통해 효율적인 메모리 사용이 가능하다.
💡 가상 메모리는 제한된 물리 메모리를 효과적으로 활용하는 핵심 기술이다.
운영체제는 파일 시스템을 통해 데이터 저장 및 접근을 관리한다.
💡 운영체제는 파일의 접근 권한을 제어하며, 안정적인 데이터 저장을 보장한다.
운영체제에서 가장 중요한 개념 중 하나는 프로세스와 스레드이다.
이 두 개념을 이해하면, 컴퓨터가 어떻게 프로그램을 실행하고 관리하는지 파악할 수 있다.
✅ 프로세스(Process): 실행 중인 프로그램
✅ 스레드(Thread): 프로세스 내에서 실행되는 작은 작업 단위
운영체제는 프로세스를 생성하고 관리하며, 각 프로세스가 CPU와 메모리를 적절히 사용할 수 있도록 조정한다.
단순히 .exe 또는 .out 같은 파일이 저장되어 있다고 프로세스가 되는 것이 아니다.
📌 프로그램이 실행되어 메모리에 로드되고 CPU가 실행을 담당해야만 프로세스가 된다.
✅ 프로세스의 주요 특징
🔹 포그라운드 프로세스(Foreground Process)
🔹 백그라운드 프로세스(Background Process)
cron, sshd)프로세스는 실행될 때 운영체제에 의해 특정한 메모리 구조를 가진다.

📌 운영체제는 프로세스가 사용할 메모리를 아래와 같이 4가지 영역으로 구분하여 관리한다.
| 구분 | 설명 |
|---|---|
| 코드(Code) 영역 | 실행할 프로그램 코드가 저장되는 공간 (CPU가 실행할 명령어 포함) |
| 데이터(Data) 영역 | 전역 변수 및 정적 변수가 저장되는 공간 |
| 힙(Heap) 영역 | 실행 중 동적으로 할당되는 메모리 (ex. new, malloc 등) |
| 스택(Stack) 영역 | 함수 호출 시 생성되는 지역 변수, 매개변수, 리턴 주소 저장 |
✅ 코드 & 데이터 영역은 "정적 할당", 힙 & 스택 영역은 "동적 할당"이 이루어진다.
💡 주의할 점
운영체제는 각 프로세스의 정보를 관리하기 위해 PCB(프로세스 제어 블록)를 사용한다.
✅ PCB는 프로세스가 실행 중인 상태를 저장하는 데이터 구조이다.
🔹 PCB에 저장되는 정보
✅ 운영체제는 PCB를 이용해 프로세스를 스케줄링하고 관리한다.
CPU가 실행할 프로세스를 변경할 때, 현재 프로세스의 상태를 저장하고 새로운 프로세스를 로드하는 과정을 의미한다.
이 과정에서 PCB를 사용하여 프로세스의 실행 상태를 저장하고 복구한다.

✅ 문맥 교환이 발생하는 경우
1️⃣ 타이머 인터럽트: 특정 프로세스가 할당된 시간이 끝남
2️⃣ 입출력(I/O) 요청: I/O 작업을 위해 대기 상태로 변경
3️⃣ 시스템 호출(SYS CALL): 프로세스가 커널 기능을 요청
✅ 문맥 교환의 주요 작업
📌 💡 문맥 교환은 필수적이지만, 불필요한 문맥 교환을 줄이면 성능이 향상된다.

멀티태스킹 환경에서는 여러 개의 프로세스나 스레드가 동시에 실행될 수 있다.
멀티스레드는 하나의 프로세스 내에서 여러 개의 스레드가 실행되는 구조이다.

✅ 멀티스레드 특징
📌 💡 단점: 스레드 동기화(Synchronization) 필요
멀티프로세스는 여러 개의 독립적인 프로세스가 동시에 실행되는 방식이다.
✅ 멀티프로세스 특징
📌 💡 단점: 문맥 교환 비용이 커서 성능이 저하될 수 있음.
IPC는 운영체제에서 서로 다른 프로세스가 데이터를 주고받는 방식을 의미한다.
멀티프로세스 환경에서는 각 프로세스가 독립적인 메모리 공간을 사용 하기 때문에 직접 데이터를 공유할 수 없다.

공유 메모리는 프로세스 간 특정 메모리 공간을 공유하여 데이터를 주고받는 방법이다.
운영체제(OS)가 공유 메모리 영역을 할당하고, 여러 프로세스가 해당 영역을 읽거나 쓸 수 있도록 한다.
📌 공유 메모리 특징
✅ 빠름 → CPU가 직접 메모리에 접근하여 데이터 복사 없이 사용 가능
✅ 많은 데이터 처리 가능 → 대량의 데이터 전송에도 유리
❌ 동기화 필요 → 여러 프로세스가 동시에 접근하면 충돌 가능 (Mutex, Semaphore 사용)
❌ 메모리 관리 필요 → 프로세스 종료 후 메모리를 해제하지 않으면 낭비 발생
| 방식 | 설명 | 장점 | 단점 |
|---|---|---|---|
| 공유 메모리(Shared Memory) | 여러 프로세스가 같은 메모리 공간을 공유하여 데이터 전달 | 속도가 빠름 (메모리 직접 접근) | 동기화 문제 발생 가능 (Mutex, Semaphore 필요) |
| 메시지 큐(Message Queue) | 운영체제(OS)가 관리하는 큐를 통해 프로세스 간 데이터 전송 | 프로세스 간 독립성 보장 | 속도가 상대적으로 느림 |
| 소켓(Socket) | 네트워크를 통해 서로 다른 프로세스가 통신 | 원격 프로세스와 통신 가능 | 오버헤드가 큼 |
| 파이프(Pipe) | 한 프로세스에서 다른 프로세스로 데이터를 순차적으로 전달 | 간단한 통신 가능 | 단방향 통신(기본), 크기 제한 존재 |
| 시그널(Signal) | 프로세스에 특정 이벤트(알림)를 전달 | 간단한 제어 가능 | 데이터 전달 불가능 |
synchronized 키워드 또는 ReentrantLock을 사용할 수 있음.✅ Java에서 파일 접근을 동기화하는 방법
import java.io.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class SharedFile {
private static final Lock lock = new ReentrantLock();
private static final String FILE_PATH = "shared_file.txt";
public static void writeToFile(String data) {
lock.lock(); // 🔒 파일 잠금
try (FileWriter fw = new FileWriter(FILE_PATH, true);
BufferedWriter bw = new BufferedWriter(fw)) {
bw.write(data);
bw.newLine();
System.out.println(Thread.currentThread().getName() + " wrote: " + data);
} catch (IOException e) {
e.printStackTrace();
} finally {
lock.unlock(); // 🔓 파일 잠금 해제
}
}
}
public class RaceConditionExample {
public static void main(String[] args) {
Thread t1 = new Thread(() -> SharedFile.writeToFile("Thread A data"));
Thread t2 = new Thread(() -> SharedFile.writeToFile("Thread B data"));
t1.start();
t2.start();
}
}
특정 개수의 스레드만 공유 자원에 접근할 수 있도록 허용하는 역할을 한다.
📌 세마포어 동작 방식
permit 값은 공유 자원에 동시에 접근할 수 있는 스레드의 최대 개수.permit = 2이면 최대 2개의 스레드만 자원에 접근 가능.acquire() 실행release()할 때까지 대기 상태.release() 실행📌 Java 세마포어 예제
다음 예제는 3개의 스레드가 동시에 최대 2개의 스레드만 실행할 수 있도록 세마포어를 설정한 코드이다.
import java.util.concurrent.Semaphore;
class SharedResource {
private final Semaphore semaphore;
public SharedResource(int permits) {
this.semaphore = new Semaphore(permits); // 최대 접근 가능 스레드 개수 설정
}
public void useResource(String threadName) {
try {
semaphore.acquire(); // 세마포어 획득 (permit 감소)
System.out.println(threadName + " : 자원 사용 시작...");
Thread.sleep(2000); // 자원 사용 시간 (2초)
System.out.println(threadName + " : 자원 사용 종료...");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release(); // 세마포어 해제 (permit 증가)
}
}
}
public class SemaphoreExample {
public static void main(String[] args) {
SharedResource resource = new SharedResource(2); // 최대 2개 스레드 허용
Thread t1 = new Thread(() -> resource.useResource("Thread A"));
Thread t2 = new Thread(() -> resource.useResource("Thread B"));
Thread t3 = new Thread(() -> resource.useResource("Thread C"));
t1.start();
t2.start();
t3.start();
}
}
📌 실행 결과
(출력 순서는 스레드 스케줄러에 따라 달라질 수 있음)
Thread A : 자원 사용 시작...
Thread B : 자원 사용 시작...
(Thread C는 대기 중)
Thread A : 자원 사용 종료...
Thread B : 자원 사용 종료...
(Thread C가 실행 가능 상태가 됨)
Thread C : 자원 사용 시작...
Thread C : 자원 사용 종료...
Thread A와 Thread B가 동시에 실행됨.Thread C는 대기 상태에 있다가, 하나의 자원이 반환되면 실행됨.📌 세마포어를 사용하는 이유
✅ 경쟁 상태(Race Condition) 방지
✅ 한정된 자원(예: 데이터베이스, 파일, 네트워크 연결) 관리 가능
✅ 멀티스레드 환경에서 과부하 방지
예제에서 semaphore(2)을 설정한 이유?
조건 변수(Condition Variable)와 모니터(Monitor)는 멀티스레딩 환경에서 스레드 간 동기화를 위한 기법이다.
공유 자원(예: 버퍼, 큐 등)에 여러 스레드가 접근할 때 데이터의 일관성을 유지하는 데 사용된다.
wait(), signal(), broadcast() 같은 메서드를 사용하여 구현.🔹 주요 메서드
wait() → 조건이 충족될 때까지 현재 스레드를 대기 상태로 변경.signal() → 하나의 스레드를 깨워서 실행 가능 상태로 변경.broadcast() → 대기 중인 모든 스레드를 깨움.📌 Java에서 synchronized 키워드를 사용하면 모니터 기능을 제공
synchronized를 사용하면 하나의 스레드만 해당 블록을 실행 가능.📌 조건 변수 & 모니터 예제
🔹 생산자-소비자(Producer-Consumer) 문제 해결
설명:
📌 Java 코드 (wait() & notify() 사용)
import java.util.LinkedList;
import java.util.Queue;
class SharedBuffer {
private final Queue<Integer> buffer = new LinkedList<>();
private final int MAX_SIZE = 5;
public synchronized void produce(int item) throws InterruptedException {
while (buffer.size() == MAX_SIZE) {
System.out.println("버퍼가 가득 참! 생산자 대기 중...");
wait(); // 버퍼가 가득 차면 대기
}
buffer.add(item);
System.out.println("생산됨: " + item);
notify(); // 소비자 깨우기
}
public synchronized int consume() throws InterruptedException {
while (buffer.isEmpty()) {
System.out.println("버퍼가 비었음! 소비자 대기 중...");
wait(); // 버퍼가 비었으면 대기
}
int item = buffer.poll();
System.out.println("소비됨: " + item);
notify(); // 생산자 깨우기
return item;
}
}
public class ConditionVariableExample {
public static void main(String[] args) {
SharedBuffer buffer = new SharedBuffer();
Thread producer = new Thread(() -> {
try {
for (int i = 1; i <= 10; i++) {
buffer.produce(i);
Thread.sleep(500); // 생산 속도 조절
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread consumer = new Thread(() -> {
try {
for (int i = 1; i <= 10; i++) {
buffer.consume();
Thread.sleep(1000); // 소비 속도 조절
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
producer.start();
consumer.start();
}
}
📌 코드 설명
wait()과 notify()의 역할
wait() → 현재 스레드를 대기 상태로 변경 (즉, wait()을 호출한 스레드는 CPU를 점유하지 않고 잠듦).notify() → 대기 중인 스레드를 깨움 (하나의 스레드만 실행 가능).notifyAll() → 모든 대기 중인 스레드를 깨움.실행 흐름
생산자 스레드가 데이터를 buffer에 추가.
버퍼가 가득 차면(wait) 생산자는 대기하고, 소비자가 notify() 호출할 때까지 기다림.
소비자 스레드가 buffer에서 데이터를 꺼냄.
버퍼가 비면(wait) 소비자는 대기하고, 생산자가 notify() 호출할 때까지 기다림.
이 과정을 반복하여 버퍼가 가득 차거나 비었을 때 스레드가 대기 상태로 변경됨.
스레드 안전(Thread Safety)과 synchronized 키워드
스레드 안전(Thread Safety)이란 멀티스레드 환경에서 여러 스레드가 동시에 공유 자원에 접근할 때, 프로그램이 예상대로 동작하는 것을 의미한다.
✅ 스레드 안전한 코드의 특징
synchronized 키워드란?
Java에서 synchronized 키워드는 스레드 동기화(Thread Synchronization)를 제공하는 키워드로, 한 번에 하나의 스레드만 특정 블록(또는 메서드)에 접근하도록 보장한다.
✅ synchronized를 사용하면 어떤 효과?
📌 교착 상태(Deadlock)란?
즉, A 프로세스가 B의 자원을 기다리고, B 프로세스는 A의 자원을 기다리는 상황이 발생하면 교착 상태가 된다.
미국 컴퓨터 과학자 E. G. Coffman이 제시한 교착 상태 발생 4가지 조건이 동시에 만족할 때 교착 상태가 발생할 수 있다.
| 조건 | 설명 |
|---|---|
| 1️⃣ 상호 배제 (Mutual Exclusion) | 한 번에 한 개의 프로세스만 자원을 사용할 수 있어야 한다. |
| 2️⃣ 점유 및 대기 (Hold and Wait) | 프로세스가 이미 할당된 자원을 유지하면서 추가 자원을 요청할 수 있어야 한다. |
| 3️⃣ 비선점 (No Preemption) | 프로세스가 자원을 자발적으로 해제하지 않으면 강제로 빼앗을 수 없어야 한다. |
| 4️⃣ 순환 대기 (Circular Wait) | 두 개 이상의 프로세스가 서로 자원을 점유한 상태에서 순환적으로 대기하는 경우 발생한다. |
✅ 교착 상태를 해결하려면 이 네 가지 조건 중 하나 이상을 제거해야 한다.
교착 상태를 해결하는 방법은 예방(Prevention), 회피(Avoidance), 탐지(Detection), 복구(Recovery)로 나뉜다.
✅ 1) 예방(Prevention)
🔹 교착 상태 발생 조건 중 하나 이상을 제거하여 발생을 원천 차단하는 방법.
단점: 자원의 활용도가 낮아질 수 있음.
| 해결 방법 | 교착 상태 발생 조건 제거 | 설명 |
|---|---|---|
| 상호 배제 방지 | 상호 배제 제거 | 모든 자원을 여러 프로세스가 동시에 사용할 수 있도록 함. (ex. 읽기 전용 파일) |
| 점유 및 대기 방지 | 점유 및 대기 제거 | 프로세스가 자원을 요청할 때 모든 자원을 한꺼번에 요청하도록 강제. (ex. 은행가 알고리즘) |
| 비선점 방지 | 비선점 제거 | 자원을 점유한 프로세스가 강제로 자원을 해제하도록 함. (ex. 높은 우선순위 프로세스가 자원을 빼앗음) |
| 순환 대기 방지 | 순환 대기 제거 | 자원에 순서를 부여하여 항상 낮은 번호의 자원부터 요청하도록 강제. |
✅ 예제 (점유 및 대기 방지)
synchronized (lockA) { // 자원 A 확보
synchronized (lockB) { // 자원 B 확보
// 안전한 코드 실행
}
}
✅ 순환 대기를 방지하기 위해 항상 lockA → lockB 순서로 자원을 획득.
✅ 2) 회피(Avoidance)
🔹 교착 상태가 발생할 가능성이 있는 요청을 사전에 차단하는 방법
대표적인 알고리즘: 은행가 알고리즘(Banker's Algorithm)
단점: 자원의 사용이 비효율적일 수 있음.
✅ 은행가 알고리즘 개념
✅ 3) 탐지(Detection) 및 복구(Recovery)
🔹 교착 상태가 발생한 후 이를 탐지하고 복구하는 방법
단점: 교착 상태가 발생한 후 처리하므로 성능 저하 발생 가능.
| 방법 | 설명 |
|---|---|
| 탐지(Detection) | - 주기적으로 자원 할당 그래프(Resource Allocation Graph, RAG)를 분석하여 순환 대기 여부 확인. |
| 복구(Recovery) | - 교착 상태에 있는 프로세스를 강제 종료하거나, 일부 자원을 강제 회수하여 교착 상태를 해결. |
✅ 예제 (강제 종료 방법)
if (isDeadlocked()) {
process.terminate(); // 교착 상태 발생 시 특정 프로세스 종료
}
✅ 예제 (자원 선점 방법)
if (isDeadlocked()) {
process.releaseResource(); // 교착 상태 해소를 위해 자원 반환
}
| 방법 | 설명 | 장점 | 단점 |
|---|---|---|---|
| 예방(Prevention) | 교착 상태 발생 조건 제거 | 확실하게 방지 가능 | 자원의 활용도가 낮아질 수 있음 |
| 회피(Avoidance) | 안전한 상태에서만 자원 할당 (은행가 알고리즘) | 교착 상태 발생 가능성을 최소화 | 자원 사용이 비효율적 |
| 탐지 및 복구(Detection & Recovery) | 교착 상태 발생 후 탐지 및 해결 | 자원 활용도를 높일 수 있음 | 교착 상태 발생 후 해결해야 하므로 비용이 큼 |
🔹 교착 상태 발생 예제
class DeadlockExample {
static final Object lockA = new Object();
static final Object lockB = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (lockA) {
System.out.println("Thread 1: lockA 획득");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lockB) {
System.out.println("Thread 1: lockB 획득");
}
}
});
Thread t2 = new Thread(() -> {
synchronized (lockB) {
System.out.println("Thread 2: lockB 획득");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lockA) {
System.out.println("Thread 2: lockA 획득");
}
}
});
t1.start();
t2.start();
}
}
✅ 이 코드는 교착 상태(Deadlock)가 발생함!
Thread 1은 lockA를 획득한 후 lockB를 기다림.Thread 2는 lockB를 획득한 후 lockA를 기다림.🔹 교착 상태 해결 방법 적용 (순서 고정)
class DeadlockSolution {
static final Object lockA = new Object();
static final Object lockB = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (lockA) { // 항상 lockA → lockB 순서로 획득
synchronized (lockB) {
System.out.println("Thread 1: 작업 완료");
}
}
});
Thread t2 = new Thread(() -> {
synchronized (lockA) { // lockA → lockB 순서로 맞춰줌
synchronized (lockB) {
System.out.println("Thread 2: 작업 완료");
}
}
});
t1.start();
t2.start();
}
}
✅ 순서를 통일(lockA → lockB)하여 순환 대기를 방지함으로써 교착 상태 해결!
Ref. 📗《이것이 취업을 위한 컴퓨터 과학이다 with CS 기술 면접》, 강민철