Skip to main content

운영체제 기초

운영체제(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) 으로 나뉩니다.
┌─────────────────────────────────────────────┐
│              사용자 공간 (User Space)          │
│  ┌──────┐ ┌──────┐ ┌──────┐ ┌──────────┐    │
│  │Python│ │ bash │ │docker│ │ PyTorch  │    │
│  └──┬───┘ └──┬───┘ └──┬───┘ └────┬─────┘    │
│     │        │        │          │           │
├─────┴────────┴────────┴──────────┴───────────┤
│           시스템 콜 인터페이스 (syscall)         │
├──────────────────────────────────────────────┤
│              커널 공간 (Kernel Space)          │
│  ┌──────────┐ ┌────────┐ ┌───────────────┐  │
│  │프로세스   │ │메모리   │ │파일시스템     │  │
│  │스케줄러   │ │관리자   │ │(VFS)         │  │
│  └──────────┘ └────────┘ └───────────────┘  │
│  ┌──────────┐ ┌────────┐ ┌───────────────┐  │
│  │네트워크   │ │디바이스 │ │GPU 드라이버   │  │
│  │스택      │ │드라이버 │ │(NVIDIA)      │  │
│  └──────────┘ └────────┘ └───────────────┘  │
├──────────────────────────────────────────────┤
│                 하드웨어                       │
│   CPU    RAM    SSD/HDD    GPU    NIC        │
└──────────────────────────────────────────────┘
  • 커널 공간: 하드웨어에 직접 접근할 수 있는 특권 영역입니다. 프로세스 스케줄링, 메모리 관리, 파일시스템, 디바이스 드라이버 등이 여기서 동작합니다.
  • 사용자 공간: 일반 애플리케이션이 실행되는 영역입니다. Python, Docker, PyTorch 등 모든 프로그램은 사용자 공간에서 실행됩니다.
  • 시스템 콜(syscall): 사용자 공간의 프로그램이 커널의 기능을 요청하는 유일한 통로입니다. 파일 열기(open), 메모리 할당(mmap), 프로세스 생성(fork) 등이 모두 시스템 콜입니다.
NVIDIA GPU 드라이버는 커널 모듈로 동작합니다. nvidia-smi가 작동하지 않는다면 커널 모듈이 로드되지 않은 것일 수 있습니다. lsmod | grep nvidia로 확인하세요.

프로세스 생명주기

프로세스는 실행 중인 프로그램의 인스턴스입니다. 모든 프로세스는 명확한 생명주기를 가집니다.
                ┌──────────┐
    fork()      │  생성     │
   ─────────►   │ (Created) │
                └────┬─────┘
                     │ 스케줄러 배정

                ┌──────────┐     I/O 요청     ┌──────────┐
                │  실행     │ ──────────────► │  대기     │
                │(Running)  │                 │(Waiting)  │
                └────┬─────┘ ◄────────────── └──────────┘
                     │           I/O 완료
                     │ exit() / 시그널

                ┌──────────┐
                │  종료     │  ──► 부모가 wait() → 자원 해제
                │ (Zombie)  │
                └──────────┘

fork와 exec

시스템 콜설명예시
fork()현재 프로세스를 복제하여 자식 프로세스를 생성DataLoader의 num_workers
exec()현재 프로세스를 새 프로그램으로 교체bash에서 python train.py 실행
wait()자식 프로세스의 종료를 기다림부모 프로세스가 자식 정리
exit()프로세스 종료학습 스크립트 완료

좀비 프로세스와 고아 프로세스

# 좀비 프로세스 확인
ps aux | grep 'Z'

# 좀비 프로세스의 부모 찾기
ps -eo pid,ppid,stat,cmd | grep 'Z'
  • 좀비 프로세스(Zombie): 자식이 종료되었지만 부모가 wait()를 호출하지 않아 프로세스 테이블에 남아있는 상태입니다. DataLoader 워커가 비정상 종료될 때 발생할 수 있습니다.
  • 고아 프로세스(Orphan): 부모가 먼저 종료되어 init(PID 1)에 입양된 프로세스입니다. 보통 init이 자동으로 정리합니다.
학습 도중 Ctrl+C로 강제 종료하면 DataLoader 워커 프로세스가 좀비로 남을 수 있습니다. ps aux | grep python으로 잔여 프로세스를 확인하고 정리하세요.

