Skip to content

필사 모드: 운영체제의 현대적 이해 — io_uring, cgroups/namespaces, eBPF, NUMA, GPU UVM, EEVDF, Zero-Copy 완벽 가이드 (2025)

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

왜 지금 OS를 다시 배우는가

"OS는 학부 때 배운 거 아냐?"라고 묻는다면, 대답:

- **io_uring (2019)** — epoll/kqueue의 후계자. Node.js, Rust tokio, PostgreSQL 17이 도입 중.

- **cgroups v2 (2016+, 2022년 주류)** — Docker·K8s 리소스 제어의 기반.

- **eBPF** — 이전 글(Observability, Network)에서 등장한 그 기술. 2020년대 커널의 혁명.

- **EEVDF (2024 Linux 6.6)** — CFS 스케줄러를 대체.

- **NUMA** — 32 vCPU 이상 쓰는 순간 성능 영향을 크게 받기 시작.

- **GPU UVM (Unified Virtual Memory)** — LLM 추론·훈련의 기반.

- **io_uring 기반 네트워킹** — AF_XDP, DPDK와 경쟁.

**2025년 엔지니어가 OS를 모르면**, 왜 자기 앱이 느린지, 왜 컨테이너가 OOM을 맞는지, 왜 CPU 100%인데 처리량이 안 오르는지 설명할 수 없다.

Part 1 — 프로세스, 스레드, 코루틴 — 현대의 비교

프로세스

- 별도 주소 공간. 격리 강력.

- 생성 비용 크다 (fork + exec).

- IPC 필요.

- 예: 유닉스 쉘 파이프, Chrome 탭.

스레드

- 같은 주소 공간 공유.

- 생성 비용 작다 (pthread_create).

- 공유 메모리로 통신 쉬움 + 위험(데이터 레이스).

- 커널 스케줄링 → 컨텍스트 스위치 오버헤드.

코루틴 / Fiber / Green Thread

- 유저 스페이스 스케줄링.

- 생성 비용 거의 없음(수천만 개 가능).

- 협력적 스케줄링 — await 지점에서 양보.

- 예: Go goroutine, Rust async, Python asyncio, Java Virtual Thread(2023).

Java Virtual Threads — Project Loom (2023)

JDK 21 LTS. "기존 스레드 코드를 거의 그대로 두고 수백만 동시 요청 처리."

**전통 스레드:** 스레드당 OS 스택 1MB+ → 수만 개가 한계.

**가상 스레드:** JVM 내부 스케줄링, 필요할 때만 스택 할당 → 수백만 가능.

블로킹 I/O를 쓰면서도 논블로킹의 동시성을 얻는다. **2024-2025년 Spring Boot 채택**으로 Java 백엔드 지형이 바뀌는 중.

Go goroutine

- M:N 스케줄링 (M 커널 스레드 : N 고루틴).

- 채널로 통신.

- 스택이 초기 2KB에서 동적 확장.

- GOMAXPROCS가 P(Processor) 수 결정.

언제 무엇을?

| 상황 | 선택 |

|---|---|

| CPU 바운드 병렬 | 스레드 + 공유 메모리 |

| I/O 바운드 대량 동시 | 코루틴/async |

| 보안 격리 강력 필요 | 프로세스 + IPC |

| JVM에서 수백만 동시 | Virtual Threads |

Part 2 — io_uring — epoll을 넘어서

I/O 진화의 역사

1. **블로킹 I/O** — `read()`가 데이터 올 때까지 블록.

2. **select/poll** — FD 세트 전부 스캔. O(n).

3. **epoll (Linux, 2002) / kqueue (BSD)** — 이벤트 기반, O(1).

4. **aio_read(POSIX AIO)** — 한계 많음. 거의 안 씀.

5. **io_uring (2019, Jens Axboe)** — 진정한 비동기.

io_uring의 구조

**Submission Queue (SQ)** + **Completion Queue (CQ)**. 둘 다 mmap된 공유 메모리. **syscall 없이** 작업을 제출하고 완료를 확인.

