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/Oread()가 데이터 올 때까지 블록.
  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커널 함수 추적
bpftraceeBPF 한 줄 스크립트
bcceBPF 도구 모음 (execsnoop, opensnoop 등)
stracesyscall 추적 (느림)
ltrace라이브러리 호출 추적
pmap프로세스 메모리 맵
iotop / biolatencyI/O 분석
perf topCPU 샘플링
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 governorperformance 모드 설정(서버).

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