Lecture 26. 운영체제
개요
핵심 질문
- 운영체제는 프로세스·메모리·파일을 어떻게 관리하는가?
- 프로세스와 스레드의 차이는 무엇이며 어떻게 동기화하는가?
- 가상 메모리와 페이징은 어떻게 동작하는가?
- AI 시스템 개발에서 운영체제 지식이 왜 필요한가?
학습 목표
- 운영체제의 핵심 역할(CPU·메모리·파일 관리)을 설명할 수 있다.
- 프로세스 상태 전이, 문맥 교환, 스레드 동기화 원리를 이해한다.
- 가상 메모리·페이징·페이지 교체 알고리즘을 설명할 수 있다.
- AI 개발자 관점에서 멀티프로세싱, 비동기 I/O, 메모리 관리의 의미를 이해한다.
핵심 개념
1. 운영체제 개요
운영체제의 역할
| 역할 | 설명 | 핵심 기술 |
|---|---|---|
| CPU 관리 | 프로세스에 CPU 할당 순서·시간 결정 | CPU 스케줄링 |
| 메모리 관리 | 실제 물리 메모리보다 큰 메모리 제공 | 가상 메모리, 페이징 |
| 파일 관리 | 보조기억장치 정보를 파일·폴더로 접근 | 파일 시스템 |
| 프로세스 관리 | 실행 순서 제어, 자원 배분 | 스케줄러, IPC |
커널 (Kernel)
운영체제의 핵심 기능을 담당하는 부분. 시스템 자원에 직접 접근할 수 있는 특권 모드에서 실행된다.
이중 모드 (Dual Mode)
- 사용자 모드: 사용자 응용 프로그램이 실행되는 모드 — 커널 자원 직접 접근 불가
- 커널 모드: 운영체제 코드 실행 모드 — 하드웨어·메모리 직접 접근 가능
시스템 콜 (System Call)
사용자 모드 프로그램이 커널 서비스를 요청하는 인터페이스. 파일 읽기, 프로세스 생성, 메모리 할당 등.
사용자 프로그램 → 시스템 콜 호출 → 소프트웨어 인터럽트 발생
→ 커널 모드 전환 → 커널 코드 실행 → 사용자 모드 복귀AI 개발자 관점: PyTorch DataLoader가 멀티프로세스로 데이터를 로드할 때, 파일 읽기·프로세스 생성·메모리 할당 모두 시스템 콜을 통해 이루어진다.
2. 프로세스
프로세스 메모리 구조
실행 중인 프로그램(프로세스)은 메모리에 다음 4개 영역으로 적재된다.
| 영역 | 내용 | 특징 |
|---|---|---|
| 코드(텍스트) 영역 | CPU가 실행할 명령어 | 읽기 전용 |
| 데이터 영역 | 전역 변수, 정적 변수 (초기값 있음) | 프로그램 종료까지 유지 |
| BSS 영역 | 전역 변수, 정적 변수 (초기값 없음) | 0으로 초기화 |
| 힙 영역 | 동적 할당 메모리 (malloc, new) | 런타임 크기 결정 |
| 스택 영역 | 지역 변수, 매개변수, 복귀 주소 | 함수 호출마다 프레임 생성 |
힙은 낮은 주소 → 높은 주소 방향으로 성장, 스택은 높은 주소 → 낮은 주소 방향으로 성장.
PCB (Process Control Block)
운영체제가 프로세스를 관리하기 위해 커널 영역에 유지하는 자료구조.
포함 정보:
- 프로세스 ID (PID)
- 프로세스 상태
- 레지스터 값 (PC, SP 등)
- CPU 스케줄링 정보 (우선순위)
- 메모리 관련 정보 (페이지 테이블 주소)
- 열린 파일 목록
프로세스 상태 전이
생성(new) → 준비(ready) ⇄ 실행(running) → 종료(terminated)
↕
대기(blocked)- 준비 → 실행: 디스패치 (CPU 할당)
- 실행 → 준비: 타이머 인터럽트 (타임슬라이스 만료)
- 실행 → 대기: I/O 요청, 자원 대기
- 대기 → 준비: I/O 완료, 자원 획득
문맥 교환 (Context Switch)
실행 중인 프로세스를 교체하는 과정.
- 현재 프로세스의 문맥(레지스터 값, PC 등)을 PCB에 저장
- 다음 프로세스의 PCB에서 문맥 복구
- 새 프로세스 실행 재개
잦은 문맥 교환은 캐시 미스 증가와 CPU 오버헤드를 유발한다.
블로킹 vs 논블로킹 I/O
- 블로킹 I/O: I/O 완료까지 프로세스가 대기 상태로 전환 — 단순하지만 CPU 낭비
- 논블로킹 I/O: I/O를 맡기고 즉시 다음 명령어 실행 — 비동기, CPU 효율적
AI 개발자 심화 — 멀티프로세싱과 데이터 로딩
PyTorch DataLoader의 num_workers > 0은 별도 자식 프로세스를 생성하여 데이터 전처리를 병렬화한다.
- 각 Worker 프로세스는 독립된 메모리 공간 보유 → 데이터 충돌 없음
fork()시스템 콜로 부모 프로세스 복제 → 자식 프로세스 생성- 공유 메모리(
/dev/shm)를 통해 메인 프로세스로 배치 전달 — 복사 오버헤드 감소 pin_memory=True: 페이지 고정 메모리 사용으로 GPU 전송 속도 향상
3. 스레드
프로세스 vs 스레드
| 프로세스 | 스레드 | |
|---|---|---|
| 메모리 | 독립 (코드·데이터·힙·스택 전부) | 코드·데이터·힙 공유, 스택만 독립 |
| 생성 비용 | 높음 (fork) | 낮음 |
| 통신 | IPC 필요 (느림) | 공유 메모리로 직접 통신 (빠름) |
| 격리 | 한 프로세스 오류가 다른 프로세스에 영향 적음 | 한 스레드 오류가 전체 프로세스에 영향 |
스레드 안전 (Thread Safety)
멀티스레드 환경에서 동시 접근해도 결과가 항상 올바른 상태.
레이스 컨디션(Race Condition): 여러 스레드가 공유 자원을 동시에 접근하여 실행 순서에 따라 결과가 달라지는 문제.
임계 구역(Critical Section): 한 번에 하나의 스레드만 실행해야 하는 코드 영역.
AI 개발자 심화 — GIL (Global Interpreter Lock)
CPython(표준 Python)은 GIL로 인해 동일 프로세스 내에서 여러 스레드가 파이썬 코드를 동시에 실행할 수 없다.
- I/O 바운드 작업: 스레드 유효 (GIL이 I/O 대기 중 해제됨)
- CPU 바운드 작업: 멀티프로세싱 사용 (
multiprocessing모듈) - PyTorch DataLoader가
num_workers로 스레드 대신 프로세스를 사용하는 이유
4. 동기화와 교착 상태
동기화의 두 가지 목표
- 상호 배제: 임계 구역에 하나의 스레드/프로세스만 접근
- 실행 순서 제어: 특정 순서에 따라 자원 접근
뮤텍스 락 (Mutex Lock)
이진 잠금 도구. 임계 구역 진입 전 acquire(), 이후 release().
acquire(): 락이 없으면 기다림, 있으면 획득 후 진입
release(): 임계 구역 종료 후 락 반환세마포 (Semaphore)
공유 자원이 여러 개인 경우의 동기화. 변수 = 가용 자원 수.
- 이진 세마포 (): 뮤텍스와 유사
- 카운팅 세마포: 여러 개의 동일 자원 관리
모니터 (Monitor)
공유 자원 + 접근 함수를 하나로 캡슐화한 동기화 도구. Java의 synchronized 키워드가 모니터 기반.
교착 상태 (Deadlock)
발생 4가지 필요 조건 (모두 충족 시 교착 상태 발생):
- 상호 배제: 자원을 한 번에 하나의 프로세스만 사용
- 점유와 대기: 자원을 점유한 채 다른 자원 대기
- 비선점: 강제로 자원을 빼앗을 수 없음
- 원형 대기: 프로세스들이 원형으로 자원 대기
해결 방법
| 방법 | 전략 | 특징 |
|---|---|---|
| 예방 | 4가지 조건 중 하나 제거 | 자원 효율 감소 |
| 회피 | 안전 상태 유지 (은행원 알고리즘) | 사전에 자원 요구량 파악 필요 |
| 탐지 후 회복 | 주기적 탐지 후 강제 종료 또는 자원 선점 | 오버헤드 발생 |
5. CPU 스케줄링
스케줄링 기준 지표
- CPU 활용률: 전체 시간 중 CPU가 일하는 비율
- 처리율(Throughput): 단위 시간당 완료된 프로세스 수
- 대기 시간: 준비 큐에서 기다린 시간
- 반환 시간: 제출부터 완료까지의 전체 시간
- 응답 시간: 요청 후 첫 응답까지의 시간
선점형 vs 비선점형
- 선점형: 운영체제가 CPU를 강제 회수 가능 — 응답성 좋음, 문맥 교환 오버헤드
- 비선점형: 프로세스가 자발적으로 반환할 때까지 대기 — 오버헤드 적음, 응답성 나쁨
주요 알고리즘
| 알고리즘 | 방식 | 특징 |
|---|---|---|
| FCFS | 도착 순서 | 단순, 호위 효과 |
| SJF | 실행 시간 짧은 것 우선 | 최적 대기 시간, 실행 시간 예측 어려움 |
| Round Robin (RR) | 타임슬라이스 순환 | 공평, 타임슬라이스 크기가 성능 결정 |
| SRT | 잔여 시간 최소 | SJF의 선점형 버전 |
| 우선순위 | 우선순위 높은 순 | 아사 현상 → 에이징으로 해결 |
| 다단계 피드백 큐 | 큐 간 이동 허용 | 유연, 현대 OS의 기반 |
리눅스 CFS (Completely Fair Scheduler)
vruntime이 가장 작은 프로세스를 다음에 실행 — 우선순위 높을수록 가중치 높아 vruntime 느리게 증가 → 더 많은 CPU 시간 할당.
AI 개발자 심화
- 학습 프로세스 우선순위: GPU 집약적 학습 프로세스에 높은 우선순위 부여 (
nice값 조정) - 실시간 추론 서버:
SCHED_FIFO또는SCHED_RR정책으로 응답 지연 최소화
6. 가상 메모리
물리 주소 vs 논리 주소
- 물리 주소: 메모리 하드웨어의 실제 주소
- 논리 주소: 프로세스별로 0부터 시작하는 가상 주소 공간
MMU (Memory Management Unit): CPU와 메모리 사이에서 논리 주소 → 물리 주소 변환.
스와핑 (Swapping)
메모리 부족 시 실행되지 않는 프로세스를 보조기억장치의 스왑 영역으로 내보내고(swap out), 필요 시 다시 메모리로 복귀(swap in).
페이징 (Paging)
논리 주소 공간을 고정 크기의 페이지(page) 로, 물리 주소 공간을 동일 크기의 프레임(frame) 으로 나누어 임의의 프레임에 페이지를 할당하는 방식.
- 외부 단편화 없음 (고정 크기)
- 내부 단편화 발생 가능 (마지막 페이지 일부 낭비)
페이지 테이블 (Page Table)
프로세스의 페이지 번호 → 물리 프레임 번호 매핑 테이블.
주요 PTE (Page Table Entry) 비트:
| 비트 | 의미 |
|---|---|
| 유효 비트 (Valid) | 1: 메모리에 적재됨, 0: 스왑 영역에 있음 |
| 보호 비트 (Protection) | 읽기(r), 쓰기(w), 실행(x) 권한 |
| 참조 비트 (Reference) | CPU가 해당 페이지 접근 여부 |
| 수정 비트 (Dirty) | 페이지 데이터 수정 여부 — 스왑 아웃 시 쓰기 필요 여부 판단 |
페이지 폴트 (Page Fault)
CPU가 접근한 페이지의 유효 비트가 0 — 보조기억장치에서 메모리로 로드 필요.
처리 과정:
- 페이지 폴트 발생 → 커널 모드 전환
- 페이지 폴트 처리 루틴 실행
- 보조기억장치에서 해당 페이지를 메모리로 적재
- 페이지 테이블 갱신 (유효 비트 1로)
- 중단된 명령어 재실행
TLB (Translation Lookaside Buffer)
페이지 테이블의 캐시 메모리. 최근 사용한 페이지 번호 → 프레임 번호 매핑 저장.
- TLB 히트: TLB에서 바로 프레임 번호 획득 → 메모리 1회 접근
- TLB 미스: 페이지 테이블 접근 필요 → 메모리 2회 접근
요구 페이징 (Demand Paging)
프로세스 시작 시 전체 페이지를 메모리에 올리지 않고, 실제 접근 시에만 페이지를 메모리에 로드하는 방식.
페이지 교체 알고리즘
메모리가 가득 찼을 때 내보낼 페이지를 선택하는 알고리즘.
| 알고리즘 | 방식 | 특징 |
|---|---|---|
| FIFO | 가장 오래 있던 페이지 교체 | 단순, 성능 불안정 (Belady의 역설) |
| 최적 (OPT) | 앞으로 가장 오래 사용 안 될 페이지 교체 | 최적이지만 미래 예측 불가 (이론적 기준) |
| LRU | 가장 오래 사용 안 된 페이지 교체 | 실제 가장 많이 사용, 참조 비트 활용 |
다단계 페이지 테이블 (Multi-level Page Table)
페이지 테이블 자체를 페이지로 나누어 계층 구조로 관리 → 페이지 테이블의 메모리 사용량 감소.
AI 개발자 심화
- VRAM OOM과 가상 메모리: GPU VRAM은 별도 메모리로 운영체제 페이징의 직접 대상이 아니다. VRAM 부족 시
torch.cuda.OutOfMemoryError발생 — 페이지 교체가 아닌 수동 관리 필요. - Huge Pages: 기본 페이지 크기(4 KB) 대신 2 MB 또는 1 GB 페이지 사용 — TLB 히트율 향상, 대용량 모델 가중치 로딩에 유리.
- 메모리 맵 파일(mmap): 파일을 가상 주소 공간에 직접 매핑 — 대용량 데이터셋을 메모리처럼 접근. HuggingFace의 대형 모델 가중치 로딩에 활용.
- Copy-on-Write (CoW):
fork()시 자식 프로세스가 부모 메모리를 즉시 복사하지 않고 수정 시점에만 복사 — DataLoader 멀티프로세싱의 메모리 효율 기반.
7. 프로세스 간 통신 (IPC)
공유 메모리 (Shared Memory)
여러 프로세스가 동일한 메모리 영역을 공유 — 가장 빠른 IPC.
메시지 전달
커널을 통해 데이터를 주고받는 방식.
| 방식 | 특징 |
|---|---|
| 파이프 (Pipe) | 단방향 통신, 부모-자식 프로세스 간 |
| 지명 파이프 (Named Pipe) | 양방향, 독립 프로세스 간 가능 |
| 시그널 (Signal) | 비동기 이벤트 알림 (SIGTERM, SIGKILL 등) |
| 소켓 (Socket) | 네트워크 기반, 원격 프로세스 간 통신 |
| RPC | 원격 함수 호출 — gRPC의 기반 |
AI 개발자 심화
- DataLoader IPC:
num_workers > 0인 DataLoader는 Worker 프로세스와 메인 프로세스 간에 공유 메모리(/dev/shm) 또는 소켓을 통해 배치 데이터를 주고받는다. - 분산 학습 프로세스 통신:
torch.distributed는 NCCL(GPU), Gloo(CPU), MPI 백엔드를 통해 프로세스 간 그래디언트를 AllReduce한다. - 비동기 I/O: LLM 추론 서버(vLLM, TGI)는 비동기 I/O(
asyncio)로 수백 개의 동시 요청을 처리하면서 GPU 연산을 블로킹하지 않는다.
8. 파일 시스템
핵심 개념
| 개념 | 설명 |
|---|---|
| 블록 | 운영체제가 파일을 읽고 쓰는 단위 (일반적으로 4 KB) |
| 파일 디스크립터 | 프로세스가 열린 파일을 식별하는 정수 (0: stdin, 1: stdout, 2: stderr) |
| 아이노드 (inode) | 파일 메타데이터 + 데이터 블록 주소 저장 |
| 마운트 | 다른 파일 시스템을 현재 트리에 연결 |
파일 할당 방식
- 연속 할당: 연속된 블록에 저장 — 빠른 접근, 외부 단편화
- 연결 할당: 각 블록이 다음 블록 주소 포함 — 유연, 순차 접근만 가능
- 색인 할당: 색인 블록에 모든 블록 주소 저장 — 직접 접근 가능, Linux ext4의 기반
AI 개발자 심화
- 대용량 학습 데이터 I/O: HDFS, NFS, S3와 같은 분산 파일 시스템을 마운트하여 로컬 파일 시스템처럼 접근.
- 체크포인트 저장: 모델 체크포인트는 수 GB에 달하므로 파일 시스템 블록 크기, 버퍼링 방식이 저장 속도에 영향.
torch.save()는pickle+ OS 파일 쓰기 시스템 콜. - /dev/shm: 리눅스의 RAM 기반 임시 파일 시스템 — 멀티프로세스 DataLoader의 배치 버퍼로 활용, 디스크 I/O 없이 메모리 속도로 공유.
수식 정리
CPU 활용률
TLB를 고려한 유효 메모리 접근 시간 (EMAT)
- : TLB 적중률, : TLB 접근 시간, : 메모리 접근 시간
리눅스 CFS vruntime
페이지 폴트율과 유효 접근 시간
- : 페이지 폴트율, : 페이지 폴트 처리 시간 (수ms ~ 수십ms)