Skip to main content

Linux Kernel Overview

커널 구조, 프로세스/메모리 관리, syscall, sysctl 튜닝


🎯 커널이란?

Kernel: 하드웨어와 소프트웨어 사이에서 자원을 관리하는 운영체제의 핵심

flowchart TB
subgraph UserApps["User Applications"]
Apps["nginx, java, python, docker"]
end

subgraph UserSpace["User Space"]
Libs["libc, libraries"]
end

SCI["System Call Interface (syscall)"]

subgraph KernelSpace["Kernel Space"]
PM["Process Manager"]
MM["Memory Manager"]
FS["File System"]
NS["Network Stack"]
DD["Device Drivers"]
end

subgraph HW["Hardware"]
Hardware["CPU, RAM, Disk, NIC"]
end

UserApps --> UserSpace --> SCI --> KernelSpace --> HW

커널이 하는 일:

  • 프로세스 관리: 프로그램 실행, CPU 시간 분배
  • 메모리 관리: RAM 할당, 가상 메모리
  • 파일시스템: 디스크 읽기/쓰기
  • 네트워크: TCP/IP 스택, 소켓
  • 디바이스 드라이버: 하드웨어 제어

🔒 Kernel Space vs User Space

CPU는 두 가지 모드로 동작함.

구분Kernel ModeUser Mode
권한모든 하드웨어 접근 가능제한된 접근
메모리전체 메모리 접근자기 영역만
실패 시시스템 패닉 (커널 패닉)프로세스만 죽음
실행 주체커널 코드애플리케이션

분리하는 이유:

  • 보안: 악성 프로그램이 시스템 전체를 망가뜨리지 못하게 함
  • 안정성: 하나의 프로그램 버그가 전체 시스템에 영향 주지 않게 함
  • 격리: 프로세스 간 서로 간섭 방지

📞 System Call (syscall)

System Call: 유저 프로그램이 커널 기능을 요청하는 인터페이스

유저 프로그램은 직접 하드웨어를 제어할 수 없음. 파일을 읽거나 네트워크 통신을 하려면 반드시 커널에게 요청해야 함.

sequenceDiagram
participant App as User Application
participant Libc as libc (glibc)
participant Kernel as Kernel
participant Disk as Disk

App->>Libc: read(fd, buf, size)
Libc->>Kernel: syscall(SYS_read, ...)
Note over Libc,Kernel: CPU 모드 전환: User → Kernel
Kernel->>Disk: 실제 디스크에서 데이터 읽기
Disk-->>Kernel: 데이터 반환
Note over Libc,Kernel: CPU 모드 전환: Kernel → User
Kernel-->>Libc: 결과 반환
Libc-->>App: 데이터 반환

주요 시스템 콜:

분류syscall설명
프로세스fork, exec, exit, wait프로세스 생성/종료
파일open, read, write, close파일 I/O
메모리mmap, brk메모리 할당
네트워크socket, bind, connect, send, recv소켓 통신
# 프로세스가 호출하는 syscall 추적
strace -c ls

# 출력 예시:
# % time calls syscall
# ------ -------- --------
# 25.00 12 openat
# 20.00 10 read
# 15.00 8 close
# ...

인프라 관점에서 중요한 이유:

  • syscall은 오버헤드가 있음 → 과도한 syscall은 성능 저하 유발
  • strace로 애플리케이션의 시스템 콜 패턴 분석 가능
  • 컨테이너는 호스트 커널의 syscall을 공유 → 보안 고려 필요

⚙️ Process & Thread

프로세스 (Process)

프로세스: 실행 중인 프로그램의 인스턴스

각 프로세스가 가지는 독립 자원:

  • 고유한 PID (Process ID)
  • 독립된 메모리 공간 (가상 주소 공간)
  • 파일 디스크립터 테이블
  • 환경 변수
flowchart TB
subgraph ProcessA["Process A (PID 1234)"]
A1["Code (Text) - 실행할 명령어"]
A2["Data - 전역 변수"]
A3["Heap - 동적 할당 메모리"]
A4["Stack - 함수 호출 스택"]
end

subgraph ProcessB["Process B (PID 1235)"]
B1["별도 메모리 공간"]
end

ProcessA <-->|완전히 분리됨| ProcessB

스레드 (Thread)

스레드: 프로세스 내에서 실행되는 흐름의 단위

같은 프로세스의 스레드들은 메모리를 공유함.

flowchart TB
subgraph Process["Process (PID 1234)"]
Shared["Code, Data, Heap (공유)"]
subgraph Threads["Threads"]
T1["Thread 1"]
T2["Thread 2"]
T3["Thread 3"]
end
Shared --> Threads
end

프로세스 vs 스레드

