procfs/sysfs/debugfs (Virtual Filesystems)

procfs, sysfs, debugfs는 커널 내부 상태를 사용자 공간에 노출하는 대표 가상 파일시스템입니다. 본 문서는 각 인터페이스의 목적과 경계, seq_file 기반 안전한 대용량 출력, kobject와 sysfs attribute 생성/소멸 수명주기, debugfs의 개발·진단 활용법, ABI 안정성 및 보안 권한 설계 원칙을 커널 모듈 코드와 함께 상세히 설명합니다.

전제 조건: VFSPage Cache 문서를 먼저 읽으세요. 파일시스템 공통 계층은 객체 생명주기와 캐시 일관성이 중심이므로, 먼저 추상 계층의 역할 경계를 고정하는 것이 중요합니다.
일상 비유: 이 주제는 도서관 분류 카드와 대출대장과 비슷합니다. 책 본문(데이터)보다 카드/대장(메타데이터) 규칙이 먼저 맞아야 전체 조회와 갱신이 안정적으로 동작합니다.

핵심 요약

  • 계층 이해 — VFS, 캐시, 하위 FS 경계를 구분합니다.
  • 메타데이터 우선 — inode/dentry 일관성을 먼저 확인합니다.
  • 저장 정책 — 저널링/압축/할당 정책 차이를 비교합니다.
  • 일관성 모델 — 로컬/원격/합성 FS의 반영 시점을 구분합니다.
  • 복구 관점 — 장애 시 재구성 경로를 함께 점검합니다.

단계별 이해

  1. 경계 계층 파악
    요청이 VFS에서 어디로 내려가는지 확인합니다.
  2. 메타/데이터 분리
    어느 경로에서 무엇이 갱신되는지 나눠 봅니다.
  3. 동기화/플러시 확인
    쓰기 반영 시점과 순서를 검증합니다.
  4. 복구 시나리오 점검
    비정상 종료 후 일관성 회복을 확인합니다.
관련 표준: POSIX.1-2017 (가상 파일시스템 인터페이스 기반) — procfs/sysfs는 POSIX 파일 API를 통해 커널 정보를 노출하는 가상 파일시스템입니다. 종합 목록은 참고자료 — 표준 & 규격 섹션을 참고하세요.

가상 파일시스템 개요

Linux 커널은 여러 가상 파일시스템을 통해 커널 내부 정보를 사용자 공간에 노출합니다. 디스크에 데이터를 저장하지 않으며, 읽기/쓰기 시 커널 함수가 동적으로 데이터를 생성합니다.

procfs 프로세스/통계 sysfs kobject 계층 debugfs 디버그 정보 Userspace
가상 FS마운트 포인트용도
procfs/proc프로세스 정보, 커널 통계
sysfs/sys디바이스/드라이버 계층 구조 (kobject 기반)
debugfs/sys/kernel/debug개발/디버깅 전용 인터페이스
configfs/sys/kernel/config사용자 공간에서 커널 오브젝트 구성
tracefs/sys/kernel/tracingftrace 추적 인터페이스

procfs (/proc)

주요 /proc 엔트리

경로내용
/proc/[pid]/프로세스별 정보 (status, maps, fd, ...)
/proc/cpuinfoCPU 정보
/proc/meminfo메모리 통계
/proc/interrupts인터럽트 카운터
/proc/sys/sysctl 커널 매개변수
/proc/buddyinfoBuddy allocator 상태
/proc/slabinfoSlab allocator 통계

procfs 엔트리 생성

#include <linux/proc_fs.h>
#include <linux/seq_file.h>

static int my_proc_show(struct seq_file *m, void *v)
{
    seq_printf(m, "Hello from kernel! jiffies=%lu\\n", jiffies);
    return 0;
}

static int my_proc_open(struct inode *inode, struct file *file)
{
    return single_open(file, my_proc_show, NULL);
}

static const struct proc_ops my_proc_ops = {
    .proc_open    = my_proc_open,
    .proc_read    = seq_read,
    .proc_lseek   = seq_lseek,
    .proc_release = single_release,
};

/* 모듈 초기화 시 */
proc_create("my_entry", 0444, NULL, &my_proc_ops);

sysfs (/sys)

sysfs는 커널의 kobject 계층 구조를 파일시스템으로 노출합니다. 디바이스, 드라이버, 버스 등의 속성을 파일로 표현합니다.

#include <linux/kobject.h>
#include <linux/sysfs.h>

static int my_value = 42;

static ssize_t my_show(struct kobject *kobj,
    struct kobj_attribute *attr, char *buf)
{
    return sysfs_emit(buf, "%d\\n", my_value);
}

static ssize_t my_store(struct kobject *kobj,
    struct kobj_attribute *attr, const char *buf, size_t count)
{
    int ret = kstrtoint(buf, 10, &my_value);
    return ret ? ret : count;
}

static struct kobj_attribute my_attr =
    __ATTR(my_value, 0664, my_show, my_store);

debugfs

debugfs는 개발/디버깅 전용 인터페이스로, 가장 간단하게 커널 데이터를 노출할 수 있습니다.

#include <linux/debugfs.h>

static struct dentry *dbg_dir;
static u32 my_debug_val = 100;

static int __init my_init(void)
{
    dbg_dir = debugfs_create_dir("my_driver", NULL);
    debugfs_create_u32("my_val", 0644, dbg_dir, &my_debug_val);
    debugfs_create_bool("enabled", 0644, dbg_dir, &my_enabled);
    return 0;
}

static void __exit my_exit(void)
{
    debugfs_remove_recursive(dbg_dir);
}
⚠️

debugfs는 프로덕션 환경에서 사용해서는 안 되며, ABI 안정성이 보장되지 않습니다. 안정적인 유저-커널 인터페이스가 필요하면 sysfs나 procfs를 사용하세요.

seq_file 인터페이스

seq_file은 큰 데이터를 안전하게 출력하는 헬퍼입니다. 버퍼 오버플로를 자동으로 처리하고, 반복자(iterator) 패턴으로 리스트를 순회합니다.

static void *my_seq_start(struct seq_file *m, loff_t *pos)
{
    return seq_list_start(&my_list, *pos);
}

static void *my_seq_next(struct seq_file *m, void *v, loff_t *pos)
{
    return seq_list_next(v, &my_list, pos);
}

static void my_seq_stop(struct seq_file *m, void *v) { }

static int my_seq_show(struct seq_file *m, void *v)
{
    struct my_item *item = list_entry(v, struct my_item, list);
    seq_printf(m, "id=%d name=%s\\n", item->id, item->name);
    return 0;
}

static const struct seq_operations my_seq_ops = {
    .start = my_seq_start,
    .next  = my_seq_next,
    .stop  = my_seq_stop,
    .show  = my_seq_show,
};

sysctl 인터페이스

/proc/sys/ 아래의 커널 매개변수를 등록하고 관리합니다:

#include <linux/sysctl.h>

static int my_param = 100;
static int my_min = 0, my_max = 1000;

static struct ctl_table my_sysctl_table[] = {
    {
        .procname   = "my_param",
        .data       = &my_param,
        .maxlen     = sizeof(int),
        .mode       = 0644,
        .proc_handler = proc_dointvec_minmax,
        .extra1     = &my_min,
        .extra2     = &my_max,
    },
    { }
};

static struct ctl_table_header *my_sysctl_header;

static int __init my_init(void)
{
    my_sysctl_header = register_sysctl("kernel/my_driver", my_sysctl_table);
    return 0;
}

/* 사용: sysctl kernel.my_driver.my_param=500 */
/* 또는: echo 500 > /proc/sys/kernel/my_driver/my_param */
핸들러용도
proc_dointvec정수 읽기/쓰기
proc_dointvec_minmax최소/최대 범위 검증 포함
proc_douintvec부호 없는 정수
proc_dostring문자열
proc_doulongvec_ms_jiffies_minmaxms→jiffies 자동 변환

디바이스 속성 (Device Attributes)

드라이버에서 sysfs 속성을 가장 간편하게 생성하는 방법입니다:

/* DEVICE_ATTR 매크로: show/store 함수를 자동으로 연결 */
static ssize_t status_show(struct device *dev,
    struct device_attribute *attr, char *buf)
{
    struct my_priv *priv = dev_get_drvdata(dev);
    return sysfs_emit(buf, "%s\\n", priv->active ? "active" : "idle");
}

static ssize_t status_store(struct device *dev,
    struct device_attribute *attr,
    const char *buf, size_t count)
{
    struct my_priv *priv = dev_get_drvdata(dev);
    priv->active = sysfs_streq(buf, "active");
    return count;
}
static DEVICE_ATTR_RW(status);

/* 속성 그룹으로 일괄 등록 */
static struct attribute *my_attrs[] = {
    &dev_attr_status.attr,
    NULL,
};
ATTRIBUTE_GROUPS(my);

/* 드라이버에서 그룹 연결 */
static struct platform_driver my_driver = {
    .driver = {
        .name = "mydev",
        .dev_groups = my_groups,  /* 자동으로 sysfs 생성/제거 */
    },
};

바이너리 속성 (Binary sysfs Attributes)

구조화된 바이너리 데이터(EEPROM, firmware 등)를 sysfs로 노출할 때 사용합니다:

static ssize_t eeprom_read(struct file *filp,
    struct kobject *kobj,
    struct bin_attribute *attr,
    char *buf, loff_t off, size_t count)
{
    struct device *dev = kobj_to_dev(kobj);
    return read_eeprom(dev, buf, off, count);
}

static BIN_ATTR_RO(eeprom, EEPROM_SIZE);
/* → /sys/devices/.../eeprom (바이너리 파일) */

configfs

sysfs가 커널→유저 방향의 정보 노출이라면, configfs는 유저→커널 방향의 오브젝트 생성/구성입니다:

#include <linux/configfs.h>

/* configfs에서 mkdir로 새 오브젝트 생성 */
/* mkdir /sys/kernel/config/my_subsys/instance1 */
/* → make_item() 콜백 호출 → 커널 오브젝트 생성 */

/* 사용 예: USB gadget configfs */
/* mkdir /sys/kernel/config/usb_gadget/g1 */
/* echo 0x1234 > idVendor */
/* mkdir configs/c.1 */
/* mkdir functions/mass_storage.0 */
💡

sysfs의 sysfs_emit()sprintf() 대신 사용하세요. 페이지 크기(PAGE_SIZE) 초과를 방지하고, 보안 감사에서도 권장됩니다. 또한 sysfs 속성은 "하나의 값, 하나의 파일" 원칙을 따르세요.

/proc 세부 경로 심화

/proc은 커널과 프로세스 상태를 사용자 공간에 노출하는 가상 파일시스템입니다. 각 경로의 정확한 의미와 활용법을 이해하면 커널 디버깅과 시스템 모니터링이 크게 향상됩니다.

프로세스별 /proc/[pid]/ 경로

경로용도주요 정보
/proc/[pid]/status프로세스 상태 요약Name, State, Tgid, Pid, PPid, Uid, VmRSS, Threads, voluntary_ctxt_switches
/proc/[pid]/maps가상 메모리 맵주소 범위, 권한(rwxp), 오프셋, 디바이스, inode, 파일 경로
/proc/[pid]/smaps상세 메모리 맵RSS, PSS, Shared/Private Clean/Dirty, Referenced, Anonymous, Swap
/proc/[pid]/smaps_rollupsmaps 합산전체 프로세스의 메모리 통계 요약 (smaps 파싱보다 빠름)
/proc/[pid]/stat프로세스 통계 (raw)utime, stime, nice, num_threads, start_time, vsize, rss
/proc/[pid]/statm메모리 통계 (페이지)size, resident, shared, text, data
/proc/[pid]/fd/열린 파일 디스크립터심볼릭 링크 → 실제 파일/소켓/파이프
/proc/[pid]/fdinfo/FD 상세 정보pos, flags, mnt_id, eventfd-count, inotify 등
/proc/[pid]/stack커널 스택 트레이스커널 모드에서의 현재 콜 스택 (root만 읽기 가능)
/proc/[pid]/wchan대기 채널프로세스가 sleep 중인 커널 함수명
/proc/[pid]/ioI/O 통계rchar, wchar, syscr, syscw, read_bytes, write_bytes
/proc/[pid]/cgroupcgroup 소속hierarchy-ID:controller-list:/path
/proc/[pid]/ns/네임스페이스각 네임스페이스의 inode (mnt, pid, net, user, uts, ipc)
/proc/[pid]/oom_scoreOOM 점수0~1000, 높을수록 OOM kill 우선
/proc/[pid]/oom_score_adjOOM 점수 조정-1000~1000 (쓰기 가능, -1000=OOM 면제)
/proc/[pid]/cmdline실행 명령줄NULL 구분 인자 리스트
/proc/[pid]/environ환경 변수NULL 구분 KEY=VALUE 리스트 (root만)
/proc/[pid]/limits리소스 제한RLIMIT 값들 (soft/hard)
/proc/[pid]/task/스레드 목록각 스레드의 [tid]/ 하위 디렉터리
# 프로세스 메모리 사용량 분석
cat /proc/1234/smaps_rollup
# Rss:              12345 kB   ← 실제 물리 메모리
# Pss:              10234 kB   ← 공유 메모리 비례 할당
# Shared_Clean:      5678 kB   ← 공유+깨끗 (라이브러리 코드)
# Shared_Dirty:       456 kB   ← 공유+더러움 (CoW 전)
# Private_Clean:     2345 kB   ← 개인+깨끗
# Private_Dirty:     3456 kB   ← 개인+더러움 (힙/스택)
# Swap:               789 kB   ← 스왑아웃된 페이지

# 프로세스의 열린 파일 확인
ls -la /proc/1234/fd/
# lrwx------ 1 user user 0 ... 0 -> /dev/pts/0
# lrwx------ 1 user user 0 ... 3 -> socket:[12345]
# lr-x------ 1 user user 0 ... 4 -> /var/log/app.log

# 프로세스의 커널 스택 확인 (D 상태 프로세스 디버깅)
cat /proc/1234/stack
# [<0>] io_schedule+0x46/0x70
# [<0>] wait_on_page_bit+0x10e/0x170
# [<0>] __filemap_fdatawait_range+0x85/0xf0

시스템 전역 /proc/ 경로

경로용도주요 정보
/proc/meminfo메모리 전체 현황MemTotal, MemFree, MemAvailable, Buffers, Cached, SwapTotal, Slab, PageTables, Hugepages
/proc/vmstat가상 메모리 통계pgfault, pgmajfault, pgscan_*, pgsteal_*, pswpin/out, compact_*, numa_*
/proc/zoneinfo메모리 존 상세각 존의 free, min/low/high 워터마크, managed, present, spanned
/proc/buddyinfoBuddy 할당자 상태각 존의 order 0~10 free 블록 수
/proc/pagetypeinfo페이지 타입별 상태Unmovable, Movable, Reclaimable 별 free 블록
/proc/slabinfoSlab 캐시 상태각 캐시의 active_objs, num_objs, objsize, objperslab
/proc/cpuinfoCPU 정보model name, MHz, cache, flags (SSE, AVX 등), bugs
/proc/interrupts인터럽트 통계IRQ별 CPU별 발생 횟수, 타입, 디바이스명
/proc/softirqsSoft IRQ 통계HI, TIMER, NET_TX, NET_RX, BLOCK, TASKLET, SCHED, HRTIMER, RCU
/proc/stat커널/시스템 통계cpu time, ctx switches, btime, processes, procs_running/blocked
/proc/loadavg시스템 부하1/5/15분 평균, 실행중/전체 스레드, 마지막 PID
/proc/diskstats디스크 I/O 통계major, minor, name, reads, writes, io_ticks
/proc/net/네트워크 정보tcp, udp, unix, dev, arp, route, nf_conntrack
/proc/sys/sysctl 인터페이스커널 튜닝 파라미터 (아래 상세)
/proc/kallsyms커널 심볼 테이블주소, 타입, 심볼명 (디버깅 필수)
/proc/modules로드된 모듈이름, 크기, 참조 수, 의존 모듈
/proc/iomem메모리 맵 I/O 영역물리 주소 범위와 디바이스 매핑
/proc/ioportsI/O 포트 영역포트 범위와 디바이스 매핑
/proc/crypto암호화 알고리즘 목록name, driver, module, type, blocksize, digestsize, keysize, refcnt
/proc/devices등록된 디바이스 번호Character/Block 섹션, major 번호, 드라이버 이름
/proc/vmallocinfovmalloc 영역 정보주소 범위, 크기, 호출자, 플래그(ioremap/phy/user 등)

/proc/stat — 커널 전체 통계

/proc/stat은 시스템 부팅 이후 누적된 커널 통계를 제공합니다. CPU 사용 시간, 컨텍스트 스위치 횟수, 부팅 시각, 프로세스 생성 수 등 시스템 전반의 활동 지표가 포함됩니다. top, vmstat, sar 같은 도구들이 이 파일을 파싱하여 CPU 사용률을 계산합니다. CPU 행은 10개 컬럼(커널 2.6.24+)으로 구성되며, 9~10번째 컬럼이 KVM 게스트 실행 시간입니다.

