procfs/sysfs/debugfs (Virtual Filesystems)
Linux 커널 procfs, sysfs, debugfs 가상 파일시스템을 통한 커널-유저 인터페이스 구현.
가상 파일시스템 개요
Linux 커널은 여러 가상 파일시스템을 통해 커널 내부 정보를 사용자 공간에 노출합니다. 디스크에 데이터를 저장하지 않으며, 읽기/쓰기 시 커널 함수가 동적으로 데이터를 생성합니다.
| 가상 FS | 마운트 포인트 | 용도 |
|---|---|---|
procfs | /proc | 프로세스 정보, 커널 통계 |
sysfs | /sys | 디바이스/드라이버 계층 구조 (kobject 기반) |
debugfs | /sys/kernel/debug | 개발/디버깅 전용 인터페이스 |
configfs | /sys/kernel/config | 사용자 공간에서 커널 오브젝트 구성 |
tracefs | /sys/kernel/tracing | ftrace 추적 인터페이스 |
procfs (/proc)
주요 /proc 엔트리
| 경로 | 내용 |
|---|---|
/proc/[pid]/ | 프로세스별 정보 (status, maps, fd, ...) |
/proc/cpuinfo | CPU 정보 |
/proc/meminfo | 메모리 통계 |
/proc/interrupts | 인터럽트 카운터 |
/proc/sys/ | sysctl 커널 매개변수 |
/proc/buddyinfo | Buddy allocator 상태 |
/proc/slabinfo | Slab 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_minmax | ms→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_rollup | smaps 합산 | 전체 프로세스의 메모리 통계 요약 (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]/io | I/O 통계 | rchar, wchar, syscr, syscw, read_bytes, write_bytes |
/proc/[pid]/cgroup | cgroup 소속 | hierarchy-ID:controller-list:/path |
/proc/[pid]/ns/ | 네임스페이스 | 각 네임스페이스의 inode (mnt, pid, net, user, uts, ipc) |
/proc/[pid]/oom_score | OOM 점수 | 0~1000, 높을수록 OOM kill 우선 |
/proc/[pid]/oom_score_adj | OOM 점수 조정 | -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/buddyinfo | Buddy 할당자 상태 | 각 존의 order 0~10 free 블록 수 |
/proc/pagetypeinfo | 페이지 타입별 상태 | Unmovable, Movable, Reclaimable 별 free 블록 |
/proc/slabinfo | Slab 캐시 상태 | 각 캐시의 active_objs, num_objs, objsize, objperslab |
/proc/cpuinfo | CPU 정보 | model name, MHz, cache, flags (SSE, AVX 등), bugs |
/proc/interrupts | 인터럽트 통계 | IRQ별 CPU별 발생 횟수, 타입, 디바이스명 |
/proc/softirqs | Soft 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/ioports | I/O 포트 영역 | 포트 범위와 디바이스 매핑 |
sysctl (/proc/sys/) 주요 경로
| 경로 | 설명 | 기본값 | 주의사항 |
|---|---|---|---|
| /proc/sys/kernel/ — 커널 전반 | |||
kernel.panic | 패닉 후 재부팅 대기 시간(초) | 0 | 0=재부팅 안 함, 양수=N초 후 재부팅 |
kernel.panic_on_oops | Oops 시 패닉 여부 | 0 | 프로덕션: 1 권장 (kdump 수집) |
kernel.sysrq | SysRq 활성화 비트마스크 | 16 | 1=전체 활성, 438=안전한 조합 |
kernel.printk | 콘솔 로그 레벨 | 4 4 1 7 | current 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 | 스왑 적극성 | 60 | 0=스왑 최소, 100=적극적 스왑 |
vm.dirty_ratio | 동기 쓰기 임계값(%) | 20 | 이 비율 초과 시 프로세스가 직접 writeback |
vm.dirty_background_ratio | 비동기 쓰기 시작(%) | 10 | 이 비율 초과 시 백그라운드 writeback 시작 |
vm.dirty_expire_centisecs | dirty 페이지 만료 시간 | 3000 | 30초 이상 dirty 상태면 writeback 대상 |
vm.min_free_kbytes | 최소 free 메모리 | 시스템 의존 | 워터마크 기준, 너무 높으면 OOM 위험 감소/메모리 낭비 |
vm.overcommit_memory | 메모리 오버커밋 정책 | 0 | 0=추정, 1=항상허용, 2=불허 |
vm.overcommit_ratio | 오버커밋 비율(%) | 50 | mode=2 시 CommitLimit = Swap + RAM*ratio/100 |
vm.vfs_cache_pressure | VFS 캐시 회수 압력 | 100 | 높을수록 inode/dentry 캐시 적극 회수 |
vm.nr_hugepages | Hugepage 수 | 0 | 런타임 변경 가능하지만 단편화 시 실패 |
vm.zone_reclaim_mode | NUMA 존 회수 모드 | 0 | 1=로컬 회수, NUMA 바운드 워크로드에서만 |
| /proc/sys/net/ — 네트워크 | |||
net.core.somaxconn | listen 백로그 최대 | 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_backlog | SYN 큐 크기 | 512 | SYN flood 방어: 증가 |
net.ipv4.tcp_syncookies | SYN 쿠키 | 1 | SYN flood 방어 (0=비활성) |
net.ipv4.tcp_tw_reuse | TIME_WAIT 소켓 재사용 | 2 | 0=비활성, 1=활성, 2=loopback만 |
net.ipv4.ip_forward | IP 포워딩 | 0 | 라우터/컨테이너 호스트: 1 |
net.ipv4.conf.all.rp_filter | 역경로 필터링 | 0 | 1=strict, 2=loose (비대칭 라우팅) |
net.ipv4.tcp_keepalive_time | keepalive 시작 시간 | 7200 | 초 단위, 빠른 감지: 300 |
net.ipv4.tcp_fin_timeout | FIN-WAIT-2 타임아웃 | 60 | 많은 연결: 30 |
net.ipv4.neigh.default.gc_thresh3 | ARP 테이블 최대 | 1024 | 대규모 L2: 4096+ |
net.netfilter.nf_conntrack_max | conntrack 테이블 최대 | 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/gpio | GPIO 상태 | GPIOLIB |
debug/suspend_stats | PM 서스펜드 통계 | PM_SLEEP |
debug/fault_around_bytes | fault 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.hostname | UTS 네임스페이스별 독립 | 컨테이너마다 다른 hostname 가능 |
kernel.domainname | UTS 네임스페이스별 독립 | 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]/status | task_lock() 획득 | D 상태 프로세스에서 블로킹 가능 |
/proc/[pid]/smaps | mmap_read_lock() → VMA 순회 | 대규모 매핑 프로세스에서 수백ms 소요 |
/proc/[pid]/maps | mmap_read_lock() | smaps보다 가볍지만 VMA가 많으면 여전히 비용 큼 |
/proc/meminfo | 전역 메모리 통계 수집 | NUMA 노드가 많으면 비용 증가 |
/proc/stat | 모든 CPU의 통계 합산 | CPU 수에 비례하여 비용 증가 |
/proc/net/tcp | sock_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/format | R/W |
kprobe_events | 동적 kprobe 이벤트 정의 | R/W |
uprobe_events | 사용자 공간 uprobe 이벤트 정의 | R/W |
set_ftrace_filter | function 트레이서 필터 (화이트리스트) | R/W |
set_ftrace_notrace | function 트레이서 필터 (블랙리스트) | R/W |
buffer_size_kb | per-CPU 트레이스 버퍼 크기 | R/W |
per_cpu/cpu*/trace | CPU별 트레이스 버퍼 | 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
| 특성 | trace | trace_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_CODE와 KERNEL_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 코드 리뷰 체크리스트:
sprintf→sysfs_emit변환 완료? (sysfs show 콜백)file_operations→proc_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 엔트리를 제거하는지?