io_uring_prep_read(sqe, fd, buf, len, offset);

io_uring_submit(&ring); // syscall 1회로 여러 요청 제출

// ... 나중에

io_uring_wait_cqe(&ring, &cqe);

**이점:**

- **Syscall 오버헤드 대폭 감소** — SQPOLL 모드는 0.

- **Batching** — 여러 I/O를 한 번에 제출.

- **파일 + 네트워크 + 타이머 + accept** 통합.

- **sendmsg, recvmsg, splice 등** 대부분 지원.

**채택:**

- **Rust tokio**(부분), **glommio**, **monoio**.

- **Node.js** — 실험적.

- **PostgreSQL 17(2024)** — async I/O 기반으로 io_uring 도입 검토.

- **QEMU, RocksDB, ScyllaDB**.

**보안 이슈:** 2023년 구글이 Chrome 샌드박스에서 io_uring 차단. 커널 공격면 넓히기. 그래서 **신뢰 환경 한정**으로 쓰라는 권고.

Part 3 — 가상 메모리의 현대

4단계 페이지 테이블 (x86_64)

`CR3 → PML4 → PDPT → PD → PT → Physical Page`

48비트 가상 주소 = 9+9+9+9+12 비트.

5단계 — 57비트 주소 공간 (Ice Lake 2021+)

대형 서버에 필요. 2024년 Linux 기본 설정으로 이동 중.

TLB (Translation Lookaside Buffer)

가상→물리 변환 캐시. 크기 수백 엔트리. **TLB miss가 성능의 숨은 살인자**.

**해결:**

- **Huge Pages (2MB, 1GB)** — 한 TLB 엔트리로 큰 영역 커버.

- **Transparent Huge Pages (THP)** — 리눅스가 자동 통합. 단, 지연 스파이크 원인이 되기도.

Memory Overcommit

Linux 기본: "요청한 메모리를 다 주는 척" → 실제 쓸 때 페이지 할당. 나중에 메모리 부족 → **OOM Killer** 발동.

echo 2 > /proc/sys/vm/overcommit_memory # strict accounting

Part 4 — cgroups + namespaces = 컨테이너

cgroups v2

리소스 그룹 계층. CPU, 메모리, I/O, PID 수 제한.

/sys/fs/cgroup/

my-app/

cpu.max # "50000 100000" → 50% CPU

memory.max # "1G"

io.max # "8:0 rbps=10485760" → 10MB/s read

namespaces

프로세스의 "자기만의 뷰".

- **mnt**: 파일시스템.

- **pid**: 프로세스 ID.

- **net**: 네트워크 인터페이스·라우팅.

- **ipc**: IPC.

- **uts**: hostname.

- **user**: UID/GID 매핑.

- **cgroup**: cgroup 뷰.

- **time**: 2020년 추가, 부팅 시간 가상화.

Docker의 본질

container = chroot + namespaces + cgroups + capabilities + seccomp + AppArmor/SELinux

**가상머신이 아니다.** 한 커널을 공유하며 "보여지는 영역"만 다르다.

rootless 컨테이너 (2020+)

**user namespace**로 루트 없이 컨테이너 실행. Podman, Buildah가 주도.

Part 5 — eBPF — 커널에 코드를 주입하기

eBPF란

**Extended BPF.** 커널 공간에서 실행되는 작은 VM. 샌드박스·검증기로 안전성 보장.

**왜 혁명인가:**

- 커널 수정 없이 기능 추가.

- 전통적으론 모듈 개발 필요, 재부팅 필요.

- eBPF는 실시간 로드, 언로드.

쓰이는 곳

- **관측성**: bpftrace, Parca, Pixie.

- **보안**: Tetragon, Falco.

- **네트워킹**: Cilium, Katran, XDP로 초고속 L4 LB.

- **프로파일링**: 연속 프로파일링(Parca, Polar Signals).