시그널

시그널은 프로세스에 전달되는 비동기 알림입니다. 학습 프로세스를 제어할 때 반드시 알아야 합니다.
시그널번호기본 동작설명실무 사용
SIGINT2종료인터럽트 (Ctrl+C)학습 중단, graceful shutdown
SIGTERM15종료종료 요청kill 기본값, 정상 종료 시도
SIGKILL9즉시 종료강제 종료 (포착 불가)응답 없는 프로세스 강제 제거
SIGHUP1종료터미널 연결 끊김SSH 세션 종료 시 발생
SIGSTOP19정지프로세스 일시정지 (포착 불가)디버깅 시 프로세스 정지
SIGCONT18재개정지된 프로세스 재개fg 명령으로 재개
SIGUSR110종료사용자 정의 시그널 1체크포인트 저장 트리거
SIGUSR212종료사용자 정의 시그널 2학습률 조정 트리거
import signal
import sys

def graceful_shutdown(signum, frame):
    print(f"시그널 {signum} 수신 — 체크포인트 저장 중...")
    # 모델 체크포인트 저장 로직
    torch.save(model.state_dict(), 'checkpoint_emergency.pt')
    sys.exit(0)

signal.signal(signal.SIGTERM, graceful_shutdown)
signal.signal(signal.SIGINT, graceful_shutdown)
학습 스크립트에 SIGTERM 핸들러를 등록하면, 서버 재시작이나 Kubernetes pod 종료 시에도 체크포인트를 안전하게 저장할 수 있습니다.

메모리 구조

물리 메모리 vs 가상 메모리

모든 프로세스는 자신만의 가상 주소 공간을 가집니다. 실제 물리 메모리(RAM)와는 페이지 테이블을 통해 매핑됩니다.
┌─────────────────────┐      ┌──────────────────┐
│  프로세스 A의         │      │   물리 메모리     │
│  가상 주소 공간       │      │   (RAM)          │
│ ┌─────────────────┐ │      │ ┌──────────────┐ │
│ │ 코드 (text)     │─┼──┐   │ │ 페이지 0     │ │
│ ├─────────────────┤ │  │   │ ├──────────────┤ │
│ │ 데이터 (data)   │─┼──┼──►│ │ 페이지 1     │ │
│ ├─────────────────┤ │  │   │ ├──────────────┤ │
│ │ 힙 (heap)  ↓   │─┼──┼──►│ │ 페이지 2     │ │
│ │                 │ │  │   │ ├──────────────┤ │
│ │ 스택 (stack) ↑  │─┼──┼──►│ │ 페이지 3     │ │
│ └─────────────────┘ │  │   │ └──────────────┘ │
└─────────────────────┘  │   └──────────────────┘
                         │   ┌──────────────────┐
                         └──►│  스왑 (Swap)      │
                             │  디스크 영역       │
                             └──────────────────┘
개념설명
페이지(Page)메모리 관리의 최소 단위 (보통 4KB). 가상 주소 → 물리 주소 매핑의 단위
페이지 폴트접근한 가상 주소가 물리 메모리에 없을 때 발생. 디스크에서 로드 필요
스왑(Swap)물리 메모리가 부족할 때 디스크를 메모리처럼 사용. 극도로 느림
OOM Killer메모리 부족 시 커널이 프로세스를 강제 종료하는 메커니즘

OOM Killer 동작 원리

# OOM Killer 로그 확인
dmesg | grep -i "out of memory"
dmesg | grep -i "killed process"

# 프로세스별 OOM 점수 확인 (높을수록 먼저 죽음)
cat /proc/<PID>/oom_score

# OOM 점수 조정 (-1000 ~ 1000, -1000이면 절대 안 죽임)
echo -1000 > /proc/<PID>/oom_score_adj
GPU 학습 중 시스템 RAM이 부족하면 OOM Killer가 학습 프로세스를 강제 종료합니다. DataLoadernum_workers가 높으면 각 워커가 데이터 복사본을 가져 메모리 사용량이 급증할 수 있습니다.

파일시스템

inode와 파일 구조