# cpu  user    nice   system   idle      iowait  irq   softirq  steal  guest  guest_nice
cpu  12345678 234567 3456789 987654321 456789 98765 234567 12345 56789 1234
cpu0  3086419  58641  864197 246913580 114197  24691  58641  3086 14197  308
cpu1  3086420  58642  864198 246913581 114198  24692  58642  3087 14198  309
cpu2  3086418  58640  864196 246913579 114196  24690  58640  3085 14196  307
cpu3  3086421  58644  864198 246913581 114198  24692  58643  3087 14199  310
# intr: 첫 값=총 인터럽트 수, 이후 IRQ 0번부터 각 라인별 카운터
intr 9876543210 234 0 0 0 0 0 0 987 0 0 1234 0 0 56789 ...
ctxt 1234567890           # 총 컨텍스트 스위치 횟수
btime 1700000000          # 부팅 시각 (Unix timestamp)
processes 987654          # 총 생성된 프로세스/스레드 수
procs_running 3           # 실행 중 또는 런큐 대기 중
procs_blocked 0           # I/O 대기로 블록된 프로세스 수
# softirq: 총계 HI TIMER NET_TX NET_RX BLOCK IRQ_POLL TASKLET SCHED HRTIMER RCU
softirq 2345678901 0 987654321 12345 234567890 123456 0 78901 456789 0 234567
필드열 위치단위설명
user1jiffies사용자 공간에서 소비한 시간 (nice 제외)
nice2jiffiesnice 값이 설정된 사용자 프로세스 CPU 시간 (renice된 프로세스)
system3jiffies커널 공간(시스템 콜, 인터럽트 핸들러 등)에서 소비한 시간
idle4jiffiesCPU가 유휴 상태였던 시간 (halt 명령 실행 중)
iowait5jiffiesI/O 완료 대기로 유휴 상태였던 시간 (NUMA·멀티코어에서 신뢰성 낮음)
irq6jiffies하드웨어 인터럽트 처리에 소비한 시간
softirq7jiffies소프트 인터럽트 처리에 소비한 시간
steal8jiffies가상화 환경에서 하이퍼바이저에 도난당한 CPU 시간 (VM 경쟁)
guest9jiffiesKVM 게스트 vCPU 실행 시간 (user에 포함됨 — 이중 계산 주의)
guest_nice10jiffiesnice된 KVM 게스트 vCPU 실행 시간 (nice에 포함됨)
ctxt별도 행횟수부팅 이후 총 컨텍스트 스위치 횟수
btime별도 행Unix timestamp시스템 부팅 시각 (초)
processes별도 행횟수부팅 이후 생성된 총 프로세스/스레드 수
procs_running별도 행개수현재 실행 중이거나 실행 대기 중인 프로세스 수
procs_blocked별도 행개수I/O 대기로 블록된 프로세스 수

softirq 행 서브타입 분해: softirq 행의 컬럼 순서는 커널 버전 간 고정입니다.

열 번호서브타입설명
0 (첫 열)총계전체 softirq 누적 합계
1HI고우선순위 tasklet (tasklet_hi_schedule())
2TIMER타이머 만료 처리 (hrtimer, timer wheel)
3NET_TX네트워크 패킷 송신 완료 처리
4NET_RX네트워크 패킷 수신 처리 (NAPI poll)
5BLOCK블록 디바이스 I/O 완료 처리
6IRQ_POLL블록 I/O 폴링 (iopoll)
7TASKLET일반 tasklet 처리 (tasklet_schedule())
8SCHED스케줄러 rebalance IPI
9HRTIMER고해상도 타이머 콜백 처리
10RCURCU(Read-Copy-Update) 콜백 처리
#!/bin/bash
# CPU 사용률 계산: 2-샘플 차분법
cpu1=($(awk '/^cpu / {print $2,$3,$4,$5,$6,$7,$8,$9}' /proc/stat))
sleep 1
cpu2=($(awk '/^cpu / {print $2,$3,$4,$5,$6,$7,$8,$9}' /proc/stat))
total1=0; for v in "${cpu1[@]}"; do ((total1+=v)); done
total2=0; for v in "${cpu2[@]}"; do ((total2+=v)); done
dtotal=$((total2 - total1))
didle=$((cpu2[3] - cpu1[3]))    # idle delta
dsteal=$((cpu2[7] - cpu1[7]))   # steal delta
# guest(열9)는 user(열1)에 이미 포함 → 별도 차감 불필요
usage=$(( (dtotal - didle) * 100 / dtotal ))
steal_pct=$(( dsteal * 100 / dtotal ))
echo "CPU usage  : ${usage}%"
echo "steal time : ${steal_pct}%   (>5% → 클라우드 CPU 경쟁 의심)"
⚠️

iowait 신뢰성 경고: iowait는 해당 CPU가 I/O를 기다리는 동안 달리 실행할 프로세스가 없었던 시간입니다. NUMA 시스템이나 멀티코어 환경에서는 다른 CPU가 I/O를 처리하는 동안 이 CPU는 다른 작업을 수행할 수 있어 iowait가 실제 I/O 부하를 과소평가합니다. 따라서 iowait 단독으로는 I/O 병목을 판단하기 어렵습니다. I/O 병목 분석에는 반드시 /proc/diskstatsutil%await를 함께 확인하세요.

/proc/meminfo — 메모리 전체 현황

/proc/meminfo는 시스템 메모리의 상세 현황을 KB 단위로 보고합니다. free, top, htop 등 대부분의 메모리 모니터링 도구의 데이터 소스입니다. 가용 메모리 판단 시 MemFree보다 MemAvailable을 우선 사용해야 합니다.

MemTotal:       16384000 kB   # 물리 메모리 총량
MemFree:         1024000 kB   # 완전히 비어 있는 메모리
MemAvailable:    8192000 kB   # 실제로 할당 가능한 메모리 (추정값)
Buffers:          512000 kB   # 블록 디바이스 I/O 버퍼
Cached:          4096000 kB   # 페이지 캐시 (파일 캐시, SwapCached 제외)
SwapCached:        32768 kB   # 스왑 후 메모리로 복귀한 페이지 (스왑 슬롯 유지)
Active:          6144000 kB   # 최근 사용된 페이지
Inactive:        2560000 kB   # 오랫동안 미사용 (회수 대상)
Active(anon):    4096000 kB   # 최근 사용된 익명 페이지
Inactive(anon):   512000 kB   # 오랫동안 미사용 익명 페이지
Active(file):    2048000 kB   # 최근 사용된 파일 페이지
Inactive(file):  2048000 kB   # 오랫동안 미사용 파일 페이지
Unevictable:       65536 kB   # 회수 불가능 (mlockall, 공유 메모리 고정)
Mlocked:           65536 kB   # mlock/mlockall로 잠긴 메모리
SwapTotal:       8192000 kB   # 스왑 공간 총량
SwapFree:        8192000 kB   # 미사용 스왑 공간
Zswap:             16384 kB   # 압축 스왑 캐시 크기 (커널 6.1+)
Zswapped:         131072 kB   # Zswap에 저장된 원본 크기
Dirty:             12288 kB   # 디스크에 쓰기 대기 중인 더티 페이지
Writeback:             0 kB   # 현재 writeback 중인 페이지
AnonPages:       4608000 kB   # 매핑되지 않은 익명 페이지
Mapped:           819200 kB   # mmap으로 매핑된 파일 페이지
Shmem:            204800 kB   # tmpfs/shmem 사용량 (Active(anon)에 포함)
KReclaimable:     901120 kB   # 회수 가능한 커널 메모리 (SReclaimable 포함)
Slab:            1024000 kB   # 커널 slab 할당자 사용 총량
SReclaimable:     819200 kB   # 회수 가능한 slab (dentry/inode 캐시)
SUnreclaim:       204800 kB   # 회수 불가능한 slab (커널 구조체)
KernelStack:       65536 kB   # 커널 스택 (스레드당 8~16KB)
PageTables:        32768 kB   # 페이지 테이블 메모리
SecPageTables:         0 kB   # 보조 페이지 테이블 (arm64 등)
NFS_Unstable:          0 kB   # NFS 서버로 전송 중인 페이지 (레거시)
Bounce:                0 kB   # 32비트 DMA 바운스 버퍼 (레거시 장치용)
WritebackTmp:          0 kB   # FUSE writeback 임시 버퍼
CommitLimit:    16384000 kB   # 허용된 총 커밋 한도
Committed_AS:    8192000 kB   # 현재까지 커밋된 메모리 총량
VmallocTotal:   34359738367 kB # vmalloc 가상 주소 공간 총량
VmallocUsed:      131072 kB   # vmalloc 실제 사용량
VmallocChunk:          0 kB   # 최대 연속 vmalloc 가용 블록
Percpu:            32768 kB   # per-CPU 할당자 사용량
HardwareCorrupted:     0 kB   # ECC 오류로 격리된 메모리
AnonHugePages:    131072 kB   # Transparent Huge Pages (THP)
ShmemHugePages:        0 kB   # tmpfs의 THP
ShmemPmdMapped:        0 kB   # PMD 단위로 매핑된 shmem THP
FileHugePages:         0 kB   # 파일 THP (DAX 등)
FilePmdMapped:         0 kB   # PMD 매핑된 파일 THP
HugePages_Total:       0      # 고정 Huge Pages 총 개수
HugePages_Free:        0      # 미사용 Huge Pages
HugePages_Rsvd:        0      # 예약되었으나 미할당
HugePages_Surp:        0      # 초과 할당된 Huge Pages
Hugepagesize:       2048 kB   # 기본 Huge Page 크기
Hugetlb:               0 kB   # Huge Page 총 메모리
DirectMap4k:      524288 kB   # 4KB 페이지로 직접 매핑
DirectMap2M:    16252928 kB   # 2MB 페이지로 직접 매핑 (TLB 효율↑)
DirectMap1G:           0 kB   # 1GB 페이지로 직접 매핑 (pdpe1gb 플래그 필요)
필드단위의미
MemAvailablekB스왑 없이 새로운 프로세스에 할당 가능한 추정량 (Buffers+Cached+SReclaimable - 워터마크 여유). MemFree보다 신뢰성 높음
BufferskB블록 디바이스 메타데이터 캐시 (파일시스템 슈퍼블록, inode 등)
SwapCachedkB스왑 후 메모리로 복귀했지만 스왑 슬롯을 유지 중인 페이지. 재스왑 시 I/O 없이 슬롯 재사용 가능
UnevictablekB회수 불가능 페이지 (mlockall/mlock, 공유 메모리 핀). RT 프로세스가 지연 방지용으로 사용
Zswap / ZswappedkBZswap: 압축 스왑 캐시가 실제로 차지하는 RAM. Zswapped: 그 안에 담긴 원본 크기. 압축률 = Zswapped/Zswap
KReclaimablekBSReclaimable 포함 상위 집합. 커널 회수 가능 메모리 전체 합계
HardwareCorruptedkBECC 오류 감지로 커널이 격리시킨 메모리. 0이 아니면 하드웨어 점검 필요
PercpukBper-CPU 변수 할당자가 사용하는 메모리. CPU 수 증가 시 비례 증가
BouncekB32비트 DMA 주소 제한 장치를 위한 바운스 버퍼. 레거시 ISA/PCI 장치만 사용
DirectMap2MkB2MB 슈퍼페이지로 직접 매핑된 RAM. 클수록 TLB 미스 감소로 성능 향상. KPTI 활성화 시 일부 해제
DirectMap1GkB1GB 기가 페이지로 직접 매핑된 RAM. pdpe1gb CPU 플래그 + 충분한 RAM 필요
Committed_ASkBmalloc으로 예약된 총 가상 메모리. CommitLimit 초과 시 OOM 위험. vm.overcommit_memory=2 시 엄격 적용
SReclaimablekB회수 가능한 slab (dentry/inode 캐시). 메모리 부족 시 커널이 자동 회수
SUnreclaimkB회수 불가능한 slab (sock, task_struct 등). 지속 증가 시 커널 모듈 누수 의심
AnonHugePageskBTHP(Transparent Huge Pages)가 자동 병합한 익명 2MB 페이지
HugePages_*개수정적 할당 Huge Pages (hugepages= 커널 파라미터로 사전 확보)
💡

실용 모니터링 공식: 실제 사용 메모리 = MemTotal - MemAvailable. 스왑 사용 판단: SwapTotal - SwapFree > 0이면 메모리 압박 시작. SUnreclaim이 수백 MB를 지속 증가하면 커널 모듈 메모리 누수 의심. HardwareCorrupted > 0이면 즉시 서버 점검. Zswap이 활성화된 경우 실제 메모리 사용량은 MemTotal - MemFree - Buffers - Cached + Zswap으로 계산합니다.

/proc/cpuinfo — CPU 상세 정보

/proc/cpuinfo는 시스템의 각 논리 CPU 코어에 대한 상세 정보를 제공합니다. 소켓 수, 물리 코어 수, 하이퍼스레딩 여부, 지원 명령어 셋, 알려진 취약점 목록 등을 확인할 수 있습니다. 각 논리 CPU는 processor:로 구분된 별도의 블록으로 표시됩니다.

processor       : 0                 # 논리 CPU 번호 (0부터)
vendor_id       : GenuineIntel      # AMD: AuthenticAMD
cpu family      : 6                 # Intel: 6=Core 계열 / AMD: 25=Zen3, 26=Zen4
model           : 165               # cpu family + model로 정확한 마이크로아키텍처 식별
model name      : Intel(R) Core(TM) i7-10700 CPU @ 2.90GHz
stepping        : 5
microcode       : 0xea              # 적용된 마이크로코드 버전 (취약점 완화에 중요)
cpu MHz         : 2900.000          # 현재 클럭 (turbo 포함 시 변동, 거버너 영향)
cache size      : 16384 KB          # L3 캐시 크기 (소켓당 공유)
physical id     : 0                 # 소켓 번호
siblings        : 16                # 같은 소켓의 논리 CPU 수
core id         : 0                 # 소켓 내 물리 코어 번호
cpu cores       : 8                 # 소켓의 물리 코어 수
apicid          : 0                 # APIC ID (인터럽트 라우팅용)
flags           : fpu vme de pse tsc msr ... vmx aes avx avx2 avx512f ht nx
                  pdpe1gb rdrand rdseed sgx clflushopt
bugs            : spectre_v1 spectre_v2 spec_store_bypass mds srbds
                  retbleed taa rfds bhi its
bogomips        : 5799.77           # 루프 기반 성능 지표 (비교 기준으로만 사용)
TLB size        : 2560 4K pages
clflush size    : 64                # 캐시 라인 크기 (bytes)
cache_alignment : 64
address sizes   : 39 bits physical, 48 bits virtual
power management:                   # 동적 클럭/전압 스케일링 기능 목록
필드설명
physical id물리 소켓 번호. 같은 값 = 동일 패키지. 소켓 수 = sort -u | wc -l
core id소켓 내 물리 코어 번호. siblings/cpu_cores > 1 이면 하이퍼스레딩 활성
siblings같은 소켓의 논리 CPU 총 수 (= cpu cores × HT 배수)
microcode현재 적용된 마이크로코드 패치 버전. iucode-tool로 업데이트. /sys/devices/system/cpu/microcode/reload 경로로 런타임 업데이트 가능
address sizes물리 주소 비트(39/46/52) × 가상 주소 비트(48/57). 48비트=256TB VA, 57비트=128PB VA(5-level paging)
power management동적 클럭/전압 스케일링 지원 기능. cpupower frequency-info로 상세 확인

주요 flags 상세:

플래그의미
vmx / svmIntel VT-x / AMD-V 하드웨어 가상화 지원 (KVM 필수)
aes하드웨어 AES-NI 명령어 (AES-128/192/256 가속)
avx / avx2256비트 SIMD 연산 (avx2=정수 포함 완전 256비트)
avx512f512비트 SIMD 기본 명령어 셋 (Xeon/일부 Core 계열)
ht하이퍼스레딩(Intel SMT) 활성화 여부
nxNo-eXecute 비트 (DEP, 스택 실행 방지)
pdpe1gb1GB 기가 페이지 지원 (DirectMap1G 활성 조건)
rdrand / rdseed하드웨어 난수 생성기 (RDRAND/RDSEED 명령어)
sgxSoftware Guard Extensions (격리 실행 환경)
clflushopt최적화된 캐시 플러시 (persistent memory 필수)

bugs 필드 — 알려진 취약점:

취약점설명완화책
spectre_v1배열 범위 확인 우회 추측 실행array_index_nospec()
spectre_v2분기 예측기 오염을 통한 정보 유출IBRS/IBPB/eIBRS/Retpoline
mdsMicroarchitectural Data Sampling (Zombieload)VERW 명령어로 버퍼 플러시
srbdsSpecial Register Buffer Data SamplingSRBDS_CTRL MSR
retbleedRetpoline 우회 (AMD Zen1/Zen2, Intel Skylake급)IBPB 강제 / Stuffing
taaTSX Asynchronous Abort (Intel TSX 한정)TSX 비활성화 또는 VERW
rfdsRegister File Data Sampling (Intel Atom 계열)VERW 강화
bhiBranch History InjectionBHI_DIS_S MSR / SW 시퀀스
itsIndirect Target Selection (Intel 최신 취약점)IBPB 강제

AMD CPU family 매핑: family 23=Zen/Zen+/Zen2 (Ryzen 1000~3000), 25=Zen3 (Ryzen 5000), 26=Zen4 (Ryzen 7000). lscpu | grep -E "family|Model name"으로 확인.

💡

NUMA 토폴로지 판별: grep "physical id" /proc/cpuinfo | sort -u | wc -l = 소켓 수. grep "cpu cores" /proc/cpuinfo | head -1 = 코어 수/소켓. siblings != cpu cores이면 하이퍼스레딩 활성. bugs 필드의 완화책 상태는 /sys/devices/system/cpu/vulnerabilities/에서 상세 확인 가능합니다. 마이크로코드 업데이트: apt install intel-microcode 또는 amd64-microcode (재부팅 없이 런타임 업데이트 가능).

/proc/buddyinfo — Buddy 할당자 상태

/proc/buddyinfo는 Buddy 메모리 할당자의 현재 상태를 NUMA 노드 및 메모리 존(Zone)별로 보여줍니다. 각 컬럼은 order 0(4KB, 1페이지)부터 order 10(4MB, 1024페이지)까지의 연속된 물리 메모리 블록 개수를 나타냅니다. 메모리 파편화(fragmentation) 정도를 직접 측정하는 핵심 지표입니다.

# NUMA 2-노드 시스템 예시
Node 0, zone      DMA      1      0      1      0      2      1      1      0      1      1      3
Node 0, zone    DMA32    760    234     89     56     21     10      4      2      1      0      0
Node 0, zone   Normal  12345   5678   2345    987    432    198     76     21      4      1      0
Node 0, zone  Movable      0      0      0      0      0      0      0      0      0      0      0
Node 1, zone   Normal   9876   4321   1987    765    321    145     54     12      2      0      0
#                        ↑      ↑      ↑      ↑      ↑      ↑      ↑      ↑      ↑      ↑      ↑
#              order:    0      1      2      3      4      5      6      7      8      9     10
#              크기:   4KB   8KB   16KB  32KB  64KB  128K  256K  512K   1M    2M    4MB
#              용도:   일반  일반  일반  일반  일반  일반  일반  일반  일반  THP  THP
항목설명
Node NNUMA 노드 번호. 다중 소켓 서버에서 노드별 메모리 불균형 파악
zone DMA하위 16MB 영역 (레거시 ISA DMA 디바이스용). 매우 희소하게 사용
zone DMA32하위 4GB 영역 (32비트 DMA 주소 제한 디바이스: 구형 NIC, USB 컨트롤러)
zone Normal일반 물리 메모리. 대부분의 커널 할당 및 사용자 프로세스 페이지
zone Movable핫플러그/메모리 오프라인 전용 존. 이동 가능 페이지만 배치되어 오프라인 시 이동 가능
order N 값2^N 페이지 크기의 연속 블록 가용 개수. order 9(512페이지=2MB)가 THP 할당 단위

Hugepage 할당 가능 여부 판별: order 9(2MB=512페이지) 값이 0이면 Transparent Huge Page(THP) 할당 실패. order 10(4MB) 값이 모두 0이면 정적 2MB HugePage 할당 불가.

#!/bin/bash
# 메모리 파편화 점수 계산 (높을수록 연속 메모리 많음)
# 파편화 점수 = Σ (order_N_count × 2^N × 4KB)
awk '/^Node 0, zone   Normal/ {
  score=0
  for(i=4; i<=NF; i++) {
    order=i-4
    score += $i * (2^order) * 4    # 단위: KB
  }
  printf "Normal zone 연속 메모리: %.1f MB\n", score/1024
  # THP(2MB) 가능 여부: order 9 = 열 13 (4+9=13번째)
  printf "order-9(2MB) 블록: %d개 (THP 가능: %s)\n", $13, ($13>0?"YES":"NO")
}' /proc/buddyinfo

# /proc/pagetypeinfo 연계: Movable/Unmovable/Reclaimable 구분
grep "Normal" /proc/pagetypeinfo | grep -E "Movable|Unmovable"
💡

파편화 분석: 고차 order(6~10)의 값이 0에 가까우면 메모리 파편화가 심한 상태입니다. 연속 메모리가 필요한 DMA 할당이나 THP 할당이 실패할 수 있습니다. echo 3 > /proc/sys/vm/drop_caches 후에도 고차 order가 복구되지 않으면 echo 1 > /proc/sys/vm/compact_memory로 메모리 압축을 시도하세요. CMA(연속 메모리 할당자) 존은 /proc/pagetypeinfo에서 별도 확인할 수 있습니다. NUMA 다중 노드 환경에서는 노드 간 값 차이로 불균형 배치를 탐지할 수 있습니다.

/proc/crypto — 암호화 알고리즘 목록

/proc/crypto는 커널에 등록된 모든 암호화 알고리즘의 목록과 상세 속성을 표시합니다. 하드웨어 가속 알고리즘과 소프트웨어 폴백 구현이 모두 나열되며, 동일 알고리즘에 여러 드라이버가 존재할 수 있습니다. dm-crypt, TLS, IPsec 등이 사용하는 알고리즘을 확인하는 데 유용합니다.

# 블록 암호 (cipher)
name         : aes
driver       : aes-aesni          # 드라이버명 접미사: -aesni(HW가속)/-avx2/-ssse3/-generic
module       : aesni_intel
priority     : 400                # 높을수록 선호 (하드웨어 가속 구현 > 소프트웨어)
refcnt       : 3                  # 0=미사용, 양수=현재 활성 (언로드 불가)
selftest     : passed
internal     : no                 # yes=래퍼 알고리즘 빌딩블록 (직접 사용 불가)
type         : cipher
blocksize    : 16
min keysize  : 16
max keysize  : 32

# AEAD 인증 암호화 (aead) — AES-GCM 예시
name         : gcm(aes)
driver       : generic-gcm-aesni
module       : gcm
priority     : 350
type         : aead               # Authenticated Encryption with Associated Data
async        : yes
blocksize    : 1
ivsize       : 12                 # 초기화 벡터 크기 (GCM: 12 bytes = 96비트)
maxauthsize  : 16                 # 최대 인증 태그 크기 (16=128비트)
geniv        : 

# 동기 해시 (shash)
name         : sha256
driver       : sha256-avx2
module       : sha256_ssse3
priority     : 170
type         : shash
blocksize    : 64
digestsize   : 32

# 비동기 해시 (ahash) — 하드웨어 가속
name         : sha256
driver       : sha256-ce           # ARM Crypto Extensions
module       : sha2-ce
priority     : 300
type         : ahash
async        : yes
blocksize    : 64
digestsize   : 32

# 키 합의 (kpp)
name         : ecdh
driver       : ecdh-generic
type         : kpp

# 난수 생성기 (rng)
name         : stdrng
driver       : drbg_nopr_hmac_sha256
type         : rng

# SIMD 최적화 정보
walksize     : 64                 # SIMD 일괄 처리 크기 (bytes). 클수록 스루풋 향상
필드설명
typecipher=블록 암호, skcipher=스트림 암호, aead=인증 암호화(GCM/CCM), shash=동기 해시, ahash=비동기 해시, kpp=키 합의(DH/ECDH), rng=난수 생성, akcipher=비대칭 암호(RSA), sig=전자서명, kdf=키 파생 함수
priority동일 name의 복수 드라이버 중 우선순위 결정. 하드웨어 가속 구현이 높은 값 (일반적으로 300~400, 소프트웨어 100~200)
refcnt현재 사용 중인 세션 수. 0이면 미사용, 양수이면 언로드 불가. refcnt > 0 필터링으로 현재 활성 알고리즘 확인 가능
internal: yes이 알고리즘은 래퍼 알고리즘의 빌딩블록으로만 사용. 직접 crypto_alloc_* 호출 불가
ivsize초기화 벡터 크기 (AEAD/skcipher). AES-GCM=12, AES-CBC=16
maxauthsizeAEAD 인증 태그 최대 크기 (bytes). AES-GCM=16(128비트), AES-CCM=16
walksizeSIMD 일괄 처리 최적화 크기. 클수록 한 번에 더 많은 데이터를 병렬 처리
driver 접미사-aesni=Intel AES-NI, -avx2=AVX2 SIMD, -ssse3=SSSE3, -ce=ARM Crypto Ext, -generic=소프트웨어 폴백
selftestpassed면 알고리즘 정상. failed면 해당 구현 사용 불가 (다음 우선순위 폴백)
💡

하드웨어 가속 확인 및 현재 활성 알고리즘: grep -B1 "refcnt.*[^0]$" /proc/crypto | grep name으로 현재 활성(refcnt > 0) 알고리즘 확인. AES 구현 확인: grep -A8 "^name.*: aes$" /proc/crypto | grep driver. aesni 포함 시 Intel AES-NI 하드웨어 가속 활성. cryptsetup benchmark로 실제 암호화 성능 비교. 특정 알고리즘 강제 지정: cryptsetup luksFormat --cipher aes-xts-plain64 --hash sha256 ...

/proc/devices — 등록된 디바이스 번호

/proc/devices는 커널에 등록된 모든 Character 디바이스와 Block 디바이스의 major 번호와 이름을 나열합니다. /dev 디렉터리의 디바이스 파일과 커널 드라이버를 연결하는 매핑 테이블입니다. udev가 이 정보를 참조하여 /dev에 자동으로 디바이스 노드를 생성합니다.

Character devices:
  1 mem          # /dev/mem, /dev/null, /dev/zero, /dev/full, /dev/random, /dev/urandom
  4 /dev/vc/0    # 가상 콘솔 (첫 번째)
  4 tty          # TTY 디바이스
  5 /dev/tty     # 현재 프로세스의 제어 터미널
  5 /dev/console # 시스템 콘솔
  5 /dev/ptmx    # 가상 터미널 마스터 (pty 쌍 생성)
  7 vcs          # 가상 콘솔 화면 덤프
 10 misc         # misc 드라이버 (minor로 구분: 229=fuse, 200=tun, 58=autofs)
 13 input        # 입력 디바이스 (마우스, 키보드, 조이스틱)
 21 sg           # SCSI 제네릭 디바이스
 29 fb           # 프레임버퍼 (/dev/fb0)
 89 i2c          # I2C 버스 어댑터
 90 mtd          # MTD (NAND/NOR 플래시)
116 snd          # ALSA 사운드 디바이스
136 pts          # 가상 터미널 슬레이브 (ssh, xterm 등)
166 ttyACM       # USB ACM 시리얼 (모뎀, Arduino)
188 ttyUSB       # USB-to-Serial 어댑터 (CP210x, FTDI)
189 usb          # USB 디바이스 raw 접근
226 drm          # DRM GPU 직접 렌더링 (/dev/dri/card0)
# 동적 할당 영역: 234~254 (커널이 런타임에 free major 자동 배정)
234 ttyXR        # (예시) 동적 할당 드라이버
235 (empty)      # 미사용

Block devices:
  7 loop         # 루프백 디바이스 (/dev/loop0~7)
  8 sd           # SCSI/SATA/SAS 디스크 (최대 16 minor/disk)
 11 sr           # SCSI/ATAPI CD-ROM
 65 sd           # SCSI 추가 major (sd[q-af], 디스크 수 초과 시)
179 mmc          # MMC/SD 카드 (/dev/mmcblk0)
252 device-mapper # LVM 논리 볼륨, dm-crypt
253 virtblk      # virtio 블록 디바이스 (KVM 게스트)
259 blkext       # NVMe, nvme0n1 (확장 블록 major)
항목설명
major 번호디바이스 드라이버 식별자. ls -l /dev의 첫 번째 숫자와 일치
Character devices바이트 단위 스트림 디바이스 (TTY, 사운드, 입력 장치 등)
Block devices블록 단위 랜덤 접근 디바이스 (디스크, 루프, LVM 등)
misc (major 10)단일 major를 공유하는 소규모 드라이버들. minor 번호로 구분: tun(200), fuse(229), autofs(235), watchdog(130) 등
동적 할당 (234~254)드라이버 로드 시 커널이 free major를 자동 배정. alloc_chrdev_region() 호출로 런타임 할당
blkext (259)블록 디바이스 major 번호 부족 해결을 위한 확장 major. NVMe는 여기에 배치

misc(major 10) 주요 minor 목록:

minor디바이스설명
58/dev/autofsautofs 파일시스템 제어
130/dev/watchdog0하드웨어 워치독 타이머
183/dev/hw_random하드웨어 난수 생성기 (RDRAND 등)
200/dev/tunTUN/TAP 가상 네트워크 인터페이스
229/dev/fuseFUSE 사용자 공간 파일시스템
236/dev/ecryptfseCryptfs 암호화 파일시스템
💡

디바이스 드라이버 매핑 확인: ls -l /dev/sda에서 8, 0이 보이면 major=8(sd), minor=0(첫 번째 디스크). grep " 8$" /proc/devices로 드라이버 이름 확인. 커스텀 드라이버 등록: 특정 번호 예약 시 register_chrdev_region(MKDEV(major, 0), count, "name"), 자동 할당 시 alloc_chrdev_region(). 등록 후 이 파일에서 확인 가능합니다.

/proc/diskstats — 디스크 I/O 통계

/proc/diskstats는 각 블록 디바이스의 누적 I/O 통계를 제공합니다. iostat, iotop, dstat 등 디스크 성능 모니터링 도구의 데이터 소스입니다. 전체 디스크와 파티션이 각각 별도의 행으로 나타납니다. 커널 4.18에서 열 15~18(discard 통계), 커널 5.5에서 열 19~20(flush 통계)이 추가되었습니다.

#열:  1   2    3        4     5       6      7     8     9      10      11    12    13     14    15   16    17    18   19   20
  8   0   sda  12345  678  9876543  56789 23456 789 8765432 123456   0  45678 180234  5678  123 234567  4321  890  123
  8   1   sda1   234    0    18720    456    12   0     192    234   0    345    690     0    0      0     0    0    0
259   0 nvme0n1 98765  432  7654321  34567 43210 654 6543210  56789   0  23456  91356  3456   87 456789  2345  567   89
# 섹터 크기 주의: 항상 512 bytes (물리 4KB 섹터와 무관한 논리 단위)
# 파티션(sda1): ios_in_progress(열12)는 항상 0 (파티션에서 미집계)
열 번호필드명단위/설명
1major디바이스 major 번호
2minor디바이스 minor 번호
3name디바이스 이름
4reads_completed성공한 읽기 요청 수 (누적)
5reads_mergedI/O 스케줄러가 병합한 읽기 요청 수
6sectors_read읽은 섹터 수 (×512B = bytes. 물리 4KB 섹터와 무관)
7ms_spent_reading읽기에 소비한 총 시간 (ms, 누적)
8writes_completed성공한 쓰기 요청 수 (누적)
9writes_mergedI/O 스케줄러가 병합한 쓰기 요청 수
10sectors_written쓴 섹터 수 (×512B = bytes)
11ms_spent_writing쓰기에 소비한 총 시간 (ms, 누적)
12ios_in_progress현재 진행 중인 I/O 수 (큐 깊이 순간값). 파티션은 항상 0
13ms_spent_doing_ioI/O 수행에 소비한 실제 시간 (ms). util% = 이 값/경과시간×100
14weighted_ms_io가중 I/O 시간. 평균 큐 깊이 = weighted_ms_io_delta / ms_elapsed
15 (4.18+)discards_completed성공한 DISCARD/TRIM 요청 수 (SSD 블록 삭제)
16 (4.18+)discards_merged병합된 DISCARD 요청 수
17 (4.18+)sectors_discardedDISCARD된 섹터 수 (×512B)
18 (4.18+)ms_spent_discardingDISCARD에 소비한 총 시간 (ms)
19 (5.5+)flush_requests_completed완료된 flush 요청 수 (fsync, fdatasync 등)
20 (5.5+)ms_spent_flushingflush에 소비한 총 시간 (ms)
#!/bin/bash
# 2-샘플 차분으로 IOPS/Bandwidth/await 계산
DISK=${1:-sda}
get_stats() { awk -v d="$DISK" '$3==d {print $4,$6,$7,$8,$10,$11,$13}' /proc/diskstats; }
read r1 rs1 rms1 w1 ws1 wms1 io1 <<< $(get_stats); sleep 1
read r2 rs2 rms2 w2 ws2 wms2 io2 <<< $(get_stats)
dr=$((r2-r1)); dw=$((w2-w1))
echo "Read  IOPS : ${dr}/s,  BW: $(( (rs2-rs1)*512/1024 )) KB/s"
echo "Write IOPS : ${dw}/s,  BW: $(( (ws2-ws1)*512/1024 )) KB/s"
total=$((dr+dw))
[ $total -gt 0 ] && echo "await : $(( (rms2-rms1+wms2-wms1)/total )) ms"
echo "util% : $(( (io2-io1)/10 ))%   (ms_io delta / 1000ms)"
💡

