운영체제 기초
운영체제(Operating System, OS)는 하드웨어와 소프트웨어 사이의 중재자입니다. Python 스크립트 한 줄을 실행할 때도, PyTorch가 GPU 메모리를 할당할 때도, 학습 데이터를 디스크에서 읽어올 때도 — 모든 동작의 밑바닥에는 운영체제가 있습니다. OS의 핵심 개념을 이해하면, GPU 서버에서 발생하는 OOM(Out of Memory) 에러, 좀비 프로세스, 권한 문제 등을 체계적으로 진단하고 해결할 수 있습니다.학습 목표
- 커널과 사용자 공간의 구조를 이해하고, 시스템 콜의 역할을 설명할 수 있다
- 프로세스의 생명주기와 시그널 메커니즘을 파악하여 학습 프로세스를 관리할 수 있다
- 가상 메모리, 스왑, OOM Killer의 동작 원리를 이해하고 메모리 문제에 대응할 수 있다
- 파일시스템과 권한 시스템을 이해하여 ML 프로젝트 환경을 안전하게 구성할 수 있다
왜 중요한가
AI/ML 엔지니어의 일상은 운영체제와 밀접하게 연결되어 있습니다.torch.cuda.OutOfMemoryError가 발생했을 때 GPU 메모리만 확인하는 것이 아니라, 시스템 전체의 메모리 상태를 파악해야 합니다. DataLoader에서 num_workers=8을 설정하면 내부적으로 fork() 시스템 콜이 호출되어 8개의 자식 프로세스가 생성됩니다. 학습 도중 Ctrl+C를 누르면 SIGINT 시그널이 전달되고, kill -9를 쓰면 SIGKILL이 전달됩니다.
이러한 동작 원리를 모르면 “왜 메모리가 부족한지”, “왜 프로세스가 죽지 않는지”, “왜 파일을 읽을 수 없는지” 같은 문제 앞에서 막막해집니다. OS 기초는 단순한 이론이 아니라, GPU 서버 위에서 모델을 학습시키는 실무의 근간입니다.
커널과 사용자 공간
운영체제는 크게 커널 공간(Kernel Space) 과 사용자 공간(User Space) 으로 나뉩니다.- 커널 공간: 하드웨어에 직접 접근할 수 있는 특권 영역입니다. 프로세스 스케줄링, 메모리 관리, 파일시스템, 디바이스 드라이버 등이 여기서 동작합니다.
- 사용자 공간: 일반 애플리케이션이 실행되는 영역입니다. Python, Docker, PyTorch 등 모든 프로그램은 사용자 공간에서 실행됩니다.
- 시스템 콜(syscall): 사용자 공간의 프로그램이 커널의 기능을 요청하는 유일한 통로입니다. 파일 열기(
open), 메모리 할당(mmap), 프로세스 생성(fork) 등이 모두 시스템 콜입니다.
NVIDIA GPU 드라이버는 커널 모듈로 동작합니다.
nvidia-smi가 작동하지 않는다면 커널 모듈이 로드되지 않은 것일 수 있습니다. lsmod | grep nvidia로 확인하세요.프로세스 생명주기
프로세스는 실행 중인 프로그램의 인스턴스입니다. 모든 프로세스는 명확한 생명주기를 가집니다.fork와 exec
| 시스템 콜 | 설명 | 예시 |
|---|---|---|
fork() | 현재 프로세스를 복제하여 자식 프로세스를 생성 | DataLoader의 num_workers |
exec() | 현재 프로세스를 새 프로그램으로 교체 | bash에서 python train.py 실행 |
wait() | 자식 프로세스의 종료를 기다림 | 부모 프로세스가 자식 정리 |
exit() | 프로세스 종료 | 학습 스크립트 완료 |
좀비 프로세스와 고아 프로세스
- 좀비 프로세스(Zombie): 자식이 종료되었지만 부모가
wait()를 호출하지 않아 프로세스 테이블에 남아있는 상태입니다.DataLoader워커가 비정상 종료될 때 발생할 수 있습니다. - 고아 프로세스(Orphan): 부모가 먼저 종료되어 init(PID 1)에 입양된 프로세스입니다. 보통 init이 자동으로 정리합니다.
시그널
시그널은 프로세스에 전달되는 비동기 알림입니다. 학습 프로세스를 제어할 때 반드시 알아야 합니다.| 시그널 | 번호 | 기본 동작 | 설명 | 실무 사용 |
|---|---|---|---|---|
SIGINT | 2 | 종료 | 인터럽트 (Ctrl+C) | 학습 중단, graceful shutdown |
SIGTERM | 15 | 종료 | 종료 요청 | kill 기본값, 정상 종료 시도 |
SIGKILL | 9 | 즉시 종료 | 강제 종료 (포착 불가) | 응답 없는 프로세스 강제 제거 |
SIGHUP | 1 | 종료 | 터미널 연결 끊김 | SSH 세션 종료 시 발생 |
SIGSTOP | 19 | 정지 | 프로세스 일시정지 (포착 불가) | 디버깅 시 프로세스 정지 |
SIGCONT | 18 | 재개 | 정지된 프로세스 재개 | fg 명령으로 재개 |
SIGUSR1 | 10 | 종료 | 사용자 정의 시그널 1 | 체크포인트 저장 트리거 |
SIGUSR2 | 12 | 종료 | 사용자 정의 시그널 2 | 학습률 조정 트리거 |
메모리 구조
물리 메모리 vs 가상 메모리
모든 프로세스는 자신만의 가상 주소 공간을 가집니다. 실제 물리 메모리(RAM)와는 페이지 테이블을 통해 매핑됩니다.| 개념 | 설명 |
|---|---|
| 페이지(Page) | 메모리 관리의 최소 단위 (보통 4KB). 가상 주소 → 물리 주소 매핑의 단위 |
| 페이지 폴트 | 접근한 가상 주소가 물리 메모리에 없을 때 발생. 디스크에서 로드 필요 |
| 스왑(Swap) | 물리 메모리가 부족할 때 디스크를 메모리처럼 사용. 극도로 느림 |
| OOM Killer | 메모리 부족 시 커널이 프로세스를 강제 종료하는 메커니즘 |
OOM Killer 동작 원리
파일시스템
inode와 파일 구조
리눅스에서 모든 파일은 inode라는 데이터 구조로 관리됩니다. 파일 이름은 inode를 가리키는 포인터일 뿐입니다.| 구성 요소 | 설명 |
|---|---|
| inode | 파일의 메타데이터 (크기, 권한, 소유자, 타임스탬프, 데이터 블록 위치) |
| 디렉터리 | 파일 이름 → inode 번호 매핑 테이블 |
| 하드 링크 | 같은 inode를 가리키는 다른 이름. 원본 삭제해도 데이터 유지 |
| 심볼릭 링크 | 다른 파일의 경로를 가리키는 특수 파일. 원본 삭제 시 깨짐 |
파일시스템 종류
| 파일시스템 | 특징 | 적합한 용도 |
|---|---|---|
| ext4 | 리눅스 기본, 안정적, 저널링 지원 | 범용 (OS, 프로젝트 코드) |
| XFS | 대용량 파일, 병렬 I/O 최적화 | 대용량 데이터셋, 체크포인트 저장 |
| tmpfs | RAM 기반, 재부팅 시 소멸 | 임시 캐시, 학습 중 중간 데이터 |
| NFS | 네트워크 파일 공유 | 분산 학습 환경, 공유 데이터셋 |
| FUSE | 사용자 공간 파일시스템 | S3 마운트(s3fs), GCS 마운트 |
마운트 개념
권한 시스템
rwx 기본 권한
리눅스의 모든 파일에는 소유자(user), 그룹(group), 기타(others) 에 대한 읽기(r), 쓰기(w), 실행(x) 권한이 있습니다.| 권한 | 파일 | 디렉터리 | 숫자 |
|---|---|---|---|
r (읽기) | 내용 읽기 | 목록 조회 | 4 |
w (쓰기) | 내용 수정 | 파일 생성/삭제 | 2 |
x (실행) | 실행 | 디렉터리 진입 | 1 |
특수 권한
| 특수 권한 | 숫자 | 설명 | 예시 |
|---|---|---|---|
| SUID | 4000 | 실행 시 파일 소유자 권한으로 실행 | /usr/bin/passwd |
| SGID | 2000 | 실행 시 그룹 권한으로 실행 / 디렉터리: 새 파일이 부모 그룹 상속 | 공유 프로젝트 디렉터리 |
| Sticky Bit | 1000 | 디렉터리 내 파일은 소유자만 삭제 가능 | /tmp |
환경변수
환경변수는 프로세스에 전달되는 설정값입니다. ML 개발에서 특히 중요한 환경변수들이 있습니다.| 환경변수 | 설명 | 예시 |
|---|---|---|
PATH | 실행 파일 검색 경로 | /usr/local/cuda/bin:$PATH |
LD_LIBRARY_PATH | 동적 라이브러리 검색 경로 | CUDA 라이브러리 경로 |
CUDA_HOME | CUDA 설치 경로 | /usr/local/cuda-12.0 |
CUDA_VISIBLE_DEVICES | 사용할 GPU 지정 | 0,1 또는 빈 문자열로 GPU 숨김 |
PYTHONPATH | Python 모듈 검색 경로 | 커스텀 패키지 경로 추가 |
HF_HOME | HuggingFace 캐시 디렉터리 | /data/cache/huggingface |
TRANSFORMERS_CACHE | Transformers 모델 캐시 | /data/models/cache |
TORCH_HOME | PyTorch 허브 캐시 | /data/models/torch |
NCCL_DEBUG | NCCL 디버그 레벨 | INFO (분산 학습 디버깅) |
OMP_NUM_THREADS | OpenMP 스레드 수 | CPU 연산 병렬화 제어 |
AI/ML에서 이 개념이 중요한 이유
| OS 개념 | ML 실무 연결 |
|---|---|
| fork() | DataLoader(num_workers=N) → N개의 자식 프로세스 생성 |
| 가상 메모리 | 대규모 데이터셋 메모리 매핑 (mmap) |
| OOM Killer | GPU 메모리 + 시스템 RAM 동시 부족 시 학습 강제 종료 |
| 시그널 | Graceful shutdown, 체크포인트 저장 트리거 |
| 파일 권한 | 공유 서버에서 데이터/모델 접근 관리 |
| 환경변수 | GPU 선택, CUDA 설정, 캐시 경로 지정 |
| 스왑 | 스왑 활성화 시 학습 속도 극심한 저하 (thrashing) |
| inode | 수백만 개의 작은 이미지 파일 → inode 고갈 가능 |
DataLoader에서 num_workers를 높이면 메모리가 부족해지는 이유
DataLoader에서 num_workers를 높이면 메모리가 부족해지는 이유
num_workers를 설정하면 fork()로 자식 프로세스가 생성됩니다. fork는 부모 프로세스의 메모리를 복사(Copy-on-Write)합니다. 워커마다 데이터 배치를 준비하며 메모리를 추가로 사용하므로, 워커 수 x 배치 크기만큼 시스템 RAM이 필요합니다. GPU 메모리가 아닌 시스템 RAM이 부족해지는 것이 핵심입니다.CUDA_VISIBLE_DEVICES는 어디서 설정해야 하나요?
CUDA_VISIBLE_DEVICES는 어디서 설정해야 하나요?
반드시 Python 스크립트가 실행되기 전에 설정해야 합니다. CUDA 런타임은 프로세스 시작 시 GPU를 초기화하기 때문입니다. 셸에서
export CUDA_VISIBLE_DEVICES=0으로 설정하거나, 스크립트 최상단에서 os.environ["CUDA_VISIBLE_DEVICES"] = "0"을 import torch보다 먼저 작성하세요.OOM Killer에 의해 학습이 죽었는지 어떻게 확인하나요?
OOM Killer에 의해 학습이 죽었는지 어떻게 확인하나요?
dmesg | grep -i "killed process"로 커널 로그를 확인합니다. OOM Killer가 작동하면 Out of memory: Kill process [PID] (python) 같은 메시지가 남습니다. journalctl -k | grep -i oom으로도 확인 가능합니다. GPU OOM은 PyTorch가 torch.cuda.OutOfMemoryError를 발생시키지만, 시스템 OOM은 프로세스가 즉시 종료되어 에러 메시지 없이 사라집니다.스왑이 활성화되면 학습 속도가 느려지나요?
스왑이 활성화되면 학습 속도가 느려지나요?
네, 극적으로 느려집니다. RAM 접근은 나노초(ns) 단위이지만, SSD 스왑은 마이크로초(us), HDD 스왑은 밀리초(ms) 단위입니다. 학습 중 스왑이 활발히 사용되면 (thrashing) 실질적으로 학습이 멈춘 것처럼 느려집니다.
free -h로 스왑 사용량을 확인하고, vmstat 1로 스왑 입출력(si/so)을 모니터링하세요.좀비 프로세스는 왜 위험한가요?
좀비 프로세스는 왜 위험한가요?
좀비 프로세스 자체는 CPU나 메모리를 거의 사용하지 않지만, 프로세스 테이블의 슬롯을 차지합니다. 시스템의 최대 PID 수(
cat /proc/sys/kernel/pid_max)에 도달하면 새 프로세스를 생성할 수 없게 됩니다. 장시간 학습을 반복 실행하며 좀비가 누적되면 서버가 새 프로세스를 시작하지 못하는 상황이 발생할 수 있습니다.pin_memory=True는 OS 수준에서 어떤 의미인가요?
pin_memory=True는 OS 수준에서 어떤 의미인가요?
pin_memory=True는 페이지 잠금(page-locked) 메모리를 할당합니다. 운영체제가 이 메모리 영역을 스왑으로 내보내지 않도록 고정(pin)하여, GPU로의 DMA(Direct Memory Access) 전송이 빨라집니다. 단, 잠긴 메모리는 다른 프로세스가 사용할 수 없으므로 과도하게 사용하면 시스템 전체 메모리 부족으로 이어질 수 있습니다.체크리스트
- 커널 공간과 사용자 공간의 차이를 설명할 수 있다
-
fork()와exec()의 역할 차이를 이해했다 - 좀비 프로세스를 확인하고 원인을 진단할 수 있다
- SIGTERM과 SIGKILL의 차이를 알고, 적절한 시그널을 선택할 수 있다
- 가상 메모리, 페이지, 스왑의 관계를 설명할 수 있다
- OOM Killer 로그를 확인하고, 원인을 분석할 수 있다
-
chmod,chown으로 파일 권한을 설정할 수 있다 -
CUDA_VISIBLE_DEVICES를 포함한 ML 관련 환경변수를 설정할 수 있다 -
DataLoader의num_workers가 시스템 메모리에 미치는 영향을 이해했다