XDP (eXpress Data Path)

네트워크 카드에서 **NIC 드라이버 레벨**에서 eBPF 실행. 패킷이 커널 스택에 들어오기 전 처리 → DDoS 방어 등에 사용. **초당 수천만 패킷 처리 가능**.

개발 스택

- **bpftrace** — 한 줄 스크립트(awk 스타일).

- **libbpf + CO-RE(Compile Once Run Everywhere)** — C/Rust.

- **Aya** (Rust) — 2024년 성숙도 높아짐.

// bpftrace 예: open() syscall 추적

bpftrace -e 'tracepoint:syscalls:sys_enter_openat { printf("%s -> %s\n", comm, str(args->filename)); }'

Part 6 — NUMA — 32코어 이상에서의 숨은 비용

NUMA란

**Non-Uniform Memory Access.** 큰 서버는 여러 소켓(물리 CPU), 각 소켓이 자기 메모리 뱅크를 가짐. **다른 소켓의 메모리 접근은 1.5-3배 느리다.**

확인

numactl --hardware

node 0 cpus: 0-23

node 0 size: 96GB

node 1 cpus: 24-47

node 1 size: 96GB

node distances: 10 (local), 21 (remote)

NUMA 바인딩

프로세스를 NUMA 노드 0에만 실행

numactl --cpunodebind=0 --membind=0 ./my-app

**DB 서버, LLM 추론, 고성능 프록시**에서 필수. 기본값에 맡기면 스케줄러가 최적이 아닌 결정을 한다.

Kubernetes + NUMA

- **Topology Manager** (알파→베타) — Pod를 NUMA 경계에 맞춰 배치.

- **CPU Manager static policy** — 전용 코어 할당.

**LLM 추론 K8s 클러스터**에선 이 설정이 처리량 30%+ 차이.

Part 7 — Linux 스케줄러의 진화

CFS (Completely Fair Scheduler, 2007-2024)

- **가상 런타임(vruntime)** 으로 공정성 추구.

- **nice 값**으로 우선순위.

- **red-black tree**로 관리.

EEVDF (Earliest Eligible Virtual Deadline First, 2024)

**Linux 6.6**부터 기본. Peter Zijlstra 주도.

- CFS의 단점(지연 민감 태스크에 불리) 해결.

- **지연 허용치(slice)** 파라미터 도입.

- 미디어·게임·대화형 워크로드에 더 친화적.

CPU 격리 기법

- **isolcpus** — 지정 코어를 일반 스케줄링에서 제외.

- **nohz_full** — 타이머 인터럽트 제거.

- **RCU 콜백 오프로드** — 지정 코어를 방해 X.

**저지연 트레이딩·HFT 인프라**에선 표준.

Part 8 — GPU 드라이버와 LLM의 관계

GPU 컨테이너의 복잡성

Docker로 GPU 쓰려면:

- **NVIDIA Container Toolkit** — 호스트 드라이버를 컨테이너에 마운트.

- **NVIDIA MIG (Multi-Instance GPU)** — H100을 7개 인스턴스로 분할.

- **MPS (Multi-Process Service)** — GPU 공유.

UVM (Unified Virtual Memory, CUDA 6+)

CPU와 GPU가 **같은 주소 공간**을 공유. 페이지 폴트 시 필요한 페이지만 전송. LLM 훈련 시 **GPU 메모리보다 큰 모델**을 다룰 때 필수.

CUDA Graph

반복되는 커널 호출 패턴을 그래프로 캡처, 오버헤드 제거. **LLM 추론의 디코딩 루프**에서 20%+ 속도 개선 사례.

2024-2025 GPU OS 이슈

- **Fractional GPU sharing** (Run.ai, Aptakube) — 프로덕션 이슈 잦음.

- **NVIDIA DCGM + K8s 메트릭** — GPU 관측 표준.

- **AMD ROCm, Intel oneAPI** — CUDA 대체 성숙도 서서히 증가.