iostat 계산 원리: 두 시점 간 차분으로 초당 값을 계산합니다. await(ms) = (ms_read_delta + ms_write_delta) / (reads_delta + writes_delta). util% = ms_io_delta / interval_ms × 100. util%가 100%에 근접하면 디스크 포화 상태. reads_merged / reads_completed 비율이 높으면 I/O 스케줄러가 효과적으로 요청을 병합하고 있음을 의미합니다. 섹터 크기는 물리 섹터(4KB)와 무관하게 항상 논리 512B 단위입니다.

/proc/interrupts — 인터럽트 통계

/proc/interrupts는 각 IRQ 라인별로 CPU별 인터럽트 발생 횟수와 인터럽트 유형, 디바이스 이름을 보여줍니다. 인터럽트 불균형 탐지, 공유 인터럽트 확인, 네트워크/디스크 지연 원인 분석에 필수적인 도구입니다.

           CPU0       CPU1       CPU2       CPU3
  0:     156234          0          0          0  IO-APIC    2-edge      timer
  1:          0          0        987          0  IO-APIC    1-edge      i8042
  8:          0          0          0          1  IO-APIC    8-edge      rtc0
 16:          0          0      12345          0  IO-APIC   16-fasteoi   uhci_hcd:usb3
 24:      98765      87654      76543      65432  PCI-MSI 327680-edge    xhci_hcd
 25:    2345678          0          0          0  PCI-MSI 524288-edge    ahci[0000:00:17.0]
 26:          0          0          0    5678901  PCI-MSI 278528-edge    enp3s0
# 가상(소프트웨어) IRQ — 각 CPU별 독립 카운터
NMI:       1234       1234       1234       1234  Non-maskable interrupts
LOC:   98765432   87654321   76543210   65432109  Local timer interrupts
SPU:          0          0          0          0  Spurious interrupts
PMI:       5678       5679       5680       5681  Performance monitoring interrupts
IWI:        123        234        345        456  IRQ work interrupts
RTR:          0          0          0          0  APIC ICR read retries
RES:     123456     234567     345678     456789  Rescheduling interrupts
CAL:       4567       5678       6789       7890  Function call interrupts
TLB:      23456      34567      45678      56789  TLB shootdowns
TRM:          0          0          0          0  Thermal event interrupts
THR:          0          0          0          0  Threshold APIC interrupts
DFR:          0          0          0          0  Deferred Error APIC interrupts
MCE:          0          0          0          0  Machine check exceptions
MCP:         12         12         12         12  Machine check polls
HYP:          0          0          0          0  Hypervisor callback interrupts
ERR:          0   # 0이 아니면 IRQ 오류 (공유 인터럽트 충돌 등)
MIS:          0   # 0이 아니면 spurious IRQ (하드웨어 문제 의심)
필드설명
IRQ 번호 (숫자)하드웨어 IRQ 라인. CPU별 처리 횟수 합산 = 총 발생 횟수
IO-APIC N-edge엣지 트리거: 신호 상승/하강 시 1회 인터럽트. 고속 디바이스에 적합
IO-APIC N-fasteoi레벨 트리거 최적화: 인터럽트 핸들러 종료 후 자동 EOI. 공유 인터럽트에 사용
PCI-MSI N-edgeMSI/MSI-X: 메모리 쓰기로 인터럽트 전달. 공유 없음, IRQ 폭풍 방지
LOC로컬 타이머 인터럽트. 각 CPU 독립 (스케줄러 틱, CONFIG_NO_HZ 활성 시 간헐적)
NMI마스크 불가 인터럽트. 하드웨어 오류, watchdog, kdump 트리거
PMI성능 카운터 오버플로 인터럽트. perf, oprofile이 사용
RESReschedule IPI. CPU 간 스케줄러 요청 (다른 CPU의 런큐에 태스크 추가 시)
CALFunction call IPI (smp_call_function). 다른 CPU에서 함수 실행 요청
TLBTLB 플러시 IPI. 페이지 테이블 변경 후 다른 CPU에 전파 (TLB shootdown)
TRM열 이벤트 인터럽트. CPU 온도 임계값 초과 시 발생
MCE/MCPMachine Check Exception/Poll. 하드웨어 오류 감지
ERRIRQ 핸들러 오류 횟수. 0이 아니면 드라이버 또는 공유 IRQ 문제
MIS처리되지 않은 Spurious 인터럽트. 0이 아니면 하드웨어(IRQ 배선) 문제 의심

smp_affinity 설정: /proc/irq/N/smp_affinity는 16진수 비트마스크로 IRQ를 처리할 CPU를 지정합니다.

# IRQ 26번(NIC)을 CPU2,3(비트마스크 0xc=1100)에 고정
echo c > /proc/irq/26/smp_affinity

# IRQ 25번(SATA)을 CPU0 전용(0x1)으로 설정
echo 1 > /proc/irq/25/smp_affinity

# 현재 affinity 확인 (smp_affinity_list: 사람이 읽기 쉬운 형식)
cat /proc/irq/26/smp_affinity_list   # → 2-3

# 모든 IRQ affinity 확인
for irq in /proc/irq/[0-9]*/smp_affinity_list; do
  echo "IRQ $(dirname $irq | xargs basename): $(cat $irq)"
done
💡

인터럽트 불균형 탐지: watch -n1 "cat /proc/interrupts | grep enp"로 NIC 인터럽트 분산 모니터링. 특정 CPU에만 집중되면 irqbalance 설치 또는 /proc/irq/N/smp_affinity로 수동 분산. TLB 값이 급증하면 메모리 압박(mmap/munmap 빈번)을 의심하세요. RES 급증은 과도한 컨텍스트 스위치의 신호. MISERR가 0이 아니면 즉시 하드웨어 점검이 필요합니다.

/proc/iomem — 물리 메모리 주소 공간 맵

/proc/iomem은 시스템의 물리 주소 공간을 구역별로 표시합니다. RAM, ROM, MMIO(메모리 맵 I/O), PCI 디바이스 레지스터, ACPI 테이블 등이 어느 물리 주소에 매핑되어 있는지 계층적 구조로 보여줍니다. 들여쓰기는 서브 영역을 나타내며, 드라이버 개발이나 하드웨어 문제 진단 시 필수 참조 자료입니다.

00000000-00000fff : Reserved
00001000-0009f7ff : System RAM
0009f800-0009ffff : Reserved
000a0000-000bffff : PCI Bus 0000:00
  000a0000-000bffff : Video RAM area   # 레거시 VGA 프레임버퍼 (640~768KB)
000c0000-000c7fff : Video ROM
000f0000-000fffff : System ROM         # BIOS/UEFI 레거시 ROM
00100000-bffdffff : System RAM         # 메인 DRAM 영역
  01000000-01c531d9 : Kernel code      # 커널 텍스트 섹션
  01c531da-02047fff : Kernel rodata    # 커널 읽기 전용 데이터
  02048000-025437ff : Kernel data      # 커널 읽기/쓰기 데이터
  025d7000-028bcfff : Kernel bss       # 커널 미초기화 데이터
  09000000-09ffffff : Crash kernel     # kdump 전용 예약 영역 (crashkernel= 파라미터)
bffe0000-bfffffff : Reserved
bf800000-bfffffff : ACPI Tables        # ACPI 테이블 (읽기 가능)
  bf900000-bf9fffff : ACPI NVS         # 절전 상태 유지 데이터 (OS 접근 불가)
  bfa00000-bfafffff : ACPI Reclaim     # 부팅 후 OS가 재사용 가능
c0000000-febfffff : PCI Bus 0000:00    # PCIe MMCONFIG 영역 시작
  d0000000-dfffffff : PCI MMCONFIG 0000 [bus 00-ff]  # PCIe 확장 구성 공간
  fd000000-fdffffff : 0000:00:02.0     # GPU MMIO (PCI 주소:버스:장치.기능)
    fd000000-fdffffff : i915 drm       # → 들여쓰기: 드라이버가 클레임한 서브 영역
  fe000000-fe003fff : 0000:00:1f.3     # HDA 사운드 MMIO
    fe000000-fe003fff : ICH HD audio
fee00000-fee00fff : Local APIC         # 각 CPU 로컬 인터럽트 컨트롤러
fec00000-fec00fff : IOAPIC 0           # I/O APIC (IRQ 라우팅)
fed00000-fed003ff : HPET 0             # 고정밀 이벤트 타이머
fedc0000-fedc0fff : PNPACPI[01]        # ACPI PNP 장치
# EFI Runtime Services — 런타임 서비스 메모리 (OS 종료 후에도 유지)
7fe00000-7fefffff : EFI Runtime Services Code
7ff00000-7fffffff : EFI Runtime Services Data
100000000-43fffffff : System RAM       # DRAM 4GB 이상 (high memory, 64비트 전용)
영역 유형설명
System RAM물리 DRAM 영역. 커널과 사용자 프로세스가 사용. 비연속 구간은 메모리 홀
Kernel code/data/bss커널 자체가 차지하는 물리 메모리 위치 (System RAM의 서브 영역)
Crash kernelkdump 전용 예약 영역. crashkernel=256M 커널 파라미터로 크기 지정. 패닉 시 보조 커널이 이 영역에서 실행
ACPI TablesACPI 펌웨어 테이블 (MADT, DSDT 등). 부팅 후 읽기 가능
ACPI NVS절전(S3/S4) 상태 유지 데이터. OS는 접근 불가, 펌웨어 전용
EFI Runtime ServicesUEFI 런타임 서비스 코드/데이터. 부팅 후에도 OS가 UEFI 함수 호출용으로 보존
PCI MMCONFIGPCIe 확장 구성 공간(256바이트→4KB). lspci -v의 BAR 주소와 매핑
Local APIC각 CPU의 로컬 인터럽트 컨트롤러 레지스터 (fee00000 고정)
IOAPICI/O 인터럽트 컨트롤러 (fec00000 고정). IRQ를 CPU로 라우팅
HPET고정밀 이벤트 타이머. 최소 100ns 해상도 (PIT 대체)
들여쓰기 = 서브 영역상위 영역의 일부를 드라이버나 펌웨어가 클레임. 중첩 가능
💡

활용: 특정 PCIe 디바이스의 MMIO 주소 확인: grep "0000:01:00" /proc/iomem. RAM 총량 계산: grep "System RAM" /proc/iomem | awk -F'[-:]' '{sum+=strtonum("0x"$2)-strtonum("0x"$1)+1} END{printf "%.1f GB\n", sum/1024/1024/1024}'. kdump 설정 확인: grep "Crash kernel" /proc/iomem — 비어 있으면 crashkernel= 파라미터 추가 필요. /dev/mem으로 MMIO 영역 직접 접근 시 반드시 이 파일로 주소 범위를 먼저 확인하세요.

/proc/ioports — x86 I/O 포트 맵

/proc/ioports는 x86 아키텍처에서 사용되는 I/O 포트 주소 공간을 영역별로 표시합니다. I/O 포트는 메모리 주소와 별개인 64KB(0x0000~0xFFFF) 주소 공간으로, in/out 명령어로 접근합니다. 0x000~0x3FF 영역은 전통적인 ISA 레거시 포트 영역입니다. 현대 PCIe 디바이스는 MMIO를 선호하지만, 레거시 디바이스(키보드, 시리얼, IDE 등)는 여전히 I/O 포트를 사용합니다.

0000-0000 : PCI Bus 0000:00
# ── ISA 레거시 영역 (0x000-0x3FF) ──────────────────────────────
0000-001f : dma1            # 8237A DMA 컨트롤러 1 (8비트 채널 0~3)
0020-0021 : pic1            # 8259A 마스터 PIC (IRQ 0~7)
0040-0043 : timer0          # 8254 PIT (1.193182 MHz 기준 클럭)
0060-0060 : keyboard        # PS/2 키보드 데이터 포트 (i8042)
0064-0064 : keyboard        # PS/2 키보드 상태/명령 포트
0070-0077 : rtc0            # MC146818 CMOS RTC + NMI 마스크 (0x70 bit7)
0080-008f : dma page reg    # DMA 페이지 레지스터 (상위 주소 비트)
00a0-00a1 : pic2            # 8259A 슬레이브 PIC (IRQ 8~15)
00c0-00df : dma2            # 8237A DMA 컨트롤러 2 (16비트 채널 4~7)
00f0-00ff : fpu             # x87 FPU 오류 상태 (레거시)
# IDE/ATA 포트
01f0-01f7 : ide0            # Primary IDE (ISA/ATA)
0170-0177 : ide1            # Secondary IDE
03f6-03f6 : ide0            # Primary IDE 제어 포트
0376-0376 : ide1            # Secondary IDE 제어 포트
# 시리얼 포트 (UART 16550)
03f8-03ff : serial          # COM1 (IRQ 4)
02f8-02ff : serial          # COM2 (IRQ 3)
03e8-03ef : serial          # COM3 (IRQ 4, 공유)
02e8-02ef : serial          # COM4 (IRQ 3, 공유)
# 병렬 포트 (LPT)
0378-037f : parport0        # LPT1 (IRQ 7)
0278-027f : parport1        # LPT2 (IRQ 5)
# 레거시 사운드 (ISA SoundBlaster)
# 0220-022f : sb_audio      # SoundBlaster 기본 포트
# 0388-038b : opl3          # OPL2/OPL3 FM 합성기 (AdLib 호환)
# PCI 구성 공간 접근
0cf8-0cff : PCI conf1       # CONFIG_ADDRESS(0xCF8)+CONFIG_DATA(0xCFC)
# PCI/PCIe 동적 할당 영역
0d00-ffff : PCI Bus 0000:00
  e000-e03f : 0000:00:1f.4  # SMBus 컨트롤러 I/O 포트
  e060-e07f : 0000:00:1f.0  # LPC 컨트롤러
포트 범위디바이스설명
0x000-0x01Fdma1레거시 ISA DMA 컨트롤러 1 (8비트, 채널 0~3)
0x020-0x021pic18259A 마스터 PIC. IRQ 0~7 처리. APIC 시스템에서도 호환성 유지
0x040-0x043timer08254 PIT (Programmable Interval Timer). 1.193182MHz 기준 클럭
0x060, 0x064keyboardi8042 PS/2 컨트롤러. 0x60=데이터, 0x64=상태/명령
0x070-0x077rtc0CMOS RTC. 0x70=인덱스(bit7=NMI마스크), 0x71=데이터
0x1F0-0x1F7ide0Primary IDE/ATA. 현대 시스템에서도 레거시 호환성으로 존재
0x170-0x177ide1Secondary IDE/ATA
0x3F8-0x3FFCOM1첫 번째 UART 시리얼 포트 (IRQ 4, 115200 baud 기본)
0x2F8-0x2FFCOM2두 번째 UART 시리얼 포트 (IRQ 3)
0x378-0x37FLPT1첫 번째 병렬 포트 (프린터, IRQ 7)
0x278-0x27FLPT2두 번째 병렬 포트 (IRQ 5)
0x388-0x38BOPL3Yamaha OPL FM 합성기 (레거시 사운드카드)
0xCF8-0xCFFPCI conf1PCI 구성 공간 접근. 0xCF8=CONFIG_ADDRESS, 0xCFC=CONFIG_DATA
0xD00-0xFFFFPCI 동적PCIe 디바이스 I/O BAR 동적 할당 영역
💡

활용: 레거시 디바이스 포트 충돌 확인 시 cat /proc/ioports. ARM, RISC-V 등 비 x86 아키텍처에서는 이 파일이 비어 있거나 존재하지 않습니다 (MMIO만 사용). 커널 드라이버에서 I/O 포트 요청: request_region(port, size, "device_name") — 등록된 영역이 이 파일에 표시됩니다. I/O 포트 직접 접근(사용자 공간): ioperm() 또는 iopl() 시스템 콜 필요 (root 권한).

/proc/slabinfo — Slab 캐시 상태

/proc/slabinfo는 커널 Slab 할당자가 관리하는 모든 오브젝트 캐시의 상세 통계를 제공합니다. 커널은 자주 할당/해제되는 구조체(태스크, 파일, inode 등)를 사전 할당된 Slab에서 관리하여 메모리 단편화와 할당 지연을 줄입니다. 현대 커널(4.x+)은 기본적으로 SLUB 할당자를 사용합니다. 커널 메모리 누수 탐지와 메모리 사용량 분석에 필수 도구입니다. root만 읽기 가능합니다.

