포스트

[CS 기초 #4] 운영체제: 프로세스, 스레드, 메모리 관리의 모든 것

[CS 기초 #4] 운영체제: 프로세스, 스레드, 메모리 관리의 모든 것

컴퓨터의 지휘자를 만나봅시다! 운영체제는 수많은 프로그램과 하드웨어를 조율하여 모든 것이 원활하게 동작하도록 합니다. 멀티태스킹의 비밀을 알아봅시다.

🎯 이 글을 읽고 나면

  • 운영체제의 역할과 4대 핵심 기능을 설명할 수 있습니다
  • 프로세스와 스레드의 차이점을 명확히 이해합니다
  • CPU 스케줄링 알고리즘(FCFS, SJF, RR)을 비교할 수 있습니다
  • 가상 메모리와 페이징의 원리를 알게 됩니다
  • 교착상태(Deadlock)의 4가지 조건과 예방법을 이해합니다

📚 사전 지식

  • 프로그래밍 기초: 프로세스와 메모리 개념 이해
  • 컴퓨터 구조: 이전 글: 컴퓨터 구조를 읽으면 도움됩니다
  • 멀티스레딩 경험: 없어도 괜찮지만, 있으면 더 쉽게 이해됩니다

💡 핵심 개념 미리보기

운영체제(OS)는 하드웨어와 응용 프로그램 사이의 중재자로, CPU/메모리/디스크 같은 자원을 효율적으로 관리합니다. 프로세스는 실행 중인 프로그램이고, 스레드는 프로세스 내의 경량 실행 단위입니다. OS는 스케줄링으로 여러 프로세스가 CPU를 공유하게 하고, 가상 메모리로 물리 메모리보다 큰 프로그램도 실행 가능하게 만듭니다!


🖥️ 들어가며: 운영체제는 왜 필요한가?

여러 프로그램이 동시에 실행되는데 충돌하지 않는 이유가 궁금하신가요? 8GB RAM으로 수십 개의 프로그램을 실행할 수 있는 비밀은 무엇일까요?

오늘은 컴퓨터 자원을 효율적으로 관리하는 운영체제(OS)의 핵심 개념들을 알아보겠습니다. 이 지식은 멀티스레딩, 동시성 프로그래밍, 시스템 최적화의 기초가 됩니다!

🎯 운영체제의 역할

운영체제는 하드웨어와 응용 프로그램 사이의 중재자 역할을 합니다.

graph TB
    subgraph "컴퓨터 시스템 계층"
        APP[응용 프로그램<br/>Chrome, VSCode, Game]
        OS[운영체제<br/>Windows, Linux, macOS]
        HW[하드웨어<br/>CPU, Memory, Disk, Network]
        
        APP --> OS
        OS --> HW
    end
    
    subgraph "OS의 주요 기능"
        PM[프로세스 관리]
        MM[메모리 관리]
        FM[파일 시스템]
        IO[I/O 관리]
        SEC[보안]
    end
    
    OS --> PM
    OS --> MM
    OS --> FM
    OS --> IO
    OS --> SEC

OS의 4대 핵심 기능

  1. 자원 관리: CPU, 메모리, 디스크 할당
  2. 추상화: 복잡한 하드웨어를 단순한 인터페이스로 제공
  3. 보호: 프로그램 간 간섭 방지
  4. 공유: 여러 프로그램이 자원을 효율적으로 공유
🔰 초보자를 위한 비유

운영체제를 교통 관제 시스템에 비유해봅시다!

  • OS (운영체제): 교통 관제탑. 모든 비행기(프로그램)의 이착륙을 조율합니다
  • 프로세스: 각각의 비행기. 독립적으로 운항하지만 관제탑의 지시를 따릅니다
  • 스레드: 비행기 내의 승무원. 같은 비행기(프로세스) 안에서 여러 일을 동시에 처리합니다
  • CPU 스케줄링: 활주로 배정. 여러 비행기가 하나의 활주로를 공유하도록 순서를 정합니다
  • 메모리 관리: 주차장 관리. 한정된 공간에 비행기들을 효율적으로 배치합니다
  • 교착상태(Deadlock): 두 비행기가 서로 상대방이 비켜주길 기다리며 멈춰있는 상황

관제탑 없이는 충돌이 일어나겠죠? OS도 이와 같이 모든 것을 조율합니다!

🔄 프로세스 (Process)

프로세스는 실행 중인 프로그램입니다. 프로그램은 디스크에 있는 정적인 파일이지만, 프로세스는 메모리에 로드되어 실행되는 동적인 개체입니다.

프로세스의 메모리 구조

graph TD
    subgraph "프로세스 메모리 레이아웃"
        STACK[스택<br/>지역변수, 함수 호출]
        HEAP[힙<br/>동적 할당 메모리]
        DATA[데이터<br/>전역변수, 정적변수]
        TEXT[텍스트<br/>프로그램 코드]
    end
    
    STACK -.->|"성장 ↓"| HEAP
    HEAP -.->|"성장 ↑"| STACK
    
    style STACK fill:#ff9999
    style HEAP fill:#99ff99
    style DATA fill:#9999ff
    style TEXT fill:#ffff99

프로세스 상태 전이도

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import time
import random
from enum import Enum

class ProcessState(Enum):
    NEW = "생성"
    READY = "준비"
    RUNNING = "실행"
    WAITING = "대기"
    TERMINATED = "종료"

class Process:
    """프로세스 시뮬레이션"""
    
    def __init__(self, pid, name):
        self.pid = pid
        self.name = name
        self.state = ProcessState.NEW
        self.cpu_burst = random.randint(1, 5)  # CPU 사용 시간
        self.io_burst = random.randint(1, 3)   # I/O 대기 시간
        
    def state_transition(self):
        """프로세스 상태 전이"""
        transitions = {
            ProcessState.NEW: ProcessState.READY,
            ProcessState.READY: ProcessState.RUNNING,
            ProcessState.RUNNING: [ProcessState.WAITING, ProcessState.TERMINATED],
            ProcessState.WAITING: ProcessState.READY,
            ProcessState.TERMINATED: None
        }
        
        if self.state == ProcessState.RUNNING:
            # 실행 중에는 대기 또는 종료 가능
            if random.random() < 0.7:  # 70% 확률로 I/O 대기
                self.state = ProcessState.WAITING
            else:
                self.state = ProcessState.TERMINATED
        else:
            next_state = transitions.get(self.state)
            if next_state:
                self.state = next_state if not isinstance(next_state, list) else next_state[0]
    
    def __str__(self):
        return f"PID {self.pid} ({self.name}): {self.state.value}"

# 프로세스 상태 전이 시뮬레이션
processes = [
    Process(1, "Chrome"),
    Process(2, "VSCode"),
    Process(3, "Python")
]

print("프로세스 상태 전이 시뮬레이션")
print("=" * 40)

for cycle in range(5):
    print(f"\n사이클 {cycle + 1}:")
    for process in processes:
        old_state = process.state
        process.state_transition()
        print(f"  {process} (이전: {old_state.value})")

프로세스 제어 블록 (PCB)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class PCB:
    """Process Control Block - 프로세스 정보 저장"""
    
    def __init__(self, pid):
        self.pid = pid                    # 프로세스 ID
        self.state = ProcessState.NEW     # 프로세스 상태
        self.program_counter = 0          # 다음 실행할 명령어 주소
        self.registers = {}                # CPU 레지스터 값들
        self.memory_limits = (0, 0)       # 메모리 경계
        self.open_files = []              # 열린 파일 목록
        self.cpu_time = 0                 # 사용한 CPU 시간
        self.priority = 0                 # 우선순위
        
    def save_context(self, pc, registers):
        """컨텍스트 저장 (Context Switching 시)"""
        self.program_counter = pc
        self.registers = registers.copy()
        
    def restore_context(self):
        """컨텍스트 복원"""
        return self.program_counter, self.registers.copy()
    
    def __repr__(self):
        return f"PCB(PID={self.pid}, State={self.state.value}, PC={self.program_counter})"

# PCB 사용 예시
pcb1 = PCB(1)
pcb1.save_context(100, {"AX": 10, "BX": 20})
print(pcb1)

pc, regs = pcb1.restore_context()
print(f"복원된 PC: {pc}, 레지스터: {regs}")

🧵 스레드 (Thread)

스레드는 프로세스 내에서 실행되는 경량 실행 단위입니다.

프로세스 vs 스레드

graph LR
    subgraph "프로세스 (무거움)"
        P1[프로세스 1]
        P1M[독립 메모리]
        P1R[독립 자원]
        P1 --> P1M
        P1 --> P1R
    end
    
    subgraph "스레드 (가벼움)"
        T1[스레드 1]
        T2[스레드 2]
        TM[공유 메모리]
        TR[공유 자원]
        T1 --> TM
        T2 --> TM
        T1 --> TR
        T2 --> TR
    end

멀티스레딩 예제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
import threading
import time
import queue

class ThreadPool:
    """간단한 스레드 풀 구현"""
    
    def __init__(self, num_threads=4):
        self.num_threads = num_threads
        self.task_queue = queue.Queue()
        self.threads = []
        self.running = True
        
    def worker(self):
        """워커 스레드 함수"""
        while self.running:
            try:
                task, args = self.task_queue.get(timeout=1)
                print(f"[{threading.current_thread().name}] 작업 시작: {task.__name__}")
                result = task(*args)
                print(f"[{threading.current_thread().name}] 작업 완료: {result}")
                self.task_queue.task_done()
            except queue.Empty:
                continue
    
    def start(self):
        """스레드 풀 시작"""
        for i in range(self.num_threads):
            t = threading.Thread(target=self.worker, name=f"Worker-{i+1}")
            t.start()
            self.threads.append(t)
    
    def submit(self, task, *args):
        """작업 제출"""
        self.task_queue.put((task, args))
    
    def shutdown(self):
        """스레드 풀 종료"""
        self.running = False
        for t in self.threads:
            t.join()

# 작업 함수들
def calculate_sum(n):
    return sum(range(n))

def calculate_factorial(n):
    result = 1
    for i in range(1, n + 1):
        result *= i
    return result

# 스레드 풀 사용
pool = ThreadPool(num_threads=3)
pool.start()

# 작업 제출
pool.submit(calculate_sum, 1000000)
pool.submit(calculate_factorial, 10)
pool.submit(calculate_sum, 500000)

time.sleep(2)  # 작업 완료 대기
pool.shutdown()

스레드 동기화 문제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
import threading
import time

class BankAccount:
    """동기화 문제를 보여주는 은행 계좌"""
    
    def __init__(self, balance=1000):
        self.balance = balance
        self.lock = threading.Lock()  # 뮤텍스
        
    def withdraw_unsafe(self, amount):
        """안전하지 않은 출금 (Race Condition 발생 가능)"""
        if self.balance >= amount:
            time.sleep(0.001)  # 실제 처리 시간 시뮬레이션
            self.balance -= amount
            return True
        return False
    
    def withdraw_safe(self, amount):
        """안전한 출금 (Lock 사용)"""
        with self.lock:
            if self.balance >= amount:
                time.sleep(0.001)
                self.balance -= amount
                return True
            return False
    
    def deposit(self, amount):
        """입금"""
        with self.lock:
            self.balance += amount

# Race Condition 시연
def multiple_withdrawals(account, method="unsafe"):
    """여러 번 출금 시도"""
    for _ in range(5):
        if method == "unsafe":
            account.withdraw_unsafe(200)
        else:
            account.withdraw_safe(200)

# 테스트
print("Race Condition 테스트:")
print("-" * 40)

# 안전하지 않은 방법
account1 = BankAccount(1000)
threads1 = []
for i in range(3):
    t = threading.Thread(target=multiple_withdrawals, args=(account1, "unsafe"))
    threads1.append(t)
    t.start()

for t in threads1:
    t.join()

print(f"안전하지 않은 방법 - 최종 잔액: ${account1.balance}")
print(f"예상 잔액: $-500 (1000 - 3*5*200)")

# 안전한 방법
account2 = BankAccount(1000)
threads2 = []
for i in range(3):
    t = threading.Thread(target=multiple_withdrawals, args=(account2, "safe"))
    threads2.append(t)
    t.start()

for t in threads2:
    t.join()

print(f"안전한 방법 - 최종 잔액: ${account2.balance}")

📅 CPU 스케줄링

여러 프로세스가 CPU를 효율적으로 사용하도록 관리하는 기법입니다.

주요 스케줄링 알고리즘

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
from collections import deque
import heapq

class Process:
    def __init__(self, pid, arrival_time, burst_time, priority=0):
        self.pid = pid
        self.arrival_time = arrival_time
        self.burst_time = burst_time
        self.remaining_time = burst_time
        self.priority = priority
        self.waiting_time = 0
        self.turnaround_time = 0
        self.completion_time = 0

class CPUScheduler:
    """CPU 스케줄링 알고리즘 시뮬레이터"""
    
    @staticmethod
    def fcfs(processes):
        """First Come First Served"""
        processes.sort(key=lambda x: x.arrival_time)
        current_time = 0
        
        for process in processes:
            if current_time < process.arrival_time:
                current_time = process.arrival_time
            
            process.waiting_time = current_time - process.arrival_time
            current_time += process.burst_time
            process.completion_time = current_time
            process.turnaround_time = process.completion_time - process.arrival_time
        
        return processes
    
    @staticmethod
    def sjf(processes):
        """Shortest Job First"""
        processes.sort(key=lambda x: (x.arrival_time, x.burst_time))
        completed = []
        ready_queue = []
        current_time = 0
        remaining = list(processes)
        
        while remaining or ready_queue:
            # 도착한 프로세스를 준비 큐에 추가
            while remaining and remaining[0].arrival_time <= current_time:
                heapq.heappush(ready_queue, (remaining[0].burst_time, remaining[0]))
                remaining.pop(0)
            
            if ready_queue:
                burst_time, process = heapq.heappop(ready_queue)
                process.waiting_time = current_time - process.arrival_time
                current_time += process.burst_time
                process.completion_time = current_time
                process.turnaround_time = process.completion_time - process.arrival_time
                completed.append(process)
            else:
                current_time += 1
        
        return completed
    
    @staticmethod
    def round_robin(processes, quantum=2):
        """Round Robin"""
        queue = deque(sorted(processes, key=lambda x: x.arrival_time))
        current_time = 0
        completed = []
        
        while queue:
            process = queue.popleft()
            
            if current_time < process.arrival_time:
                current_time = process.arrival_time
            
            execution_time = min(quantum, process.remaining_time)
            current_time += execution_time
            process.remaining_time -= execution_time
            
            if process.remaining_time == 0:
                process.completion_time = current_time
                process.turnaround_time = process.completion_time - process.arrival_time
                process.waiting_time = process.turnaround_time - process.burst_time
                completed.append(process)
            else:
                queue.append(process)
        
        return completed
    
    @staticmethod
    def print_results(algorithm_name, processes):
        """결과 출력"""
        print(f"\n{algorithm_name} 스케줄링 결과:")
        print("-" * 60)
        print("PID | 도착 | 실행 | 대기 | 반환 | 완료")
        print("-" * 60)
        
        total_waiting = 0
        total_turnaround = 0
        
        for p in processes:
            print(f"{p.pid:3} | {p.arrival_time:4} | {p.burst_time:4} | "
                  f"{p.waiting_time:4} | {p.turnaround_time:4} | {p.completion_time:4}")
            total_waiting += p.waiting_time
            total_turnaround += p.turnaround_time
        
        n = len(processes)
        print("-" * 60)
        print(f"평균 대기 시간: {total_waiting/n:.2f}")
        print(f"평균 반환 시간: {total_turnaround/n:.2f}")

# 스케줄링 알고리즘 테스트
test_processes = [
    Process(1, 0, 8),
    Process(2, 1, 4),
    Process(3, 2, 9),
    Process(4, 3, 5)
]

# 각 알고리즘 실행
scheduler = CPUScheduler()

# FCFS
fcfs_processes = [Process(p.pid, p.arrival_time, p.burst_time) 
                  for p in test_processes]
scheduler.print_results("FCFS", scheduler.fcfs(fcfs_processes))

# SJF
sjf_processes = [Process(p.pid, p.arrival_time, p.burst_time) 
                 for p in test_processes]
scheduler.print_results("SJF", scheduler.sjf(sjf_processes))

# Round Robin
rr_processes = [Process(p.pid, p.arrival_time, p.burst_time) 
                for p in test_processes]
scheduler.print_results("Round Robin (Quantum=2)", 
                       scheduler.round_robin(rr_processes, quantum=2))

💾 메모리 관리

가상 메모리와 페이징

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
class Page:
    def __init__(self, page_number, frame_number=None):
        self.page_number = page_number
        self.frame_number = frame_number
        self.valid = frame_number is not None
        self.referenced = False
        self.modified = False

class PageTable:
    """페이지 테이블 시뮬레이션"""
    
    def __init__(self, num_pages):
        self.pages = [Page(i) for i in range(num_pages)]
        
    def map_page(self, page_number, frame_number):
        """페이지를 프레임에 매핑"""
        if 0 <= page_number < len(self.pages):
            self.pages[page_number].frame_number = frame_number
            self.pages[page_number].valid = True
    
    def get_physical_address(self, virtual_address, page_size=4096):
        """가상 주소를 물리 주소로 변환"""
        page_number = virtual_address // page_size
        offset = virtual_address % page_size
        
        if page_number >= len(self.pages) or not self.pages[page_number].valid:
            raise Exception(f"Page Fault: 페이지 {page_number}가 메모리에 없습니다")
        
        frame_number = self.pages[page_number].frame_number
        physical_address = frame_number * page_size + offset
        
        return physical_address

# 페이지 테이블 사용 예시
page_table = PageTable(num_pages=8)

# 일부 페이지만 물리 메모리에 매핑
page_table.map_page(0, 3)  # 페이지 0 → 프레임 3
page_table.map_page(1, 7)  # 페이지 1 → 프레임 7
page_table.map_page(3, 2)  # 페이지 3 → 프레임 2

# 주소 변환 테스트
test_addresses = [0, 4096, 12288, 8192]
for va in test_addresses:
    try:
        pa = page_table.get_physical_address(va)
        print(f"가상 주소 {va:5} → 물리 주소 {pa:5}")
    except Exception as e:
        print(f"가상 주소 {va:5}{e}")

페이지 교체 알고리즘

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
class MemoryManager:
    """메모리 관리자 - 페이지 교체 알고리즘"""
    
    def __init__(self, num_frames):
        self.num_frames = num_frames
        self.frames = []
        self.page_faults = 0
        
    def fifo(self, page_references):
        """FIFO 페이지 교체"""
        self.frames = []
        self.page_faults = 0
        
        for page in page_references:
            if page not in self.frames:
                self.page_faults += 1
                if len(self.frames) < self.num_frames:
                    self.frames.append(page)
                else:
                    self.frames.pop(0)  # 가장 오래된 페이지 제거
                    self.frames.append(page)
        
        return self.page_faults
    
    def lru(self, page_references):
        """LRU (Least Recently Used) 페이지 교체"""
        self.frames = []
        self.page_faults = 0
        recent_use = {}
        
        for i, page in enumerate(page_references):
            if page not in self.frames:
                self.page_faults += 1
                if len(self.frames) < self.num_frames:
                    self.frames.append(page)
                else:
                    # 가장 오래 사용되지 않은 페이지 찾기
                    lru_page = min(self.frames, key=lambda x: recent_use[x])
                    self.frames[self.frames.index(lru_page)] = page
            
            recent_use[page] = i
        
        return self.page_faults
    
    def optimal(self, page_references):
        """Optimal 페이지 교체 (이론적)"""
        self.frames = []
        self.page_faults = 0
        
        for i, page in enumerate(page_references):
            if page not in self.frames:
                self.page_faults += 1
                if len(self.frames) < self.num_frames:
                    self.frames.append(page)
                else:
                    # 가장 늦게 사용될 페이지 찾기
                    future_use = {}
                    for frame_page in self.frames:
                        try:
                            future_use[frame_page] = page_references[i+1:].index(frame_page)
                        except ValueError:
                            future_use[frame_page] = float('inf')
                    
                    victim = max(future_use, key=future_use.get)
                    self.frames[self.frames.index(victim)] = page
        
        return self.page_faults

# 페이지 교체 알고리즘 비교
page_references = [7, 0, 1, 2, 0, 3, 0, 4, 2, 3, 0, 3, 2, 1, 2, 0, 1, 7, 0, 1]
num_frames = 3

manager = MemoryManager(num_frames)

print("페이지 교체 알고리즘 비교")
print(f"페이지 참조 열: {page_references}")
print(f"프레임 수: {num_frames}")
print("-" * 40)

fifo_faults = manager.fifo(page_references)
print(f"FIFO: {fifo_faults} 페이지 폴트")

lru_faults = manager.lru(page_references)
print(f"LRU: {lru_faults} 페이지 폴트")

optimal_faults = manager.optimal(page_references)
print(f"Optimal: {optimal_faults} 페이지 폴트")

🔒 교착상태 (Deadlock)

교착상태는 두 개 이상의 프로세스가 서로의 자원을 기다리며 무한 대기하는 상태입니다.

교착상태 필요조건 (4가지 모두 만족해야 발생)

graph TD
    D[교착상태]
    D --> M[상호배제<br/>Mutual Exclusion]
    D --> H[점유와 대기<br/>Hold and Wait]
    D --> N[비선점<br/>No Preemption]
    D --> C[순환대기<br/>Circular Wait]

교착상태 시뮬레이션

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import threading
import time

class DeadlockDemo:
    """교착상태 시연"""
    
    def __init__(self):
        self.lock1 = threading.Lock()
        self.lock2 = threading.Lock()
        
    def process1(self):
        """프로세스 1: lock1 → lock2 순서로 획득"""
        with self.lock1:
            print("프로세스 1: Lock1 획득")
            time.sleep(0.1)  # 교착상태 유도
            
            print("프로세스 1: Lock2 대기중...")
            with self.lock2:
                print("프로세스 1: Lock2 획득")
    
    def process2(self):
        """프로세스 2: lock2 → lock1 순서로 획득 (반대)"""
        with self.lock2:
            print("프로세스 2: Lock2 획득")
            time.sleep(0.1)  # 교착상태 유도
            
            print("프로세스 2: Lock1 대기중...")
            with self.lock1:
                print("프로세스 2: Lock1 획득")
    
    def safe_process1(self):
        """안전한 프로세스 1: 순서 통일"""
        with self.lock1:
            print("안전한 프로세스 1: Lock1 획득")
            with self.lock2:
                print("안전한 프로세스 1: Lock2 획득")
    
    def safe_process2(self):
        """안전한 프로세스 2: 순서 통일"""
        with self.lock1:  # lock1을 먼저 획득 (순서 통일)
            print("안전한 프로세스 2: Lock1 획득")
            with self.lock2:
                print("안전한 프로세스 2: Lock2 획득")

# 교착상태 예방 시연
demo = DeadlockDemo()

print("안전한 방법 (교착상태 예방):")
t1 = threading.Thread(target=demo.safe_process1)
t2 = threading.Thread(target=demo.safe_process2)
t1.start()
t2.start()
t1.join()
t2.join()

print("\n주의: 실제 교착상태는 프로그램이 멈추므로 시연하지 않습니다.")

🎯 핵심 정리

꼭 기억해야 할 5가지

  1. 프로세스는 독립적 실행 단위, 스레드는 프로세스 내 경량 실행 단위
  2. 컨텍스트 스위칭은 오버헤드가 있지만 동시성을 제공
  3. 가상 메모리로 물리 메모리보다 큰 프로그램 실행 가능
  4. 동기화로 Race Condition 방지
  5. 교착상태는 4가지 조건이 모두 만족될 때 발생

실무 활용 팁

  • 멀티스레딩 시 항상 동기화 고려
  • CPU 바운드 작업은 프로세스, I/O 바운드는 스레드 활용
  • 메모리 누수 방지를 위한 적절한 자원 해제
  • 교착상태 예방을 위한 일관된 락 순서

📚 추가 학습 자료

추천 도서

  • “Operating System Concepts” (공룡책)
  • “Modern Operating Systems” - Tanenbaum
  • “The Linux Programming Interface”

실습 프로젝트

  1. 간단한 셸(Shell) 구현
  2. 프로세스 스케줄러 시뮬레이터
  3. 메모리 할당자 구현

🚀 다음 시간 예고

다음 포스트에서는 “네트워크 기초”를 다룹니다. OSI 7계층, TCP/IP, HTTP 등 네트워크의 핵심 개념을 알아보겠습니다!

✅ 이 글에서 배운 것

스스로 확인해보세요! 각 항목을 설명할 수 있다면 체크하세요.

개념 이해

  • 운영체제의 4대 핵심 기능 (자원 관리, 추상화, 보호, 공유)
  • 프로세스와 스레드의 차이 (독립 메모리 vs 공유 메모리)
  • 프로세스 상태 전이 (New → Ready → Running → Waiting → Terminated)
  • 컨텍스트 스위칭과 PCB의 역할
  • 가상 메모리와 페이징의 원리
  • 교착상태의 4가지 필요조건

실용적 이해

  • 멀티스레딩이 필요한 이유와 동기화 문제
  • CPU 스케줄링 알고리즘 비교 (FCFS, SJF, RR)
  • Race Condition과 Lock을 이용한 해결법
  • 페이지 교체 알고리즘 비교 (FIFO, LRU, Optimal)
  • 교착상태 예방 방법 (일관된 락 순서)

실습 완료

  • 프로세스 상태 전이 시뮬레이션을 실행했다
  • 스레드 풀 구현 코드를 이해했다
  • Race Condition과 안전한 동기화를 비교해봤다
  • CPU 스케줄링 알고리즘을 테스트했다
  • 페이지 교체 알고리즘의 성능 차이를 확인했다

📌 시리즈 네비게이션

순서 제목 링크
1 CS 입문: 컴퓨터 사이언스가 무엇인가? 처음으로
2 컴퓨터 구조: CPU, 메모리, 저장장치의 동작 원리 이전 글
3 이진수와 논리 게이트: 컴퓨터가 생각하는 방법 ← 이전 글
4 운영체제 기초: 프로세스, 스레드, 메모리 관리 현재 글
5 네트워크 기초: OSI 7계층, TCP/IP 이해하기 다음 글 →
📋 전체 시리즈 목차 보기

기초 이론편

  1. CS 입문 ✅
  2. 컴퓨터 구조 ✅
  3. 이진수와 논리 게이트 ✅
  4. 운영체제 기초 ✅
  5. 네트워크 기초

자료구조편

  1. 배열과 연결 리스트
  2. 스택과 큐
  3. 트리 구조
  4. 그래프
  5. 해시 테이블

알고리즘편

  1. 복잡도 분석
  2. 정렬 알고리즘
  3. 탐색 알고리즘
  4. 동적 계획법
  5. 그리디와 분할정복

실무 응용편

  1. 데이터베이스 이론
  2. 소프트웨어 공학
  3. 보안 기초
  4. 분산 시스템
  5. CS 종합과 면접 준비
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.