Part 9 — I/O 성능의 끝판왕

Zero-Copy

전통: `read() → buffer → write()` 각 단계 복사.

Zero-copy: `sendfile(), splice(), io_uring, MSG_ZEROCOPY` — 커널이 직접 DMA.

**실제 사례:** Kafka가 consumer에 데이터 보낼 때 `sendfile()` 사용 → CPU 사용률 절반으로.

DMA (Direct Memory Access)

CPU 관여 없이 디바이스가 메모리 직접 접근. 네트워크 카드, SSD, GPU 모두 DMA 엔진 내장.

RDMA (Remote DMA)

**다른 머신의 메모리에 CPU 관여 없이 직접 접근**. Infiniband, RoCE(RDMA over Converged Ethernet).

- 지연 시간 1μs 대.

- **HFT, HPC, 대형 데이터 웨어하우스**.

- **2024년 AI 훈련 클러스터의 기본** — NVIDIA GPUDirect RDMA.

DPDK / AF_XDP

커널 네트워크 스택을 **우회**해 사용자 공간에서 직접 NIC 다룸. 클라우드 가상 스위치, 5G UPF, 고성능 프록시(Cloudflare, Fastly).

Part 10 — WSL2, 컨테이너, 가상화의 교차점

WSL2 (Windows Subsystem for Linux 2)

- 실제로 **경량 Hyper-V VM** 안에서 리눅스 커널 실행.

- Windows 커널과 리눅스 커널 공존.

- 파일 성능: **WSL 네이티브 FS는 빠름, Windows /mnt/c는 느림**.

- 2024년 systemd 기본 지원 추가.

Firecracker (AWS Lambda 기반)

- 2018년 오픈소스화. microVM.

- 부팅 125ms 이하.

- KVM 기반, 120줄 미만의 VMM.

- **Fly.io, Kata Containers**도 사용.

gVisor (Google)

- 사용자 공간 커널 재구현.

- syscall을 사용자 공간 Sentry가 처리 → 호스트 커널 공격면 축소.

- 성능 오버헤드 있지만 격리 강력.

Kata Containers

- OCI 호환 컨테이너 런타임 + microVM.

- 컨테이너의 편리함 + VM의 격리.

- 멀티테넌트 K8s에서 유용.

Part 11 — 관측의 도구들

| 도구 | 용도 |

|---|---|

| **perf** | 하드웨어 + 소프트웨어 이벤트 |

| **ftrace** | 커널 함수 추적 |

| **bpftrace** | eBPF 한 줄 스크립트 |

| **bcc** | eBPF 도구 모음 (execsnoop, opensnoop 등) |

| **strace** | syscall 추적 (느림) |

| **ltrace** | 라이브러리 호출 추적 |

| **pmap** | 프로세스 메모리 맵 |

| **iotop / biolatency** | I/O 분석 |

| **perf top** | CPU 샘플링 |

| **flame graph** | 스택 시각화 (Brendan Gregg) |

Continuous Profiling

- **Parca, Pyroscope, Polar Signals**.

- eBPF 기반 상시 프로파일링 오버헤드 1% 미만.

- "P99이 느린데 CPU는 한가" 같은 수수께끼를 푼다.

Part 12 — OS 체크리스트 (12항목)

1. **ulimit 확인** — 프로덕션 서버의 FD 제한, nproc 제한.

2. **Swap 정책** — swappiness=1(DB) 또는 0(레이턴시 민감).

3. **Transparent Huge Pages** — DB는 대부분 끄는 게 낫다.

4. **NUMA 바인딩** — 소켓 2개 이상 시스템.

5. **io_uring 지원 확인** — 최신 커널에서 fd 한도 조정.

6. **cgroups v2 사용** — v1은 기능 제한.

7. **seccomp 프로필** — 컨테이너 syscall 제한.

8. **기본 TCP 파라미터 튜닝** — somaxconn, tcp_max_syn_backlog.