slabinfo - version: 2.1
# name         <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab>
#            : tunables <limit> <batchcount> <sharedfactor>
#            : slabdata <active_slabs> <num_slabs> <sharedavail>
task_struct        1234    1280    7168    4    8 : tunables    0    0    0 : slabdata    320    320      0
files_cache         876     896     832   19    4 : tunables    0    0    0 : slabdata     47     47      0
dentry            45678   46080     192   21    1 : tunables    0    0    0 : slabdata   2194   2194      0
inode_cache        8765    9024     648   25    4 : tunables    0    0    0 : slabdata    361    361      0
ext4_inode_cache   3456    3465    1128   29    8 : tunables    0    0    0 : slabdata    119    119      0
# kmalloc 계열: 범용 커널 메모리 할당 (kmalloc() 호출 시 크기에 따라 선택)
kmalloc-8             0      0       8  512    1 : tunables    0    0    0 : slabdata      0      0      0
kmalloc-16          234    256      16  256    1 : tunables    0    0    0 : slabdata      1      1      0
kmalloc-32         1234   1280      32  128    1 : tunables    0    0    0 : slabdata     10     10      0
kmalloc-64        98765  102400     64   64    1 : tunables    0    0    0 : slabdata   1600   1600      0
kmalloc-128       23456   23552    128   32    1 : tunables    0    0    0 : slabdata    736    736      0
kmalloc-256       12345   13056    256   16    1 : tunables    0    0    0 : slabdata    816    816      0
kmalloc-512        5678    5632    512    8    1 : tunables    0    0    0 : slabdata    704    704      0
kmalloc-1k         2345    2352   1024    8    2 : tunables    0    0    0 : slabdata    294    294      0
kmalloc-2k          456     456   2048    4    2 : tunables    0    0    0 : slabdata    114    114      0
kmalloc-4k          123     128   4096    4    4 : tunables    0    0    0 : slabdata     32     32      0
# SLUB에서 tunables 값은 항상 0 (SLAB에서만 의미 있음)
컬럼설명
nameSlab 캐시 이름. 구조체명 또는 kmalloc-N(N=크기)
active_objs현재 할당되어 사용 중인 오브젝트 수
num_objsSlab에 준비된 총 오브젝트 수 (active + free)
objsize오브젝트 하나의 크기 (bytes). 정렬 패딩 포함
objperslabSlab 하나에 들어가는 오브젝트 수
pagesperslabSlab 하나가 차지하는 페이지 수 (= 2^order)
tunables limitSLAB: CPU당 최대 캐시 오브젝트 수. SLUB: 항상 0
tunables batchcountSLAB: 한 번에 이동하는 오브젝트 수. SLUB: 항상 0
tunables sharedfactorSLAB: CPU 간 공유 캐시 배율. SLUB: 항상 0
slabdata active_slabs오브젝트가 할당된 Slab 수 (partial + full)
slabdata num_slabs총 Slab 수. active_slabs - num_slabs = 완전 빈 Slab 수
slabdata sharedavailSLAB: 공유 캐시에서 사용 가능한 오브젝트. SLUB: 항상 0

SLUB vs SLAB: 현대 커널은 기본으로 SLUB(CONFIG_SLUB) 사용. SLAB은 CONFIG_SLAB으로 선택 가능 (레거시). SLUB은 tunables 미지원 대신 /sys/kernel/slab/에서 per-캐시 세부 통계를 제공합니다.

# Slab 메모리 사용량 상위 20개 확인 (objsize × num_objs = 캐시 총 메모리)
awk 'NR>2 {print $1, $3*$4/1024"KB"}' /proc/slabinfo | sort -t' ' -k2 -rn | head -20

# 누수 의심 캐시 모니터링: 5초 간격으로 num_objs 증가 추적
watch -d -n5 "awk 'NR>2 {print \$1, \$3}' /proc/slabinfo | sort -k2 -rn | head -20"

# reclaimable slab 즉시 회수 (dentry/inode 캐시)
echo 2 > /proc/sys/vm/drop_caches

# SLUB per-캐시 세부 통계 경로 (root 필요)
ls /sys/kernel/slab/task_struct/
# alloc_calls, free_calls, partial, cpu_slabs, objects 등
💡

메모리 누수 탐지: watch -d -n5 "cat /proc/slabinfo | sort -k3 -rn | head -20"로 상위 Slab 모니터링. num_objs가 지속적으로 증가하고 감소하지 않는 Slab이 있다면 해당 커널 구조체의 누수를 의심하세요. slabtop -s c가 더 편리한 인터페이스를 제공합니다. /sys/kernel/slab/캐시명/에서 할당 호출 스택(alloc_calls)을 확인하면 누수 위치를 추적할 수 있습니다.

/proc/vmstat — 가상 메모리 세부 통계

/proc/vmstat은 커널 가상 메모리 서브시스템의 수백 가지 카운터를 제공합니다. 페이지 폴트, 스왑 I/O, 메모리 스캔/회수, 페이지 압축, NUMA 균형 조정 등의 활동을 상세히 추적합니다. vmstat 명령어 및 sar -B가 이 파일을 파싱합니다.

# ── 페이지 수 (현재 상태, 누적 아님) ──────────────────────────
nr_free_pages 524288         # 현재 free 페이지 수 (MemFree/4KB)
nr_zone_inactive_anon 12800  # inactive LRU 익명 페이지 (스왑 대상)
nr_zone_active_anon 104960   # active LRU 익명 페이지
nr_zone_inactive_file 52480  # inactive LRU 파일 페이지 (드롭 대상)
nr_zone_active_file 52480    # active LRU 파일 페이지
nr_zone_unevictable 3200     # 회수 불가 페이지 (mlockall, 핀드 메모리)
nr_slab_reclaimable 204800   # 회수 가능 slab 페이지 수
nr_slab_unreclaimable 51200  # 회수 불가 slab 페이지 수
nr_writeback 0               # 현재 writeback 중인 더티 페이지 수
nr_page_table_pages 8192     # 페이지 테이블이 차지하는 페이지 수
# ── 이벤트 카운터 (부팅 이후 누적) ────────────────────────────
pgfault 98765432             # 총 페이지 폴트 (minor + major)
pgmajfault 1234              # Major 폴트 (디스크 I/O 발생)
pgfree 456789012             # 해제된 페이지 수
pgalloc_normal 432109876     # Normal 존 할당 성공 횟수
pgalloc_dma32 12345          # DMA32 존 할당 횟수
# ── 직접 회수 (pgreclaim_direct → 응용 프로그램 블로킹) ────────
allocstall_normal 0          # Normal 존 직접 회수 발동 횟수
allocstall_dma32 0           # DMA32 존 직접 회수 발동 횟수
# ── kswapd 회수 ───────────────────────────────────────────────
pgscan_kswapd 2345678        # kswapd가 스캔한 페이지 수
pgsteal_kswapd 1987654       # kswapd가 실제로 회수한 페이지 수
kswapd_low_wmark_hit_quickly 12  # kswapd가 low wmark 빠르게 복구한 횟수
kswapd_high_wmark_hit_quickly 8  # kswapd가 high wmark 빠르게 복구한 횟수
# ── 스왑 I/O ──────────────────────────────────────────────────
pswpin 0                     # 스왑에서 읽은 페이지 (swap in)
pswpout 0                    # 스왑으로 쓴 페이지 (swap out)
# ── Working Set 통계 ──────────────────────────────────────────
workingset_refault_anon 0    # anon 페이지 재폴트 (스왑 후 재접근)
workingset_refault_file 12345 # 파일 페이지 재폴트 (드롭 후 재접근)
workingset_activate_anon 0   # anon 페이지 active LRU로 승격
workingset_activate_file 3456 # 파일 페이지 active LRU로 승격
workingset_restore_anon 0    # anon 페이지 shadow entry로 복원
workingset_restore_file 890  # 파일 페이지 shadow entry로 복원
# ── OOM 및 직접 회수 ──────────────────────────────────────────
oom_kill 0                   # OOM 킬러 발동으로 종료된 프로세스 수
pgreclaim_direct 234         # 직접 회수(응용 블로킹) 발생 횟수
# ── 메모리 압축 ───────────────────────────────────────────────
compact_migrate_scanned 456  # 압축 시 스캔한 이동 가능 페이지
compact_free_scanned 789     # 압축 시 스캔한 free 페이지
compact_isolated 234         # 압축으로 격리된 페이지
# ── NUMA 균형 ─────────────────────────────────────────────────
numa_local 9876543           # 로컬 NUMA 노드 할당 성공
numa_miss 12345              # 원하는 NUMA 노드 할당 실패
numa_foreign 5678            # 다른 NUMA 노드에서 할당된 페이지
numa_interleave 0            # interleave 정책으로 할당된 페이지
# ── THP ───────────────────────────────────────────────────────
thp_fault_alloc 5678         # 페이지 폴트 시 THP 할당 성공
thp_fault_fallback 123       # THP 할당 실패 → 4KB 폴백
thp_collapse_alloc 234       # 기존 4KB 페이지 → THP 병합 성공
thp_split_page 45            # THP → 4KB 분할 횟수 (파편화 원인)
# ── VM 풍선 (가상화) ──────────────────────────────────────────
balloon_inflate 0            # 하이퍼바이저가 게스트 메모리 회수
balloon_deflate 0            # 하이퍼바이저가 게스트 메모리 반환
카운터 그룹주요 카운터이상 신호
페이지 폴트pgmajfault지속 증가 시 스왑/파일 읽기 과다 → I/O 병목
스왑 I/Opswpin / pswpout0이 아니면 메모리 부족. pswpout 증가 시 OOM 전조
직접 회수allocstall_normal / pgreclaim_direct증가 시 응용 프로그램이 메모리 할당에서 블로킹됨 (지연 급증)
OOMoom_kill0이 아니면 메모리 부족으로 프로세스 강제 종료 발생
페이지 회수pgscan_kswapd / pgsteal_kswapd스캔 대비 회수율(pgsteal/pgscan) 저하 시 메모리 압박 심각
Working Setworkingset_refault_file높으면 캐시가 부족하여 파일 페이지를 반복 재로드
writebacknr_writeback지속적으로 높으면 더티 페이지가 플러시 속도를 초과
메모리 압축compact_migrate_scanned과도하면 파편화 심각. CPU 오버헤드 유발
NUMA 균형numa_miss높으면 NUMA 불균형 → vm.zone_reclaim_mode 검토
THPthp_fault_fallback / thp_split_pagefallback 비율 높으면 파편화로 THP 할당 실패
VM 풍선balloon_inflate / deflate가상화 환경에서 하이퍼바이저의 메모리 압박 지표
💡

스왑 및 OOM 모니터링: watch -n1 "grep -E 'pswp|pgmajfault|oom_kill|allocstall' /proc/vmstat". pswpout이 증가하면 즉시 메모리 사용 분석 시작. oom_kill > 0이면 시스템 로그(dmesg | grep -i oom)로 대상 프로세스 확인. allocstall_normal 증가는 직접 회수로 인한 응용 지연의 직접 증거입니다. vmstat 1 10 명령으로 1초 간격 실시간 확인이 더 편리합니다.

/proc/vmallocinfo — vmalloc 영역 상세

/proc/vmallocinfo는 커널 vmalloc 영역의 각 할당 블록에 대한 상세 정보를 제공합니다. vmalloc은 물리적으로 불연속적인 메모리를 가상 주소 공간에서 연속적으로 보이게 매핑할 때 사용합니다. 커널 모듈, ioremap, vmalloc 직접 호출 등이 이 영역을 사용합니다. 블록 사이에는 가드 페이지(4KB)가 삽입되어 주소가 불연속적입니다. root 권한이 필요합니다.

# 형식: start-end  size  caller  [flags] [phys=...] [pages=N] [NX=M]
0xffffc90000000000-0xffffc90000009000   36864 alloc_large_system_hash+0x... phys=0x1234000 ioremap
0xffffc90001000000-0xffffc90001201000 2101248 __vmalloc_node_range+0x... pages=512 vmalloc N0=512
0xffffc90001201000-0xffffc90001400000 2093056 text_poke_bp_batch+0x...  pages=511 vmalloc
0xffffc90004000000-0xffffc90004021000  135168 ioremap+0x...             phys=0xfe000000 ioremap
0xffffc90005000000-0xffffc90005041000  266240 my_module_init [mymod]+0x0 pages=64 vmalloc N0=64
#   ↑ 가상 주소 범위           ↑ 크기   ↑ 호출자(함수+심볼) [모듈명]    ↑ 플래그/속성

# vm_map_ram: vmap_block 이용한 RAM 직접 매핑 (소규모, 단편화 방지)
0xffffc90006000000-0xffffc90006001000    4096 vmap_block_queue_drain+0x... vm_map_ram

# vmap_block: 소규모 vmap 블록 풀 (vmalloc 영역 단편화 감소)
0xffffc90007000000-0xffffc90007020000  131072 vb_alloc+0x...              vmap_block

# vpages: 물리 페이지 없는 가상 주소만 예약 (guard hole 등)
0xffffc90008000000-0xffffc90008001000    4096 guard_page+0x...             vpages

# 커널 모듈: module text/data는 vmalloc 영역에 배치
0xffffffffc0000000-0xffffffffc0040000  262144 load_module+0x...  pages=63 vmalloc N0=63
#   ↑ 모듈 전용 영역 (0xffffffffc0000000~)                         [CONFIG_MODULES_TREE_LOOKUP]

# user: userspace가 요청한 vmalloc 매핑 (mmap with MAP_POPULATE 등)
0xffffc9000a000000-0xffffc9000a010000   65536 vmap_pfn+0x...              user
필드/플래그설명
주소 범위가상 커널 주소 범위 (start-end). 블록 간 4KB 가드 페이지로 불연속
크기 (bytes)할당된 가상 메모리 크기 (가드 페이지 제외)
caller [모듈명]vmalloc을 호출한 커널 함수 + 오프셋. [모듈명] 포함 시 해당 모듈이 원인
phys=0x...MMIO 매핑 시 물리 주소 기반 (ioremap 계열만)
pages=N매핑된 물리 페이지 수 (×4KB = 실제 메모리 사용량)
vmalloc일반 vmalloc 할당. vmalloc(), vzalloc(), __vmalloc()
ioremapMMIO 영역 매핑. ioremap()(캐시 비활성), ioremap_wc()(write-combining)
vm_map_ramRAM 페이지를 가상 연속으로 매핑 (소규모, vmap() 내부 사용)
vmap_block소규모 vmap 블록 풀. 단편화 방지를 위한 SLUB처럼 동작
vpages물리 페이지 없는 가상 주소 예약. 가드 홀 또는 주소 예약용
user사용자 공간 요청으로 커널이 생성한 vmalloc 매핑
N0=M / N1=MNUMA 노드별 할당 페이지 수. 합산 = pages 값과 일치

가드 페이지: 각 vmalloc 블록 뒤에 매핑되지 않은 4KB 가드 페이지가 삽입됩니다. 버퍼 오버플로를 커널 패닉으로 즉시 탐지하는 메모리 보호 장치입니다. 이 때문에 vmalloc 블록 간 주소가 불연속하게 보입니다.

모듈 영역 패턴: 커널 모듈은 0xffffffffc0000000 이상의 모듈 전용 vmalloc 영역에 배치됩니다. text(실행 코드), data, bss 섹션이 각각 별도 vmalloc 항목으로 나타납니다.

💡

vmalloc 사용 모듈 식별: grep "\[모듈명\]" /proc/vmallocinfo로 특정 모듈의 vmalloc 사용량 확인. awk '{sum+=$2} END{printf "vmalloc total: %.1f MB\n", sum/1024/1024}' /proc/vmallocinfo로 총 vmalloc 사용량 계산. VmallocUsed(/proc/meminfo)와 비교하여 무결성 확인. /sys/kernel/slab/에서 특정 캐시의 vmalloc 사용량도 병행 확인하세요.

/proc/zoneinfo — 메모리 존 상세

/proc/zoneinfo는 각 NUMA 노드의 메모리 존(DMA, DMA32, Normal, Movable)별 상세 통계를 제공합니다. 워터마크(watermark), 실제 가용 페이지, 페이지 타입별 통계, LRU 리스트 크기 등을 포함합니다. OOM(Out Of Memory) 킬러 동작 분석과 메모리 압박 임계값 이해에 필수적인 파일입니다.

# ── NUMA 2-노드 예시 ────────────────────────────────────────────
Node 0, zone      DMA
  pages free      1234
        min          3
        low          3
        high         4
        spanned      4096
        present      3998
        managed      3977
        cma             0
  protection[]: 0, 3035, 15608, 15608, 15608   # 하위 존 보호 예약 배열