구분ProcessThread
메모리독립 (격리)공유
생성 비용높음낮음
통신IPC 필요 (복잡)메모리 공유 (쉬움)
안정성하나 죽어도 다른 것 영향 없음하나 죽으면 전체 영향
예시nginx workerJava 스레드 풀

프로세스 상태

stateDiagram-v2
[*] --> Created: fork()
Created --> Ready: 스케줄러 선택
Ready --> Running: CPU 할당
Running --> Ready: time slice expired
Running --> Waiting: I/O 요청 등
Waiting --> Ready: I/O 완료
Running --> Zombie: exit()
Zombie --> [*]: wait()
상태설명
Running (R)CPU에서 실행 중
Ready (R)실행 가능, CPU 대기
Waiting (S/D)I/O 등 이벤트 대기
Zombie (Z)종료됨, 부모가 수거 안 함
Stopped (T)중지됨 (SIGSTOP)
# 프로세스 상태 확인
ps aux

# STAT 컬럼:
# R: Running
# S: Sleeping (Interruptible)
# D: Disk Sleep (Uninterruptible) ← I/O 대기, kill 안 됨
# Z: Zombie
# T: Stopped

Zombie 프로세스가 많으면?

  • 부모 프로세스가 wait()를 안 하고 있음
  • 직접적 리소스 소모는 적지만 PID 고갈 가능
  • 부모 프로세스 점검 필요

D 상태가 많으면?

  • 디스크 I/O 병목 가능성
  • NFS 등 네트워크 스토리지 문제 의심
  • 스토리지 성능 확인 필요

🧠 Memory Management

가상 메모리 (Virtual Memory)

각 프로세스는 자신만의 가상 주소 공간을 가짐. 실제 물리 메모리(RAM)와 매핑되어 동작함.

flowchart LR
subgraph ProcessA["Process A - Virtual Memory"]
A1["Stack"]
A2["Heap"]
A3["Data"]
A4["Code"]
end

subgraph Physical["Physical Memory (RAM)"]
F1["Frame"]
F2["Frame"]
F3["Frame"]
F4["Frame"]
end

subgraph ProcessB["Process B - Virtual Memory"]
B1["Stack"]
B2["Heap"]
B3["Data"]
B4["Code"]
end

A1 --> F1
A2 --> F2
B1 --> F3
B4 --> F4

MMU["MMU가 변환"]

가상 메모리의 장점:

  • 프로세스 간 격리 (서로의 메모리 접근 불가)
  • 물리 메모리보다 큰 주소 공간 사용 가능
  • 메모리 단편화 해결
  • Copy-on-Write로 효율적인 fork()

Swap

Swap: 물리 메모리가 부족할 때 디스크를 메모리처럼 사용하는 메커니즘

flowchart TB
subgraph RAM["Physical RAM (가득참)"]
R1["A-1"]
R2["B-1"]
R3["A-2"]
R4["C-1"]
R5["B-2"]
end

RAM -->|RAM 부족<br/>오래 안 쓴 페이지를 Swap으로| Swap

subgraph Swap["Swap (Disk)"]
S1["A-1"]
S2["C-1"]
end
# Swap 사용량 확인
free -h

# 출력:
# total used free shared buff/cache available
# Mem: 16Gi 8.0Gi 2.0Gi 500Mi 6.0Gi 7.5Gi
# Swap: 4.0Gi 500Mi 3.5Gi