9. **커널 버전 확인** — 최신 LTS(6.6+)가 EEVDF, io_uring 성숙.

10. **eBPF 관측 인프라** — 프로파일링 도구 하나는 상시 가동.

11. **OOM Killer 로그 모니터링** — dmesg의 단서.

12. **CPU governor** — `performance` 모드 설정(서버).

Part 13 — 10대 안티패턴

1. **컨테이너를 VM으로 취급** — "커널 공유"를 잊으면 보안·성능 오해 발생.

2. **한 프로세스에 수만 스레드** — 컨텍스트 스위치 지옥. 코루틴이 답.

3. **블로킹 I/O로 대량 동시성** — 이벤트 루프 / 코루틴 / Virtual Thread.

4. **swappiness=60 (기본) 유지** — DB는 낮춰야 한다.

5. **대용량 RAM에서 THP 무시** — 지연 스파이크의 원인이 될 수 있다.

6. **NUMA 무시** — 큰 머신이라고 그냥 프로세스 하나 돌리면 절반 성능.

7. **syscall 잦은 앱에 strace 상시 가동** — 100배 느려짐. eBPF 쓰라.

8. **컨테이너에 루트로 실행** — rootless 또는 drop capabilities.

9. **Firecracker를 '일반 K8s 컨테이너'와 같이 취급** — 스토리지·네트워크 차이.

10. **GPU를 docker run에 그냥 연결** — Toolkit + 드라이버 버전 매트릭스 확인.

마치며 — OS는 여전히 '성능의 경계'

2025년 앱의 성능 상한은 종종 OS가 정한다. io_uring을 아느냐 모르느냐가 네트워크 서버의 처리량 2배 차이를 만들고, cgroups v2 설정이 컨테이너 OOM을 결정하고, NUMA 바인딩이 LLM 추론 비용을 좌우한다.

OS는 "내 앱보다 아래"가 아니라 **"내 앱의 일부"**다. 좋은 엔지니어는 필요한 순간에 이 경계를 넘을 수 있다. `/proc/`를 탐험하고, `perf top`을 돌리고, `bpftrace` 한 줄로 답을 얻는다.

학부 OS 수업에서 배운 것들(페이지 테이블, 스케줄러, 세마포어)은 여전히 살아있다. 다만 형태가 진화했다. 2025년의 OS는 단일 커널 + 수천 컨테이너 + GPU + RDMA가 공존하는 세계. 이 세계를 이해하는 것이 엔지니어의 새로운 기초다.

다음 글 예고 — "컴파일러와 현대 언어 런타임" — LLVM, JIT, GC, Inline Caching, Escape Analysis, WASM 런타임까지

OS 아래는 하드웨어, OS 위에는 런타임. 다음은 **언어가 어떻게 실행되는가**.

- **LLVM의 지배** — Rust, Swift, Julia, Zig, Crystal이 공유하는 뼈대

- **JIT 컴파일러 내부** — V8의 TurboFan, JVM의 C2, LuaJIT

- **Hidden Class와 Inline Caching** — V8이 JS를 빠르게 만드는 비밀

- **Escape Analysis** — 스택에 남길까 힙에 올릴까

- **Garbage Collector 계보** — Mark & Sweep부터 ZGC, Shenandoah, G1, Go의 3색 동시

- **Tiered Compilation** — V8 Ignition/Sparkplug/Maglev/TurboFan

- **Rust의 Monomorphization** — 제네릭이 왜 빠른가

- **Go의 work-stealing** — goroutine 스케줄러 내부

- **Python의 Specializing Adaptive Interpreter** — 3.13의 도약

- **WASM 런타임들** — Wasmtime, wasmer, WasmEdge 비교

"내 코드가 실행되기까지 무슨 일이 벌어지는가?" 다음 글에서.

현재 단락 (1/200)

"OS는 학부 때 배운 거 아냐?"라고 묻는다면, 대답:

작성 글자: 0원문 글자: 8,034작성 단락: 0/200