Node 0, zone   Normal
  pages free     524288       # 현재 즉시 할당 가능한 free 페이지
        min       12345       # min 워터마크 (Direct Reclaim 발동 임계값)
        low       15431       # low 워터마크 (kswapd 깨어남 임계값)
        high      18517       # high 워터마크 (kswapd 수면 복귀 임계값)
        spanned  4194304
        present  4100000
        managed  4050000      # buddy 할당자 관리 페이지 (=present-reserved-holes)
        cma           0       # CMA(연속 메모리 할당자) 예약 페이지
  nr_zone_inactive_anon 12800
  nr_zone_active_anon   104960
  nr_zone_inactive_file 52480
  nr_zone_active_file   52480
  nr_zone_unevictable   3200  # mlockall/공유 메모리 고정 페이지
  nr_zone_write_pending 1024  # dirty + writeback 페이지 합계
  nr_mlock              3200  # mlock/mlockall로 잠긴 페이지
  nr_page_table_pages    512
  nr_kernel_misc_reclaimable 0
  nr_bounce                0  # 32비트 DMA 바운스 버퍼 페이지
  nr_free_cma              0  # 미사용 CMA 예약 페이지
  lowmem_reserve[]: 0  0  0  0   # [DMA,DMA32,Normal,Movable] 하위존 예약량

Node 0, zone  Movable         # 핫플러그/메모리 오프라인 전용 존
  pages free      98765
        min        2345
        low        2931
        high       3517
        managed   524288      # 이동 가능 페이지만 배치

Node 1, zone   Normal         # 두 번째 NUMA 소켓
  pages free     389000
        min       11000
        low       13750
        high      16500
        managed  3800000
필드단위설명
pages free페이지현재 즉시 사용 가능한 free 페이지 수
min페이지최소 워터마크: 이 이하 → Direct Reclaim 발동. vm.min_free_kbytes 기반 계산
low페이지낮음 워터마크: 이 이하 → kswapd 깨어남. min × (1 + scale/10000)
high페이지높음 워터마크: kswapd가 이 이상 복구하면 수면. min × (1 + 2×scale/10000)
managed페이지buddy 할당자 관리 페이지 (= present - reserved - holes - CMA)
present페이지존에 실제로 존재하는 물리 메모리 페이지
spanned페이지존의 주소 범위 총 페이지 수 (메모리 홀 포함)
cma페이지CMA(Contiguous Memory Allocator) 예약 페이지. 카메라/GPU 드라이버용
protection[]페이지상위 존 할당 실패 시 하위 존 보호 예약량 배열. [DMA, DMA32, Normal, Movable] 순서
nr_zone_unevictable페이지mlockall() RT 프로세스나 공유 메모리 핀으로 고정된 회수 불가 페이지
nr_zone_write_pending페이지dirty + writeback 페이지 합산. 높으면 쓰기 부하 증가
nr_bounce페이지32비트 DMA 제한 장치의 바운스 버퍼 페이지
lowmem_reserve[]페이지상위 존(Normal) 할당 실패가 하위 존(DMA)을 고갈시키지 않도록 예약
zone Movable-핫플러그/메모리 오프라인 전용. 이동 가능 페이지만 배치되어 온라인/오프라인 시 페이지 이동 가능

워터마크 계산 공식: vm.min_free_kbytesmin_free_pages = min_free_kbytes / 4 (4KB 기준). 각 존의 워터마크 = min_free_pages × (zone_managed / total_managed) 비례 배분.

sysctl 파라미터기본값설명
vm.min_free_kbytes시스템 RAM 기반 자동min 워터마크 기준. 높이면 OOM 예방, 낮추면 사용 가능 메모리 증가
vm.watermark_scale_factor10low/high 워터마크 간격 비율. 10 = min의 0.1배. 높이면 kswapd 조기 활성으로 응답성 향상
vm.watermark_boost_factor15000메모리 압박 시 워터마크 일시 상승 배율(1/10000). 0=비활성
vm.zone_reclaim_mode0NUMA: 0=다른 노드 허용, 1=로컬 노드 우선 회수. 값이 높을수록 NUMA 로컬리티 강제
💡

OOM 분석: pages freemin 워터마크 이하로 내려가면 OOM 위험 상태입니다. vm.min_free_kbytes를 높이면 워터마크가 올라가 여유 공간을 더 확보하지만 실제 사용 가능 메모리가 감소합니다. 다중 NUMA 노드 환경에서 특정 노드의 pages freemin 이하이지만 다른 노드는 충분한 경우, vm.zone_reclaim_mode를 조정하세요. nr_zone_unevictable이 크면 mlockall()을 사용하는 RT 프로세스 메모리 확인이 필요합니다.

sysctl (/proc/sys/) 주요 경로

경로설명기본값주의사항
/proc/sys/kernel/ — 커널 전반
kernel.panic패닉 후 재부팅 대기 시간(초)00=재부팅 안 함, 양수=N초 후 재부팅
kernel.panic_on_oopsOops 시 패닉 여부0프로덕션: 1 권장 (kdump 수집)
kernel.sysrqSysRq 활성화 비트마스크161=전체 활성, 438=안전한 조합
kernel.printk콘솔 로그 레벨4 4 1 7current default min boot_default
kernel.pid_max최대 PID 값32768대규모 시스템: 4194304까지
kernel.threads-max최대 스레드 수시스템 의존메모리 기반 자동 계산
kernel.sched_*스케줄러 파라미터가변sched_min_granularity_ns, sched_latency_ns 등
/proc/sys/vm/ — 가상 메모리
vm.swappiness스왑 적극성600=스왑 최소, 100=적극적 스왑
vm.dirty_ratio동기 쓰기 임계값(%)20이 비율 초과 시 프로세스가 직접 writeback
vm.dirty_background_ratio비동기 쓰기 시작(%)10이 비율 초과 시 백그라운드 writeback 시작
vm.dirty_expire_centisecsdirty 페이지 만료 시간300030초 이상 dirty 상태면 writeback 대상
vm.min_free_kbytes최소 free 메모리시스템 의존워터마크 기준, 너무 높으면 OOM 위험 감소/메모리 낭비
vm.overcommit_memory메모리 오버커밋 정책00=추정, 1=항상허용, 2=불허
vm.overcommit_ratio오버커밋 비율(%)50mode=2 시 CommitLimit = Swap + RAM*ratio/100
vm.vfs_cache_pressureVFS 캐시 회수 압력100높을수록 inode/dentry 캐시 적극 회수
vm.nr_hugepagesHugepage 수0런타임 변경 가능하지만 단편화 시 실패
vm.zone_reclaim_modeNUMA 존 회수 모드01=로컬 회수, NUMA 바운드 워크로드에서만
/proc/sys/net/ — 네트워크
net.core.somaxconnlisten 백로그 최대4096고부하 서버: 65535
net.core.netdev_max_backlog수신 큐 최대1000고속 NIC: 10000+
net.core.rmem_max수신 버퍼 최대(바이트)212992고대역폭: 16MB+
net.core.wmem_max송신 버퍼 최대(바이트)212992고대역폭: 16MB+
net.ipv4.tcp_max_syn_backlogSYN 큐 크기512SYN flood 방어: 증가
net.ipv4.tcp_syncookiesSYN 쿠키1SYN flood 방어 (0=비활성)
net.ipv4.tcp_tw_reuseTIME_WAIT 소켓 재사용20=비활성, 1=활성, 2=loopback만
net.ipv4.ip_forwardIP 포워딩0라우터/컨테이너 호스트: 1
net.ipv4.conf.all.rp_filter역경로 필터링01=strict, 2=loose (비대칭 라우팅)
net.ipv4.tcp_keepalive_timekeepalive 시작 시간7200초 단위, 빠른 감지: 300
net.ipv4.tcp_fin_timeoutFIN-WAIT-2 타임아웃60많은 연결: 30
net.ipv4.neigh.default.gc_thresh3ARP 테이블 최대1024대규모 L2: 4096+
net.netfilter.nf_conntrack_maxconntrack 테이블 최대65536방화벽/NAT: 262144+
⚠️

sysctl 설정 시 주의사항:

  • 변경 사항은 재부팅 시 초기화됩니다. 영구 적용: /etc/sysctl.d/*.conf
  • 일부 파라미터는 네임스페이스별로 독립 (net.ipv4 등) — 컨테이너 환경 주의
  • vm.min_free_kbytes를 과도하게 높이면 사용 가능 메모리가 줄어 OOM 유발 가능
  • 네트워크 파라미터 변경 후 기존 연결에는 적용되지 않을 수 있음

/sys (sysfs) 세부 경로 심화

sysfs는 커널 오브젝트(kobject) 계층을 사용자 공간에 반영합니다. 디바이스, 드라이버, 버스, 클래스 등의 정보와 제어를 제공합니다.

sysfs 주요 디렉터리 구조

경로용도주요 내용
/sys/bus/버스 유형별 디바이스/드라이버pci/, usb/, i2c/, spi/, platform/, acpi/
/sys/class/디바이스 클래스별 분류net/, block/, tty/, input/, gpio/, hwmon/, thermal/
/sys/devices/물리적 디바이스 트리system/, pci0000:00/, platform/, virtual/
/sys/module/로드된 모듈 정보각 모듈의 parameters/, sections/
/sys/kernel/커널 전역 속성mm/, debug/, slab/, iommu_groups/
/sys/firmware/펌웨어 인터페이스acpi/, efi/, dmi/, devicetree/
/sys/fs/파일시스템 정보cgroup/, pstore/, ext4/, btrfs/
/sys/power/전원 관리state, mem_sleep, wakeup_count
# PCI 디바이스 정보 확인
ls /sys/bus/pci/devices/0000:01:00.0/
# vendor, device, class, irq, resource, driver, numa_node, ...

cat /sys/bus/pci/devices/0000:01:00.0/vendor  # 0x8086 (Intel)
cat /sys/bus/pci/devices/0000:01:00.0/device  # 디바이스 ID
cat /sys/bus/pci/devices/0000:01:00.0/numa_node  # NUMA 노드

# 네트워크 인터페이스 정보
ls /sys/class/net/eth0/
# address, carrier, duplex, mtu, operstate, speed, statistics/, ...

cat /sys/class/net/eth0/speed     # 링크 속도 (Mbps)
cat /sys/class/net/eth0/carrier   # 1=링크 업, 0=다운
cat /sys/class/net/eth0/mtu       # MTU (쓰기로 변경 가능)

# 블록 디바이스 정보
cat /sys/block/sda/queue/scheduler      # I/O 스케줄러
cat /sys/block/sda/queue/nr_requests    # 큐 깊이
cat /sys/block/sda/queue/rotational     # 0=SSD, 1=HDD
cat /sys/block/sda/queue/read_ahead_kb  # 읽기 선행 크기

# CPU 토폴로지
cat /sys/devices/system/cpu/cpu0/topology/core_id
cat /sys/devices/system/cpu/cpu0/topology/physical_package_id
cat /sys/devices/system/cpu/cpu0/cache/index0/size  # L1 캐시 크기

# NUMA 메모리 정보
cat /sys/devices/system/node/node0/meminfo

# 모듈 파라미터 확인/변경
cat /sys/module/my_module/parameters/debug_level
echo 3 > /sys/module/my_module/parameters/debug_level

# 전원 상태
cat /sys/power/state            # freeze mem disk
cat /sys/power/mem_sleep        # s2idle [deep]

sysfs에서 드라이버 속성 구현

/* 드라이버에서 sysfs 속성 노출 */
static ssize_t my_attr_show(struct device *dev,
                            struct device_attribute *attr,
                            char *buf)
{
    struct my_device *mydev = dev_get_drvdata(dev);
    return sysfs_emit(buf, "%d\\n", mydev->value);
    /* sysfs_emit: sprintf 대신 사용 (버퍼 오버플로 방지) */
}

static ssize_t my_attr_store(struct device *dev,
                             struct device_attribute *attr,
                             const char *buf, size_t count)
{
    struct my_device *mydev = dev_get_drvdata(dev);
    int ret = kstrtoint(buf, 10, &mydev->value);
    return ret ? ret : count;
}

static DEVICE_ATTR_RW(my_attr);
/* DEVICE_ATTR_RO(name): 읽기 전용 */
/* DEVICE_ATTR_WO(name): 쓰기 전용 */

/* 속성 그룹으로 등록 (권장) */
static struct attribute *my_attrs[] = {
    &dev_attr_my_attr.attr,
    NULL,
};
ATTRIBUTE_GROUPS(my);

/* 드라이버에서 사용 */
static struct platform_driver my_driver = {
    .driver = {
        .name = "my_device",
        .dev_groups = my_groups,  /* 자동 생성/제거 */
    },
};
☢️

sysfs 구현 주의사항:

  • sprintf() 대신 반드시 sysfs_emit() 사용 — PAGE_SIZE 초과 방지
  • 하나의 sysfs 파일에 하나의 값만 (one-value-per-file 규칙)
  • store에서 입력 검증 필수 — 사용자가 임의 값을 쓸 수 있음
  • kstrtoint(), kstrtoul() 등으로 안전한 파싱
  • sysfs는 ABI — 한번 노출된 경로는 삭제/변경 불가 (Documentation/ABI/)

/sys/kernel/debug (debugfs) 세부 경로 심화

debugfs는 개발/디버깅 전용 가상 파일시스템입니다. ABI 안정성이 보장되지 않으므로 프로덕션 도구가 의존해서는 안 됩니다.

debugfs 주요 경로

경로용도필요 CONFIG
debug/tracing/ftrace 트레이싱 인터페이스FTRACE
debug/tracing/trace트레이스 버퍼 출력FTRACE
debug/tracing/available_tracers사용 가능한 트레이서FTRACE
debug/tracing/events/이벤트 트레이싱 설정FTRACE
debug/kmemleak메모리 누수 탐지DEBUG_KMEMLEAK
debug/slab/SLUB 디버깅SLUB_DEBUG
debug/dma-buf/DMA 버퍼 현황DMA_SHARED_BUFFER
debug/ieee80211/Wi-Fi 디버깅MAC80211
debug/usb/USB 디버깅USB
debug/bdi/Block Device Info항상
debug/clk/클록 트리COMMON_CLK
debug/regmap/레지스터 맵 덤프REGMAP
debug/pinctrl/핀 제어 상태PINCTRL
debug/gpioGPIO 상태GPIOLIB
debug/suspend_statsPM 서스펜드 통계PM_SLEEP
debug/fault_around_bytesfault around 크기항상
debug/extfrag/외부 단편화 지수항상
debug/page_owner페이지 소유자 추적PAGE_OWNER
debug/rcu/RCU 상태RCU_*
debug/x86/x86 특화 디버깅x86 아키텍처
debug/iommu/IOMMU 디버깅IOMMU_*
# debugfs 마운트 확인/수동 마운트
mount | grep debugfs
mount -t debugfs none /sys/kernel/debug

# kmemleak으로 메모리 누수 탐지
echo scan > /sys/kernel/debug/kmemleak
cat /sys/kernel/debug/kmemleak
# unreferenced object 0xffff88810a3b4c00 (size 128):
#   comm "my_app", pid 1234, jiffies 4294967296
#   backtrace:
#     kmalloc_trace+0x28/0x40
#     my_alloc_func+0x15/0x30 [my_module]

# kmemleak 필터링
echo clear > /sys/kernel/debug/kmemleak          # 결과 초기화
echo "not scan" > /sys/kernel/debug/kmemleak      # 스캔 중지

# GPIO 상태 확인
cat /sys/kernel/debug/gpio
# gpiochip0: GPIOs 0-31, parent: platform/fe200000.gpio, pinctrl-bcm2711:
#  gpio-4   (                    |sysfs    ) out hi
#  gpio-17  (                    |button   ) in  lo IRQ

# 클록 트리 확인 (임베디드/SoC)
cat /sys/kernel/debug/clk/clk_summary

# regmap 레지스터 덤프
cat /sys/kernel/debug/regmap/0-0050/registers
# 00: 01 02 03 04
# 04: aa bb cc dd

debugfs 파일 생성 방법

/* 드라이버에서 debugfs 인터페이스 추가 */
struct dentry *dbg_dir;

static int __init my_init(void)
{
    dbg_dir = debugfs_create_dir("my_driver", NULL);
    if (IS_ERR(dbg_dir))
        return PTR_ERR(dbg_dir);

    /* 단일 값 파일 (자동 read/write 구현) */
    debugfs_create_u32("counter", 0644, dbg_dir, &my_counter);
    debugfs_create_bool("enabled", 0644, dbg_dir, &my_enabled);
    debugfs_create_x64("reg_value", 0444, dbg_dir, &my_reg);
    debugfs_create_size_t("buf_size", 0444, dbg_dir, &my_size);

    /* 커스텀 파일 (file_operations) */
    debugfs_create_file("status", 0444, dbg_dir, my_dev, &my_fops);

    /* 바이너리 Blob (레지스터 덤프 등) */
    my_blob.data = reg_base;
    my_blob.size = 256;
    debugfs_create_blob("reg_dump", 0444, dbg_dir, &my_blob);

    return 0;
}