리눅스에서 모든 파일은 inode라는 데이터 구조로 관리됩니다. 파일 이름은 inode를 가리키는 포인터일 뿐입니다.
# inode 정보 확인
ls -i filename.txt        # inode 번호 확인
stat filename.txt         # 상세 inode 정보

# 파일시스템 inode 사용량 확인
df -i                     # inode 부족 시 파일 생성 불가
구성 요소설명
inode파일의 메타데이터 (크기, 권한, 소유자, 타임스탬프, 데이터 블록 위치)
디렉터리파일 이름 → inode 번호 매핑 테이블
하드 링크같은 inode를 가리키는 다른 이름. 원본 삭제해도 데이터 유지
심볼릭 링크다른 파일의 경로를 가리키는 특수 파일. 원본 삭제 시 깨짐

파일시스템 종류

파일시스템특징적합한 용도
ext4리눅스 기본, 안정적, 저널링 지원범용 (OS, 프로젝트 코드)
XFS대용량 파일, 병렬 I/O 최적화대용량 데이터셋, 체크포인트 저장
tmpfsRAM 기반, 재부팅 시 소멸임시 캐시, 학습 중 중간 데이터
NFS네트워크 파일 공유분산 학습 환경, 공유 데이터셋
FUSE사용자 공간 파일시스템S3 마운트(s3fs), GCS 마운트

마운트 개념

# 현재 마운트 상태 확인
lsblk                     # 블록 디바이스와 마운트 포인트
mount | column -t          # 상세 마운트 정보
df -hT                     # 파일시스템 종류 포함 용량 확인

# 디스크 마운트 예시
sudo mount /dev/sdb1 /data/datasets
sudo mount -t tmpfs -o size=16G tmpfs /tmp/cache

권한 시스템

rwx 기본 권한

리눅스의 모든 파일에는 소유자(user), 그룹(group), 기타(others) 에 대한 읽기(r), 쓰기(w), 실행(x) 권한이 있습니다.
-rwxr-xr-- 1 user group 4096 Jan 1 12:00 train.py
│├──┤├──┤├──┤
│ U   G   O

└── 파일 유형 (-: 일반, d: 디렉터리, l: 심볼릭 링크)
권한파일디렉터리숫자
r (읽기)내용 읽기목록 조회4
w (쓰기)내용 수정파일 생성/삭제2
x (실행)실행디렉터리 진입1
# 권한 변경
chmod 755 train.py         # rwxr-xr-x
chmod u+x script.sh        # 소유자에 실행 권한 추가
chown user:group file.txt  # 소유자 변경

# umask: 새 파일 생성 시 기본 권한 마스크
umask 022                  # 파일: 644, 디렉터리: 755

특수 권한

특수 권한숫자설명예시
SUID4000실행 시 파일 소유자 권한으로 실행/usr/bin/passwd
SGID2000실행 시 그룹 권한으로 실행 / 디렉터리: 새 파일이 부모 그룹 상속공유 프로젝트 디렉터리
Sticky Bit1000디렉터리 내 파일은 소유자만 삭제 가능/tmp
# 특수 권한 설정
chmod 4755 executable      # SUID 설정
chmod 2775 shared_dir/     # SGID 설정 (팀 공유 디렉터리)
chmod 1777 /tmp            # Sticky Bit (공용 임시 디렉터리)

환경변수

환경변수는 프로세스에 전달되는 설정값입니다. ML 개발에서 특히 중요한 환경변수들이 있습니다.
환경변수설명예시
PATH실행 파일 검색 경로/usr/local/cuda/bin:$PATH
LD_LIBRARY_PATH동적 라이브러리 검색 경로CUDA 라이브러리 경로
CUDA_HOMECUDA 설치 경로/usr/local/cuda-12.0
CUDA_VISIBLE_DEVICES사용할 GPU 지정0,1 또는 빈 문자열로 GPU 숨김
PYTHONPATHPython 모듈 검색 경로커스텀 패키지 경로 추가
HF_HOMEHuggingFace 캐시 디렉터리/data/cache/huggingface
TRANSFORMERS_CACHETransformers 모델 캐시/data/models/cache
TORCH_HOMEPyTorch 허브 캐시/data/models/torch
NCCL_DEBUGNCCL 디버그 레벨INFO (분산 학습 디버깅)
OMP_NUM_THREADSOpenMP 스레드 수CPU 연산 병렬화 제어
# GPU 선택
export CUDA_VISIBLE_DEVICES=0          # GPU 0번만 사용
export CUDA_VISIBLE_DEVICES=0,1        # GPU 0, 1번 사용
export CUDA_VISIBLE_DEVICES=""         # GPU 사용 안 함