# Swap 사용 프로세스 확인
for f in /proc/*/status; do
awk '/VmSwap|Name/{printf $2 " " $3}END{print ""}' $f
done | sort -k 2 -n | tail -10

Swappiness: 커널이 얼마나 적극적으로 Swap을 사용할지 결정하는 값

# 현재 값 확인 (0-100, 기본값 60)
cat /proc/sys/vm/swappiness

# 값 변경 (런타임)
sysctl vm.swappiness=10

# 영구 설정 (/etc/sysctl.conf)
vm.swappiness=10
동작
0Swap 거의 안 씀 (OOM 위험)
10-30서버 권장 (메모리 우선)
60기본값
100적극적으로 Swap 사용

OOM Killer (Out of Memory)

메모리가 완전히 부족하면 커널이 프로세스를 강제 종료함.

flowchart TB
A["Memory 사용량 증가"] --> B["RAM 부족<br/>Swap도 부족"]
B --> C["OOM Killer 발동"]
C --> D["oom_score가 높은<br/>프로세스 선택"]
D --> E["프로세스 SIGKILL(9)"]
# OOM 발생 로그 확인
dmesg | grep -i "out of memory"
journalctl -k | grep -i oom

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

# OOM 점수 조정 (-1000 ~ 1000, -1000이면 OOM 대상 제외)
echo -500 > /proc/<PID>/oom_score_adj

OOM 방지 전략:

  • 메모리 충분히 확보
  • Swap 적절히 설정
  • 중요 프로세스 oom_score_adj 낮추기
  • cgroup으로 메모리 제한 (컨테이너)

💾 I/O Scheduler

디스크 I/O 요청을 어떤 순서로 처리할지 결정함.

I/O 스케줄러 종류

스케줄러특징적합한 환경
none (noop)순서대로 처리, 스케줄링 없음NVMe SSD, 가상화
mq-deadline데드라인 보장, 기아 방지범용, DB 서버
bfq공정한 대역폭 분배데스크탑, 멀티미디어
kyber저지연 목표빠른 응답 필요
# 현재 스케줄러 확인
cat /sys/block/sda/queue/scheduler

# 출력: [mq-deadline] kyber bfq none

# 스케줄러 변경 (런타임)
echo "none" > /sys/block/sda/queue/scheduler

# NVMe는 보통 none이 기본
cat /sys/block/nvme0n1/queue/scheduler

SSD vs HDD:

  • HDD: mq-deadline 권장 (헤드 이동 최소화)
  • SSD: none 또는 mq-deadline (물리적 탐색 없음)
  • NVMe: none (충분히 빠름)

🌐 Network Stack

리눅스 네트워크는 커널에서 처리됨.

flowchart TB
App["Application (nginx, curl)"]
Socket["Socket Layer"]
Transport["TCP / UDP (Transport)"]
Network["IP (Network)"]
Driver["Device Driver (e1000, ixgbe)"]
NIC["NIC Hardware (Physical)"]

App -->|"socket(), send(), recv()"| Socket
Socket --> Transport --> Network --> Driver --> NIC

주요 네트워크 버퍼:

# 수신 버퍼 크기
cat /proc/sys/net/core/rmem_max
cat /proc/sys/net/core/rmem_default

# 송신 버퍼 크기
cat /proc/sys/net/core/wmem_max
cat /proc/sys/net/core/wmem_default

# TCP 버퍼 (min, default, max)
cat /proc/sys/net/ipv4/tcp_rmem
cat /proc/sys/net/ipv4/tcp_wmem

🔧 Kernel Parameter Tuning (sysctl)

커널 파라미터는 /proc/sys/ 아래에 파일로 노출됨. sysctl 명령으로 조회/변경 가능함.

주요 튜닝 파라미터

네트워크

# 동시 연결 수 (기본 128, 웹서버는 높여야 함)
sysctl net.core.somaxconn=65535

# TCP 연결 대기 큐
sysctl net.ipv4.tcp_max_syn_backlog=65535

# TIME_WAIT 소켓 재사용 (주의해서 사용)
sysctl net.ipv4.tcp_tw_reuse=1

# 로컬 포트 범위 (클라이언트 연결 많을 때)
sysctl net.ipv4.ip_local_port_range="1024 65535"

# IP 포워딩 (라우터/NAT 역할)
sysctl net.ipv4.ip_forward=1

메모리

# Swappiness (서버는 낮게)
sysctl vm.swappiness=10

# 파일시스템 캐시 비율
sysctl vm.dirty_ratio=20
sysctl vm.dirty_background_ratio=5

# OOM 시 패닉 (선택)
sysctl vm.panic_on_oom=0

파일 & 프로세스

# 최대 열 수 있는 파일 수 (시스템 전체)
sysctl fs.file-max=2097152

# inotify 감시 수 (컨테이너 환경)
sysctl fs.inotify.max_user_watches=524288

영구 설정

# /etc/sysctl.conf 또는 /etc/sysctl.d/*.conf

# 웹서버 튜닝 예시
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
net.ipv4.ip_local_port_range = 1024 65535
vm.swappiness = 10
fs.file-max = 2097152

# 적용
sysctl -p

실무 시나리오별 튜닝

웹서버 (Nginx, Apache):

net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
net.core.netdev_max_backlog = 65535

DB 서버:

vm.swappiness = 1
vm.dirty_ratio = 40
vm.dirty_background_ratio = 10

컨테이너 호스트:

fs.inotify.max_user_watches = 524288
fs.inotify.max_user_instances = 512
net.bridge.bridge-nf-call-iptables = 1

📊 커널 정보 확인 명령어

# 커널 버전
uname -r
# 출력: 5.15.0-91-generic

# 커널 상세 정보
uname -a

# 커널 모듈 목록
lsmod

# 특정 모듈 정보
modinfo <module_name>

# 커널 로그
dmesg
journalctl -k

# 커널 파라미터 전체 조회
sysctl -a

# 시스템 콜 통계 (프로세스별)
strace -c <command>

# CPU 정보
cat /proc/cpuinfo

# 메모리 정보
cat /proc/meminfo

🔗 관련 포스트


🔗 참고 자료