static void __exit my_exit(void)
{
    debugfs_remove_recursive(dbg_dir);  /* 하위 전체 제거 */
}
💡

proc vs sysfs vs debugfs 선택 가이드:

  • procfs: 프로세스 정보 (/proc/[pid]/) 또는 레거시 시스템 정보. 새로운 항목 추가는 권장하지 않음
  • sysfs: 디바이스/드라이버 속성. ABI 안정 — 한번 노출하면 변경 불가. 프로덕션 도구가 의존 가능
  • debugfs: 디버깅 전용. ABI 불안정 — 언제든 변경/삭제 가능. 프로덕션 도구가 의존하면 안 됨
  • 새 정보 노출: sysfs(안정적 인터페이스) 또는 debugfs(디버깅용) 사용. procfs 신규 항목은 피할 것

보안과 접근 제어

가상 파일시스템은 커널 내부 정보를 노출하므로 보안 관점에서 접근 제어가 매우 중요합니다. 특히 컨테이너와 멀티테넌트 환경에서는 정보 유출 방지가 필수입니다.

hidepid — /proc 프로세스 정보 격리

hidepid 마운트 옵션은 다른 사용자의 /proc/[pid]/ 정보 접근을 제한합니다:

hidepid 값효과사용 시나리오
0 (기본)모든 /proc/[pid]/ 접근 가능개발 환경, 단일 사용자 시스템
1다른 사용자의 /proc/[pid]/ 디렉터리 목록 가능하나 내부 파일 접근 불가약한 격리
2다른 사용자의 /proc/[pid]/ 디렉터리 자체가 보이지 않음공유 서버, 호스팅 환경
invisible (5.8+)hidepid=2와 동일 + ptrace 제한 강화높은 보안 요구 환경
# hidepid 적용 (런타임)
mount -o remount,hidepid=2 /proc

# /etc/fstab에서 영구 적용
proc  /proc  proc  defaults,hidepid=2,gid=monitor  0  0

# gid 옵션: 지정된 그룹은 제한 면제 (모니터링 도구용)
# 예: gid=monitor → monitor 그룹 사용자는 모든 프로세스 볼 수 있음

# subset 옵션 (5.8+): 특정 항목만 숨김
mount -o remount,subset=pid /proc  # PID 디렉터리만 제한

Capability 기반 접근 제어

Capability영향 범위주의사항
CAP_SYS_ADMIN대부분의 /proc, /sys 민감 정보 접근권한이 너무 넓어 "the new root"로 불림. 최소 권한 원칙 위반의 원인
CAP_DAC_READ_SEARCH파일 읽기 권한 우회/proc/[pid]/environ, /proc/kcore 등 접근 가능
CAP_SYS_PTRACE/proc/[pid]/mem, stack, syscall 등 접근디버깅 도구(strace, gdb)에 필요
CAP_NET_ADMIN/proc/sys/net/ 쓰기네트워크 파라미터 변경 가능
CAP_SYSLOG/proc/kallsyms 주소, dmesg 접근심볼 주소 노출은 KASLR 우회에 활용될 수 있음

YAMA ptrace_scope

# /proc/sys/kernel/yama/ptrace_scope
# 0: 클래식 ptrace 권한 (같은 uid면 가능)
# 1: 부모 프로세스만 ptrace 가능 (기본값, Ubuntu 등)
# 2: CAP_SYS_PTRACE를 가진 프로세스만 ptrace 가능
# 3: ptrace 완전 비활성화 (재부팅 전까지 변경 불가)

# scope=1일 때 특정 프로세스를 디버깅 허용하려면:
# 대상 프로세스에서 prctl(PR_SET_PTRACER, debugger_pid)

SELinux / AppArmor 정책

MAC(Mandatory Access Control)은 /proc, /sys 접근에 추가적인 정책 계층을 적용합니다:

# SELinux: /proc 접근 거부 감사 로그
# avc: denied { read } for pid=1234 comm="app" name="kcore"
#   scontext=user_t tcontext=proc_kcore_t tclass=file

# SELinux 부울 제어
setsebool -P deny_ptrace 1          # ptrace 전역 차단

# AppArmor: 프로필에서 /proc 접근 제어
# /etc/apparmor.d/my_app:
# deny /proc/*/mem r,
# deny /proc/sys/** w,
# /proc/sys/net/** rw,  # 네트워크 튜닝만 허용

debugfs 접근 제한과 lockdown

커널 lockdown 기능(5.4+)은 Secure Boot 환경에서 debugfs 등을 통한 커널 변조를 방지합니다:

# lockdown 모드 확인
cat /sys/kernel/security/lockdown
# [none] integrity confidentiality

# integrity 모드: 커널 코드/데이터 변경 차단
#   - debugfs 쓰기 제한
#   - /dev/mem, /dev/kmem, /proc/kcore 접근 차단
#   - kexec_load 차단
#   - 모듈 서명 강제

# confidentiality 모드: integrity + 커널 정보 읽기 차단
#   - /proc/kallsyms 주소 숨김
#   - dmesg 접근 제한
#   - perf 제한

# debugfs 마운트 자체를 커널 파라미터로 비활성화
# 부팅 옵션: debugfs=off  (또는 debugfs=no-mount)
⚠️

프로덕션 보안 체크리스트:

  • hidepid=2로 /proc 프로세스 정보 격리
  • kptr_restrict=2로 커널 포인터 숨김 (/proc/kallsyms)
  • dmesg_restrict=1로 커널 로그 접근 제한
  • debugfs=off 부팅 옵션으로 debugfs 비활성화
  • 컨테이너에서 /proc, /sys를 읽기 전용 마운트 또는 마스킹

네임스페이스와 가상 FS

Linux 네임스페이스는 /proc, /sys의 보이는 내용을 격리하여 컨테이너 환경에서 프로세스별로 독립된 뷰를 제공합니다.

PID 네임스페이스와 /proc 격리

각 PID 네임스페이스는 독립된 /proc 뷰를 가집니다. 컨테이너 내부에서 /proc를 마운트하면 해당 네임스페이스의 프로세스만 보입니다:

/* PID 네임스페이스 생성 후 /proc 마운트 */
/* unshare(CLONE_NEWPID) → fork() → mount("proc", "/proc", "proc", 0, NULL) */

/* 커널 내부: proc_pid_readdir()가 현재 pid_namespace 기준으로 필터링 */
struct pid_namespace *ns = task_active_pid_ns(current);
/* ns->child_reaper = 네임스페이스 내 init(PID 1) 프로세스 */
/* ns->level = 네임스페이스 깊이 (루트=0) */
# 새 PID 네임스페이스에서 /proc 확인
unshare --pid --fork --mount-proc bash
ps aux   # 네임스페이스 내 프로세스만 보임
ls /proc/ # PID 1 = 현재 bash

# /proc/[pid]/status에서 NSpid 확인
cat /proc/self/status | grep NSpid
# NSpid:  1234  1   (호스트 PID: 1234, 네임스페이스 내 PID: 1)

sysctl 네임스페이스 인식 파라미터

일부 sysctl 파라미터는 네임스페이스별로 독립적입니다:

파라미터 그룹네임스페이스 인식비고
net.*네트워크 네임스페이스별 독립각 net namespace가 독자적인 네트워크 파라미터 보유
kernel.hostnameUTS 네임스페이스별 독립컨테이너마다 다른 hostname 가능
kernel.domainnameUTS 네임스페이스별 독립NIS 도메인명
user.max_*사용자 네임스페이스별 독립네임스페이스 내 리소스 제한
vm.*, kernel.pid_max전역 (init 네임스페이스만)컨테이너에서 변경 불가 — 호스트 설정에 의존
# 네트워크 네임스페이스에서 독립적인 sysctl
ip netns add test_ns
ip netns exec test_ns sysctl net.ipv4.ip_forward=1
# → test_ns 네임스페이스에서만 IP 포워딩 활성화

# 호스트의 net.ipv4.ip_forward는 변경되지 않음
sysctl net.ipv4.ip_forward
# → 0 (호스트 기본값 유지)

컨테이너에서의 procfs/sysfs 마운트 주의

⚠️

컨테이너 /proc, /sys 마운트 보안 주의사항:

  • /proc/sys 쓰기 — 컨테이너에서 /proc/sys를 쓰기 가능하게 마운트하면 호스트의 전역 sysctl 변경 가능 (네임스페이스 비인식 파라미터)
  • /proc/kcore — 호스트 물리 메모리 전체 접근 가능. 컨테이너에서 반드시 마스킹 필요
  • /sys 쓰기 — sysfs 쓰기로 호스트 디바이스 제어 가능. 읽기 전용 마운트 권장
  • /proc/acpi, /proc/scsi — 호스트 하드웨어 정보 노출. Docker는 기본으로 마스킹
# Docker 기본 /proc 마스킹 확인
docker run --rm alpine mount | grep proc
# proc on /proc type proc (rw,nosuid,nodev,noexec)
# tmpfs on /proc/acpi type tmpfs (ro,...)
# tmpfs on /proc/kcore type tmpfs (rw,...,size=0k)
# tmpfs on /proc/scsi type tmpfs (ro,...)

# OCI 런타임 스펙의 maskedPaths/readonlyPaths
# maskedPaths: /proc/kcore, /proc/keys, /proc/timer_list, ...
# readonlyPaths: /proc/bus, /proc/fs, /proc/irq, /proc/sys, ...

lxcfs를 통한 /proc 가상화

컨테이너 내부의 /proc/meminfo, /proc/cpuinfo 등은 호스트의 값을 보여줍니다. lxcfs는 이를 cgroup 제한에 맞게 가상화합니다:

# lxcfs 설치 및 시작
lxcfs /var/lib/lxcfs

# 컨테이너에서 lxcfs 마운트
docker run -v /var/lib/lxcfs/proc/meminfo:/proc/meminfo:ro \
           -v /var/lib/lxcfs/proc/cpuinfo:/proc/cpuinfo:ro \
           -v /var/lib/lxcfs/proc/stat:/proc/stat:ro \
           -v /var/lib/lxcfs/proc/uptime:/proc/uptime:ro \
           --memory 512m alpine cat /proc/meminfo
# MemTotal:      524288 kB   ← cgroup 제한(512MB)을 반영
# (lxcfs 없으면 호스트의 전체 메모리가 보임)

# lxcfs가 해결하는 문제:
# - JVM 등이 /proc/meminfo를 읽어 힙 크기를 잘못 결정
# - top/free 등 도구가 호스트 메모리를 보여 혼란 유발
# - /proc/cpuinfo가 호스트의 전체 CPU를 보여줌

성능과 부작용

가상 파일시스템의 읽기/쓰기는 커널 내부 자료구조에 직접 접근하므로, 예상치 못한 성능 비용과 부작용이 발생할 수 있습니다.

/proc 읽기 비용

경로잠금/비용대규모 환경 영향
/proc/[pid]/statustask_lock() 획득D 상태 프로세스에서 블로킹 가능
/proc/[pid]/smapsmmap_read_lock() → VMA 순회대규모 매핑 프로세스에서 수백ms 소요
/proc/[pid]/mapsmmap_read_lock()smaps보다 가볍지만 VMA가 많으면 여전히 비용 큼
/proc/meminfo전역 메모리 통계 수집NUMA 노드가 많으면 비용 증가
/proc/stat모든 CPU의 통계 합산CPU 수에 비례하여 비용 증가
/proc/net/tcpsock_lock 순회수만 소켓 환경에서 매우 느림. ss 명령이 더 효율적
# /proc/[pid]/smaps 읽기 비용 측정
time cat /proc/$(pgrep -f java)/smaps > /dev/null
# real: 0m0.350s  ← JVM 프로세스의 수천 VMA 순회

# 대안: smaps_rollup (합산값만, 5.4+)
time cat /proc/$(pgrep -f java)/smaps_rollup > /dev/null
# real: 0m0.005s  ← 70x 빠름

# /proc 순회 비용 (대량 프로세스)
time ls /proc/*/status > /dev/null 2>&1  # 1만 프로세스 → 수 초

대량 프로세스 환경에서의 /proc 순회

/* /proc 디렉터리 순회 시 tasklist_lock 비용 */
/* proc_pid_readdir() → next_tgid() → rcu_read_lock() + for_each_process() */
/*
 * 문제: 프로세스가 10,000개 이상이면 /proc 순회 자체가 느림
 * - ps aux, top 등이 모든 /proc/[pid]/ 순회
 * - 모니터링 에이전트가 주기적으로 순회하면 CPU 부담
 *
 * 대안:
 * - /proc/[pid]/stat만 읽기 (smaps 대신 statm)
 * - cgroup 기반 모니터링 (cgroupfs가 더 효율적)
 * - BPF iter (5.8+)로 task 순회 (커널 내에서 필터링)
 * - pidfd_open() + pidfd_getfd()로 특정 프로세스만 조회
 */

seq_file 메모리 사용

seq_file은 출력을 임시 버퍼에 저장한 뒤 사용자 공간으로 복사합니다. 대량 데이터 출력 시 메모리 사용이 급증할 수 있습니다:

/* seq_file 내부 버퍼 관리 */
/*
 * seq_read() → traverse() → show()
 * 초기 버퍼: PAGE_SIZE (4KB)
 * 부족 시 2배씩 증가: 4K → 8K → 16K → ... → kmalloc 한계까지
 *
 * 주의: single_open()은 전체 출력을 한 번에 메모리에 저장
 *       대량 데이터 시 seq_operations (start/next/stop/show) 사용 필수
 */

/* 나쁜 예: single_open으로 대량 데이터 출력 */
static int bad_show(struct seq_file *m, void *v)
{
    struct my_item *item;
    list_for_each_entry(item, &huge_list, list)
        seq_printf(m, "%d %s\\n", item->id, item->name);
    return 0;
    /* → 리스트가 크면 버퍼가 수십 MB까지 증가 */
}

/* 좋은 예: seq_operations으로 반복자 패턴 사용 */
/* → 한 번에 하나의 항목만 출력, 버퍼 크기 일정 유지 */

sysfs 폴링 vs uevent

/* 나쁜 패턴: 사용자 공간에서 sysfs를 주기적으로 폴링 */
/* while (1) { read("/sys/class/net/eth0/carrier"); sleep(1); } */
/* → 매 읽기마다 커널 show 콜백 호출, CPU 낭비 */

/* 좋은 패턴: uevent / poll() 사용 */
/* 1. sysfs_notify()로 커널에서 변경 알림 */
sysfs_notify(&dev->kobj, NULL, "status");
/* 2. 사용자 공간에서 poll()/select()로 대기 */
/* fd = open("/sys/..../status"); poll(fd, POLLPRI | POLLERR); */
/* → 변경 시에만 깨어남, CPU 사용 최소화 */

/* 또는 udev/netlink로 uevent 수신 */
kobject_uevent(&dev->kobj, KOBJ_CHANGE);
/* → udevadm monitor 로 확인 가능 */

debugfs와 lock contention

⚠️

debugfs 성능 주의사항:

  • debugfs_create_file()로 만든 파일의 콜백에서 mutex/spinlock을 잡으면, 디버깅 도구가 해당 파일을 읽을 때 lock contention 발생 가능
  • 프로덕션 환경에서 debugfs를 모니터링 용도로 사용하면 안 됨 — 디버깅 콜백이 핫 패스의 lock을 잡을 수 있음
  • /sys/kernel/debug/tracing/trace 읽기는 trace 버퍼 전체를 순회하므로 비용이 큼. trace_pipe는 소비 방식으로 더 효율적
  • debugfs 파일에 대한 동시 접근 시 file_operations 콜백이 직렬화되므로 병목 가능

tracefs 심화

tracefs는 커널 5.1+에서 debugfs로부터 분리된 독립 파일시스템으로, ftrace 트레이싱 인터페이스를 제공합니다. /sys/kernel/tracing/ (또는 레거시 경로 /sys/kernel/debug/tracing/)에 마운트됩니다.

tracefs 구조

경로용도읽기/쓰기
current_tracer현재 활성 트레이서 설정R/W: nop, function, function_graph, ...
trace트레이스 버퍼 내용 (스냅샷)R: 비소비적 읽기 (읽어도 버퍼 유지)
trace_pipe트레이스 버퍼 스트림R: 소비적 읽기 (읽으면 버퍼에서 제거)
trace_marker사용자 공간에서 트레이스 이벤트 기록W: 문자열 기록
available_tracers사용 가능한 트레이서 목록R
available_events사용 가능한 트레이스 이벤트 목록R
events/이벤트 카테고리별 enable/filter/formatR/W
kprobe_events동적 kprobe 이벤트 정의R/W
uprobe_events사용자 공간 uprobe 이벤트 정의R/W
set_ftrace_filterfunction 트레이서 필터 (화이트리스트)R/W
set_ftrace_notracefunction 트레이서 필터 (블랙리스트)R/W
buffer_size_kbper-CPU 트레이스 버퍼 크기R/W
per_cpu/cpu*/traceCPU별 트레이스 버퍼R
instances/독립 트레이스 인스턴스 생성mkdir/rmdir