# 환경변수 영구 설정 (~/.bashrc 또는 ~/.zshrc)
echo 'export CUDA_HOME=/usr/local/cuda' >> ~/.bashrc
echo 'export PATH=$CUDA_HOME/bin:$PATH' >> ~/.bashrc
echo 'export LD_LIBRARY_PATH=$CUDA_HOME/lib64:$LD_LIBRARY_PATH' >> ~/.bashrc
source ~/.bashrc
프로젝트별 환경변수는 .env 파일에 관리하고, python-dotenv로 로드하세요. .env 파일은 반드시 .gitignore에 추가하여 비밀값이 노출되지 않도록 합니다.

AI/ML에서 이 개념이 중요한 이유

OS 개념ML 실무 연결
fork()DataLoader(num_workers=N) → N개의 자식 프로세스 생성
가상 메모리대규모 데이터셋 메모리 매핑 (mmap)
OOM KillerGPU 메모리 + 시스템 RAM 동시 부족 시 학습 강제 종료
시그널Graceful shutdown, 체크포인트 저장 트리거
파일 권한공유 서버에서 데이터/모델 접근 관리
환경변수GPU 선택, CUDA 설정, 캐시 경로 지정
스왑스왑 활성화 시 학습 속도 극심한 저하 (thrashing)
inode수백만 개의 작은 이미지 파일 → inode 고갈 가능
# DataLoader와 fork의 관계
from torch.utils.data import DataLoader

# num_workers=4 → fork()로 4개의 워커 프로세스 생성
# 각 워커가 데이터를 병렬로 로드하여 GPU에 공급
train_loader = DataLoader(
    dataset,
    batch_size=32,
    num_workers=4,        # fork() 4회 호출
    pin_memory=True,      # 페이지 잠금 메모리 사용 → GPU 전송 가속
    persistent_workers=True  # 워커를 에폭 간 유지 → fork 오버헤드 감소
)
num_workers를 설정하면 fork()로 자식 프로세스가 생성됩니다. fork는 부모 프로세스의 메모리를 복사(Copy-on-Write)합니다. 워커마다 데이터 배치를 준비하며 메모리를 추가로 사용하므로, 워커 수 x 배치 크기만큼 시스템 RAM이 필요합니다. GPU 메모리가 아닌 시스템 RAM이 부족해지는 것이 핵심입니다.
반드시 Python 스크립트가 실행되기 전에 설정해야 합니다. CUDA 런타임은 프로세스 시작 시 GPU를 초기화하기 때문입니다. 셸에서 export CUDA_VISIBLE_DEVICES=0으로 설정하거나, 스크립트 최상단에서 os.environ["CUDA_VISIBLE_DEVICES"] = "0"import torch보다 먼저 작성하세요.
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는 페이지 잠금(page-locked) 메모리를 할당합니다. 운영체제가 이 메모리 영역을 스왑으로 내보내지 않도록 고정(pin)하여, GPU로의 DMA(Direct Memory Access) 전송이 빨라집니다. 단, 잠긴 메모리는 다른 프로세스가 사용할 수 없으므로 과도하게 사용하면 시스템 전체 메모리 부족으로 이어질 수 있습니다.

체크리스트

  • 커널 공간과 사용자 공간의 차이를 설명할 수 있다
  • fork()exec()의 역할 차이를 이해했다
  • 좀비 프로세스를 확인하고 원인을 진단할 수 있다
  • SIGTERM과 SIGKILL의 차이를 알고, 적절한 시그널을 선택할 수 있다
  • 가상 메모리, 페이지, 스왑의 관계를 설명할 수 있다
  • OOM Killer 로그를 확인하고, 원인을 분석할 수 있다
  • chmod, chown으로 파일 권한을 설정할 수 있다
  • CUDA_VISIBLE_DEVICES를 포함한 ML 관련 환경변수를 설정할 수 있다
  • DataLoadernum_workers가 시스템 메모리에 미치는 영향을 이해했다

다음 문서