procfs/sysfs/debugfs (Virtual Filesystems)

Linux 커널 procfs, sysfs, debugfs 가상 파일시스템을 통한 커널-유저 인터페이스 구현.

관련 표준: POSIX.1-2017 (가상 파일시스템 인터페이스 기반) — procfs/sysfs는 POSIX 파일 API를 통해 커널 정보를 노출하는 가상 파일시스템입니다. 종합 목록은 참고자료 — 표준 & 규격 섹션을 참고하세요.

가상 파일시스템 개요

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

가상 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 포트 영역포트 범위와 디바이스 매핑

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 엔트리를 제거하는지?