ftrace 이벤트 설정

# tracefs 마운트 위치 확인
mount | grep tracefs
# tracefs on /sys/kernel/tracing type tracefs (rw,nosuid,nodev,noexec)

# 스케줄러 이벤트 트레이싱
echo 1 > /sys/kernel/tracing/events/sched/sched_switch/enable
cat /sys/kernel/tracing/trace_pipe
# bash-1234 [002] 12345.678: sched_switch: prev=bash next=kworker/2:0

# 특정 이벤트 필터링
echo 'prev_comm == "my_app"' > /sys/kernel/tracing/events/sched/sched_switch/filter

# 여러 이벤트 카테고리 동시 활성화
echo 1 > /sys/kernel/tracing/events/net/enable     # 네트워크 이벤트 전체
echo 1 > /sys/kernel/tracing/events/block/enable   # 블록 I/O 이벤트 전체

# 이벤트 포맷 확인
cat /sys/kernel/tracing/events/sched/sched_switch/format
# name: sched_switch
# field:char prev_comm[16]; field:pid_t prev_pid; ...

kprobe / uprobe 동적 트레이싱

# kprobe: 커널 함수 진입점에 동적 트레이스 포인트 삽입
echo 'p:my_probe do_sys_open filename=+0(%si):string' > /sys/kernel/tracing/kprobe_events
echo 1 > /sys/kernel/tracing/events/kprobes/my_probe/enable
cat /sys/kernel/tracing/trace_pipe
# bash-1234 [001] 12345.678: my_probe: (do_sys_open+0x0) filename="/etc/passwd"

# kretprobe: 함수 리턴 값 캡처
echo 'r:my_ret do_sys_open ret=$retval' >> /sys/kernel/tracing/kprobe_events

# uprobe: 사용자 공간 함수 트레이싱
echo 'p:my_uprobe /usr/bin/bash:0x4a2e0' > /sys/kernel/tracing/uprobe_events
echo 1 > /sys/kernel/tracing/events/uprobes/my_uprobe/enable

# kprobe/uprobe 제거
echo '-:my_probe' >> /sys/kernel/tracing/kprobe_events

# 주의: kprobe는 인라인 함수, static 함수에는 동작하지 않을 수 있음
# /proc/kallsyms에서 주소를 확인하여 사용

trace_pipe vs trace

특성tracetrace_pipe
읽기 방식비소비적 (여러 번 읽기 가능)소비적 (한 번 읽으면 버퍼에서 제거)
블로킹비블로킹 (즉시 반환)블로킹 (새 데이터 올 때까지 대기)
용도스냅샷 분석, 사후 분석실시간 스트리밍, 파이프라인 처리
메모리링 버퍼가 가득 차면 오래된 이벤트 덮어씀읽은 데이터는 즉시 해제
다중 소비자여러 프로세스가 동일 데이터 읽기 가능한 소비자만 데이터 수신

trace_marker — 사용자 공간 이벤트 기록

/* 사용자 공간 프로그램에서 커널 트레이스 버퍼에 마커 기록 */
int trace_fd = open("/sys/kernel/tracing/trace_marker", O_WRONLY);
dprintf(trace_fd, "my_app: transaction_start id=%d\\n", txn_id);
/* ... 작업 수행 ... */
dprintf(trace_fd, "my_app: transaction_end id=%d latency=%lldns\\n",
        txn_id, latency);
close(trace_fd);

/* trace_marker_raw: 바이너리 데이터 기록 (구조화된 이벤트) */
/* 사용 예: Android의 atrace/systrace가 trace_marker 사용 */

Android atrace/Perfetto: Android의 시스템 트레이싱은 커널의 trace_marker를 핵심으로 활용한다. atrace 도구가 Android 프레임워크 이벤트를 trace_marker에 기록하고, 커널 tracepoint(sched, binder, block 등)와 함께 수집한다. 현재 표준 도구인 Perfetto는 유저스페이스와 커널 트레이스를 통합하며, /sys/kernel/tracing/의 ftrace 인프라를 백엔드로 사용한다. Binder 트랜잭션 지연, 스케줄러 문제, I/O 병목 등을 분석할 때 커널 tracepoint가 핵심 데이터를 제공한다. 자세한 내용은 Android 커널 — 디버깅ftrace/Tracepoints를 참고하라.

per-CPU 버퍼와 인스턴스

# per-CPU 버퍼 크기 설정 (기본: 1408KB)
echo 4096 > /sys/kernel/tracing/buffer_size_kb  # 4MB per CPU

# CPU별 독립 트레이스 읽기
cat /sys/kernel/tracing/per_cpu/cpu0/trace
cat /sys/kernel/tracing/per_cpu/cpu3/trace_pipe

# 독립 트레이스 인스턴스 생성 (다중 사용자/도구 동시 사용)
mkdir /sys/kernel/tracing/instances/my_trace
echo function > /sys/kernel/tracing/instances/my_trace/current_tracer
echo 'vfs_*' > /sys/kernel/tracing/instances/my_trace/set_ftrace_filter
# → 메인 버퍼와 독립적으로 VFS 함수만 트레이싱

# 인스턴스 삭제
rmdir /sys/kernel/tracing/instances/my_trace

# 주의: 인스턴스마다 per-CPU 버퍼를 할당하므로 메모리 사용량 증가
# 인스턴스 수 × buffer_size_kb × nr_cpus = 총 메모리 사용량

커널 버전별 변경사항

procfs, sysfs, debugfs API는 커널 버전에 따라 지속적으로 변경됩니다. 모듈 코드의 호환성을 유지하려면 주요 변경사항을 파악해야 합니다.

proc_ops 도입 (5.6+)

/* 커널 5.6 이전: file_operations 사용 */
static const struct file_operations my_proc_fops = {
    .owner   = THIS_MODULE,
    .open    = my_proc_open,
    .read    = seq_read,
    .llseek  = seq_lseek,
    .release = single_release,
};
proc_create("my_entry", 0444, NULL, &my_proc_fops);

/* 커널 5.6 이후: proc_ops 전용 구조체 */
static const struct proc_ops my_proc_ops = {
    .proc_open    = my_proc_open,
    .proc_read    = seq_read,
    .proc_lseek   = seq_lseek,
    .proc_release = single_release,
    /* .owner 필드 없음 — 항상 THIS_MODULE */
    /* proc_flags 추가: PROC_ENTRY_PERMANENT 등 */
};
proc_create("my_entry", 0444, NULL, &my_proc_ops);

/* 변경 이유: file_operations는 VFS 전반에 사용되어 불필요한 필드가 많음.
 * proc_ops는 procfs에 필요한 필드만 포함하여 구조체 크기 절약.
 * proc_compat_ioctl 등 compat 지원도 proc_ops에 직접 포함. */

sysfs_emit 도입 (5.10+)

/* 커널 5.10 이전: sprintf/scnprintf 사용 (위험) */
static ssize_t old_show(struct kobject *kobj,
    struct kobj_attribute *attr, char *buf)
{
    return sprintf(buf, "%d\\n", value);
    /* 위험: buf는 PAGE_SIZE 크기이지만 sprintf는 경계 검사 없음 */
}

/* 커널 5.10 이후: sysfs_emit 사용 (권장) */
static ssize_t new_show(struct kobject *kobj,
    struct kobj_attribute *attr, char *buf)
{
    return sysfs_emit(buf, "%d\\n", value);
    /* 안전: 항상 PAGE_SIZE 경계 내에서 출력 */
    /* buf가 PAGE_SIZE 경계에 정렬되어 있는지 검증 (WARN) */
}

/* sysfs_emit_at: 오프셋 지정 출력 (여러 값을 순차 출력할 때) */
ssize_t len = 0;
len += sysfs_emit_at(buf, len, "field1: %d\\n", val1);
len += sysfs_emit_at(buf, len, "field2: %d\\n", val2);
/* 주의: one-value-per-file 원칙에 어긋나지만
 * 일부 레거시 sysfs 파일에서 불가피하게 사용 */

register_sysctl 변경 (6.x)

/* 커널 6.5 이전: register_sysctl_table(deprecated) 또는 register_sysctl() */
static struct ctl_table my_table[] = {
    { .procname = "my_param", .data = &val, .maxlen = sizeof(int),
      .mode = 0644, .proc_handler = proc_dointvec },
    { }  /* sentinel */
};
my_header = register_sysctl("kernel/mydriver", my_table);

/* 커널 6.6+: register_sysctl_table 제거, register_sysctl만 유지 */
/* ctl_table의 child 필드 제거 (중첩 테이블 불가) */
/* 경로는 register_sysctl()의 첫 인자로 지정 */

/* 커널 6.11+: 센티널(빈 엔트리) 불필요 */
static struct ctl_table my_table[] = {
    { .procname = "my_param", .data = &val, .maxlen = sizeof(int),
      .mode = 0644, .proc_handler = proc_dointvec },
    /* 센티널 불필요 — 배열 크기를 register_sysctl_sz()에 전달 */
};
my_header = register_sysctl_sz("kernel/mydriver", my_table, ARRAY_SIZE(my_table));

debugfs 권한 강화 이력

커널 버전변경 내용
4.7+debugfs_create_* 함수가 에러 시 ERR_PTR 반환 (이전: NULL)
5.0+debugfs 반환값 확인 불필요 정책 — Greg KH: "just don't check the return value"
5.4+커널 lockdown 모드에서 debugfs 접근 제한
5.6+debugfs=off 부팅 옵션 추가
6.0+debugfs에 대한 BPF 접근 제한 강화
💡

커널 버전 호환성 매크로: LINUX_VERSION_CODEKERNEL_VERSION() 매크로를 사용하여 버전별 조건 컴파일하세요:

#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,6,0)
  /* proc_ops 사용 */
#else
  /* file_operations 사용 */
#endif

procfs에서 BPF iter (5.8+)

/* BPF iter: /proc 순회 대신 커널 내에서 직접 task 순회 */
/* 장점: 커널-유저 공간 전환 최소화, 필터링 효율적 */

/* BPF 프로그램 (bpf_iter__task) */
SEC("iter/task")
int dump_task(struct bpf_iter__task *ctx)
{
    struct task_struct *task = ctx->task;
    if (!task) return 0;

    if (task->tgid == task->pid)  /* 메인 스레드만 */
        BPF_SEQ_PRINTF(ctx->meta->seq, "%d %s %llu\\n",
                       task->pid, task->comm, task->utime);
    return 0;
}

/* 사용: bpf_iter_link_create()로 /proc/my_tasks에 바인딩 */
/* 읽기 시 BPF 프로그램이 실행되어 필터링된 결과만 출력 */
/* → /proc/[pid]/ 일일이 순회하는 것보다 수십 배 빠름 */

흔한 실수와 함정

가상 파일시스템 코드에서 자주 발생하는 실수와 그 해결법을 정리합니다.

실수 1: sprintf 사용 (sysfs)

/* ✗ 나쁜 코드: sprintf로 sysfs 출력 */
static ssize_t show(struct kobject *kobj,
    struct kobj_attribute *attr, char *buf)
{
    return sprintf(buf, "%s\\n", very_long_string);
    /* 위험: very_long_string이 PAGE_SIZE(4096)를 초과하면 버퍼 오버플로 */
    /* 커널 패닉이나 메모리 손상 발생 가능 */
}

/* ✓ 올바른 코드: sysfs_emit 사용 */
static ssize_t show(struct kobject *kobj,
    struct kobj_attribute *attr, char *buf)
{
    return sysfs_emit(buf, "%s\\n", very_long_string);
    /* 안전: PAGE_SIZE 초과 시 잘려서 출력, 오버플로 없음 */
}

/* checkpatch.pl이 sprintf→sysfs_emit 변환을 경고함 */
/* WARNING: use sysfs_emit or sysfs_emit_at ... */

실수 2: proc 엔트리 race condition

/* ✗ 나쁜 코드: 모듈 언로드 시 race condition */
static void __exit my_exit(void)
{
    remove_proc_entry("my_entry", NULL);
    kfree(my_data);  /* 위험! 다른 프로세스가 아직 읽는 중일 수 있음 */
}

/* 시나리오:
 * 1. 프로세스 A가 /proc/my_entry를 open() → proc_ops.proc_open() 호출
 * 2. 모듈 rmmod 실행 → remove_proc_entry() → kfree(my_data)
 * 3. 프로세스 A가 read() → show() 콜백에서 해제된 my_data 접근 → UAF!
 */

/* ✓ 올바른 코드: proc_remove()와 적절한 동기화 */
static void __exit my_exit(void)
{
    proc_remove(my_proc_entry);
    /* proc_remove()는 진행 중인 read/write가 완료될 때까지 대기 */
    /* 이후 my_data를 안전하게 해제 가능 */
    kfree(my_data);
}

/* 추가 보호: pde_data() 사용으로 private data를 proc_dir_entry에 연결 */
proc_create_data("my_entry", 0444, NULL, &my_proc_ops, my_data);
/* show에서: struct my_data *data = pde_data(file_inode(file)); */

실수 3: sysfs에서 여러 값 출력

/* ✗ sysfs "one value per file" 원칙 위반 */
static ssize_t status_show(struct device *dev,
    struct device_attribute *attr, char *buf)
{
    return sysfs_emit(buf, "%d %d %d %s\\n",
                       dev->temp, dev->voltage, dev->rpm, dev->state);
    /* 문제:
     * - 파싱이 복잡해짐 (어떤 값이 어떤 필드인지 불명확)
     * - ABI 변경 어려움 (필드 추가/순서 변경 불가)
     * - Documentation/ABI/ 심사에서 거부됨
     */
}

/* ✓ 올바른 패턴: 값 하나당 파일 하나 */
/* /sys/devices/.../temp → "45\n" */
/* /sys/devices/.../voltage → "3300\n" */
/* /sys/devices/.../rpm → "1200\n" */
/* /sys/devices/.../state → "active\n" */

/* 예외: hwmon 등 일부 서브시스템은 관례적으로 여러 값 허용 */

실수 4: debugfs에 ABI 의존

# ✗ 나쁜 패턴: 프로덕션 스크립트가 debugfs에 의존
# my_monitoring_script.sh:
# cat /sys/kernel/debug/my_driver/stats  ← 커널 업데이트 시 경로 변경 가능!

# debugfs의 ABI 정책:
# "debugfs interfaces are not considered a stable ABI"
# "They can change at any time without notice"
# "No userspace tool should depend on them"

# ✓ 올바른 패턴:
# - 안정적 인터페이스가 필요하면 sysfs 사용
# - 모니터링 → sysfs 또는 netlink
# - 디버깅 → debugfs (개발자만 사용)
# - 프로파일링 → perf/BPF (안정적 tracepoint)

실수 5: seq_file 콜백에서 sleep/mutex

/* ✗ 위험한 패턴: show 콜백에서 장시간 블로킹 */
static int my_show(struct seq_file *m, void *v)
{
    mutex_lock(&global_mutex);  /* 핫 패스 mutex를 잡음 */
    seq_printf(m, "%d\\n", shared_counter);
    mutex_unlock(&global_mutex);
    return 0;
    /* 문제:
     * - 사용자가 cat /proc/my_entry 할 때마다 global_mutex 경합
     * - seq_file이 버퍼 리사이즈하면 show()가 재호출됨!
     *   → start() → show() [버퍼 부족] → stop() → start() → show() [재시도]
     *   → mutex를 두 번 잡으려고 시도할 수 있음 (deadlock 아니면 데이터 불일치)
     */
}

/* ✓ 개선 패턴: start/stop에서 lock, show에서는 lock 없이 */
static void *my_start(struct seq_file *m, loff_t *pos)
{
    mutex_lock(&my_mutex);
    return seq_list_start(&my_list, *pos);
}
static void my_stop(struct seq_file *m, void *v)
{
    mutex_unlock(&my_mutex);
}
/* start()~stop() 사이에서 show()가 여러 번 호출되어도 lock은 한 번만 */

함정 요약 체크리스트

💡

가상 FS 코드 리뷰 체크리스트:

  • sprintfsysfs_emit 변환 완료? (sysfs show 콜백)
  • file_operationsproc_ops 변환 완료? (커널 5.6+)
  • sysfs 파일당 하나의 값만 출력하는지?
  • proc 엔트리 제거 시 proc_remove() 사용하고, 이후에 private data 해제하는지?
  • seq_file의 show()에서 불필요한 lock을 잡지 않는지?
  • large output은 single_open() 대신 seq_operations(iterator) 사용하는지?
  • debugfs 인터페이스에 사용자 도구가 의존하지 않는지?
  • sysfs store()에서 입력 검증(kstrtoint 등)을 하는지?
  • 모듈 언로드 시 모든 proc/sysfs/debugfs 엔트리를 제거하는지?

procfs/sysfs와 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.