커널 API 레퍼런스

커널 코드 리뷰에서 반복해서 등장하는 함수, 매크로, 심볼을 컨텍스트 중심으로 대규모 정리했습니다. 각 항목은 "언제 쓰는가", "자주 나는 실수", "대체 API", "소스 경로"를 함께 담아 실전 참고서로 바로 사용할 수 있게 구성했습니다.

전제 조건: C 언어 & 커널 C 관용어 문서를 먼저 읽으세요. 특히 ERR_PTR, READ_ONCE, container_of의 의미를 이해하고 오면 이 문서를 훨씬 빠르게 사용할 수 있습니다.
일상 비유: 이 개념은 정비 현장의 공구함과 비슷합니다. 드라이버를 수리할 때 매번 새 공구를 만드는 대신, 검증된 공구를 상황에 맞게 꺼내 쓰듯이 커널도 검증된 API 조합을 재사용합니다.

핵심 | 핵심 요약

이 요약은 표 전체를 읽기 전에 반드시 고정해야 할 판단 축을 압축한 구간입니다. 아래 항목을 먼저 이해하면 이후 섹션에서 API 이름이 달라도 판단 기준을 일관되게 유지할 수 있습니다. 특히 반환 규약, 락 규약, 수명주기 순서는 코드 리뷰에서 반복적으로 지적되는 핵심 실패 지점입니다.

실무 체크포인트(Checkpoint): 구현 전에 이 5개 항목을 PR 설명의 "사전 점검" 항목으로 그대로 복사해 사용하세요.

  • 반환 규약 — 포인터 반환 API는 NULL인지 ERR_PTR인지 먼저 확인합니다.
  • 락 규약 — 컨텍스트에 맞는 락을 선택하고 sleep 가능 여부를 반드시 구분합니다.
  • 수명주기 — 할당, 등록, 해제의 순서를 쌍으로 기억합니다.
  • 가시성 — 심볼 export는 ABI 계약이므로 최소한만 공개합니다.
  • 관측성 — 문제 재현 전 로그 레벨, tracepoint, 통계를 먼저 설계합니다.

핵심 | 단계별 이해

단계별 이해는 "문맥 확인 → API 선택 → 실패 경로 설계"를 강제로 순서화해 실수를 줄이기 위한 절차입니다. 항목을 건너뛰면 대부분 해제 누락이나 문맥 오용이 발생하므로, 코드 작성 전에 순서를 먼저 확정하는 것이 안전합니다.

실무 체크포인트: 단계 1에서 컨텍스트를 확정하지 못하면 구현을 진행하지 말고 호출 경로부터 다시 추적하세요.

  1. 문맥 파악
    프로세스(Process) 문맥인지 인터럽트(Interrupt) 문맥인지 먼저 결정합니다.
  2. 기본 API 선택
    메모리, 락, 에러 처리의 기본 세트를 정합니다.
  3. 실패 경로 설계
    중간 실패 시 되돌릴 순서를 코드보다 먼저 정합니다.
  4. 심볼 노출 점검
    정말 외부 모듈이 필요한 심볼만 export 합니다.
  5. 관측 포인트 추가
    pr_debug, tracepoint, 통계 카운터를 최소 단위로 배치합니다.
문서 범위: 항목 수 제한 없이 공통 커널 개발 기준으로 계속 확장되는 레퍼런스입니다. 드라이버, 메모리 관리(Memory Management), 네트워킹, 파일시스템(Filesystem), 보안, 관측성까지 동일한 규칙으로 누적 관리합니다.

핵심 | 이 문서를 빠르게 읽는 법

이 페이지(Page)는 "API 목록"이 아니라 "의사결정 지원서"입니다. 각 표는 같은 구조(언제 사용/실수 패턴/대체안)로 작성되어 있으므로, 문제가 발생했을 때 아래 순서로 접근하면 가장 빠릅니다.

  1. 문맥 확인
    현재 코드가 프로세스 문맥인지, IRQ 문맥인지, sleep 가능한지 먼저 확정합니다.
  2. 반환 규약 확인
    호출 API가 NULL 반환형인지 ERR_PTR 반환형인지 먼저 고정합니다.
  3. 짝 API 확인
    할당/해제, lock/unlock, get/put 쌍이 모두 존재하는지 체크합니다.
  4. 실수 패턴 역검증
    표의 "실수 패턴" 열을 기준으로 현재 코드가 이미 같은 실수를 반복하는지 확인합니다.
  5. 대체안 검토
    현재 경로가 문맥과 맞지 않으면 즉시 "대체/보완" API로 치환 전략을 세웁니다.
실전 팁: 신규 코드 작성 전에 이 문서에서 먼저 실패 경로를 설계하면, 리뷰 지적의 절반 이상(해제 누락/문맥 오용/반환값 누락)을 선제적으로 줄일 수 있습니다.

핵심 | API 선택 의사결정 플로우

질문Yes일 때No일 때핵심 참고 섹션
현재 문맥에서 sleep 가능한가?mutex, GFP_KERNEL 계열 우선spinlock, GFP_ATOMIC 계열 우선컨텍스트별 API 선택 매트릭스
반환값이 포인터인가?IS_ERR/PTR_ERR 또는 NULL 규약 먼저 확인정수 errno 규약으로 처리반환값 계약 카탈로그
핫패스(고빈도) 경로인가?로그/락/복사 비용 최소화, 관측 필터 적용가독성과 안정성 우선관측성 카탈로그
모듈 외부에 기능을 노출해야 하는가?EXPORT_SYMBOL* + 네임스페이스(Namespace) 정책 적용static 내부화 유지심볼 Export/네임스페이스
오류 재현이 간헐적인가?tracepoint + rate-limit + 재현 스크립트 고정단계별 로그로 충분문제 유형별 대응 인덱스
시작: 코드 문맥 확인 (sleep 가능 여부) 반환 규약 판별 (NULL vs ERR_PTR) 짝 API 점검 (alloc/free, get/put) 실수 패턴 역검증 (문맥 위반/해제 누락) 대체안 적용 (표의 대체 API로 치환) 최종: 실패 경로 포함 코드 확정
문맥 확인 → 반환 규약 → 짝 API 점검 → 실패 경로 확정 순서로 의사결정을 고정합니다.

실무 체크포인트: 코드 리뷰 전에 문맥, 반환규약, 해제쌍 3가지를 PR 설명에 먼저 명시하세요.

핵심 | 빠른 이동 인덱스

검색형 레퍼런스로 사용할 때는 아래 인덱스에서 도메인을 먼저 고르고, 해당 카탈로그로 바로 이동하세요.

영역즉시 이동메모
핵심 규약컨텍스트 매트릭스, 반환값 계약, 락 선택리뷰 지적의 대부분을 선제 차단
네트워크네트워킹 카탈로그, 프로토콜skb/NAPI/Netfilter 중심
파일시스템파일시스템 카탈로그, 파일시스템VFS와 ext4/XFS/Btrfs 분리 확인
메모리메모리 관리 카탈로그, GFP 카탈로그GFP/folio/reclaim 우선 점검
디바이스/미디어버스(Bus) 카탈로그, GPU/V4L2/ALSAprobe/remove + DMA 경계
보안/운영보안/LSM, 취약점(Vulnerability) 대응, 패닉 대응운영 절차와 코드 수정을 함께 관리
GPIO/DTGPIO/Pinctrl, Device TreeConsumer/Provider, of_* 파싱
문자 디바이스chrdev/misc전통 경로 vs misc 단축
RCURCU 변종Classic/SRCU/리스트 연산
시간/클럭Clock/Timektime/hrtimer/clk framework
암호화Crypto APIahash/skcipher/AEAD
참조/인터페이스kref/kobject, sysfs/procfs/debugfs수명주기 + 사용자 노출

핵심 | 개요

아래 다이어그램은 함수, 매크로, 심볼이 어떻게 연결되는지 보여줍니다. 함수는 동작 단위, 매크로는 규약 단축, 심볼은 모듈 간 계약입니다.

함수 kmalloc, mutex_lock 매크로 container_of, IS_ERR 심볼 EXPORT_SYMBOL* 실전 규약 반환값 검사, 락 순서, 수명주기, ABI 안정성
함수·매크로·심볼은 분리된 지식이 아니라 하나의 실전 규약으로 연결되어 동작합니다.

실무 체크포인트: 신규 심볼 export 전에는 반드시 기존 내부 함수로 대체 가능한지 먼저 확인하세요.

/* 실패 경로를 먼저 설계한 전형적인 초기화 패턴 */
static int __init sample_init(void)
{
    int ret;

    ret = register_chrdev(0, "sample", &fops);
    if (ret < 0)
        return ret;

    if (unlikely(!ready)) {
        unregister_chrdev(ret, "sample");
        return -EINVAL;
    }

    pr_info("sample initialized\n");
    return 0;
}

핵심 | 로깅과 출력 함수

로깅 API는 단순 출력 함수가 아니라 장애 대응 속도를 결정하는 운영 인터페이스입니다. 이 표는 로그 레벨 선택, 디바이스 컨텍스트 포함 여부, 폭주 억제 전략을 함께 판단하기 위한 기준으로 읽어야 합니다. 특히 핫패스에서는 정보량보다 샘플링 정책과 레이트 리밋이 우선입니다.

실무 체크포인트: 실패 로그에는 반드시 "실패 동작 + 식별자 + errno"를 한 줄에 남기고, 정상 경로 로그는 기본적으로 억제하세요.

항목언제 사용흔한 실수대체/짝 API
printk기본 커널 로그 출력레벨 미지정pr_*
pr_emerg시스템 즉시 중단급 장애일반 오류에 남용panic
pr_alert즉시 조치가 필요한 오류중복 출력pr_err
pr_crit핵심 기능 손상복구 가능 오류에 사용WARN_ON
pr_err실패 경로 보고에러 코드 누락dev_err
pr_warn비정상 상태 경고정상 경로에서 남용dev_warn
pr_notice운영자가 알아야 할 상태 변화정보 로그와 혼용pr_info
pr_info일반 상태 정보고빈도 경로에 과다 사용pr_debug
pr_debug디버그 빌드 또는 동적 디버그필수 로그를 debug로만 남김dynamic_debug
dev_err디바이스 컨텍스트 포함 오류 로그struct device 없이 사용dev_warn, dev_info
/* 로그 레벨 실전 사용 예시 */
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

static int demo_hw_init(struct device *dev)
{
    u32 status = readl(base + STATUS_REG);

    if (!(status & HW_READY_BIT)) {
        dev_err(dev, "hw not ready, status=0x%08x\n", status);
        return -ETIMEDOUT;
    }

    if (status & HW_DEGRADED_BIT)
        dev_warn(dev, "hw running in degraded mode\n");

    dev_info(dev, "hw init ok, version=%u\n",
             (status >> 16) & 0xFF);

    dev_dbg(dev, "full status register: 0x%08x\n", status);
    return 0;
}

/* pr_fmt 매크로 — 모듈명 자동 접두사 */
/* 출력: "demo_drv: hw init ok, version=3" */

/* 레이트 리밋 패턴 — 핫패스 로그 폭주 방지 */
static void demo_handle_error(int err_code)
{
    pr_err_ratelimited("hw error %d (further messages suppressed)\n",
                       err_code);
}

/* 동적 디버그 — 런타임 토글 */
/* echo 'module demo_drv +p' > /sys/kernel/debug/dynamic_debug/control */
pr_debug("packet rx: len=%u proto=0x%04x\n", len, proto);

핵심 | 메모리 할당/해제

메모리 API 선택은 크기보다 실행 문맥과 회수 전략을 먼저 결정해야 안전합니다. 같은 할당이라도 sleep 가능 여부와 reclaim 허용 범위가 다르면 실패 확률과 지연(Latency) 특성이 크게 달라집니다. 표를 볼 때는 "할당 성공"보다 "실패 시 되돌리기"가 닫히는지부터 확인해야 합니다.

기술 문서: 메모리 관리 개요에서 페이지 할당자, 슬랩, 가상 메모리 구조를 함께 확인하세요.
메모리 할당이 필요한가? 작은 크기 (< PAGE_SIZE) kmalloc / kzalloc 큰 크기 (연속 불필요) vmalloc / vzalloc 크기 불확실 (fallback) kvzalloc / kvmalloc 반복 객체 (슬랩 캐시) kmem_cache_alloc 디바이스 관리형 devm_kzalloc 페이지 단위 alloc_pages / __get_free_pages 해제 규칙: kmalloc→kfree, vmalloc→vfree, kvmalloc→kvfree
할당 크기와 용도에 따라 슬랩(kmalloc), 가상(vmalloc), 자동 fallback(kvmalloc), 관리형(devm), 페이지 단위로 분류합니다.

실무 체크포인트: 모든 할당 지점에 대응하는 해제 API를 함수 단위가 아니라 실패 경로 단위로 짝지어 점검하세요.

항목언제 사용흔한 실수대체/짝 API
kmalloc작은 연속 메모리문맥에 맞지 않는 GFPkzalloc
kzalloc0 초기화가 필요한 구조체(Struct)불필요한 중복 memsetkmalloc
kcalloc배열 할당곱셈 오버플로 직접 계산kvcalloc
krealloc버퍼(Buffer) 크기 변경실패 시 원본 포인터 덮어쓰기kvrealloc
kfreekmalloc 계열 해제중복 해제kvfree
vmalloc큰 비연속 가상 메모리(Virtual Memory)DMA 가능한 메모리로 오해vzalloc
vzalloc0 초기화된 vmalloc 영역빈번한 소량 할당에 사용vmalloc
kvzalloc큰 메모리 할당 fallback해제를 kfree로만 처리kvfree
kmem_cache_create반복 객체 할당 최적화ctor 과도 사용KMEM_CACHE
kmem_cache_allocslab 객체 할당캐시(Cache) 파괴 전 해제 누락kmem_cache_free
/* 메모리 할당 실전 패턴 — 문맥별 GFP 선택 */

/* 프로세스 문맥: GFP_KERNEL (sleep 가능, reclaim 허용) */
static int demo_probe_alloc(struct device *dev)
{
    struct demo_priv *p = devm_kzalloc(dev, sizeof(*p), GFP_KERNEL);
    if (!p)
        return -ENOMEM;

    /* 사용자 제어 크기 — kcalloc로 오버플로 방지 */
    p->entries = devm_kcalloc(dev, p->num_entries,
                              sizeof(*p->entries), GFP_KERNEL);
    if (!p->entries)
        return -ENOMEM;

    return 0;
}

/* IRQ/softirq 문맥: GFP_ATOMIC (sleep 불가) */
static irqreturn_t demo_irq(int irq, void *dev_id)
{
    struct demo_event *evt;

    evt = kmalloc(sizeof(*evt), GFP_ATOMIC);
    if (!evt) {
        /* 할당 실패 허용 — 이벤트 드롭 */
        pr_warn_ratelimited("event alloc failed\n");
        return IRQ_HANDLED;
    }
    /* evt 처리 후 kfree */
    return IRQ_HANDLED;
}

/* 파일시스템 문맥: GFP_NOFS (재진입 방지) */
void *buf = kmalloc(4096, GFP_NOFS);
/* GFP_NOFS: 메모리 회수 시 파일시스템 콜백 금지 */

핵심 | 사용자 공간(User Space) 접근

사용자 공간 접근 API는 보안 경계와 오류 전파 규약이 동시에 걸린 고위험 구간입니다. 표의 핵심은 복사 함수 선택 자체보다 반환값 해석과 부분 복사 처리 규칙을 고정하는 데 있습니다. 주소 검증과 실제 복사는 별개의 단계라는 점을 항상 분리해서 보아야 합니다.

실무 체크포인트: `copy_*_user` 반환값을 무시하는 코드는 기능 동작과 무관하게 즉시 수정 대상으로 분류하세요.

항목언제 사용흔한 실수대체/짝 API
copy_from_user유저 버퍼 입력 수신반환값 미검사get_user
copy_to_user유저 버퍼 출력부분 복사 처리 누락put_user
get_user스칼라 값 1개 읽기포인터 유효성 가정copy_from_user
put_user스칼라 값 1개 쓰기에러 코드 무시copy_to_user
strncpy_from_user문자열 길이 제한 복사NUL 보장 오해strscpy_from_user
strnlen_user유저 문자열 길이 확인반환값 단위 해석 오류strlen 사용 금지
access_ok유저 주소 범위 1차 확인복사 안전성 보장으로 오해copy_*_user와 병행
import_iovec벡터형 I/O 인자 검증복잡한 iov 직접 파싱iov_iter
pin_user_pages장기 DMA 고정unpin 누락get_user_pages
unpin_user_pagespin 해제참조 카운트(Reference Count) 누락put_page
/* copy_from_user / copy_to_user 안전 패턴 */
static ssize_t demo_write(struct file *f,
                          const char __user *ubuf,
                          size_t count, loff_t *ppos)
{
    struct demo_dev *d = f->private_data;
    char kbuf[128];
    size_t len;

    /* 크기 제한 검증 */
    len = min(count, sizeof(kbuf) - 1);

    /* 반환값 반드시 검사: 0이면 성공, 양수이면 복사 실패 바이트 수 */
    if (copy_from_user(kbuf, ubuf, len))
        return -EFAULT;

    kbuf[len] = '\0';

    mutex_lock(&d->lock);
    strscpy(d->label, kbuf, sizeof(d->label));
    mutex_unlock(&d->lock);

    return len;
}

static ssize_t demo_read(struct file *f,
                         char __user *ubuf,
                         size_t count, loff_t *ppos)
{
    struct demo_dev *d = f->private_data;
    char kbuf[128];
    ssize_t len;

    mutex_lock(&d->lock);
    len = scnprintf(kbuf, sizeof(kbuf), "%s\n", d->label);
    mutex_unlock(&d->lock);

    return simple_read_from_buffer(ubuf, count, ppos, kbuf, len);
}

/* get_user / put_user — 스칼라 값 단일 전송 */
static long demo_ioctl(struct file *f, unsigned int cmd,
                      unsigned long arg)
{
    struct demo_dev *d = f->private_data;
    u32 val;

    switch (cmd) {
    case DEMO_GET_STATUS:
        if (put_user(d->status, (u32 __user *)arg))
            return -EFAULT;
        return 0;
    case DEMO_SET_MODE:
        if (get_user(val, (u32 __user *)arg))
            return -EFAULT;
        if (val > DEMO_MODE_MAX)
            return -EINVAL;
        d->mode = val;
        return 0;
    default:
        return -ENOTTY;
    }
}

핵심 | 에러 처리 매크로

에러 처리 매크로는 코드 스타일이 아니라 함수 계약을 표현하는 수단입니다. 포인터 계열과 정수 계열 계약을 혼동하면 정상 경로에서도 잘못된 분기가 만들어져 장애를 유발합니다. 이 섹션은 "검사 매크로 선택"보다 "반환 계약 명시"를 먼저 수행하는 기준으로 사용해야 합니다.

실무 체크포인트: 포인터 반환 함수 선언부 주석에 `NULL` 계열인지 `ERR_PTR` 계열인지를 명시하고 그 규약만 허용하세요.

항목언제 사용흔한 실수대체/짝 API
ERR_PTR포인터 반환 함수 에러 인코딩양수 errno 전달PTR_ERR
IS_ERR에러 포인터 판별NULL 검사로 대체IS_ERR_OR_NULL
PTR_ERR에러 코드 추출정상 포인터에 적용PTR_ERR_OR_ZERO
IS_ERR_OR_NULLNULL 포함 에러 판별설계 문제 은폐IS_ERR
PTR_ERR_OR_ZEROint 반환 함수 연결반환값 의미 혼동PTR_ERR
WARN_ON비정상 조건 경고복구 가능한 조건에서 과다 사용WARN_ON_ONCE
WARN_ON_ONCE반복 경고 억제원인 추적 전 너무 일찍 사용pr_warn_ratelimited
BUG_ON치명적 불변식 위반운영 경로에서 남용WARN_ON
pr_err_ratelimited폭주 로그 억제상태 정보 손실net_ratelimit
might_sleepsleep 가능 문맥 검증atomic 경로에서 호출lockdep_assert_held
/* ERR_PTR / IS_ERR / PTR_ERR 완전 패턴 */
static struct demo_obj *demo_create(const char *name)
{
    struct demo_obj *obj;

    if (!name || !*name)
        return ERR_PTR(-EINVAL);

    obj = kzalloc(sizeof(*obj), GFP_KERNEL);
    if (!obj)
        return ERR_PTR(-ENOMEM);

    obj->name = kstrdup(name, GFP_KERNEL);
    if (!obj->name) {
        kfree(obj);
        return ERR_PTR(-ENOMEM);
    }
    return obj;
}

/* 호출자: IS_ERR + PTR_ERR 조합 */
static int demo_setup(void)
{
    struct demo_obj *obj;

    obj = demo_create("sensor0");
    if (IS_ERR(obj)) {
        pr_err("create failed: %ld\n", PTR_ERR(obj));
        return PTR_ERR(obj);
    }
    /* obj 사용 */
    return 0;
}

/* PTR_ERR_OR_ZERO: 포인터→int 변환 단축 */
static int demo_get_clk(struct device *dev)
{
    struct clk *c = devm_clk_get(dev, "core");
    return PTR_ERR_OR_ZERO(c);  /* 성공=0, 실패=음수 errno */
}
/* goto cleanup 단계별 에러 처리 — 3단계 자원 할당 */
static int demo_complex_init(struct device *dev)
{
    struct demo_priv *priv;
    void __iomem *base;
    int ret;

    priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
    if (!priv)
        return -ENOMEM;

    base = devm_ioremap_resource(dev,
                platform_get_resource(to_platform_device(dev), IORESOURCE_MEM, 0));
    if (IS_ERR(base))
        return PTR_ERR(base);  /* devm가 priv 정리 */

    priv->clk = devm_clk_get_enabled(dev, "bus");
    if (IS_ERR(priv->clk))
        return PTR_ERR(priv->clk);  /* devm가 base+priv 정리 */

    dev_info(dev, "init complete, base=%pR\n", base);
    return 0;
}

핵심 | 자료구조 매크로

자료구조 매크로는 성능 최적화 수단이 아니라 불변식 유지 수단입니다. 순회, 삽입, 삭제에서 한 단계만 어긋나도 use-after-free나 순회 손상이 즉시 발생하므로, 매크로의 전제 조건을 먼저 확인해야 합니다. 특히 RCU, 리스트 safe 순회, ID 할당 회수는 수명주기 규약과 함께 검토해야 합니다.

실무 체크포인트: 순회 중 삭제 가능성이 있으면 기본 순회 매크로 대신 safe 변형을 우선 검토하세요.

항목언제 사용흔한 실수대체/짝 API
container_of멤버 포인터에서 상위 구조체 복원잘못된 member 지정list_entry
ARRAY_SIZE정적 배열 길이 계산포인터에 사용sizeof
list_add이중 연결 리스트(Linked List) 삽입초기화 없는 노드 삽입list_add_tail
list_del리스트 노드 제거다시 순회 시 재사용list_del_init
list_for_each_entry타입 안전 리스트 순회순회 중 삭제list_for_each_entry_safe
hlist_add_head해시(Hash) 버킷 헤드 삽입pprev 의미 오해hlist_del
xa_storeXArray 저장락 컨텍스트 누락xa_load
xa_loadXArray 조회RCU 규약 누락xa_for_each
idr_allocIDR 기반 ID 할당해제 누락ida_alloc
idr_removeIDR ID 회수중복 제거ida_free
/* list_head 기본 패턴 — 삽입/순회/안전 삭제 */
struct demo_item {
    struct list_head node;
    int id;
    char name[32];
};

static LIST_HEAD(demo_list);
static DEFINE_MUTEX(demo_list_lock);

/* 삽입 */
static int demo_add(int id, const char *name)
{
    struct demo_item *item;

    item = kzalloc(sizeof(*item), GFP_KERNEL);
    if (!item)
        return -ENOMEM;

    item->id = id;
    strscpy(item->name, name, sizeof(item->name));

    mutex_lock(&demo_list_lock);
    list_add_tail(&item->node, &demo_list);
    mutex_unlock(&demo_list_lock);
    return 0;
}

/* 안전 삭제 — list_for_each_entry_safe 필수 */
static void demo_remove_all(void)
{
    struct demo_item *item, *tmp;

    mutex_lock(&demo_list_lock);
    list_for_each_entry_safe(item, tmp, &demo_list, node) {
        list_del(&item->node);
        kfree(item);
    }
    mutex_unlock(&demo_list_lock);
}

/* container_of 역추적 — 타이머 콜백에서 부모 구조체 접근 */
static void demo_timer_cb(struct timer_list *t)
{
    struct demo_dev *d = container_of(t, struct demo_dev, poll_timer);
    schedule_work(&d->poll_work);
}
/* hlist — 해시 테이블 삽입/탐색 패턴 */
#define DEMO_HASH_BITS  8
static DEFINE_HASHTABLE(demo_htable, DEMO_HASH_BITS);

struct demo_entry {
    struct hlist_node hnode;
    u32 key;
    u64 value;
};

static void demo_ht_insert(struct demo_entry *e)
{
    hash_add(demo_htable, &e->hnode, e->key);
}

static struct demo_entry *demo_ht_find(u32 key)
{
    struct demo_entry *e;
    hash_for_each_possible(demo_htable, e, hnode, key) {
        if (e->key == key)
            return e;
    }
    return NULL;
}

/* XArray — 인덱스 기반 저장/조회/순회 */
static DEFINE_XARRAY(demo_xa);

static int demo_xa_store(unsigned long idx, struct demo_obj *obj)
{
    void *old = xa_store(&demo_xa, idx, obj, GFP_KERNEL);
    return xa_err(old);
}

static void demo_xa_dump(void)
{
    struct demo_obj *obj;
    unsigned long idx;

    xa_for_each(&demo_xa, idx, obj)
        pr_info("xa[%lu] = %s\n", idx, obj->name);
}

핵심 | 락/동기화

락 선택은 동시성 제어와 지연 특성을 함께 결정하는 아키텍처 선택입니다. 이 표는 락의 이름을 외우기보다 호출 문맥, 임계영역 길이, sleep 가능 여부를 먼저 고정하기 위한 기준입니다. 락 자체보다 락 순서와 해제 경로 대칭성을 함께 확인해야 교착과 경합(Contention) 회귀를 줄일 수 있습니다.

기술 문서: 스핀락, 뮤텍스, 읽기-쓰기 락, Lockdep 문서에서 각 락의 내부 구현과 디버깅을 확인하세요.
락 선택 의사결정 트리 임계영역에서 sleep이 필요한가? No → spinlock 계열 짧은 atomic 보호 Yes → mutex 계열 sleep 가능 상호배제 읽기 우세 → RCU 락리스 읽기 경로 IRQ 경쟁 → spin_lock_irqsave R/W 분리 → rw_semaphore 짧은 스냅샷 → seqlock 필수: lockdep 활성화 + 락 획득 순서 문서화 + 임계영역 최소화
sleep 가능 여부가 spinlock/mutex 선택을 결정하고, 읽기 비율이 높으면 RCU/seqlock을 검토합니다.

실무 체크포인트: 새 락을 도입할 때 기존 락과의 획득 순서를 문서화하고 lockdep 경고 가능성을 사전에 점검하세요.

항목언제 사용흔한 실수대체/짝 API
mutex_locksleep 가능한 상호배제IRQ 문맥에서 호출mutex_unlock
spin_lock짧은 atomic 보호락 상태에서 sleepspin_unlock
spin_lock_irqsave인터럽트 경쟁 동시 보호flags 복원 누락spin_unlock_irqrestore
rwlock_t읽기 많고 쓰기 적은 경로기아(Starvation) 상태 무시rw_semaphore
down_readrw_semaphore 읽기 락락 순서 역전up_read
down_writerw_semaphore 쓰기 락긴 임계영역up_write
rcu_read_lock락리스 읽기 보호sleep 호출rcu_read_unlock
synchronize_rcuRCU grace period 대기핫패스에서 사용call_rcu
seqlock_t짧은 쓰기, 긴 읽기읽기 재시도 누락seqcount_t
completion일회성 이벤트 동기화재초기화 누락wait_event

핵심 | 원자 연산/배리어

원자 연산과 배리어는 "동작한다"가 아니라 "가시성 계약이 맞다"를 검증해야 하는 영역입니다. `READ_ONCE`/`WRITE_ONCE`는 컴파일러 최적화(Compiler Optimization) 제어이고, 순서 보장(Ordering)은 acquire/release 또는 메모리 배리어(Memory Barrier)가 담당한다는 역할 분리가 핵심입니다. 락리스 코드에서는 재시도 루프와 수명주기 조건을 반드시 함께 설계해야 합니다.

실무 체크포인트: 락리스 변경에는 쌍이 되는 acquire/release 지점을 코드 리뷰 항목으로 고정하세요.

항목언제 사용흔한 실수대체/짝 API
atomic_readatomic_t 값 조회동기화 의미 과대평가READ_ONCE
atomic_setatomic_t 값 설정초기화 이후 경쟁 고려 누락WRITE_ONCE
atomic_inc카운터 증가오버플로 무시refcount_inc
atomic_dec_and_test0 도달 판단수명주기와 분리 사용refcount_dec_and_test
refcount_inc_not_zeroUAF 방지 참조 획득반환값 무시kref_get_unless_zero
READ_ONCE컴파일러 재정렬 억제배리어 대체로 오해smp_load_acquire
WRITE_ONCE단일 저장 보장게시 순서 보장으로 오해smp_store_release
smp_mb양방향 메모리 장벽불필요한 과사용smp_rmb, smp_wmb
smp_load_acquire획득 의미 읽기쌍 API 누락smp_store_release
cmpxchg락리스 CAS 갱신루프 재시도 누락try_cmpxchg
/* refcount_t 안전 패턴 — atomic_t 대신 사용 */
struct demo_conn {
    refcount_t refcnt;
    struct rcu_head rcu;
    char name[32];
};

static struct demo_conn *demo_conn_get(struct demo_conn *c)
{
    if (c && refcount_inc_not_zero(&c->refcnt))
        return c;
    return NULL;
}

static void demo_conn_release(struct rcu_head *rcu)
{
    struct demo_conn *c = container_of(rcu, struct demo_conn, rcu);
    kfree(c);
}

static void demo_conn_put(struct demo_conn *c)
{
    if (refcount_dec_and_test(&c->refcnt))
        call_rcu(&c->rcu, demo_conn_release);
}

/* acquire/release 쌍 — 락리스 게시 패턴 */
static struct config *shared_cfg;

/* 게시자 */
static void demo_publish_config(struct config *new_cfg)
{
    /* new_cfg 필드를 먼저 채운 후 release로 게시 */
    smp_store_release(&shared_cfg, new_cfg);
}

/* 소비자 */
static struct config *demo_read_config(void)
{
    return smp_load_acquire(&shared_cfg);
}

/* try_cmpxchg 락리스 갱신 루프 */
static void demo_atomic_max(atomic_t *v, int new_val)
{
    int old = atomic_read(v);

    while (old < new_val) {
        if (try_cmpxchg(&v->counter, &old, new_val))
            break;
        /* old는 try_cmpxchg가 현재 값으로 갱신 */
    }
}

핵심 | 스케줄링/대기

대기와 스케줄링 API는 CPU 사용률보다 깨어남 조건의 정확성이 먼저입니다. 대기 큐(Wait Queue), 타임아웃, 인터럽트 가능 여부를 혼동하면 간헐적 행과 응답 지연이 발생합니다. 이 섹션은 "어떻게 기다릴지"보다 "누가 어떤 조건에서 깨우는지"를 먼저 고정하는 용도로 읽어야 합니다.

실무 체크포인트: wait 계열 호출에는 대응되는 wake 경로와 종료 조건을 같은 패치(Patch) 안에서 함께 제시하세요.

기술 문서: 스케줄러, 대기 큐 문서에서 CFS/EEVDF 정책과 대기 메커니즘 상세를 확인하세요.
항목언제 사용흔한 실수대체/짝 API
schedule명시적 스케줄 포인트락 보유 상태 호출cond_resched
cond_resched긴 루프에서 자발 양보(Yield)atomic 문맥에서 사용might_resched
msleep밀리초 단위 sleep정밀 타이밍 기대usleep_range
usleep_range마이크로초 단위 sleep범위 과도하게 좁힘fsleep
wait_event조건 대기조건식에 락 규약 누락wake_up
wait_event_interruptible시그널(Signal) 중단 가능한 대기반환값 무시wait_event_killable
wake_up대기 큐 깨우기(Wakeup)상태 변경 전에 호출wake_up_interruptible
kthread_run커널 스레드(Kernel Thread) 생성+실행정지 경로 누락kthread_create
kthread_should_stop스레드(Thread) 종료 조건 확인루프에 미반영kthread_stop
kthread_stop스레드 종료 요청+join자기 자신에서 호출complete_and_exit
/* wait_event + wake_up 조건 대기 패턴 */
static DECLARE_WAIT_QUEUE_HEAD(demo_wq);
static bool data_ready;

/* 생산자 — 데이터 준비 후 깨우기 */
static void demo_produce(void)
{
    /* ... 데이터 채움 ... */
    WRITE_ONCE(data_ready, true);
    wake_up_interruptible(&demo_wq);
}

/* 소비자 — 인터럽트 가능 대기 */
static int demo_consume(void)
{
    int ret;

    ret = wait_event_interruptible(demo_wq,
                                   READ_ONCE(data_ready));
    if (ret)
        return -ERESTARTSYS;  /* 시그널로 중단됨 */

    WRITE_ONCE(data_ready, false);
    /* ... 데이터 소비 ... */
    return 0;
}

/* completion — 초기화 완료 동기화 */
static DECLARE_COMPLETION(hw_init_done);

static int hw_init_thread(void *arg)
{
    /* 하드웨어 초기화 수행 */
    msleep(200);
    complete(&hw_init_done);
    return 0;
}

static int demo_wait_hw(void)
{
    unsigned long timeout;

    timeout = wait_for_completion_timeout(&hw_init_done,
                                          msecs_to_jiffies(5000));
    if (!timeout) {
        pr_err("hw init timed out\n");
        return -ETIMEDOUT;
    }
    return 0;
}

/* kthread 완전 수명주기 */
static struct task_struct *demo_task;

static int demo_kthread_fn(void *data)
{
    while (!kthread_should_stop()) {
        if (wait_event_interruptible(demo_wq,
                READ_ONCE(data_ready) || kthread_should_stop()))
            continue;
        if (kthread_should_stop())
            break;
        /* 작업 수행 */
        WRITE_ONCE(data_ready, false);
    }
    return 0;
}

static int __init demo_start(void)
{
    demo_task = kthread_run(demo_kthread_fn, NULL, "demo_worker");
    return IS_ERR(demo_task) ? PTR_ERR(demo_task) : 0;
}

static void __exit demo_stop(void)
{
    if (demo_task)
        kthread_stop(demo_task);  /* 스레드 종료 대기 */
}

핵심 | 모듈/디바이스 수명주기

모듈/디바이스 수명주기 API는 등록 성공보다 정리 순서의 일관성이 중요합니다. probe/remove, init/exit, get/put 경계가 어긋나면 언로드 실패와 참조 누수가 장기적으로 누적됩니다. 표를 볼 때는 "생성 경로"와 "실패 경로"를 항상 쌍으로 확인해야 합니다.

devm 관리형 리소스 수명주기 probe 진입 devm_* 리소스 할당 정상 동작 devres 스택에 누적 remove/에러 devres 역순 해제 자동 정리 수동 free 불필요 할당 순서: devm_kzalloc → devm_ioremap_resource → devm_request_irq → devm_clk_get_enabled 해제 순서(자동 역순): clk_disable → free_irq → iounmap → kfree 금지: devm_kzalloc으로 할당 후 수동 kfree 호출 → 이중 해제 위험 원칙: 한 함수 내에서 devm과 수동 관리를 혼용하지 않는다
devm 리소스는 probe에서 할당한 역순으로 자동 해제되므로 수동 free를 혼용하면 이중 해제(Double Free)가 발생합니다.

실무 체크포인트: `devm_*` 사용 범위를 먼저 결정하고 수동 해제 API와 혼용하지 않도록 경계를 명확히 하세요.

항목언제 사용흔한 실수대체/짝 API
module_init모듈 진입점(Entry Point) 등록오류 unwind 누락module_exit
module_exit모듈 정리 경로 등록등록 해제 순서 역전__exit
MODULE_LICENSE라이선스 선언누락으로 taint 발생MODULE_AUTHOR
module_param모듈 파라미터 노출권한 설정 부정확module_param_named
devm_kzalloc디바이스 관리형 메모리수동 해제 혼용kzalloc
devm_request_irq관리형 IRQ 등록핸들러(Handler) 공유 플래그 누락request_irq
platform_get_resource리소스 조회존재 가정devm_platform_ioremap_resource
devm_ioremap_resourceMMIO 매핑(Mapping)+검증ERR_PTR 검사 누락ioremap
request_irqIRQ 핸들러 등록free_irq 누락devm_request_irq
free_irqIRQ 해제잘못된 dev_id 전달synchronize_irq
/* module_param — 런타임 파라미터 노출 */
static int debug_level = 0;
module_param(debug_level, int, 0644);
MODULE_PARM_DESC(debug_level, "Debug verbosity (0=off, 1=basic, 2=verbose)");

static char *device_name = "demo";
module_param(device_name, charp, 0444);
MODULE_PARM_DESC(device_name, "Device name prefix");

/* 사용: insmod demo.ko debug_level=2 device_name=mydev */
/* sysfs: /sys/module/demo/parameters/debug_level (읽기+쓰기) */
/* sysfs: /sys/module/demo/parameters/device_name (읽기 전용) */

/* devm_* 리소스 관리 — 수동 해제 불필요 */
static int demo_all_devm(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    struct demo_priv *p;
    void __iomem *base;
    struct clk *clk;

    /* 모든 할당이 devm — probe 실패 시 자동 역순 해제 */
    p = devm_kzalloc(dev, sizeof(*p), GFP_KERNEL);       /* ① */
    base = devm_platform_ioremap_resource(pdev, 0);    /* ② */
    clk = devm_clk_get_enabled(dev, "core");            /* ③ */

    /* ③ 실패 시: ②+① 자동 해제 */
    /* remove 시: ③→②→① 역순 자동 해제 */

    if (!p)
        return -ENOMEM;
    if (IS_ERR(base))
        return PTR_ERR(base);
    if (IS_ERR(clk))
        return PTR_ERR(clk);

    return 0;
}

핵심 | 타이머(Timer)/워크큐

타이머와 워크큐는 비동기 실행의 편의성만큼 종료 시점 관리가 어렵습니다. 스케줄링 시점보다 취소, 동기 삭제, flush 순서를 먼저 설계해야 use-after-free를 예방할 수 있습니다. 특히 자기 워커 문맥에서 동기 취소 호출 여부를 반드시 검토해야 합니다.

타이머 / 워크큐 수명주기 초기화 timer_setup 스케줄 mod_timer 콜백 실행 (softirq 문맥) 동기 삭제 del_timer_sync 재스케줄 (mod_timer in callback) 초기화 INIT_WORK 큐잉 queue_work 핸들러 실행 (프로세스 문맥) 동기 취소 cancel_work_sync 핵심: 객체 해제 전 반드시 del_timer_sync / cancel_work_sync 호출
타이머(softirq)와 워크큐(프로세스 문맥)는 실행 문맥이 다르지만, 객체 해제 전 동기 삭제가 필수라는 점은 동일합니다.

실무 체크포인트: 객체 해제 전 `del_timer_sync`/`cancel_*_sync`/`flush_*` 순서를 문서로 고정하세요.

항목언제 사용흔한 실수대체/짝 API
timer_setup타이머 초기화컨텍스트 제약 무시hrtimer_init
mod_timer타이머 재스케줄jiffies 변환 누락add_timer
del_timer_sync안전한 타이머 제거락 순서 역전timer_shutdown_sync
INIT_WORKwork_struct 초기화스택 객체 사용INIT_DELAYED_WORK
schedule_work기본 워크큐 비동기 실행중복 스케줄 오해queue_work
queue_work특정 워크큐 실행큐 수명주기 미관리flush_workqueue
queue_delayed_work지연 실행취소 경로 누락mod_delayed_work
cancel_work_sync워크 취소+완료 대기자기 워커에서 호출flush_work
flush_workqueue큐 내 작업 drain호출 빈도 과다drain_workqueue
alloc_workqueue전용 워크큐 생성플래그 선택 부정확system_wq
/* 타이머 완전 패턴 — 주기적 폴링 + 안전 종료 */
struct demo_poller {
    struct timer_list timer;
    void __iomem *base;
    bool active;
};

static void demo_poll_cb(struct timer_list *t)
{
    struct demo_poller *p = from_timer(p, t, timer);
    u32 status;

    status = readl(p->base + STATUS_REG);
    if (status & ERROR_BIT)
        pr_warn("hw error detected: 0x%x\n", status);

    /* 재스케줄: 100ms 후 */
    if (READ_ONCE(p->active))
        mod_timer(&p->timer, jiffies + msecs_to_jiffies(100));
}

static void demo_poll_start(struct demo_poller *p)
{
    WRITE_ONCE(p->active, true);
    timer_setup(&p->timer, demo_poll_cb, 0);
    mod_timer(&p->timer, jiffies + msecs_to_jiffies(100));
}

static void demo_poll_stop(struct demo_poller *p)
{
    WRITE_ONCE(p->active, false);
    del_timer_sync(&p->timer);  /* 콜백 완료까지 대기 */
}

/* hrtimer — 나노초 정밀 타이머 */
static struct hrtimer demo_hrt;

static enum hrtimer_restart demo_hrt_cb(struct hrtimer *timer)
{
    /* 1ms 주기 작업 */
    do_periodic_work();
    hrtimer_forward_now(timer, ms_to_ktime(1));
    return HRTIMER_RESTART;
}

static void demo_hrt_init(void)
{
    hrtimer_init(&demo_hrt, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
    demo_hrt.function = demo_hrt_cb;
    hrtimer_start(&demo_hrt, ms_to_ktime(1), HRTIMER_MODE_REL);
}

static void demo_hrt_exit(void)
{
    hrtimer_cancel(&demo_hrt);
}

핵심 | 디버깅(Debugging)/트레이싱

디버깅 API는 문제를 빨리 찾기 위한 도구이지만 잘못 쓰면 시스템 자체를 불안정하게 만듭니다. 임시 추적, 상시 관측, 운영 경보를 목적별로 분리하고 오버헤드(Overhead) 상한을 먼저 정해야 합니다. 이 표는 계측 정확도와 운영 안전성의 균형을 맞추는 기준으로 사용해야 합니다.

실무 체크포인트: `trace_printk` 같은 임시 도구는 리뷰 단계에서 반드시 제거 여부를 체크리스트로 확인하세요.

항목언제 사용흔한 실수대체/짝 API
dump_stack즉시 호출 경로 확인핫패스 상시 호출WARN_ON
trace_printkftrace 버퍼 임시 추적운영 코드 잔존trace_event
tracepoint구조화된 이벤트 관측필드 ABI 무시ftrace
DEFINE_DYNAMIC_DEBUG_METADATA동적 디버그 제어정적 로그와 혼용pr_debug
lockdep_assert_held락 보유 검증락 클래스 오인might_lock
CONFIG_KASAN 계열메모리 오류 탐지 계측운영 커널에 무분별 적용KFENCE
ftrace_set_filter함수 추적 범위 축소전체 추적으로 오버헤드 증가set_ftrace_notrace
register_trace_* tracepoint 핸들러 등록unregister 누락tracepoint_probe_register
dynamic_pr_debug런타임 디버그 토글포맷 문자열 불일치pr_debug
panic복구 불가 오류 처리복구 가능 에러에서 사용BUG, WARN
/* 디버깅 API 실전 사용 패턴 */

/* lockdep 검증 — 함수 진입 시 락 보유 확인 */
static void demo_update_locked(struct demo_dev *d, u32 val)
{
    lockdep_assert_held(&d->lock);  /* 락 미보유 시 경고 */
    d->reg_val = val;
    writel(val, d->base + DATA_REG);
}

/* WARN_ON_ONCE — 불변식 위반 1회 경고 */
static void demo_enqueue(struct demo_queue *q, struct demo_item *item)
{
    if (WARN_ON_ONCE(q->count >= q->capacity)) {
        /* 복구 가능 — 경고만 남기고 리턴 */
        return;
    }
    q->items[q->count++] = item;
}

/* tracepoint 정의 — include/trace/events/demo.h */
/*
 * TRACE_EVENT(demo_rx_packet,
 *     TP_PROTO(struct sk_buff *skb, int len),
 *     TP_ARGS(skb, len),
 *     TP_STRUCT__entry(
 *         __field(int, len)
 *         __string(dev, skb->dev->name)
 *     ),
 *     TP_fast_assign(
 *         __entry->len = len;
 *         __assign_str(dev, skb->dev->name);
 *     ),
 *     TP_printk("dev=%s len=%d", __get_str(dev), __entry->len)
 * );
 */

/* dump_stack — 호출 경로 즉시 확인 (디버그 전용) */
if (unexpected_state)
    dump_stack();  /* dmesg에 call trace 출력 */

핵심 | 심볼 Export/네임스페이스

심볼 export는 코드 재사용 편의가 아니라 모듈 간 ABI 계약을 외부에 공개하는 행위입니다. 한 번 노출된 인터페이스는 유지 비용이 커지므로, 최소 공개 원칙과 네임스페이스 정책을 동시에 적용해야 합니다. 이 표는 "export 가능 여부"보다 "내부화 가능성"을 먼저 검토하는 흐름으로 읽어야 합니다.

실무 체크포인트: 새 export에는 사용 모듈, 제거 불가능성, 네임스페이스 선택 근거를 커밋 메시지에 함께 남기세요.

더 깊게 보기: 이 섹션은 빠른 API 레퍼런스입니다. 커널 심볼(Kernel Symbol) 문서는 심볼 종류, build/runtime 해석, kallsyms, CRC, GPL/namespace 정책, 장애 대응 순서를 포함한 독립 가이드입니다.
항목언제 사용흔한 실수대체/짝 API
EXPORT_SYMBOL모듈 공용 API 공개내부 구현까지 공개EXPORT_SYMBOL_GPL
EXPORT_SYMBOL_GPLGPL 전용 API 공개라이선스 영향 미고려EXPORT_SYMBOL
EXPORT_SYMBOL_NS네임스페이스 포함 exportimport 누락MODULE_IMPORT_NS
EXPORT_SYMBOL_NS_GPLGPL+네임스페이스 export네임 충돌 방치EXPORT_SYMBOL_NS
MODULE_IMPORT_NS외부 네임스페이스 사용 선언문자열 오타depends 관리
symbol_get선택적 심볼 참조put 누락symbol_put
symbol_putsymbol_get 참조 반환중복 호출try_module_get
THIS_MODULE모듈 참조 카운트 연계NULL 가정module_put
try_module_get모듈 언로드 방지 참조 획득실패 경로 무시module_put
module_put모듈 참조 반납언밸런스 카운트try_module_get

핵심 | 버전/호환성 보조 매크로

호환성 매크로는 분기문 추가 도구가 아니라 유지보수 비용 제어 장치입니다. 버전 비교, Kconfig 조건, 컴파일 타임 검증을 혼용하면 백포트와 장기 유지에서 오류가 누적됩니다. 이 표는 런타임 분기보다 빌드 타임 계약을 우선 고정하는 기준으로 사용해야 합니다.

실무 체크포인트: 버전 조건을 추가할 때는 제거 시점과 대상 안정 커널 범위를 주석에 함께 기록하세요.

항목언제 사용흔한 실수대체/짝 API
KERNEL_VERSION버전 비교 상수 생성런타임 검사와 혼용LINUX_VERSION_CODE
LINUX_VERSION_CODE빌드 대상 커널 버전 조건백포트 정책 무시IS_ENABLED
IS_ENABLEDKconfig y/m 조건 분기#ifdef 과잉 사용IS_BUILTIN
IS_BUILTINbuilt-in 여부 분기모듈 경로 무시IS_MODULE
IS_MODULE모듈 빌드 조건 분기런타임 로직에 사용MODULE
BUILD_BUG_ON컴파일 타임 불변식 검사런타임 값 전달static_assert
static_assert표준 정적 검증메시지 누락BUILD_BUG_ON
FIELD_PREP레지스터(Register) 비트필드 설정마스크 불일치FIELD_GET
FIELD_GET비트필드 추출정수 폭 미스매치GENMASK
GENMASK비트 마스크 생성상하 비트 순서 실수BIT
/* 버전/호환성 매크로 실전 사용 */

/* IS_ENABLED — Kconfig 조건 분기 (if문, #ifdef 불필요) */
static void demo_optional_feature(void)
{
    if (IS_ENABLED(CONFIG_DEMO_ADVANCED)) {
        /* CONFIG_DEMO_ADVANCED=y 또는 =m 일 때만 실행 */
        enable_advanced_mode();
    }
    /* 비활성 시 컴파일러가 dead code 제거 — 링크 에러 없음 */
}

/* BUILD_BUG_ON — 컴파일 타임 불변식 검사 */
BUILD_BUG_ON(sizeof(struct demo_msg) != 64);
/* 구조체 크기가 64가 아니면 빌드 실패 */

static_assert(sizeof(u64) == 8,
             "u64 must be 8 bytes");

/* GENMASK / BIT / FIELD_PREP / FIELD_GET — 레지스터 비트 조작 */
#define REG_MODE_MASK   GENMASK(7, 4)    /* 비트 7:4 */
#define REG_ENABLE_BIT  BIT(0)           /* 비트 0 */

static void demo_set_mode(void __iomem *base, u32 mode)
{
    u32 val = readl(base + CTRL_REG);

    val &= ~REG_MODE_MASK;
    val |= FIELD_PREP(REG_MODE_MASK, mode);
    val |= REG_ENABLE_BIT;

    writel(val, base + CTRL_REG);
}

static u32 demo_get_mode(void __iomem *base)
{
    u32 val = readl(base + CTRL_REG);
    return FIELD_GET(REG_MODE_MASK, val);
}

핵심 | 핵심 묶음 해설

앞의 핵심 묶음 표는 "자주 쓰는 API 리스트"가 아니라 "실수 예방 인덱스"입니다. 실무에서는 다음 세 가지 축을 동시에 맞춰야 안정적인 코드가 됩니다.

즉, "컴파일이 된다"는 기준만으로는 충분하지 않습니다. "실패 경로가 닫혔는가", "문맥 위반이 없는가", "운영 중 원인 추적이 가능한가"까지 포함해 API를 선택해야 합니다.

핵심 | 도메인별 예제

핵심 항목을 실제 코드 흐름으로 묶는 예제입니다. 아래 3개 패턴은 드라이버 리뷰에서 자주 보는 전형적인 형태입니다.

예제 1: probe 경로 (devm + ERR_PTR + 로그)

static int demo_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    struct demo_dev *d;
    int irq, ret;

    d = devm_kzalloc(dev, sizeof(*d), GFP_KERNEL);
    if (!d)
        return -ENOMEM;

    irq = platform_get_irq(pdev, 0);
    if (irq < 0)
        return irq;

    ret = devm_request_irq(dev, irq, demo_irq, 0, "demo", d);
    if (ret)
        return ret;

    dev_info(dev, "demo probe complete\\n");
    return 0;
}

예제 2: 락리스 플래그 전달 (READ_ONCE/WRITE_ONCE)

static bool stop_flag;

static int worker_thread(void *arg)
{
    while (!READ_ONCE(stop_flag)) {
        do_one_job();
        cond_resched();
    }
    return 0;
}

static void stop_worker(void)
{
    WRITE_ONCE(stop_flag, true);
}

예제 3: 심볼 export + 네임스페이스

int demo_calc_crc(const void *buf, size_t len)
{
    if (!buf || !len)
        return -EINVAL;
    return crc32_le(~0U, buf, len);
}
EXPORT_SYMBOL_NS_GPL(demo_calc_crc, DEMO_CORE);

/* 소비 모듈 */
MODULE_IMPORT_NS(DEMO_CORE);

확장 | 컨텍스트별 API 선택 매트릭스

같은 함수라도 실행 문맥이 다르면 안전성이 달라집니다. 아래 표는 코드 리뷰에서 가장 먼저 확인해야 하는 "문맥-API 적합성" 규칙입니다.

실행 컨텍스트 계층과 API 제약 하드IRQ (sleep 금지, 최소 작업) spin_lock_irqsave / this_cpu_inc / GFP_ATOMIC / queue_work 소프트IRQ / BH (sleep 금지) spin_lock_bh / GFP_ATOMIC / napi_schedule / netif_receive_skb 프로세스 문맥 (sleep 가능) mutex_lock / GFP_KERNEL / copy_from_user / wait_event / msleep 사용자 문맥 (시스콜 진입) copy_to_user / access_ok / signal_pending / preemptible ← 제약 강함 제약 약함 →
안쪽 문맥은 바깥 문맥의 모든 API를 사용할 수 있지만, 바깥 문맥(하드IRQ)은 안쪽 API(sleep 등)를 사용할 수 없습니다.
문맥사용 가능주의 필요금지
프로세스 문맥 (sleep 가능)mutex_lock, kmalloc(GFP_KERNEL), copy_from_usermight_sleep로 경계 점검해당 없음
소프트IRQspin_lock_bh, kmalloc(GFP_ATOMIC)NAPI 경합 구간 최소화mutex_lock, msleep
하드IRQspin_lock_irqsave, this_cpu_inc핸들러는 짧게 유지copy_to_user, schedule
타이머 콜백(Callback)queue_work, mod_timer공유 데이터 락 보호mutex_lock, usleep_range
RCU read-sidercu_dereference, READ_ONCE포인터 수명 보장블로킹 API 전반
워크큐 핸들러mutex_lock, flush_work, kvzalloc장시간 작업 분할IRQ 전용 가정 코드
kthread 루프wait_event_interruptible, kthread_should_stop종료 경로 보장무한 busy loop
atomic sectionspin_lock, atomic_inc임계영역 최소화GFP_KERNEL, 모든 sleep API

확장 | GFP 플래그 실전 카탈로그

메모리 할당 실패의 절반은 플래그 선택 오류에서 시작됩니다. 아래는 실무에서 반복되는 결정표입니다.

sleep 가능한 문맥인가? Yes → GFP_KERNEL 기본 No → GFP_ATOMIC 기본 FS 재진입 방지? → GFP_NOFS IO 재진입 방지? → GFP_NOIO 실패 허용 가능? → GFP_NOWAIT 공통: 실패 경로 설계 필수 + __GFP_NOWARN은 예상된 실패에만 No Yes
GFP 플래그 선택은 sleep 가능성 → 재진입 방지 필요성 → 실패 허용 여부 순서로 결정합니다.
플래그의미대표 사용처자주 나는 실수
GFP_KERNELsleep 허용, 기본 reclaimprobe/remove, 파일시스템 일반 경로IRQ 문맥에서 사용
GFP_ATOMICsleep 금지, 비상 풀 사용IRQ/softirq 핸들러대형 버퍼 반복 할당
GFP_NOWAIT즉시 실패, reclaim 최소화지연 민감 경로실패 경로 설계 누락
GFP_NOIOIO 재진입 방지블록 계층 reclaim 경로일반 경로에서 남용
GFP_NOFS파일시스템 재진입 방지FS 락 보유 경로필요 없는 전역 적용
__GFP_ZERO0 초기화민감 구조체 초기값 보장성능 경로에서 불필요 사용
__GFP_HIGH우선순위(Priority) 높은 할당핵심 네트워크 버퍼일반 경로 확장 남용
__GFP_NOWARN경고 로그 억제예상 가능한 실패 경로원인 추적 포인트 삭제
__GFP_NORETRY강한 reclaim 회피실패 허용 가능한 캐시필수 경로에서 사용
__GFP_RETRY_MAYFAIL재시도 후 실패 허용큰 버퍼, 희소 경로무조건 성공으로 가정
__GFP_COMP복합 페이지 표시hugepage 관련 버퍼해제 API 혼동
__GFP_DMA32DMA32 존 요구32비트 DMA 장치dma_mask 설정 없이 사용
/* 컨텍스트별 API 사용 예시 비교 */

/* 1. 프로세스 문맥 — sleep 가능 */
mutex_lock(&dev->lock);
buf = kmalloc(size, GFP_KERNEL);
if (!buf) { mutex_unlock(&dev->lock); return -ENOMEM; }
mutex_unlock(&dev->lock);

/* 2. 하드IRQ — sleep 금지, 최소 작업 */
static irqreturn_t demo_isr(int irq, void *data)
{
    struct demo_dev *d = data;
    u32 status = readl(d->regs + REG_STATUS);

    if (!(status & IRQ_PENDING))
        return IRQ_NONE;

    writel(status, d->regs + REG_ACK);
    this_cpu_inc(d->stats->irqs);
    queue_work(d->wq, &d->rx_work);   /* 무거운 처리는 워크큐로 */
    return IRQ_HANDLED;
}

/* 3. 소프트IRQ (NAPI) — sleep 금지, atomic 할당만 */
spin_lock(&ring->lock);
skb = netdev_alloc_skb(ndev, len);  /* GFP_ATOMIC 내부 */
spin_unlock(&ring->lock);

확장 | 락 선택 매트릭스

락 선택 매트릭스는 성능 최적화 표가 아니라 실패 모드 비교표입니다. 각 락의 장점보다 우선해서, 어떤 워크로드에서 기아·경합·지연이 발생하는지를 먼저 확인해야 합니다. 선택 이후에는 락 교체 기준과 관측 지표를 함께 정의해야 회귀를 빠르게 감지할 수 있습니다.

실무 체크포인트: 락 변경 패치에는 최소 하나의 경합 지표와 회귀 기준을 함께 첨부하세요.

락/기법강점약점적합한 워크로드대체안
mutex단순, 디버깅 용이sleep 불가 문맥 미사용드라이버 제어 경로ww_mutex
spinlockatomic 경로 보호오래 잡으면 지연 증가IRQ 공유 자료raw_spinlock
rw_semaphore읽기 병렬성쓰기 기아 가능성읽기 우세 메타데이터seqlock
seqlock/seqcount읽기 경로 빠름재시도 루프 필요짧은 스냅샷 데이터RCU
RCU락리스 읽기수명주기 설계 복잡읽기 압도적 워크로드srcu
SRCUsleep 가능한 read-side오버헤드 큼긴 콜백 기반 경로mutex
percpu_rwsem읽기 스케일링 우수쓰기 비용 큼CPU 로컬 fast pathrw_semaphore
completion이벤트 동기화 간결다회성 설계에 약함초기화 완료 신호wait_event
waitqueue조건 대기 표현력조건/락 연동 실수상태 기반 대기completion
atomic/refcount락 없이 카운팅복합 상태 보호 불가참조 카운트kref
/* spinlock + IRQ 보호 패턴 — 프로세스+인터럽트 공유 데이터 */
struct demo_ring {
    spinlock_t lock;
    u32 head, tail;
    u8 buf[256];
};

/* IRQ 핸들러: spin_lock만 (IRQ는 이미 비활성) */
static irqreturn_t demo_irq_handler(int irq, void *dev_id)
{
    struct demo_ring *ring = dev_id;

    spin_lock(&ring->lock);
    ring->buf[ring->head++ & 0xFF] = inb(0x60);
    spin_unlock(&ring->lock);
    return IRQ_HANDLED;
}

/* 프로세스 문맥: spin_lock_irqsave 필수 (IRQ와 경쟁) */
static ssize_t demo_read(struct file *f, char __user *ubuf,
                         size_t cnt, loff_t *off)
{
    struct demo_ring *ring = f->private_data;
    unsigned long flags;
    u8 byte;

    spin_lock_irqsave(&ring->lock, flags);
    if (ring->head == ring->tail) {
        spin_unlock_irqrestore(&ring->lock, flags);
        return 0;
    }
    byte = ring->buf[ring->tail++ & 0xFF];
    spin_unlock_irqrestore(&ring->lock, flags);

    if (copy_to_user(ubuf, &byte, 1))
        return -EFAULT;
    return 1;
}

/* mutex 일반 패턴 — sleep 가능 드라이버 제어 경로 */
static DEFINE_MUTEX(demo_cfg_lock);

static int demo_update_config(struct demo_dev *d, u32 new_val)
{
    mutex_lock(&demo_cfg_lock);
    d->config_val = new_val;
    writel(new_val, d->base + CFG_REG);
    (void)readl(d->base + CFG_REG);  /* posted write flush */
    mutex_unlock(&demo_cfg_lock);
    return 0;
}

/* seqlock 읽기 재시도 패턴 */
static seqlock_t demo_seq;
static struct timespec64 demo_ts;

static struct timespec64 demo_get_ts(void)
{
    struct timespec64 ts;
    unsigned seq;

    do {
        seq = read_seqbegin(&demo_seq);
        ts = demo_ts;
    } while (read_seqretry(&demo_seq, seq));
    return ts;
}

확장 | 반환값 계약 카탈로그

패턴성공 값실패 값검사 방식대표 API
정수 errno0 또는 양수음수 errnoif (ret)request_irq, clk_prepare_enable
포인터+ERR_PTR유효 포인터ERR_PTR(-Exxx)IS_ERR/PTR_ERRdevm_ioremap_resource
포인터+NULL유효 포인터NULLif (!ptr)kmalloc 일부 래퍼
길이 반환처리 바이트 수음수 errnoret < 0read/write 계열
bool 반환true/false별도 없음의미 문서 확인kthread_should_stop
0/1 의미1 성공0 실패API별 주석 확인try_module_get
반환 타입 확인 포인터 반환 IS_ERR / NULL 규약 확인 정수 반환 0 또는 -errno 처리 길이 반환 ret < 0 에러 분기 실패 경로 및 로그 형식 통일
반환 타입별 분기 규칙을 고정하면 오류 처리 코드의 일관성과 리뷰 속도가 크게 향상됩니다.

실무 체크포인트: 포인터 반환 API는 함수 주석에 NULL 또는 ERR_PTR 규약을 명시하고 시작하세요.

반환값 규약 경고: 포인터 API를 일괄적으로 if (!ptr)로 처리하면 ERR_PTR를 놓칩니다. 호출 전 해당 API가 NULL 계열인지 ERR_PTR 계열인지 먼저 문서화하세요.

확장 | 비차단(Non-blocking) 경로 전용 API

비차단 경로는 평균 성능보다 최악 지연을 제한하는 것이 우선 목표입니다. 따라서 "성공률을 높이는 API"보다 "실패를 빠르게 표면화하는 API"를 선택해야 합니다. 표의 각 항목은 대체 경로 설계를 포함해야 의미가 있으므로 fallback 없는 사용은 금지에 가깝게 다뤄야 합니다.

실무 체크포인트: non-blocking 경로에는 실패 시 defer할 큐 또는 재시도 정책을 코드로 명시하세요.

분류권장 API피해야 할 API비고
메모리kmalloc(GFP_ATOMIC)kvzalloc(GFP_KERNEL)실패 대비 fallback 필수
spin_trylockmutex_lock실패 시 지연 큐로 이관
로그tracepoint, pr_debug_ratelimited대량 pr_info폭주 로그 차단
대기사용 금지 원칙wait_event, schedule_timeout워크큐로 defer
복사사전 pin + 사후 처리copy_from_user 직접 호출문맥별 예외 점검
/* 비차단 경로 — GFP_ATOMIC + 실패 defer 패턴 */
static void demo_irq_alloc(struct demo_dev *d, size_t len)
{
    struct demo_buf *buf;

    buf = kmalloc(sizeof(*buf) + len, GFP_ATOMIC);
    if (!buf) {
        /* 할당 실패 — 워크큐로 지연 처리 */
        atomic_inc(&d->alloc_fails);
        schedule_work(&d->retry_work);
        return;
    }
    buf->len = len;
    list_add_tail(&buf->node, &d->rx_list);
}

/* spin_trylock 비차단 락 — 실패 시 후처리 큐잉 */
static void demo_try_update(struct demo_dev *d)
{
    if (!spin_trylock(&d->stats_lock)) {
        /* 락 획득 실패 — per-CPU 카운터에 임시 저장 */
        this_cpu_inc(d->pending_updates);
        return;
    }
    d->stats.total += this_cpu_read(d->pending_updates);
    this_cpu_write(d->pending_updates, 0);
    spin_unlock(&d->stats_lock);
}

확장 | 핵심 API 소스 경로 맵

소스 경로 맵은 API 사용법보다 구현 근거를 빠르게 찾기 위한 역추적(Backtrace) 인덱스입니다. 리뷰에서 의견이 갈리면 표의 헤더와 구현 경로를 동시에 열어 계약과 실제 동작을 교차 확인해야 합니다. 특히 인라인 헤더 매크로는 호출부만 보면 의미가 왜곡되기 쉬워 원본 정의 확인이 필수입니다.

실무 체크포인트: 동작 논쟁이 발생하면 헤더 선언과 구현 경로를 함께 링크해 근거 기반으로 결론을 내리세요.

주제주요 헤더핵심 구현 경로리뷰 시 확인 포인트
에러 포인터include/linux/err.hinclude/linux/err.hIS_ERR 누락 여부
메모리 할당include/linux/slab.hmm/slub.c, mm/page_alloc.cGFP 플래그 적합성
리스트include/linux/list.h헤더 인라인 중심순회 중 삭제 안전성
include/linux/spinlock.hkernel/locking/*락 순서/문맥
RCUinclude/linux/rcupdate.hkernel/rcu/*grace period 비용
워크큐include/linux/workqueue.hkernel/workqueue.c취소/flush 경로
타이머include/linux/timer.hkernel/time/timer.c동기 삭제 필요성
스케줄링include/linux/sched.hkernel/sched/*sleep 가능성
모듈include/linux/module.hkernel/module/*export 범위 최소화
트레이싱include/linux/tracepoint.hkernel/trace/*이벤트 ABI 안정성

확장 | 고밀도 실전 스니펫

고밀도 스니펫은 복붙 예제가 아니라 실패 경로 템플릿을 압축한 참고 코드입니다. 각 스니펫은 정상 경로보다 unwind, 참조 반납, 동기화 종료 조건을 보여주는 데 목적이 있습니다. 적용 시에는 변수명보다 제어 흐름과 정리 순서를 우선 이식해야 합니다.

실무 체크포인트: 스니펫 적용 후에는 의도적으로 중간 실패를 주입해 unwind 경로가 닫히는지 먼저 검증하세요.

스니펫 1: 단계별 unwind 템플릿

static int demo_open(struct inode *inode, struct file *filp)
{
    struct demo_ctx *ctx;
    int ret;

    ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
    if (!ctx)
        return -ENOMEM;

    mutex_init(&ctx->lock);

    ret = alloc_ring(ctx);
    if (ret)
        goto err_destroy_lock;

    ret = register_fastpath(ctx);
    if (ret)
        goto err_free_ring;

    filp->private_data = ctx;
    return 0;

err_free_ring:
    free_ring(ctx);
err_destroy_lock:
    mutex_destroy(&ctx->lock);
err_free_ctx:
    kfree(ctx);
    return ret;
}

스니펫 2: RCU 리스트 갱신 패턴

void demo_insert(struct demo_node *n)
{
    spin_lock(&demo_lock);
    list_add_rcu(&n->link, &demo_head);
    spin_unlock(&demo_lock);
}

struct demo_node *demo_find(int id)
{
    struct demo_node *pos, *ret = NULL;

    rcu_read_lock();
    list_for_each_entry_rcu(pos, &demo_head, link) {
        if (READ_ONCE(pos->id) == id) {
            /* 예제 가정: demo_node는 refcount_t refcnt를 가진다 */
            if (refcount_inc_not_zero(&pos->refcnt))
                ret = pos;
            break;
        }
    }
    rcu_read_unlock();
    return ret;
}

확장 | 코드 리뷰 규칙

아래 규칙은 스타일 가이드가 아니라 장애 예방을 위한 최소 수용 기준입니다. 규칙 간 우선순위는 문맥 안전성, 수명주기 완결성, 관측 가능성 순서로 해석해야 합니다. 규칙 위반이 불가피한 경우에는 예외 근거와 복구 계획을 패치 설명에 반드시 포함해야 합니다.

실무 체크포인트: 리뷰 단계에서 규칙 위반 항목은 "후속 수정"으로 미루지 말고 같은 변경 집합에서 닫으세요.

확장 | 문제 유형별 즉시 대응 인덱스

문제 대응 인덱스는 원인 추정을 빠르게 좁히기 위한 초기 분류표입니다. 증상과 의심 지점을 1:1로 고정하지 말고, 표를 시작점으로 삼아 로그·트레이스·락 상태를 교차 확인해야 합니다. 특히 간헐 장애는 단일 도구 결과로 결론 내리지 않는 것이 중요합니다.

실무 체크포인트: 1차 대응에서 최소 두 가지 독립 증거를 확보한 뒤 수정 방향을 결정하세요.

증상1차 의심 지점확인 명령/도구우선 확인 API
Unknown symbol모듈 의존성/라이선스modinfo, cat /proc/kallsymsEXPORT_SYMBOL_GPL, MODULE_LICENSE
슬립(Sleep) 경고 (sleeping in atomic)문맥 오용dmesg, lockdepmutex_lock, might_sleep
간헐적 UAF참조 카운트/RCU 수명KASAN, KFENCErefcount_inc_not_zero, kfree_rcu
IRQ 폭주ack/마스킹 누락/proc/interruptsdisable_irq_nosync, napi_schedule
메모리 누수실패 경로 해제 누락kmemleakkfree, kvfree, devm_*
데드락락 순서 역전lockdep graphspin_lock_irqsave, mutex_lock
성능 회귀핫패스 로그/락 경합perf, ftracepr_debug, rcu_dereference

확장 | 네트워킹 카탈로그

네트워킹 경로는 패킷(Packet)당 수백 ns 단위 최적화가 필요하므로, API 선택 기준을 명확히 고정해야 합니다.

기술 문서: 네트워킹 개요, NAPI 문서에서 수신 경로 아키텍처를 함께 확인하세요.

skb 수명주기/조작

분류핵심 API/심볼언제 사용실수 패턴대체/보완
skb 수명alloc_skb패킷 버퍼 생성헤드룸 부족netdev_alloc_skb
skb 수명consume_skb참조 카운트 기반 해제kfree_skb와 혼용dev_kfree_skb_any
skb 수명skb_clone헤더 공유 복제쓰기 전 skb_cow 누락pskb_copy
헤더 조작skb_pull헤더 제거길이 검사 누락pskb_may_pull
헤더 조작skb_put테일 확장tailroom 오버런skb_tailroom

NAPI/수신 경로

분류핵심 API/심볼언제 사용실수 패턴대체/보완
NAPInapi_schedulepoll 트리거인터럽트 마스킹 누락__napi_schedule_irqoff
NAPInapi_complete_donepoll 종료work_done 불일치napi_gro_receive
RX 경로netif_receive_skb일반 수신 경로 진입softirq 문맥 혼동netif_rx

송신/큐 제어

분류핵심 API/심볼언제 사용실수 패턴대체/보완
TX 경로dev_queue_xmitL2 송신락 보유 상태로 호출sch_direct_xmit
큐 제어netif_stop_queueTX 큐 중지wake 누락netif_wake_queue
큐 제어netif_tx_stop_queue멀티큐 stop큐 인덱스 오류netif_tx_wake_queue

해시/체크섬/오프로드

분류핵심 API/심볼언제 사용실수 패턴대체/보완
해시skb_get_hashflow 분산seed 가정 오류skb_set_hash
체크섬(Checksum)skb_checksum_helpSW checksum 보조오프로드 플래그 불일치csum_partial
오프로드skb_is_gsoGSO 여부 판별세그먼트 크기 무시skb_gso_segment

XDP/소켓/라우팅/conntrack

분류핵심 API/심볼언제 사용실수 패턴대체/보완
XDPbpf_prog_run_xdpXDP 프로그램 실행리턴 코드 처리 누락xdp_do_redirect
XDPxdp_return_frameXDP 프레임 반환page_pool 연계 누락page_pool_put_page
소켓(Socket)sock_alloc_send_pskb소켓 송신 버퍼 생성GFP 문맥 부적합sk_stream_alloc_skb
타이머sk_reset_timer소켓 타이머 갱신락 순서 역전inet_csk_reset_xmit_timer
경로탐색ip_route_output_key_hashIPv4 라우팅(Routing)namespace 누락ip6_dst_lookup_flow
conntracknf_conntrack_find_getCT 조회참조 반납 누락nf_ct_put
/* NAPI poll 최소 패턴 */
static int demo_poll(struct napi_struct *napi, int budget)
{
    int work_done = 0;

    while (work_done < budget && rx_has_packet()) {
        struct sk_buff *skb = build_skb(rx_pop(), 0);
        if (!skb)
            break;
        napi_gro_receive(napi, skb);
        work_done++;
    }

    if (work_done < budget) {
        napi_complete_done(napi, work_done);
        enable_irq(demo_irq);
    }
    return work_done;
}

확장 | 파일시스템 카탈로그

파일시스템 코드는 락 계층, 페이지 캐시(Page Cache), writeback 경계가 복잡하므로 VFS 진입점과 address_space 연계를 함께 확인해야 합니다.

마이그레이션 추세: 신규 파일시스템은 generic_file_* 대신 iomap 기반 연산자를 사용하는 추세입니다. XFS는 이미 iomap 전환 완료, ext4도 점진적으로 전환 중입니다.

inode/dentry

분류핵심 API/심볼언제 사용실수 패턴대체/보완
inodenew_inode새 inode 할당초기 필드 누락alloc_inode_sb
inodeiget_lockedinode 캐시 조회/생성unlock_new_inode 누락ilookup
inodeiputinode 참조 반납이중 iputihold
dentryd_make_root루트 dentry 생성에러 처리 누락d_obtain_root
dentryd_addlookup 결과 연결음수 dentry 처리 미흡d_splice_alias

슈퍼블록/마운트

분류핵심 API/심볼언제 사용실수 패턴대체/보완
슈퍼블록(Superblock)mount_bdev블록 기반 마운트(Mount)kill_sb 누락mount_nodev

읽기/쓰기

분류핵심 API/심볼언제 사용실수 패턴대체/보완
파일 연산generic_file_read_iter기본 read_iter 구현i_size 경계 처리 누락filemap_read
파일 연산generic_file_write_iter기본 write_iter 구현락 순서 역전iomap_file_buffered_write

페이지 캐시/writeback

분류핵심 API/심볼언제 사용실수 패턴대체/보완
페이지 캐시filemap_get_foliofolio 조회/생성folio 잠금(Lock) 규약 위반filemap_grab_folio
페이지 캐시folio_mark_dirtydirty 설정writeback 태깅 누락set_page_dirty
writebackfilemap_fdatawritedirty 페이지 flush에러 전파 누락sync_inode_metadata
writebackfilemap_fdatawaitI/O 완료 대기timeout 설계 누락sync_filesystem
저널링(Journaling)jbd2_journal_start트랜잭션(Transaction) 시작핸들 종료 누락jbd2_journal_stop

디렉터리/권한/sync

분류핵심 API/심볼언제 사용실수 패턴대체/보완
디렉터리iterate_sharedreaddir 구현ctx pos 처리 오류dir_emit
권한inode_permission권한 점검idmap 무시generic_permission
링크vfs_link하드링크 생성nlink 갱신 누락simple_link
삭제vfs_unlinkunlink 공통 경로락 경계 위반simple_unlink
renamevfs_renamerename 공통 경로교차 디렉터리 규칙 누락lock_rename
fsnotifyfsnotify_modify수정 이벤트 알림이벤트 누락fsnotify_access
syncvfs_fsync_range부분 fsync데이터/메타 경계 오해vfs_fsync
/* 최소 VFS 연산자 골격 */
static const struct file_operations demo_fops = {
    .owner = THIS_MODULE,
    .read_iter = generic_file_read_iter,
    .write_iter = generic_file_write_iter,
    .mmap = generic_file_mmap,
    .llseek = generic_file_llseek,
    .fsync = generic_file_fsync,
};

확장 | 메모리 관리 카탈로그

메모리 관리 경로는 할당 정책, reclaim 압력, TLB/페이지 테이블(Page Table) 동기화까지 동시에 고려해야 합니다.

기술 문서: Memory Cgroup, OOM Killer 문서에서 메모리 제어와 OOM 정책을 함께 확인하세요.
folio 전환: 5.16+ 커널에서 struct page 기반 API를 struct folio 기반으로 전환하는 작업이 진행 중입니다. 신규 코드는 folio API 우선 사용을 권장합니다.

페이지 할당/해제

분류핵심 API/심볼언제 사용실수 패턴대체/보완
페이지 할당alloc_pages고차 페이지 필요order 과대 설정alloc_page
페이지 할당__get_free_pages연속 가상 주소(Virtual Address) 필요해제 API 불일치free_pages
페이지 해제__free_pagesorder 기반 해제order mismatchput_page

folio/매핑

분류핵심 API/심볼언제 사용실수 패턴대체/보완
매핑kmap_local_page고메모리 단기 매핑언맵 누락kunmap_local
매핑vmap페이지 배열 가상 연속 매핑vmalloc과 혼동vunmap
foliofolio_get참조 카운트 증가put 누락folio_put
foliofolio_lockfolio 변경 보호락 순서 위반folio_unlock

VMA/fault

분류핵심 API/심볼언제 사용실수 패턴대체/보완
MMUflush_tlb_mm_range매핑 변경 후 TLB 동기화범위 과대 flushflush_tlb_page
VMAvma_lookup주소 기반 VMA 탐색mmap_lock 누락find_vma
VMAvma_modify속성 변경충돌 영역 처리 누락mprotect_fixup
faulthandle_mm_fault페이지 폴트(Page Fault) 처리리턴 플래그 오해do_page_fault 경로 추적

reclaim/슬랩

분류핵심 API/심볼언제 사용실수 패턴대체/보완
reclaimshrink_node노드 reclaim 수행스캔 비율 튜닝 누락balance_pgdat
reclaimtry_to_free_pages직접 reclaimlatency 급증wakeup_kswapd
슬랩kmem_cache_alloc캐시 객체 할당ctor 부작용kmem_cache_zalloc
슬랩kmem_cache_free객체 해제다른 cache에 반환none
mmapvm_mmap커널 내부 mmap 요청권한 플래그 오류do_mmap

DMA/핀/NUMA

분류핵심 API/심볼언제 사용실수 패턴대체/보완
pin_user_pages_fast빠른 GUP pinlong-term 플래그 누락unpin_user_pages
DMAdma_alloc_coherent일관성 메모리dma_mask 미설정dma_map_single
보안 해제kvfree_sensitive민감 데이터 해제일반 kfree 사용memzero_explicit
NUMAalloc_pages_node노드 지정 할당fallback 정책 무시kmalloc_node
/* folio writeback 패턴 — bio 제출 + 오류 경로 포함 */
static int demo_writepage(struct folio *folio,
                          struct writeback_control *wbc)
{
    struct inode *inode = folio->mapping->host;
    struct block_device *bdev = inode->i_sb->s_bdev;
    sector_t sector;
    struct bio *bio;

    folio_lock(folio);

    /* 블록 매핑 조회 */
    sector = demo_folio_to_sector(folio);
    if (sector == (sector_t)-1) {
        folio_unlock(folio);
        return -EIO;
    }

    folio_start_writeback(folio);
    folio_unlock(folio);

    /* bio 생성 및 제출 */
    bio = bio_alloc(bdev, 1, REQ_OP_WRITE | REQ_SYNC, GFP_NOFS);
    bio->bi_iter.bi_sector = sector;
    bio_add_folio_nofail(bio, folio, folio_size(folio), 0);
    bio->bi_end_io = demo_writeback_endio;
    bio->bi_private = folio;

    submit_bio(bio);
    return 0;
}

static void demo_writeback_endio(struct bio *bio)
{
    struct folio *folio = bio->bi_private;

    if (bio->bi_status) {
        folio_set_error(folio);
        mapping_set_error(folio->mapping,
                         blk_status_to_errno(bio->bi_status));
    }
    folio_end_writeback(folio);
    bio_put(bio);
}

확장 | 스토리지 I/O 카탈로그

스토리지 경로는 bio/request/queue 경계와 완료 처리 규약이 핵심입니다. 특히 실패 경로와 타임아웃 회수 정책을 반드시 포함해야 합니다.

bio 생성/관리

분류핵심 API/심볼언제 사용실수 패턴대체/보완
bio 생성bio_alloc_biosetblock I/O 요청 생성bioset 수명주기 누락bio_init
bio 관리bio_add_page페이지 연결길이/오프셋(Offset) 경계 오류bio_iov_iter_get_pages
bio 제출submit_bio블록 계층 제출opf 플래그 오설정submit_bio_noacct
bio 완료bio_endio완료 콜백 종료반복 호출blk_status_to_errno

request/큐

분류핵심 API/심볼언제 사용실수 패턴대체/보완
requestblk_mq_alloc_request직접 request 확보timeout 정책 누락blk_get_request
requestblk_mq_start_request디바이스 전송 시작시작/완료 순서 어긋남blk_mq_end_request
requestblk_mq_end_request요청 완료 처리잔여 바이트 처리 누락blk_update_request
queue 제어blk_mq_stop_hw_queues하드웨어 큐 일시 중지재개 누락blk_mq_start_stopped_hw_queues
timeoutblk_mq_rq_timed_out요청 타임아웃 핸들링중복 abortblk_abort_request
태그셋blk_mq_alloc_tag_set큐 태그 자원 준비hctx 수 과대 설정blk_mq_free_tag_set
큐 초기화blk_mq_init_sq_queue단일 큐 초기화queue limits 누락blk_mq_init_queue
큐 제한blk_queue_max_hw_sectors최대 전송 크기 제한장치 한계 초과blk_queue_chunk_sectors

완료/오류/통계

분류핵심 API/심볼언제 사용실수 패턴대체/보완
flushblkdev_issue_flush캐시 flush 요청오류 무시REQ_PREFLUSH
discardblkdev_issue_discardtrim/unmap정렬 단위 무시REQ_OP_DISCARD
readaheadpage_cache_ra_unbounded적응형 readahead랜덤 I/O 경로 오남용ondemand_readahead
직접 I/Oiomap_dio_rwDIO 경로 공통 처리정렬 검증 누락blockdev_direct_IO
멀티패스blk_mq_map_queuesCPU-hctx 매핑NUMA 불균형set->map 튜닝
통계part_stat_add디바이스 통계 갱신완료 경로 누락blk_account_io_done
폴링(Polling)blk_pollbusy polling 완료 확인CPU 소모 과다io_uring IOPOLL
오류 변환blk_status_to_errnoblk_status_t를 errno 변환직접 숫자 매핑errno_to_blk_status
/* bio 제출 최소 패턴 */
static void demo_submit_read(struct block_device *bdev,
                              sector_t sector, struct page *page)
{
    struct bio *bio = bio_alloc(bdev, 1, REQ_OP_READ, GFP_KERNEL);

    bio->bi_iter.bi_sector = sector;
    __bio_add_page(bio, page, PAGE_SIZE, 0);
    bio->bi_end_io = demo_bio_done;
    submit_bio(bio);
}

static void demo_bio_done(struct bio *bio)
{
    if (bio->bi_status)
        pr_err("bio error: %d\n",
               blk_status_to_errno(bio->bi_status));
    bio_put(bio);
}

확장 | 보안/LSM 카탈로그

보안 경로는 훅 호출 순서, cred 수명주기, 정책 캐시 일관성(Cache Coherency)을 동시에 고려해야 합니다.

credential/권한

분류핵심 API/심볼언제 사용실수 패턴대체/보완
cred 조회current_cred현재 태스크(Task) cred 참조수정 가능한 포인터로 오해get_current_cred
cred 획득prepare_creds권한 변경 사본 생성abort 누락commit_creds
cred 반영commit_creds새 cred 적용검증 없이 적용override_creds
임시 권한override_creds권한 위임 구간revert 누락revert_creds
capabilitycapable전역 CAP 검사ns 컨텍스트 누락ns_capable

LSM 훅

분류핵심 API/심볼언제 사용실수 패턴대체/보완
LSM 훅security_file_openfile open 정책 검사반환값 무시security_inode_permission
LSM 훅security_bprm_checkexec 검증중복 검사security_bprm_creds_for_exec
inode 정책inode_permission접근 권한 검사MAY_* 플래그 누락security_inode_permission
seccompseccomp_modeseccomp 상태 확인필터 우회 가정secure_computing

audit/IMA/키

분류핵심 API/심볼언제 사용실수 패턴대체/보완
auditaudit_log_start감사 로그 시작메모리 실패 경로 누락audit_log_end
IMAima_file_check파일 무결성(Integrity) 검사정책 비활성 가정evm_verifyxattr
키 관리request_key커널 keyring 조회권한 도메인 오해key_lookup

하드닝/보호

분류핵심 API/심볼언제 사용실수 패턴대체/보완
하드닝CONFIG_FORTIFY_SOURCE버퍼 경계 검증 강화경고 무시FORTIFY 경고 수정
메모리 보호set_memory_ro페이지 읽기전용 전환TLB 동기화 누락set_memory_rw
사용자 복사copy_struct_from_user확장 가능한 ABI 복사size 호환성 누락copy_from_user
랜덤get_random_bytes커널 난수 획득초기화 전 사용get_random_u32
스택 검증check_copy_size복사 크기 검증직접 memcpy 사용copy_to_user
로그pr_warn_ratelimited보안 이벤트 경고로그 폭주audit_log*
정책 질의security_locked_downlockdown 제약 검사에러 전파 누락kernel_is_locked_down
레이블security_secid_to_secctxsecid->문자열 변환free 누락security_release_secctx
/* cred 변경 안전 패턴 */
static int demo_elevate(void)
{
    struct cred *new;
    int ret;

    new = prepare_creds();
    if (!new)
        return -ENOMEM;

    /* capability 추가 검증 */
    if (!ns_capable(new->user_ns, CAP_SYS_ADMIN)) {
        abort_creds(new);
        return -EPERM;
    }

    commit_creds(new);
    return 0;
}

확장 | 가상화(Virtualization)/KVM 카탈로그

KVM 경로는 VM-Exit 처리 비용, vCPU 동기화, 메모리 슬롯 일관성이 성능과 안정성을 동시에 좌우합니다.

분류핵심 API/심볼언제 사용실수 패턴대체/보완
VM 생성kvm_create_vmVM 인스턴스 생성아키텍처 init 누락kvm_arch_init_vm
vCPU 생성kvm_vm_ioctl_create_vcpuvCPU 생성 ioctlcpuid 설정 누락kvm_arch_vcpu_create
실행 루프kvm_arch_vcpu_ioctl_run게스트 실행/VM-Exitexit reason 미분기vcpu_enter_guest
메모리 슬롯kvm_set_memory_regionguest phys map 등록겹침 슬롯 검증 누락kvm_arch_prepare_memory_region
페이지 테이블kvm_mmu_mapgva/gpa 매핑TLB flush 경계 오류kvm_flush_remote_tlbs
인터럽트kvm_set_irq가상 IRQ 주입irqchip 모드 오해kvm_irq_delivery_to_apic
타이머kvm_lapic_expired_hv_timerAPIC 타이머 만료 처리주기 계산 오류hrtimer 연계
MSRkvm_get_msr_commonMSR read 에뮬레이션권한/가시성 누락kvm_set_msr_common
CPUIDkvm_update_cpuid_runtime런타임 cpuid 동기화feature mismatchkvm_set_cpu_caps
PIO/MMIOkvm_emulate_ioI/O exit 에뮬레이션반복 exit 폭주coalesced MMIO
dirty loggingkvm_get_dirty_log라이브 마이그레이션 추적비트맵(Bitmap) 스캔 비용 과소평가dirty ring
async PFkvm_arch_async_page_readyAPF 완료 처리guest wakeup 누락kvm_make_request
PV clockkvm_write_wall_clock게스트 시간 동기화TSC 안정성 가정kvm_guest_time_update
halt pollkvm_vcpu_blockvCPU sleep/폴링poll tuning 미흡halt_poll_ns 조정
IOMMU/VFIOvfio_pin_pages디바이스 패스스루 pinunpin 누락vfio_unpin_pages
migrationkvm_arch_save_pending_timer상태 저장장치 상태 누락KVM_GET/SET_* ioctls
nestednested_vmx_run중첩 가상화 실행state sync 누락vmcs12 검증
tracetrace_kvm_exitexit reason 프로파일링샘플링 과소perf kvm stat
kvm->slots_lockmemslot 보호락 순서 역전srcu_read_lock
요청 플래그kvm_make_requestvcpu 간 동기 이벤트 전파kick 누락kvm_vcpu_kick
/* KVM run 루프 개념 스니펫 */
for (;;) {
    int r = kvm_arch_vcpu_ioctl_run(vcpu);
    if (r < 0)
        break;

    switch (vcpu->run->exit_reason) {
    case KVM_EXIT_IO:
        handle_pio(vcpu);
        break;
    case KVM_EXIT_MMIO:
        handle_mmio(vcpu);
        break;
    case KVM_EXIT_HLT:
        return;
    default:
        trace_kvm_exit(vcpu->run->exit_reason);
        break;
    }
}

확장 | 인터럽트/타이머 카탈로그

인터럽트 경로는 지연과 안정성이 직결되므로 핸들러 최소화, 하단 처리 분리, 타이머 취소 순서가 핵심입니다.

기술 문서: 스레드 IRQ, 하단 처리, 워크큐 문서에서 인터럽트 처리 아키텍처 전체를 확인하세요.

IRQ 등록/관리

분류핵심 API/심볼언제 사용실수 패턴대체/보완
IRQ 등록request_irq기본 IRQ 핸들러 등록free_irq 누락devm_request_irq
IRQ 공유IRQF_SHARED공유 인터럽트 라인dev_id 고유성 누락request_threaded_irq
스레드 IRQrequest_threaded_irqsleep 가능한 후반 처리top half에서 과도 작업IRQ_WAKE_THREAD
마스킹disable_irq_nosync즉시 인터럽트 차단재활성화 누락enable_irq
동기화synchronize_irq진행 중 핸들러 종료 대기락 보유 상태 호출synchronize_hardirq

softirq/tasklet

분류핵심 API/심볼언제 사용실수 패턴대체/보완
softirqraise_softirqsoftirq 스케줄컨텍스트 혼동__raise_softirq_irqoff
tasklettasklet_schedule하단 처리 경량 분리긴 작업 처리workqueue

타이머/hrtimer

분류핵심 API/심볼언제 사용실수 패턴대체/보완
timertimer_setup타이머 초기화콜백 문맥 오해hrtimer_init
timermod_timer타이머 갱신jiffies 계산 오류mod_timer_pending
timerdel_timer_sync동기 제거교착 가능 락 순서timer_shutdown_sync
hrtimerhrtimer_start_range_ns고정밀 타이머슬랙 과소 설정hrtimer_forward_now
clockktime_get_ns단조 시간 측정realtime 혼용ktime_get_boottime_ns

지연/IPI/기타

분류핵심 API/심볼언제 사용실수 패턴대체/보완
지연usleep_range절전 친화 지연IRQ 문맥 사용fsleep
busy waitudelay매우 짧은 대기긴 대기 오남용usleep_range
workqueuequeue_delayed_work지연 하단 처리취소 경로 누락mod_delayed_work
IPIsmp_call_function_single원격 CPU 콜백dead cpu 대상 호출on_each_cpu
irqdomainirq_domain_alloc_irqs논리 IRQ 할당해제 누락irq_domain_free_irqs
affinityirq_set_affinity_hintIRQ CPU 힌트NUMA 무시irq_set_affinity
통계kstat_incr_irq_this_cpuIRQ 카운터 갱신정확도 오해/proc/interrupts
경고WARN_ON_ONCE핸들러 이상 감지반복 경고 폭주pr_warn_ratelimited
/* 스레드 IRQ + hrtimer 조합 패턴 */
static irqreturn_t demo_hard_isr(int irq, void *data)
{
    struct demo_dev *d = data;
    u32 status = readl(d->regs + IRQ_STATUS);

    if (!(status & DEMO_IRQ_MASK))
        return IRQ_NONE;

    writel(status, d->regs + IRQ_ACK);
    return IRQ_WAKE_THREAD;  /* 후반 처리 위임 */
}

static irqreturn_t demo_thread_isr(int irq, void *data)
{
    struct demo_dev *d = data;

    mutex_lock(&d->lock);       /* sleep 가능 */
    demo_process_data(d);
    mutex_unlock(&d->lock);
    return IRQ_HANDLED;
}

/* 등록 */
devm_request_threaded_irq(dev, irq,
    demo_hard_isr, demo_thread_isr,
    IRQF_SHARED, "demo", d);

확장 | 드라이버 버스(PCI/I2C/SPI) 카탈로그

버스 드라이버는 열거, 자원 매핑, 전원관리, 오류 복구를 일관된 순서로 처리해야 안정적입니다.

PCI

분류핵심 API/심볼언제 사용실수 패턴대체/보완
PCI 등록pci_register_driverPCI 드라이버 등록(Driver Registration)remove 경로 누락module_pci_driver
PCI enablepcim_enable_device관리형 장치 활성화BAR 요청 전 사용pci_enable_device_mem
PCI BARpcim_iomap_regionsBAR 매핑region 마스크 오류pci_iomap
PCI DMAdma_set_mask_and_coherentDMA 주소폭 설정실패 무시dma_set_mask
PCI IRQpci_alloc_irq_vectorsMSI/MSI-X 벡터 확보fallback 미구현pci_irq_vector
PCI 에러pci_enable_pcie_error_reportingAER 활성화복구 콜백 미구현pci_error_handlers

I2C/SPI

분류핵심 API/심볼언제 사용실수 패턴대체/보완
I2C 등록i2c_add_driverI2C 드라이버 등록id table 누락module_i2c_driver
I2C 전송i2c_transfer복수 메시지 트랜잭션retries 누락i2c_smbus_read_byte_data
I2C 검증i2c_check_functionality어댑터 기능 확인기능 없는 op 호출adapter->algo 점검
SPI 등록spi_register_driverSPI 드라이버 등록of_match 누락module_spi_driver
SPI 준비spi_setupmode/speed 반영cs 변화 무시spi_sync
SPI 동기spi_sync_transfer동기 전송버퍼 수명주기 오류spi_async
regmapdevm_regmap_init_i2cI2C regmap 초기화endian 설정 오류devm_regmap_init_spi

공통 리소스(클럭/리셋/레귤레이터/GPIO)

분류핵심 API/심볼언제 사용실수 패턴대체/보완
클럭devm_clk_get_enabled클럭 획득+enablerate 설정 누락clk_set_rate
리셋devm_reset_control_get_optional_exclusive리셋 라인 제어deassert 누락reset_control_deassert
레귤레이터devm_regulator_get_enable전원 레일 제어전압 범위 검증 누락regulator_set_voltage
GPIOdevm_gpiod_getGPIO 리소스 획득active-low 처리 누락gpiod_set_value_cansleep
PM runtimepm_runtime_resume_and_get런타임 PM 활성화put 누락pm_runtime_put_autosuspend
펌웨어(Firmware)device_property_read_u32DT/ACPI 속성 공통 읽기기본값 누락fwnode_property_read_u32
매칭of_device_get_match_dataSoC별 데이터 선택null match 처리 누락device_get_match_data
/* PCI 드라이버 probe 최소 골격 */
static int demo_pci_probe(struct pci_dev *pdev,
                           const struct pci_device_id *id)
{
    struct demo_dev *d;
    int ret;

    ret = pcim_enable_device(pdev);
    if (ret)
        return ret;

    ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
    if (ret)
        return ret;

    ret = pcim_iomap_regions(pdev, BIT(0), "demo");
    if (ret)
        return ret;

    d = devm_kzalloc(&pdev->dev, sizeof(*d), GFP_KERNEL);
    if (!d)
        return -ENOMEM;

    d->regs = pcim_iomap_table(pdev)[0];
    pci_set_master(pdev);
    pci_set_drvdata(pdev, d);

    ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSI | PCI_IRQ_LEGACY);
    if (ret < 0)
        return ret;

    dev_info(&pdev->dev, "probe complete\n");
    return 0;
}

/* I2C regmap 패턴 */
static const struct regmap_config demo_regmap_cfg = {
    .reg_bits = 8,
    .val_bits = 8,
    .max_register = 0xFF,
};

static int demo_i2c_probe(struct i2c_client *client)
{
    struct regmap *map;
    unsigned int val;

    map = devm_regmap_init_i2c(client, &demo_regmap_cfg);
    if (IS_ERR(map))
        return PTR_ERR(map);

    regmap_read(map, 0x00, &val);
    dev_info(&client->dev, "chip id: 0x%x\n", val);
    return 0;
}

확장 | 컨테이너(Container)/cgroups 카탈로그

컨테이너 경로는 namespace 격리(Isolation)와 cgroup 자원 제어가 결합되어 동작합니다. 계층별 책임을 분리해서 봐야 원인 분석이 빠릅니다.

cgroup v1 vs v2: cgroup v2가 기본 계층입니다. v1은 호환성 목적으로만 유지되며 신규 기능은 v2에만 추가됩니다. cgroup2 마운트 여부를 먼저 확인하세요.

namespace

분류핵심 API/심볼언제 사용실수 패턴대체/보완
ns 생성copy_namespacesclone 시 namespace 복제참조 카운트 누락unshare_nsproxy_namespaces
pid nstask_active_pid_ns태스크 pid namespace 조회init ns 가정ns_of_pid
mnt nscopy_mnt_ns마운트 namespace 복제propagation 오해setns
net nsget_netnet namespace 참조 획득put 누락put_net
user nsmake_kuidid 매핑 변환invalid kuid 무시from_kuid_munged

cgroup core/attach

분류핵심 API/심볼언제 사용실수 패턴대체/보완
cgroup attachcgroup_attach_task태스크 cgroup 이동권한 검사 누락cgroup_migrate
css 조회task_csssubsys state 조회rcu 규약 누락task_css_check

자원 제어(mem/cpu/io)

분류핵심 API/심볼언제 사용실수 패턴대체/보완
메모리 제어mem_cgroup_charge_skmemsock 메모리 과금uncharge 누락mem_cgroup_uncharge_skmem
메모리 reclaimmem_cgroup_try_charge페이지 과금 시도oom 경로 미처리try_charge_memcg
CPU 제어sched_cgroup_forkfork 시 CPU cgroup 연결weight 반영 누락cpu_cgroup_css_alloc
cpusetcpuset_cpus_allowed허용 CPU 집합 조회hotplug 변화 무시cpuset_cpus_allowed_fallback
io 제어blkcg_bio_issue_initbio cgroup 컨텍스트 초기화issue path 누락blkcg_set_ioprio

PSI/freezer/디버그

분류핵심 API/심볼언제 사용실수 패턴대체/보완
freezercgroup_freezingfreezing 상태 확인중단 불가 작업 누락try_to_freeze
psipsi_task_changepressure 상태 갱신상태 전이 누락psi_group_change
rstatcgroup_rstat_flush통계 플러시(Flush)빈도 과다cgroup_rstat_updated
bpf cgroupcgroup_bpf_run_filter_skbcgroup BPF 필터 실행리턴 코드 무시BPF_CGROUP_RUN_PROG_*
네트워크 classidtask_cls_statetc classid 연계stale state 사용sock_cgroup_set_classid
OOMmem_cgroup_oom_synchronizememcg OOM 처리kthread 예외 누락out_of_memory
releasecss_putcss 참조 반환이중 putcss_get
debugcgroup_path현재 cgroup 경로 출력버퍼 크기 과소task_cgroup_path

확장 | 전원관리/열 카탈로그

전원과 열 관리(Thermal Management)는 성능/안정성/수명에 동시에 영향을 주므로 PM 런타임, cpufreq, thermal 정책의 연결을 함께 봐야 합니다.

runtime PM

분류핵심 API/심볼언제 사용실수 패턴대체/보완
runtime PMpm_runtime_resume_and_get장치 활성화put 누락pm_runtime_put_autosuspend
runtime PMpm_runtime_enableruntime PM 시작disable 누락pm_runtime_disable

시스템 suspend/resume

분류핵심 API/심볼언제 사용실수 패턴대체/보완
시스템 suspenddpm_suspend_startsuspend 진입순서 의존성 누락dpm_suspend_end
시스템 resumedpm_resume_startresume 복귀클럭 복구 누락dpm_resume_end

cpufreq/idle/OPP

분류핵심 API/심볼언제 사용실수 패턴대체/보완
cpufreqcpufreq_register_drivercpufreq 드라이버 등록policy init 미흡cpufreq_unregister_driver
cpufreqcpufreq_update_policy정책 재평가과도 호출cpufreq_cpu_get
cpuidlecpuidle_register_driveridle state 등록target residency 부정확cpuidle_unregister_driver
OPPdev_pm_opp_set_rateOPP 기반 주파수 전환전압 스케일 누락dev_pm_opp_get_opp_count
EMem_dev_register_perf_domainEnergy Model 등록비용 테이블 부정확em_pd_get

thermal/powercap

분류핵심 API/심볼언제 사용실수 패턴대체/보완
열 구역thermal_zone_device_registerthermal zone 등록센서 단위 변환 오류thermal_zone_device_unregister
열 완화thermal_cdev_updatecooling device 상태 갱신state clamp 누락thermal_zone_device_update
파워캡powercap_register_control_typepowercap 타입 등록constraint 노출 누락powercap_unregister_control_type
RAPLrapl_read_data_raw에너지 계측랩어라운드 처리 누락powercap sysfs
열 추적trace_thermal_temperature온도 이벤트 관측과도 추적 오버헤드tracefs 필터

wakeup/QoS/진단

분류핵심 API/심볼언제 사용실수 패턴대체/보완
wakeupdevice_set_wakeup_capablewakeup 능력 설정enable 경로 누락device_set_wakeup_enable
wakeup source__pm_stay_awake절전 지연 필요 구간relax 누락__pm_relax
QoScpu_latency_qos_add_request지연 요구 등록remove 누락cpu_latency_qos_update_request
regulatorregulator_set_voltage_triplet전압 범위 조정enable 상태 무시regulator_set_voltage
clockclk_bulk_prepare_enable다수 클럭 enabledisable 역순 누락clk_bulk_disable_unprepare
진단pm_pr_dbgPM 경로 디버그운영 빌드 잔존dynamic_debug

확장 | 관측성(perf/ftrace/eBPF) 카탈로그

관측성은 "무엇을, 얼마나, 어디서" 수집할지 설계가 먼저입니다. 이벤트 스키마와 오버헤드 제어를 함께 정의해야 합니다.

ftrace/tracepoint

분류핵심 API/심볼언제 사용실수 패턴대체/보완
ftraceregister_ftrace_function함수 트레이스 훅필터 없이 전체 계측ftrace_set_filter
ftraceunregister_ftrace_function훅 해제모듈 언로드 전 누락ftrace_shutdown
tracepointtracepoint_probe_register정적 이벤트 구독unregister 누락register_trace_*
tracepointtracepoint_probe_unregister구독 해제콜백 수명주기 오류synchronize_rcu

perf

분류핵심 API/심볼언제 사용실수 패턴대체/보완
perf eventperf_event_create_kernel_counter커널 PMU 카운터CPU hotplug 대응 누락perf_event_enable
perf samplingperf_output_sample샘플 출력ring buffer overflow 무시perf_event_overflow

eBPF/kprobe

분류핵심 API/심볼언제 사용실수 패턴대체/보완
bpf attachbpf_prog_attachcgroup/tc/xdp attachattach type 불일치bpf_link_create
bpf run (내부)bpf_prog_runBPF 내부 실행 경로 이해직접 호출 가능한 API로 오해bpf_prog_attach, bpf_link_create
bpf mapbpf_map_lookup_elem맵 조회per-cpu map 의미 오해bpf_map_update_elem
kproberegister_kprobe동적 함수 진입 계측핫패스 남용tracepoint
kretproberegister_kretprobe함수 반환 계측maxactive 과소 설정fentry/fexit BPF
uprobesuprobe_register유저 함수 계측symbol offset 오류perf probe

static key/통계/디버그

분류핵심 API/심볼언제 사용실수 패턴대체/보완
static keystatic_branch_enable런타임 분기 토글초기값/토글 불일치DEFINE_STATIC_KEY_FALSE
ringbufbpf_ringbuf_outputBPF 이벤트 전달큰 레코드 빈발perf buffer
scheduler tracetrace_sched_switch문맥 전환(Context Switch) 추적전체 시스템 장시간 추적trace-cmd filter
lock tracelock_acquire trace락 경합 분석stack depth 과다lockstat
latencytrace_irqsoffirq-off 지연 추적운영 환경 장시간 사용osnoise
event filterset_event_pidpid 범위 축소필터 누락으로 오버헤드 증가tracefs filter
synthetic eventsynth_event_create복합 이벤트 생성필드 타입 불일치hist trigger
debugtrace_printk임시 추적운영 코드 잔존pr_debug, tracepoint
/* DEFINE_EVENT tracepoint 정의 패턴 */

/* include/trace/events/demo.h */
#undef TRACE_SYSTEM
#define TRACE_SYSTEM demo

#if !defined(_TRACE_DEMO_H) || defined(TRACE_HEADER_MULTI_READ)
#define _TRACE_DEMO_H
#include <linux/tracepoint.h>

TRACE_EVENT(demo_op,
    TP_PROTO(const char *name, int ret),
    TP_ARGS(name, ret),
    TP_STRUCT__entry(
        __string(name, name)
        __field(int, ret)
    ),
    TP_fast_assign(
        __assign_str(name);
        __entry->ret = ret;
    ),
    TP_printk("name=%s ret=%d", __get_str(name), __entry->ret)
);

#endif
#include <trace/define_trace.h>

/* 사용 측 */
trace_demo_op("write", ret);
# ftrace 실전 사용 예시

# 1. 함수 추적 활성화
echo demo_probe > /sys/kernel/debug/tracing/set_ftrace_filter
echo function > /sys/kernel/debug/tracing/current_tracer
echo 1 > /sys/kernel/debug/tracing/tracing_on

# 2. tracepoint 이벤트 활성화
echo 1 > /sys/kernel/debug/tracing/events/demo/demo_op/enable
cat /sys/kernel/debug/tracing/trace_pipe

# 3. 인터럽트 off 지연 추적
echo irqsoff > /sys/kernel/debug/tracing/current_tracer
echo 1 > /sys/kernel/debug/tracing/tracing_on
cat /sys/kernel/debug/tracing/trace  # 최대 지연 확인

확장 | 컴파일러/링커(GCC/binutils) 카탈로그

빌드 옵션은 런타임 동작을 바꾸므로 경고/최적화/보안 옵션과 ELF 결과물을 함께 점검해야 합니다.

분류핵심 옵션/도구언제 사용실수 패턴대체/보완
경고-Wall기본 경고 활성화경고 무시 문화-Wextra
경고-WerrorCI 엄격 모드로컬/CI 불일치-Wno-error=...
최적화-O2기본 커널 최적화무근거 -O3 변경per-file CFLAGS
디버그-g심볼 포함 빌드strip 후 분석 불가CONFIG_DEBUG_INFO
보안-fstack-protector-strong스택 보호핵심 파일 제외 빌드CONFIG_STACKPROTECTOR
보안-D_FORTIFY_SOURCE=2버퍼 검증 강화최적화 옵션과 충돌 오해CONFIG_FORTIFY_SOURCE
호환성-fno-strict-aliasing커널 alias 규약 유지부분 파일 누락READ_ONCE 규약
LTOCONFIG_LTO_CLANG_THIN링크단 최적화가시성 속성 누락__visible
CFICONFIG_CFI_CLANG제어흐름 무결성함수 포인터 캐스팅 남용__nocfi 최소 사용
objdumpobjdump -drS기계어(Machine Code)/소스 대응 확인심볼 없는 바이너리 분석llvm-objdump
readelfreadelf -Ws심볼 테이블(Symbol Table) 점검가시성/바인딩 미확인nm -n
재배치(Relocation)readelf -rrelocation 확인section mismatch 간과objdump -r
사이즈size텍스트/데이터 크기 비교회귀 추적 누락bloat-o-meter
심볼 검색nm정의/참조 파악정렬 없는 비교nm -n
섹션 추출objcopy --only-section특정 섹션 분석디버그 섹션 유실llvm-objcopy
asm 확인-S -fverbose-asm코드 생성 검증최적화 레벨 차이 무시Compiler Explorer
링커 스크립트SECTIONS, PHDRS배치 제어정렬/경계 누락vmlinux.lds.h
버전 점검scripts/min-tool-version.sh최소 툴체인 확인개발기/CI 버전 불일치containerized build
경고 분석sparse주소공간/타입 검증C=2 누락smatch
패턴 리팩터링Coccinelle대규모 API 변환semantic patch 검증 부족git range-diff

확장 | 네트워크 프로토콜(TCP/UDP/Netfilter) 카탈로그

프로토콜 경로는 상태 전이, 타이머, conntrack, NAT 훅 순서가 복합적으로 얽혀 있습니다. 경로별 핵심 API를 함께 봐야 회귀를 줄일 수 있습니다.

분류핵심 API/심볼언제 사용실수 패턴대체/보완
TCP 송신tcp_sendmsg스트림 데이터 송신잠금 범위 과대tcp_sendpage
TCP 수신tcp_recvmsg스트림 데이터 수신peek 플래그 오해tcp_read_sock
TCP 상태tcp_set_state상태 전이 갱신타이머 정리 누락tcp_done
TCP 타이머tcp_write_timer_handler재전송(Retransmission)/지연 ACK 처리RTO 조정 오류tcp_retransmit_timer
TCP 혼잡tcp_cong_avoid_ai혼잡 회피 알고리즘 구현cwnd 단위 혼동tcp_slow_start
UDP 송신udp_sendmsg데이터그램 송신MTU 초과 처리 누락ip_append_data
UDP 수신udp_recvmsg데이터그램 수신trunc 처리 누락skb_copy_datagram_msg
IP 출력ip_local_outIPv4 local outputnetfilter 훅 우회 가정ip_output
IP 입력ip_local_deliver로컬 입력 전달fragments 처리 누락ip_rcv_finish
IPv6 출력ip6_local_outIPv6 local output확장헤더 길이 오해ip6_output
conntracknf_conntrack_in연결 추적(Connection Tracking) 진입zone 고려 누락nf_ct_get
NATnf_nat_setup_infoNAT 매핑 설정충돌 처리 누락nf_nat_alloc_null_binding
Netfilter 훅nf_register_net_hook훅 등록우선순위 충돌nf_unregister_net_hook
rule 평가nf_hook_slow훅 체인 순회성능 경로 과부하flowtable offload
flowtablenf_flow_table_offload_add_cb플로우 오프로드 등록만료 처리 누락nf_flow_table_cleanup
socket lookup__inet_lookup_establishedTCP 소켓 조회ehash lock 누락inet_lookup_listener
큐 제어sk_stream_wait_memory송신 버퍼 대기signal 처리 누락sk_wait_event
ECNINET_ECN_set_ce혼잡 표시 비트 설정체크섬 재계산 누락INET_ECN_encapsulate
GROtcp_gro_receiveTCP GRO 병합옵션 파싱 누락udp_gro_receive
XFRMxfrm_lookup_routeIPsec 정책 경로 조회policy fallback 누락xfrm_policy_lookup

확장 | 파일시스템(ext4/XFS/Btrfs) 카탈로그

파일시스템 경로는 저널링/트랜잭션/지연할당/extent 트리 동작을 함께 이해해야 디스크 손상 없이 성능을 올릴 수 있습니다.

분류핵심 API/심볼언제 사용실수 패턴대체/보완
ext4 트랜잭션ext4_journal_start메타데이터 변경 시작handle 크기 과소ext4_journal_stop
ext4 블록 매핑ext4_map_blockslogical->physical 매핑unwritten extent 처리 누락ext4_ext_map_blocks
ext4 할당ext4_mb_new_blocksmballoc 블록 할당goal block 오해ext4_claim_free_clusters
ext4 writebackext4_writepagesdirty page flushwbc sync 모드 누락mpage_map_and_submit_extent
ext4 fsyncext4_sync_file파일 동기화journal commit 순서 오류jbd2_log_start_commit
XFS 트랜잭션xfs_trans_alloc트랜잭션 확보reserve 과소xfs_trans_commit
XFS inodexfs_igetinode 조회ilock 모드 오류xfs_ilock
XFS extentxfs_bmapi_writeextent 할당cow fork 누락xfs_bmapi_convert_delalloc
XFS logxlog_cil_commitCIL commitpush 타이밍 과도 지연xfs_log_force
XFS reclaimxfs_reclaim_inodesinode reclaimAG 스캔 불균형xfs_icwalk
Btrfs 트랜잭션btrfs_start_transactiontree 변경 시작nested trans 과다btrfs_end_transaction
Btrfs extentbtrfs_reserve_extentextent 예약space info 오해btrfs_free_reserved_extent
Btrfs delayed refsbtrfs_run_delayed_refs지연 참조 처리flush 전략 미흡btrfs_start_delalloc_roots
Btrfs COWbtrfs_cow_block메타데이터 COWgeneration 체크 누락btrfs_search_slot
Btrfs balancebtrfs_balance청크 재배치필터 과도 설정btrfs_relocate_chunk
공통 iomapiomap_write_beginiomap 기반 쓰기 시작folio uptodate 처리 누락iomap_write_end
공통 daxdax_iomap_rwDAX direct accesscache flush 누락dax_writeback_mapping_range
공통 quotadquot_alloc_inodeinode quota 과금error unwind 누락dquot_free_inode
공통 freezefreeze_superfs freeze 진입unfreeze 누락thaw_super
공통 trimfstrim_rangediscard 전달정렬 제약 누락blkdev_issue_discard

확장 | 디바이스 특화(GPU/V4L2/ALSA) 카탈로그

미디어/그래픽/오디오 서브시스템은 사용자 ABI 호환성과 DMA 버퍼 공유 규약이 핵심입니다.

분류핵심 API/심볼언제 사용실수 패턴대체/보완
DRM 등록drm_dev_registerDRM 디바이스 공개unregister 누락drm_dev_unplug
DRM 메모리drm_gem_object_initGEM 객체 초기화refcount 누락drm_gem_private_object_init
KMS atomicdrm_atomic_helper_commit원자적(Atomic) 모드셋 적용fence 대기 누락drm_atomic_commit
DMA-BUFdma_buf_export버퍼 공유 핸들 제공map/unmap 불균형dma_buf_get
GPU 스케줄drm_sched_job_init작업 초기화entity 연결 누락drm_sched_entity_push_job
GPU 펜스dma_fence_signal작업 완료 신호중복 signaldma_fence_wait_timeout
V4L2 등록video_register_device비디오 노드 등록minor 충돌 처리 누락video_unregister_device
V4L2 queuevb2_queue_init버퍼 큐 초기화ops 구현 불일치vb2_reqbufs
V4L2 buffervb2_buffer_done프레임 완료 반환state 잘못 반환VB2_BUF_STATE_ERROR
V4L2 subdevv4l2_async_register_subdev비동기 센서 연결notifier 누락v4l2_async_nf_register
ALSA cardsnd_card_new카드 객체 생성free 경로 누락snd_card_free
ALSA PCMsnd_pcm_newPCM 디바이스 생성ops 등록 누락snd_pcm_set_ops
ALSA DMAsnd_pcm_lib_preallocate_pages버퍼 사전 할당크기 과소snd_pcm_lib_malloc_pages
ALSA triggersnd_pcm_period_elapsedperiod 완료 통지IRQ 경로 지연snd_timer_interrupt
ASoC 컴포넌트devm_snd_soc_register_componentcodec/platform 등록dai link 매칭 오류snd_soc_register_component
ASoC DAPMsnd_soc_dapm_new_controls전원 위젯 구성경로 이름 불일치snd_soc_dapm_add_routes
media requestmedia_request_object_bind요청 기반 동기화lifetime 누락media_request_put
ioctl 검증v4l2_ioctl_ops유저 ABI 처리compat_ioctl 누락video_usercopy
디버그drm_dbg, v4l2_dbg, dev_dbg서브시스템별 로그정적 로그 남용dynamic_debug
PM 연계pm_runtime_force_suspend시스템 suspend 보조resume 대칭 누락pm_runtime_force_resume

확장 | 문자열/버퍼 조작 카탈로그

커널 문자열 API는 사용자 공간 libc와 계약이 다릅니다. 특히 NUL 종료 보장, 버퍼 오버런 방지, 반환값 규약이 함수마다 다르므로 표의 "반환값" 열을 가장 먼저 확인해야 합니다. strscpystrlcpy/strncpy를 대체하는 커널 권장 API입니다.

실무 체크포인트: 새 코드에서 strcpy/strncpy/strlcpy를 발견하면 strscpy로 교체 가능 여부를 먼저 검토하세요.

문자열 복사가 필요한가? 고정 크기 버퍼 strscpy(dst, src, sz) 동적 할당 복제 kstrdup / kasprintf 포맷 문자열 출력 scnprintf / snprintf 메모리 비교/검색 memcmp / memchr / memscan 민감 데이터 소거 memzero_explicit 문자열 → 정수 변환 kstrtoul / kstrtoint 공통 규칙: 반환값 검사 + NUL 보장 확인 + FORTIFY_SOURCE 활성화
문자열 API 선택은 복사 방식(고정/동적/포맷)과 용도(비교/소거/변환)를 먼저 분류한 뒤 반환값 규약을 고정합니다.
참고: C 언어 & 커널 C 관용어에서 FORTIFY_SOURCE, __counted_by 등 컴파일러 보호 기법을 함께 확인하세요.

복사/복제

분류핵심 API언제 사용실수 패턴대체/보완
복사strscpy고정 크기 버퍼 안전 복사반환값(-E2BIG) 무시strncpy 사용 금지
복사strscpy_pad나머지 영역 0 채움 복사패딩(Padding) 필요 없는 곳에 사용strscpy
복제kstrdup힙에 문자열 복제kfree 누락kstrdup_const
복제kstrdup_const상수 문자열 중복 방지kfree_const 미사용kstrdup
복제kmemdup바이너리 데이터 복제길이 불일치memcpy + kmalloc

포맷 출력

분류핵심 API언제 사용실수 패턴대체/보완
포맷scnprintf버퍼 내 실제 출력 길이 반환snprintf 반환값과 혼동snprintf
포맷snprintf포맷 문자열 안전 출력잘림 검사 누락(반환값 > size)scnprintf
포맷kasprintf동적 포맷 문자열 생성NULL 반환 미검사devm_kasprintf
분류핵심 API언제 사용실수 패턴대체/보완
비교strcmp/dev/null 종료 문자열 비교길이 미보장 문자열에 사용strncmp
비교strncasecmp대소문자 무시 비교로케일 가정strncmp
검색strstr부분 문자열 검색/dev/null 미종료 입력strnstr
검색strchr문자 위치 탐색NULL 반환 미검사strrchr
분리strsep토큰 분리원본 포인터 변경 인지 부족strtok 사용 금지

변환

분류핵심 API언제 사용실수 패턴대체/보완
변환kstrtoul문자열→unsigned long에러 반환 무시kstrtouint
변환kstrtoint문자열→int오버플로 미처리kstrtol
변환kstrtobooly/n/1/0 해석빈 문자열 처리 누락strtobool deprecated

메모리/소거/해시

분류핵심 API언제 사용실수 패턴대체/보완
메모리memcpy비중첩 메모리 복사겹침 영역에 사용memmove
메모리memmove겹침 허용 복사불필요한 사용으로 오버헤드memcpy
메모리memset메모리 초기화kzalloc과 중복 사용memset_after
소거memzero_explicit민감 데이터 확실 소거일반 memset 사용 (최적화 제거됨)kfree_sensitive
해시xxhash비암호화 고속 해시보안 용도 사용siphash
해시siphash해시 테이블(Hash Table) 보안 해시키 초기화 누락hsiphash
/* strscpy + scnprintf 조합 패턴 */
static ssize_t demo_show(struct device *dev,
                         struct device_attribute *attr,
                         char *buf)
{
    struct demo_dev *d = dev_get_drvdata(dev);
    char name[64];
    ssize_t ret;

    ret = strscpy(name, d->label, sizeof(name));
    if (ret < 0)
        strscpy(name, "(truncated)", sizeof(name));

    return scnprintf(buf, PAGE_SIZE,
                     "name=%s ver=%u\n", name, d->version);
}

/* kasprintf 동적 할당 패턴 */
char *label = kasprintf(GFP_KERNEL, "%s-%d", prefix, idx);
if (!label)
    return -ENOMEM;
/* ... 사용 ... */
kfree(label);

/* kstrtoul 변환 패턴 */
unsigned long val;
int ret = kstrtoul(buf, 0, &val);
if (ret)
    return ret;
if (val > MAX_LIMIT)
    return -ERANGE;

확장 | Per-CPU 변수 카탈로그

Per-CPU 변수는 락 없이 CPU별 독립 데이터를 관리하는 핵심 기법입니다. 성능 카운터, 통계, 캐시에 자주 사용되지만, 선점(preemption) 제어를 빠뜨리면 잘못된 CPU 데이터에 접근하는 미묘한 버그가 발생합니다. 이 표는 "어떤 접근 API를 쓸지"보다 "선점 보호를 어떻게 보장할지"를 먼저 고정하는 기준으로 읽어야 합니다.

실무 체크포인트: Per-CPU 변수 접근 시 get_cpu()/put_cpu() 또는 this_cpu_* 시리즈의 선점 보호 범위를 반드시 확인하세요.

Per-CPU 접근 필요 단일 연산 (원자적) this_cpu_inc / this_cpu_add 복합 연산 (선점 비활성) get_cpu_var / put_cpu_var 타 CPU 읽기 per_cpu(var, cpu) 선점 보호 없이 접근 → 잘못된 CPU 데이터 읽기 총합 수집: for_each_possible_cpu() 합산
Per-CPU 접근은 단일 원자 연산, 복합 연산(선점 비활성), 타 CPU 읽기로 분류하고 각각의 보호 규약을 고정합니다.
선택 기준: 단일 카운터 증감은 this_cpu_inc/this_cpu_add가 가장 빠릅니다. 복합 연산이 필요할 때만 get_cpu_var/put_cpu_var를 사용하세요.

선언/할당

분류핵심 API언제 사용실수 패턴대체/보완
선언DEFINE_PER_CPU정적 per-cpu 변수 정의동적 할당과 혼동DEFINE_PER_CPU_SHARED_ALIGNED
선언DECLARE_PER_CPU외부 선언정의와 타입 불일치DEFINE_PER_CPU와 짝
동적 할당alloc_percpu런타임 per-cpu 할당free_percpu 누락alloc_percpu_gfp
동적 해제free_percpu동적 per-cpu 해제이중 해제none

this_cpu 연산

분류핵심 API언제 사용실수 패턴대체/보완
단일 연산this_cpu_inc현재 CPU 카운터 증가오버플로 무시this_cpu_add
단일 연산this_cpu_read현재 CPU 값 읽기선점 후 stale 값 사용get_cpu_var
단일 연산this_cpu_write현재 CPU 값 쓰기복합 연산에서 사용this_cpu_xchg
단일 연산this_cpu_cmpxchgCAS 기반 갱신루프 재시도 누락this_cpu_xchg
복합 접근get_cpu_var선점 비활성+참조put_cpu_var 누락put_cpu_var
복합 접근get_cpu()선점 비활성+CPU 번호put_cpu() 누락put_cpu()

타 CPU 접근/집계

분류핵심 API언제 사용실수 패턴대체/보완
타 CPU 접근per_cpu(var, cpu)특정 CPU 데이터 접근동기화 없이 쓰기per_cpu_ptr
총합for_each_possible_cpu전체 CPU 합산online/possible 혼동for_each_online_cpu
포인터per_cpu_ptr동적 per-cpu 포인터 역참조(Dereference)NULL per-cpu 주소raw_cpu_ptr
IRQ safe__this_cpu_incIRQ 컨텍스트 내부 사용선점 가능 문맥에서 사용this_cpu_inc
/* Per-CPU 카운터 패턴 */
static DEFINE_PER_CPU(u64, demo_packets);
static DEFINE_PER_CPU(u64, demo_bytes);

static void demo_account(unsigned int len)
{
    this_cpu_inc(demo_packets);
    this_cpu_add(demo_bytes, len);
}

static u64 demo_total_packets(void)
{
    u64 sum = 0;
    int cpu;

    for_each_possible_cpu(cpu)
        sum += per_cpu(demo_packets, cpu);
    return sum;
}

/* 동적 per-cpu 할당 패턴 */
int __percpu *counters = alloc_percpu(int);
if (!counters)
    return -ENOMEM;

this_cpu_inc(*counters);

/* 정리 */
free_percpu(counters);

확장 | 알림 체인(Notifier Chain) 카탈로그

알림 체인은 커널 서브시스템 간 이벤트 전파 메커니즘입니다. CPU hotplug, 네트워크 인터페이스 상태 변화, reboot 등의 시스템 이벤트를 구독/통지하는 표준 패턴입니다. 체인 유형(blocking/atomic/raw/SRCU)에 따라 콜백 내부에서 sleep 가능 여부가 달라지므로, 등록 시 체인 유형을 먼저 확인해야 합니다.

실무 체크포인트: 콜백 내부에서 sleep이 필요하면 반드시 blocking notifier를 사용하고, atomic notifier 체인에 등록하지 마세요.

분류핵심 API언제 사용실수 패턴대체/보완
blocking 등록blocking_notifier_chain_registersleep 가능 콜백 등록unregister 누락blocking_notifier_chain_unregister
blocking 호출blocking_notifier_call_chainsleep 허용 통지리턴값 무시NOTIFY_STOP 검사
atomic 등록atomic_notifier_chain_registerIRQ 안전 콜백 등록sleep 콜백 등록atomic_notifier_chain_unregister
atomic 호출atomic_notifier_call_chainatomic 문맥 통지긴 처리 콜백raw_notifier_call_chain
SRCU 등록srcu_notifier_chain_registerSRCU 보호 콜백 등록체인 타입 혼동srcu_notifier_chain_unregister
raw 등록raw_notifier_chain_register직접 락 관리 필요동기화 누락raw_notifier_chain_unregister
netdevregister_netdevice_notifier네트워크 장치 이벤트 구독NETDEV_* 이벤트 필터 누락unregister_netdevice_notifier
inetaddrregister_inetaddr_notifierIPv4 주소 변경 구독net namespace 미고려register_inet6addr_notifier
rebootregister_reboot_notifier시스템 종료 전 정리우선순위 무시unregister_reboot_notifier
CPUcpuhp_setup_stateCPU hotplug 이벤트 구독teardown 콜백 누락cpuhp_remove_state
PMregister_pm_notifier시스템 suspend/resume 구독resume 경로 무시unregister_pm_notifier
OOMregister_oom_notifierOOM 이벤트 구독경합 증가unregister_oom_notifier
콜백 반환NOTIFY_DONE처리 완료, 계속 전파NOTIFY_OK과 혼동NOTIFY_STOP
콜백 반환NOTIFY_STOP처리 완료, 전파 중단불필요한 전파 차단NOTIFY_BAD
/* netdev notifier 최소 패턴 */
static int demo_netdev_event(struct notifier_block *nb,
                              unsigned long event, void *ptr)
{
    struct net_device *dev = netdev_notifier_info_to_dev(ptr);

    switch (event) {
    case NETDEV_UP:
        pr_info("device %s up\n", dev->name);
        break;
    case NETDEV_DOWN:
        pr_info("device %s down\n", dev->name);
        break;
    }
    return NOTIFY_DONE;
}

static struct notifier_block demo_nb = {
    .notifier_call = demo_netdev_event,
};

/* init */
register_netdevice_notifier(&demo_nb);
/* exit */
unregister_netdevice_notifier(&demo_nb);

확장 | sysfs/procfs/debugfs 인터페이스 카탈로그

커널-사용자 공간 인터페이스는 용도에 따라 sysfs(장치 속성), procfs(프로세스/시스템 정보), debugfs(디버그 전용)로 분리합니다. 인터페이스 선택을 잘못하면 ABI 유지 비용이 불필요하게 높아지거나, 디버그 전용 정보가 영구 ABI로 굳어지는 문제가 발생합니다.

실무 체크포인트: 새 인터페이스를 추가할 때 "이것이 안정 ABI여야 하는가?"를 먼저 결정하세요. 디버그/개발 전용이면 debugfs, 장치 속성이면 sysfs, 커널 전역 정보면 procfs가 원칙입니다.

사용자 공간에 정보 노출 필요 장치 속성 (안정 ABI) sysfs DEVICE_ATTR_* 커널/프로세스 정보 procfs proc_create / seq_file 디버그 전용 (비안정) debugfs debugfs_create_* 주의: sysfs는 한번 공개 시 영구 ABI debugfs는 lockdown 시 접근 불가 공통: one-value-per-file 원칙 (sysfs), seq_file 패턴 (procfs)
인터페이스 선택은 ABI 안정성 요구 여부로 먼저 분류하고, 대상(장치/프로세스/디버그)에 따라 최종 결정합니다.

sysfs 속성

분류핵심 API언제 사용실수 패턴대체/보완
sysfs 속성DEVICE_ATTR_RO읽기 전용(Read-Only) 장치 속성show 반환값 오류DEVICE_ATTR_RW
sysfs 속성DEVICE_ATTR_RW읽기/쓰기 장치 속성store 검증 누락DEVICE_ATTR_WO
sysfs 그룹sysfs_create_group속성 그룹 일괄 생성remove 누락devm_device_add_group
sysfs 이진BIN_ATTR_RO바이너리 속성크기 불일치sysfs_create_bin_file

procfs 인터페이스

분류핵심 API언제 사용실수 패턴대체/보완
procfs 생성proc_createproc 엔트리 생성remove 누락proc_create_data
procfs 단일값proc_create_single단일 show 함수 proc불필요한 seq_file 사용proc_create
procfs netproc_create_netnet namespace aware procnamespace 미전달proc_create_net_single
seq_fileseq_printf순차적 데이터 출력버퍼 오버플로(Buffer Overflow) 가정seq_puts
seq_fileseq_openseq_file 연결stop에서 리소스 해제 누락single_open
single_opensingle_open단일 show 함수 seqsingle_release 미사용single_release

debugfs 인터페이스

분류핵심 API언제 사용실수 패턴대체/보완
debugfs 생성debugfs_create_dir디버그 디렉터리NULL/ERR 혼동debugfs_remove_recursive
debugfs 파일debugfs_create_file커스텀 디버그 파일fops 불완전debugfs_create_u32
debugfs 단순debugfs_create_u32단일 정수 노출동기화 없이 변수 공유debugfs_create_u64
debugfs 불값debugfs_create_boolbool 토글false positive 의존debugfs_create_file
debugfs 제거debugfs_remove_recursive디렉터리+하위 전체 제거NULL dentry 처리 오류debugfs_remove
debugfs blobdebugfs_create_blob바이너리 덤프(Dump)수명주기 불일치debugfs_create_file
configfsconfigfs_register_subsystem사용자 구성 객체 트리item ops 불완전configfs_unregister_subsystem
sysctlregister_sysctlsysctl 테이블 등록proc_handler 오류unregister_sysctl_table
/* sysfs 속성 패턴 */
static ssize_t status_show(struct device *dev,
                           struct device_attribute *attr,
                           char *buf)
{
    struct demo_dev *d = dev_get_drvdata(dev);
    return sysfs_emit(buf, "%u\n", READ_ONCE(d->status));
}
static DEVICE_ATTR_RO(status);

/* seq_file 패턴 */
static int demo_seq_show(struct seq_file *m, void *v)
{
    struct demo_entry *e = v;
    seq_printf(m, "%-16s %8u %8u\n", e->name, e->rx, e->tx);
    return 0;
}

/* debugfs 디렉터리+파일 패턴 */
static struct dentry *demo_debugfs;

static int __init demo_debugfs_init(void)
{
    demo_debugfs = debugfs_create_dir("demo", NULL);
    debugfs_create_u32("counter", 0444, demo_debugfs, &demo_cnt);
    debugfs_create_bool("enabled", 0644, demo_debugfs, &demo_en);
    return 0;
}

static void __exit demo_debugfs_exit(void)
{
    debugfs_remove_recursive(demo_debugfs);
}

확장 | 참조 카운팅(kref/kobject) 카탈로그

참조 카운팅은 커널 객체의 수명주기 계약을 코드로 표현하는 핵심 패턴입니다. kref는 단순 참조 카운팅, kobject는 sysfs 노출과 계층 관리를 추가합니다. 참조 카운팅 버그는 UAF(use-after-free)나 메모리 누수로 직결되므로, get/put 경로의 대칭성을 가장 먼저 검증해야 합니다.

실무 체크포인트: 모든 get 호출에 대응하는 put 경로가 정상/실패/종료 경로 모두에서 도달 가능한지 확인하세요.

kref_init refcount = 1 kref_get refcount++ kref_put refcount-- refcount == 0 release 콜백 사용 중 반복 kobject = kref + sysfs + 계층(parent/kset) kobject_init_and_add → kobject_put (release에서 kfree) 핵심: release 콜백에서만 메모리 해제 — 직접 kfree 금지
kref는 init(1) → get(++) → put(--) → release(0) 순서로 동작하며, kobject는 여기에 sysfs 연결과 계층 관계를 추가합니다.

kref 수명주기

분류핵심 API언제 사용실수 패턴대체/보완
kref 초기화kref_init참조 카운트 1로 초기화이중 초기화refcount_set
kref 획득kref_get참조 카운트 증가이미 0인 객체에 getkref_get_unless_zero
kref 안전 획득kref_get_unless_zeroUAF 방지 참조 획득반환값 무시refcount_inc_not_zero
kref 반납kref_put참조 감소 + 0이면 releaserelease 콜백 미구현kref_put_lock
kref 락+반납kref_put_mutex락 보호 하에 해제교착 위험kref_put_lock

kobject/kset

분류핵심 API언제 사용실수 패턴대체/보완
kobject 생성kobject_init_and_add초기화+sysfs 등록실패 시 put 누락kobject_create_and_add
kobject 해제kobject_put참조 반납+sysfs 제거직접 kfree 호출kobject_del
kobject 삭제kobject_delsysfs만 제거(참조 유지)put 없이 del만 호출kobject_put
kset 생성kset_create_and_addkobject 그룹 관리unregister 누락kset_unregister
ktypekobj_type.release해제 콜백 정의release에서 자원 해제 누락none

refcount 원시 API

분류핵심 API언제 사용실수 패턴대체/보완
refcountrefcount_set원시 참조 카운트 설정초기값 0으로 설정refcount_inc
refcountrefcount_dec_and_test감소+0 판별판별 결과 무시refcount_dec_and_lock
refcountrefcount_dec_and_mutex_lock감소+mutex 획득교착refcount_dec_and_lock
/* kref 기본 수명주기 패턴 */
struct demo_obj {
    struct kref refcount;
    char *name;
    struct list_head link;
};

static void demo_release(struct kref *ref)
{
    struct demo_obj *obj = container_of(ref, struct demo_obj, refcount);
    kfree(obj->name);
    kfree(obj);
}

static struct demo_obj *demo_create(const char *name)
{
    struct demo_obj *obj = kzalloc(sizeof(*obj), GFP_KERNEL);
    if (!obj)
        return NULL;

    obj->name = kstrdup(name, GFP_KERNEL);
    if (!obj->name) {
        kfree(obj);
        return NULL;
    }
    kref_init(&obj->refcount);
    return obj;
}

static void demo_get(struct demo_obj *obj) { kref_get(&obj->refcount); }
static void demo_put(struct demo_obj *obj) { kref_put(&obj->refcount, demo_release); }

확장 | MMIO/PIO 접근 카탈로그

MMIO(Memory-Mapped I/O)와 PIO(Port I/O)는 하드웨어 레지스터 접근의 두 축입니다. MMIO는 메모리 주소 공간(Address Space)에 매핑된 레지스터에 접근하고, PIO는 x86의 IN/OUT 명령어 기반입니다. 접근 너비(8/16/32/64비트), 순서 보장(relaxed 여부), 엔디안(Endianness) 변환이 핵심 체크 포인트입니다.

실무 체크포인트: MMIO 접근에는 readl/writel을 기본으로 사용하고, 핫패스에서만 readl_relaxed를 검토하세요. 직접 포인터 역참조로 레지스터를 읽지 마세요.

하드웨어 레지스터 접근 MMIO (메모리 매핑) ioremap → readl/writel → iounmap 순서 보장 / 엔디안 변환 포함 PIO (포트 I/O, x86 전용) inb/outb, inw/outw, inl/outl 느림 / 레거시 장치 전용 relaxed 변형 (핫패스) readl_relaxed / writel_relaxed 추상화 계층 (ioreadN/iowriteN) MMIO/PIO 자동 판별 금지: 직접 포인터 역참조 (*(volatile u32 *)addr) — 순서/가시성 미보장
MMIO는 ioremap+readl/writel, PIO는 inb/outb 계열이며, 추상화가 필요하면 ioreadN/iowriteN을 사용합니다.

MMIO 접근

분류핵심 API언제 사용실수 패턴대체/보완
MMIO 매핑ioremapMMIO 주소 매핑iounmap 누락devm_ioremap
MMIO 매핑devm_ioremap_resource관리형 매핑+검증ERR_PTR 검사 누락devm_platform_ioremap_resource
MMIO 읽기readb/readw/readl/readq8/16/32/64비트 MMIO 읽기크기 불일치ioread8~ioread64
MMIO 쓰기writeb/writew/writel/writeq8/16/32/64비트 MMIO 쓰기쓰기 후 읽기 확인 누락iowrite8~iowrite64
relaxed 읽기readl_relaxed순서 보장 불필요 핫패스DMA 동기화 필요 구간에서 사용readl
relaxed 쓰기writel_relaxed순서 보장 불필요 핫패스완료 보장 없이 종료writel

PIO/추상화 접근

분류핵심 API언제 사용실수 패턴대체/보완
추상화 읽기ioread32MMIO/PIO 자동 판별불필요한 간접 호출readl
추상화 쓰기iowrite32MMIO/PIO 자동 판별성능 오버헤드writel
PIO 읽기inb/inw/inlx86 포트 I/O 읽기아키텍처 의존성ioread8
PIO 쓰기outb/outw/outlx86 포트 I/O 쓰기아키텍처 의존성iowrite8

블록 접근/배리어/regmap

분류핵심 API언제 사용실수 패턴대체/보완
블록 읽기memcpy_fromioMMIO 영역 블록 읽기비정렬 접근ioread32_rep
블록 쓰기memcpy_toioMMIO 영역 블록 쓰기비정렬 접근iowrite32_rep
블록 초기화memset_ioMMIO 영역 초기화일반 memset 사용none
비트필드FIELD_GET레지스터 필드 추출마스크 불일치FIELD_PREP
비트필드FIELD_PREP레지스터 필드 설정OR 연산 순서 실수GENMASK
write+확인writel+readl쓰기 완료 보장(posted write flush)flush 읽기 누락none
regmapregmap_read추상화된 레지스터 읽기endian 설정 오류regmap_write
regmapregmap_update_bitsread-modify-write 원자적 수행락 범위 오류regmap_write_bits
/* MMIO 레지스터 접근 기본 패턴 */
static void __iomem *base;

/* 레지스터 읽기/쓰기 래퍼 */
static inline u32 demo_read(u32 offset)
{
    return readl(base + offset);
}

static inline void demo_write(u32 offset, u32 val)
{
    writel(val, base + offset);
}

/* 비트필드 조작 패턴 */
#define REG_CTRL         0x00
#define CTRL_ENABLE      BIT(0)
#define CTRL_MODE_MASK   GENMASK(3, 1)

static void demo_set_mode(u32 mode)
{
    u32 val = demo_read(REG_CTRL);
    val &= ~CTRL_MODE_MASK;
    val |= FIELD_PREP(CTRL_MODE_MASK, mode);
    val |= CTRL_ENABLE;
    demo_write(REG_CTRL, val);
    /* posted write flush */
    (void)demo_read(REG_CTRL);
}

확장 | DMA 매핑 카탈로그

DMA 매핑은 CPU와 디바이스 간 메모리 가시성 계약입니다. 스트리밍 매핑(map/unmap), 일관성 매핑(alloc_coherent), DMA 풀 패턴을 용도에 따라 분리해야 하며, 방향(direction) 플래그와 dma_mask 설정을 먼저 고정해야 합니다. 매핑 에러 검사를 생략하면 무효 DMA 주소로 인한 데이터 손상이 발생합니다.

실무 체크포인트: 모든 dma_map_* 호출 직후 dma_mapping_error를 검사하세요. 이 검사가 누락된 코드는 즉시 수정 대상입니다.

필수: dma_map_* 호출 전에 dma_set_mask_and_coherent로 DMA 마스크를 반드시 설정하세요. 미설정 시 32비트 주소만 사용되어 대용량 메모리 시스템에서 실패합니다.
분류핵심 API언제 사용실수 패턴대체/보완
마스크 설정dma_set_mask_and_coherentDMA 주소폭 설정실패 무시dma_set_mask
일관성 할당dma_alloc_coherent일관성 메모리 (설명자 링)dma_handle 보관 누락dma_free_coherent
일관성 해제dma_free_coherent일관성 메모리 해제크기 불일치none
스트리밍 매핑dma_map_single단일 버퍼 스트리밍 매핑에러 검사 누락dma_unmap_single
스트리밍 해제dma_unmap_single스트리밍 매핑 해제방향(direction) 불일치none
에러 검사dma_mapping_error매핑 성공 확인검사 생략none
SG 매핑dma_map_sgscatter-gather 매핑nents 반환값 무시dma_unmap_sg
SG 해제dma_unmap_sgSG 매핑 해제원래 nents 전달(mapped 아닌)none
동기화dma_sync_single_for_cpuCPU 접근 전 동기화방향 불일치dma_sync_single_for_device
동기화dma_sync_single_for_device디바이스 접근 전 동기화크기 불일치dma_sync_single_for_cpu
DMA 풀dma_pool_create소형 DMA 객체 반복 할당destroy 누락dma_pool_destroy
DMA 풀 할당dma_pool_alloc풀에서 객체 할당GFP 문맥 오류dma_pool_free
방향DMA_TO_DEVICECPU→디바이스양방향 남용DMA_FROM_DEVICE
방향DMA_FROM_DEVICE디바이스→CPU방향 반대DMA_TO_DEVICE
방향DMA_BIDIRECTIONAL양방향 전송불필요한 양방향 사용단방향 우선
페이지 매핑dma_map_page페이지 단위 매핑에러 검사 누락dma_unmap_page
리소스 매핑dma_map_resource물리 주소(Physical Address) 직접 매핑사용처 제한 미인지dma_unmap_resource
속성dma_alloc_attrs속성 지정 할당DMA_ATTR_* 오용dma_alloc_coherent
/* DMA 스트리밍 매핑 패턴 */
dma_addr_t dma_handle;
void *buf = kmalloc(BUF_SIZE, GFP_KERNEL);
if (!buf)
    return -ENOMEM;

dma_handle = dma_map_single(dev, buf, BUF_SIZE, DMA_TO_DEVICE);
if (dma_mapping_error(dev, dma_handle)) {
    kfree(buf);
    return -EIO;
}

/* 디바이스에 DMA 전송 시작 */
start_dma_transfer(dma_handle, BUF_SIZE);

/* 완료 후 해제 */
dma_unmap_single(dev, dma_handle, BUF_SIZE, DMA_TO_DEVICE);
kfree(buf);

/* 일관성 메모리 패턴 (descriptor ring) */
dma_addr_t ring_dma;
struct demo_desc *ring;

ring = dma_alloc_coherent(dev, ring_size, &ring_dma, GFP_KERNEL);
if (!ring)
    return -ENOMEM;

/* ring 사용 — CPU와 디바이스 모두 동일 내용 참조 */
ring[0].addr = cpu_to_le64(data_dma);
ring[0].len  = cpu_to_le32(data_len);

/* 해제 */
dma_free_coherent(dev, ring_size, ring, ring_dma);

확장 | 펌웨어 로딩 카탈로그

펌웨어 로딩은 디바이스 초기화 경로의 외부 의존성을 관리하는 API입니다. 로딩 시점(probe/resume), 실패 정책(선택적/필수), 보안 검증을 함께 결정해야 합니다. 특히 initramfs에 펌웨어가 포함되지 않으면 시스템 부팅이 실패할 수 있으므로, fallback 전략이 필수입니다.

실무 체크포인트: 펌웨어 경로/이름을 하드코딩하지 말고 MODULE_FIRMWARE로 선언하여 빌드 시스템(Build System)에서 추적 가능하게 하세요.

분류핵심 API언제 사용실수 패턴대체/보완
동기 로딩request_firmware펌웨어 필수 로딩release 누락release_firmware
선택적 로딩firmware_request_nowarn펌웨어 없어도 진행실패 시 fallback 누락request_firmware
비동기 로딩request_firmware_nowaitprobe 비차단콜백 수명주기 오류request_firmware
직접 로딩request_firmware_directuevent 비사용 로딩사용자 공간 fallback 기대request_firmware
캐시firmware_request_cachesuspend 전 캐시캐시 만료 관리 누락request_firmware
해제release_firmware펌웨어 버퍼 해제이중 해제none
선언MODULE_FIRMWARE의존 펌웨어 선언선언 누락 (배포 패키지에서 누락)none
내장CONFIG_EXTRA_FIRMWARE커널 이미지에 펌웨어 포함라이선스 충돌initramfs 포함
크기 검증fw->size펌웨어 크기 확인크기 미검증으로 오버런헤더 매직/CRC 검증
데이터 접근fw->data펌웨어 바이너리 접근해제 후 접근복사 후 사용
/* 펌웨어 로딩 기본 패턴 */
static int demo_load_fw(struct device *dev)
{
    const struct firmware *fw;
    int ret;

    ret = request_firmware(&fw, "demo/fw.bin", dev);
    if (ret) {
        dev_err(dev, "firmware load failed: %d\n", ret);
        return ret;
    }

    if (fw->size < sizeof(struct demo_fw_hdr)) {
        dev_err(dev, "firmware too small: %zu\n", fw->size);
        ret = -EINVAL;
        goto out;
    }

    /* 펌웨어를 디바이스에 전송 */
    ret = demo_upload_fw(dev, fw->data, fw->size);

out:
    release_firmware(fw);
    return ret;
}

MODULE_FIRMWARE("demo/fw.bin");

확장 | GPIO/Pinctrl API 카탈로그

GPIO 서브시스템(GPIO Subsystem)은 Consumer(gpiod_get/gpiod_set_value)와 Provider(gpiochip) 모델로 분리됩니다. Consumer는 디바이스 트리(Device Tree)의 GPIO 바인딩을 통해 핀을 요청하고, Provider는 GPIO 컨트롤러 드라이버가 gpiochip_add_data로 칩을 등록합니다. Pinctrl 서브시스템과 연계하여 핀 멀티플렉싱(Muxing)과 전기적 속성(Pull-up/down, Drive Strength)을 Device Tree에서 선언적으로 관리합니다.

실무 체크포인트: legacy integer GPIO API(gpio_request 등)는 더 이상 사용하지 마세요. 반드시 descriptor 기반 gpiod_* API를 사용하세요.

GPIO 서브시스템 Consumer (드라이버) devm_gpiod_get / gpiod_set_value Provider (컨트롤러) gpiochip_add_data / irq_chip Pinctrl 연계 pinctrl_select_state / mux DT 바인딩: gpios / gpio-controller / pinctrl-0

Consumer API

분류핵심 API언제 사용실수 패턴대체/보완
요청gpiod_getGPIO descriptor 획득에러 미검사devm_gpiod_get
선택적 요청gpiod_get_optionalGPIO 없어도 진행NULL 미검사devm_gpiod_get_optional
입력 설정gpiod_direction_input핀 입력 모드 전환방향 설정 없이 읽기DT에서 방향 지정
출력 설정gpiod_direction_output핀 출력 모드 + 초기값초기값 미지정DT에서 방향 지정
출력 쓰기gpiod_set_valueatomic 출력 변경sleep 컨텍스트에서 사용gpiod_set_value_cansleep
출력 쓰기 (sleep)gpiod_set_value_cansleepI2C/SPI GPIO 출력IRQ 컨텍스트에서 호출gpiod_set_value
입력 읽기gpiod_get_valueatomic 입력 읽기sleep GPIO에서 사용gpiod_get_value_cansleep
입력 읽기 (sleep)gpiod_get_value_cansleepI2C/SPI GPIO 읽기IRQ 컨텍스트에서 호출gpiod_get_value
IRQ 변환gpiod_to_irqGPIO→IRQ 번호 획득반환값 음수 미검사devm_request_irq
devm 요청devm_gpiod_get자동 해제 GPIO 획득수동 해제 시도gpiod_get
devm 선택적devm_gpiod_get_optional선택적 자동 해제에러와 NULL 혼동gpiod_get_optional
이름 설정gpiod_set_consumer_name디버그용 이름 부여미설정 시 디버그 어려움none
해제gpiod_putGPIO descriptor 해제devm 사용 시 불필요devm_gpiod_get

Provider API

분류핵심 API언제 사용실수 패턴대체/보완
칩 등록gpiochip_add_dataGPIO 컨트롤러 등록에러 경로 미정리devm_gpiochip_add_data
칩 제거gpiochip_removeGPIO 컨트롤러 해제사용 중 제거devm 사용

Pinctrl 연계

분류핵심 API언제 사용실수 패턴대체/보완
핀 컨트롤 획득pinctrl_getpinctrl 핸들 획득해제 누락devm_pinctrl_get
상태 전환pinctrl_select_state핀 상태 전환 (default/sleep)상태 이름 오타DT pinctrl-names 확인
devm 획득devm_pinctrl_get자동 해제 pinctrl수동 해제 시도pinctrl_get
/* GPIO consumer: devm_gpiod_get + gpiod_to_irq + devm_request_irq 조합 */
static int demo_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    struct gpio_desc *gpio;
    int irq, ret;

    /* DT에서 "reset-gpios" 프로퍼티 읽기 */
    gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
    if (IS_ERR(gpio))
        return dev_err_probe(dev, PTR_ERR(gpio), "reset GPIO\n");

    /* IRQ 핀: 입력 모드, 인터럽트 획득 */
    gpio = devm_gpiod_get(dev, "event", GPIOD_IN);
    if (IS_ERR(gpio))
        return dev_err_probe(dev, PTR_ERR(gpio), "event GPIO\n");

    irq = gpiod_to_irq(gpio);
    if (irq < 0)
        return dev_err_probe(dev, irq, "GPIO to IRQ\n");

    ret = devm_request_irq(dev, irq, demo_isr,
                           IRQF_TRIGGER_FALLING, "demo-event", dev);
    if (ret)
        return dev_err_probe(dev, ret, "request IRQ\n");

    return 0;
}
경고: legacy integer GPIO API(gpio_request, gpio_free, gpio_direction_input 등)는 커널 6.x에서 제거 예정입니다. 반드시 descriptor 기반 gpiod_* API를 사용하세요. cansleep 변종을 사용하지 않으면 I2C/SPI expander GPIO에서 sleep-in-atomic 버그가 발생합니다.

관련: I2C / SPI / GPIO | Device Tree

확장 | Device Tree / OF API 카탈로그

Device Tree(DT) 바인딩 파싱을 위한 of_* API와 DT/ACPI 양쪽 호환을 위한 통합 fwnode API를 정리합니다. 드라이버의 probe 함수에서 하드웨어 구성 정보를 읽을 때 사용하며, 노드 탐색, 속성 읽기, phandle 참조 해석이 핵심 패턴입니다. 참조한 노드는 반드시 of_node_put으로 해제해야 합니다.

실무 체크포인트: fwnode 통합 API(device_property_read_*)를 사용하면 DT와 ACPI 플랫폼 모두에서 동작하는 드라이버를 작성할 수 있습니다.

DT 바인딩 (.dts) compatible, reg, clocks... of_* / fwnode 파싱 of_property_read_u32 등 드라이버 구조체 struct demo_priv 초기화 of_node_put() 필수 — 참조 카운트 누수 방지

속성 읽기

분류핵심 API언제 사용실수 패턴대체/보완
정수 읽기of_property_read_u32단일 u32 속성에러 미검사device_property_read_u32
문자열 읽기of_property_read_string문자열 속성반환 포인터 수명fwnode_property_read_string
불리언of_property_read_bool존재 여부 확인값 있는 속성에 사용device_property_read_bool
배열 크기of_property_count_elems_of_size배열 속성 원소 수크기 단위 오류none
phandle 참조of_parse_phandle다른 노드 참조of_node_put 누락none

노드 탐색

분류핵심 API언제 사용실수 패턴대체/보완
이름 검색of_find_node_by_name이름으로 노드 탐색of_node_put 누락of_find_compatible_node
compatible 검색of_find_compatible_nodecompatible 문자열로 탐색참조 해제 누락device_get_match_data
자식 순회for_each_child_of_node모든 자식 노드 순회break 시 put 누락for_each_available_child_of_node
활성 자식 순회for_each_available_child_of_nodestatus="okay" 자식만break 시 put 누락none
자식 수of_get_child_count자식 노드 개수비활성 노드 포함of_get_available_child_count
참조 해제of_node_put노드 참조 카운트 감소누락 시 메모리 누수none

fwnode 통합 API

분류핵심 API언제 사용실수 패턴대체/보완
match datadevice_get_match_datacompatible별 드라이버 데이터NULL 미검사none
fwnode 정수fwnode_property_read_u32fwnode에서 u32 읽기of_* 직접 사용device_property_read_u32
fwnode 문자열fwnode_property_read_stringfwnode에서 문자열of_* 직접 사용device_property_read_string
device 정수device_property_read_u32device에서 u32 (DT/ACPI 호환)DT 전용 of_* 사용none
자식 순회device_for_each_child_nodedevice fwnode 자식 순회break 시 put 누락none
/* probe 내 DT 속성 읽기 패턴 */
static int demo_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    struct device_node *np = dev->of_node;
    struct device_node *phy_np;
    u32 bus_width;
    int ret;

    ret = of_property_read_u32(np, "bus-width", &bus_width);
    if (ret) {
        dev_err(dev, "missing bus-width: %d\n", ret);
        return ret;
    }

    /* phandle 참조 — 반드시 of_node_put 필요 */
    phy_np = of_parse_phandle(np, "phy-handle", 0);
    if (!phy_np) {
        dev_err(dev, "missing phy-handle\n");
        return -ENODEV;
    }

    /* phy_np 사용 ... */

    of_node_put(phy_np);
    return 0;
}
참고: fwnode 통합 API(device_property_read_*)는 Device Tree와 ACPI 양쪽에서 동작하므로, 새 드라이버에서는 of_* 직접 호출 대신 통합 API 사용이 권장됩니다. 특히 서버/데스크톱 플랫폼 지원을 고려하면 처음부터 fwnode API를 채택하세요.

관련: Device Tree

확장 | 문자 디바이스 등록 카탈로그

문자 디바이스(Character Device) 등록에는 전통적 경로(alloc_chrdev_regioncdev_initcdev_adddevice_create)와 misc_register 단축 경로가 있습니다. 전통적 경로는 다수의 minor 번호와 전용 class가 필요한 복잡한 드라이버에, misc_device는 단일 minor로 충분한 간단한 인터페이스에 적합합니다.

실무 체크포인트: cdev_add 호출 전에 모든 내부 상태 초기화를 완료하세요. cdev_add 이후 즉시 open이 호출될 수 있어 초기화 경쟁이 발생합니다.

전통적 chrdev 경로 alloc_chrdev_region cdev_init cdev_add device_create /dev/demoN misc_device 단축 경로 misc_register /dev/demo 주의: cdev_add 이후 즉시 open 가능 — 초기화 완료 후 호출할 것

전통적 chrdev 등록

분류핵심 API언제 사용실수 패턴대체/보완
번호 할당alloc_chrdev_region동적 major/minor 할당해제 누락register_chrdev_region
정적 할당register_chrdev_region고정 major 번호번호 충돌alloc_chrdev_region
초기화cdev_initcdev 구조체 초기화fops 미설정none
등록cdev_add커널에 cdev 등록초기화 미완 상태 등록none
제거cdev_delcdev 등록 해제순서 오류 (device_destroy 전)none
번호 해제unregister_chrdev_regionmajor/minor 반환cdev_del 전 호출none
클래스 생성class_create장치 클래스 생성에러 미검사none
디바이스 생성device_create/dev 노드 자동 생성에러 경로 정리 누락none
디바이스 제거device_destroy/dev 노드 제거cdev_del 후 호출none
클래스 제거class_destroy장치 클래스 제거순서 오류none

misc_device 단축 경로

분류핵심 API언제 사용실수 패턴대체/보완
등록misc_register단일 minor 간단 장치fops 미설정chrdev 전체 경로
해제misc_deregistermisc 장치 해제사용 중 해제none
/* 전통적 chrdev 전체 경로 */
static struct class *demo_class;
static struct cdev demo_cdev;
static dev_t demo_devno;

static int __init demo_init(void)
{
    int ret;

    ret = alloc_chrdev_region(&demo_devno, 0, 1, "demo");
    if (ret)
        return ret;

    cdev_init(&demo_cdev, &demo_fops);
    ret = cdev_add(&demo_cdev, demo_devno, 1);
    if (ret)
        goto err_region;

    demo_class = class_create("demo");
    if (IS_ERR(demo_class)) {
        ret = PTR_ERR(demo_class);
        goto err_cdev;
    }
    device_create(demo_class, NULL, demo_devno, NULL, "demo%d", 0);
    return 0;

err_cdev:
    cdev_del(&demo_cdev);
err_region:
    unregister_chrdev_region(demo_devno, 1);
    return ret;
}

/* misc_device 단축 경로 비교 */
static struct miscdevice demo_misc = {
    .minor = MISC_DYNAMIC_MINOR,
    .name  = "demo",
    .fops  = &demo_fops,
};

static int __init demo_init(void)
{
    return misc_register(&demo_misc);
}
경고: cdev_add 호출 전에 fops 내부에서 접근하는 모든 구조체 초기화를 완료하세요. cdev_add 직후부터 사용자 공간에서 open()이 가능하며, 초기화되지 않은 필드에 접근하면 커널 패닉(Kernel Panic) 또는 데이터 손상이 발생합니다.

확장 | RCU 카탈로그

RCU(Read-Copy-Update) 변종 선택은 읽기 측 컨텍스트(atomic/preemptible/sleep 가능)에 따라 결정됩니다. Classic RCU는 hardirq/softirq/preempt-disabled 구간에서, RCU-bh는 softirq 보호에서, SRCU는 sleep 가능한 읽기 경로에서 사용합니다. RCU Tasks는 trampoline/tracing 등 특수 용도입니다.

실무 체크포인트: RCU 읽기 측이 sleep 가능해야 하면 반드시 SRCU를 사용하세요. Classic RCU 읽기 구간에서 sleep하면 RCU stall이 발생합니다.

읽기 측 컨텍스트는? Classic RCU rcu_read_lock/unlock RCU-bh rcu_read_lock_bh SRCU (sleep 가능) srcu_read_lock/unlock RCU Tasks tracing/BPF 전용 sleep 금지 (stall 위험) per-CPU srcu_struct 필요

기본 RCU (Classic/BH)

분류핵심 API언제 사용실수 패턴대체/보완
포인터 게시rcu_assign_pointer새 데이터 게시 (쓰기 측)직접 대입none
포인터 읽기rcu_dereference읽기 측에서 포인터 역참조읽기 구간 밖 사용rcu_dereference_protected
동기 대기synchronize_rcu모든 기존 reader 종료 대기softirq에서 호출 (deadlock)call_rcu
비동기 해제call_rcugrace period 후 콜백콜백 오프셋 오류kfree_rcu
BH 읽기 진입rcu_read_lock_bhsoftirq 비활성 읽기 구간classic lock과 혼용rcu_read_lock
BH 읽기 종료rcu_read_unlock_bhBH 읽기 구간 종료짝 불일치rcu_read_unlock
간편 해제kfree_rcugrace period 후 kfreercu_head 오프셋 오류call_rcu
콜백 배리어rcu_barrier모듈 해제 전 콜백 완료 보장모듈 exit에서 누락none

SRCU

분류핵심 API언제 사용실수 패턴대체/보완
읽기 진입srcu_read_locksleep 가능 읽기 구간idx 저장 누락rcu_read_lock
읽기 종료srcu_read_unlockSRCU 읽기 구간 종료idx 불일치rcu_read_unlock
동기 대기synchronize_srcuSRCU reader 종료 대기잘못된 srcu_struct 사용call_srcu

RCU 리스트 연산

분류핵심 API언제 사용실수 패턴대체/보완
리스트 추가list_add_rcuRCU 보호 리스트에 추가일반 list_add 사용none
리스트 삭제list_del_rcuRCU 보호 리스트에서 삭제일반 list_del 사용none
해시 추가hlist_add_head_rcuRCU 보호 해시 리스트 추가일반 hlist_add_head 사용none
/* SRCU reader + publisher 패턴 */
DEFINE_SRCU(demo_srcu);

struct demo_data {
    struct rcu_head rcu;
    int value;
};

static struct demo_data __rcu *global_data;

/* 읽기 측 — sleep 가능 */
int demo_read(void)
{
    struct demo_data *d;
    int idx, val;

    idx = srcu_read_lock(&demo_srcu);
    d = srcu_dereference(global_data, &demo_srcu);
    val = d ? d->value : -1;
    srcu_read_unlock(&demo_srcu, idx);
    return val;
}

/* 쓰기 측 — 새 데이터 게시 */
void demo_update(int new_val)
{
    struct demo_data *new_d, *old_d;

    new_d = kmalloc(sizeof(*new_d), GFP_KERNEL);
    if (!new_d)
        return;
    new_d->value = new_val;

    old_d = rcu_dereference_protected(global_data, 1);
    rcu_assign_pointer(global_data, new_d);
    if (old_d)
        kfree_rcu(old_d, rcu);
}
경고: synchronize_rcu()의 grace period는 수 밀리초~수십 밀리초가 소요됩니다. 핫 패스에서 반복 호출하면 성능이 극적으로 저하됩니다. 대량 해제에는 kfree_rcu 또는 call_rcu를 사용하고, 모듈 해제 시에만 synchronize_rcu + rcu_barrier를 사용하세요.

관련: RCU

확장 | Clock/Time API 카탈로그

커널 내부 시간 관리는 클럭 소스(Clock Source) 선택이 핵심입니다. ktime_get(monotonic)은 일반 경과 시간에, ktime_get_boottime은 suspend 포함 경과에, ktime_get_real은 UTC 벽시계에 사용합니다. hrtimer는 나노초 정밀도 타이머를, clk framework는 SoC 하드웨어 클럭을 관리합니다.

실무 체크포인트: 타임아웃/경과 측정에는 반드시 monotonic 시간원을 사용하세요. ktime_get_real은 NTP 보정으로 역행할 수 있습니다.

커널 시간원 (Clock Source) MONOTONIC ktime_get() 경과 시간 (기본) BOOTTIME ktime_get_boottime() suspend 포함 REALTIME ktime_get_real() UTC 벽시계 RAW MONOTONIC ktime_get_raw() NTP 미보정 REALTIME은 NTP 보정으로 역행 가능 — 타임아웃에 사용 금지

ktime/시간원 선택

분류핵심 API언제 사용실수 패턴대체/보완
monotonicktime_get경과 시간 (기본 선택)realtime 사용ktime_get_ns
monotonic (ns)ktime_get_ns나노초 단위 경과오버플로 미고려ktime_get
boottimektime_get_boottimesuspend 포함 경과monotonic과 혼동ktime_get_boottime_ns
realtimektime_get_realUTC 벽시계 (로그 전용)타임아웃에 사용ktime_get
변환 (ns)ktime_to_nsktime_t → 나노초부호 오류ktime_to_us
변환 (역)ns_to_ktime나노초 → ktime_t음수 입력none

hrtimer API

분류핵심 API언제 사용실수 패턴대체/보완
초기화hrtimer_inithrtimer 초기화클럭 선택 오류none
시작hrtimer_start타이머 시작모드 불일치hrtimer_start_range_ns
취소hrtimer_cancel타이머 취소 + 콜백 대기hrtimer_try_to_cancel 오용none
갱신hrtimer_forward_now주기적 타이머 재시작만료 누적 미처리hrtimer_forward

clk framework 연계

분류핵심 API언제 사용실수 패턴대체/보완
활성화clk_prepare_enable클럭 준비 + 활성화비활성화 누락devm_clk_get + 자동 관리
주파수 조회clk_get_rate현재 클럭 주파수비활성 상태에서 조회none
주파수 설정clk_set_rate클럭 주파수 변경부모 클럭 제약 무시clk_round_rate
획득devm_clk_get자동 해제 클럭 획득DT con_id 오류clk_get
/* hrtimer 주기적 콜백 패턴 */
static struct hrtimer demo_timer;

static enum hrtimer_restart demo_timer_cb(struct hrtimer *timer)
{
    /* 주기적 작업 수행 */
    do_periodic_work();

    /* 다음 주기로 갱신 (1ms 주기) */
    hrtimer_forward_now(timer, ns_to_ktime(1000000));
    return HRTIMER_RESTART;
}

static int demo_start_timer(void)
{
    hrtimer_init(&demo_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
    demo_timer.function = demo_timer_cb;
    hrtimer_start(&demo_timer, ns_to_ktime(1000000),
                  HRTIMER_MODE_REL);
    return 0;
}

static void demo_stop_timer(void)
{
    hrtimer_cancel(&demo_timer);
}
시간원 선택 기준: 경과 시간/타임아웃 → ktime_get()(monotonic), suspend 포함 경과 → ktime_get_boottime(), 사용자 표시용 시각 → ktime_get_real(). 벤치마크/프로파일링에서 NTP 보정 없이 순수 하드웨어 카운터가 필요하면 ktime_get_raw()를 사용하세요.

관련: ktime/Clocks

확장 | Crypto API 카탈로그

커널 Crypto API는 계층 아키텍처(Layered Architecture)로 설계되어 있습니다. 사용자 공간은 AF_ALG 소켓으로, 커널 내부는 crypto_alloc_*으로 알고리즘에 접근합니다. 각 알고리즘은 template(예: cbc, xts)과 driver(예: aesni)로 분리되며, 하드웨어 가속이 가능한 경우 자동으로 hw driver가 선택됩니다.

실무 체크포인트: 비동기 API(ahash, skcipher)의 반환값이 -EINPROGRESS/-EBUSY일 때 crypto_wait_req로 완료를 대기해야 합니다.

User / AF_ALG kcapi / openssl Crypto Core API crypto_alloc_* Template cbc / xts / gcm HW Driver aesni / ccp / caam 우선순위 기반 자동 선택: HW 가속 > SW fallback

해시(Hash) API

분류핵심 API언제 사용실수 패턴대체/보완
ahash 할당crypto_alloc_ahash비동기 해시 알고리즘 할당해제 누락crypto_alloc_shash
ahash 요청ahash_request_alloc비동기 해시 요청 객체해제 누락none
ahash 계산crypto_ahash_digest한번에 해시 계산-EINPROGRESS 미처리crypto_shash_digest
shash 할당crypto_alloc_shash동기 해시 알고리즘 할당해제 누락crypto_alloc_ahash
shash 계산crypto_shash_digest동기 해시 한번에 계산desc 스택 할당 크기 오류none
해제crypto_free_ahashahash 해제이중 해제none

대칭 암호화(Symmetric Cipher)

분류핵심 API언제 사용실수 패턴대체/보완
skcipher 할당crypto_alloc_skcipher대칭 암호 할당알고리즘 이름 오류none
요청 할당skcipher_request_alloc암호화 요청 객체해제 누락none
암호화crypto_skcipher_encrypt데이터 암호화-EINPROGRESS 미처리none
해제crypto_free_skcipherskcipher 해제요청 미해제none

AEAD/공통

분류핵심 API언제 사용실수 패턴대체/보완
AEAD 할당crypto_alloc_aead인증 암호화 할당authsize 미설정none
완료 대기crypto_wait_req비동기 요청 완료 대기-EINPROGRESS 직접 처리none
/* shash digest 계산 패턴 (SHA-256) */
static int demo_sha256(const u8 *data, unsigned int len,
                       u8 *digest)
{
    struct crypto_shash *tfm;
    struct shash_desc *desc;
    int ret;

    tfm = crypto_alloc_shash("sha256", 0, 0);
    if (IS_ERR(tfm))
        return PTR_ERR(tfm);

    desc = kmalloc(sizeof(*desc) + crypto_shash_descsize(tfm),
                   GFP_KERNEL);
    if (!desc) {
        ret = -ENOMEM;
        goto out_tfm;
    }
    desc->tfm = tfm;

    ret = crypto_shash_digest(desc, data, len, digest);

    kfree(desc);
out_tfm:
    crypto_free_shash(tfm);
    return ret;
}
참고: akcipher(비대칭 암호)와 KPP(Key-agreement Protocol Primitives)는 비교적 신규 API로, 커널 내부에서의 RSA/ECDSA 서명 검증이나 DH/ECDH 키 교환에 사용됩니다. IMA(Integrity Measurement Architecture)와 함께 활용되는 사례가 증가하고 있습니다.

관련: 암호화 서브시스템

확장 | 실전 함정 패턴 모음

코드 리뷰에서 반복적으로 발견되는 실전 함정을 패턴화한 모음입니다. 각 항목은 "컴파일은 통과하지만 장애를 유발하는" 코드를 중심으로, 잘못된 코드와 올바른 수정을 나란히 제시합니다. 새 패치를 제출하기 전에 이 목록을 체크리스트로 역검증하면 리뷰 지적의 상당수를 선제적으로 제거할 수 있습니다.

실무 체크포인트: 아래 패턴 중 하나라도 현재 코드에 존재하면, 기능 동작과 무관하게 즉시 수정 우선순위로 분류하세요.

실전 함정 패턴 분류 수명주기 함정 UAF, 이중해제, 누수 문맥 위반 함정 sleep in atomic, 락 교착 반환값 함정 NULL/ERR_PTR 혼동 순서/가시성 함정 배리어 누락, 순서 역전 공통 대응: 정적 분석(sparse/smatch) + lockdep + KASAN/KCSAN 활성화
함정 패턴은 수명주기, 문맥 위반, 반환값, 순서/가시성의 4대 분류로 나뉘며, 정적 분석과 런타임 검증을 병행해야 탐지 가능합니다.
자동 탐지: 이 함정 패턴의 상당수는 Coccinelle semantic patch로 자동 탐지 가능합니다. scripts/coccinelle/ 디렉터리에서 기존 규칙을 확인하세요.

수명주기 함정

#함정 이름잘못된 코드올바른 수정분류
2krealloc 원본 유실p = krealloc(p, ...) → 실패 시 원본 유실tmp = krealloc(p, ...); if (!tmp) { ... } p = tmp;수명주기
5devm+수동 혼용devm_kzallockfree 수동 호출devm_*만 사용 또는 수동만 사용수명주기
6순회 중 삭제list_for_each_entrylist_dellist_for_each_entry_safe 사용수명주기
9unwind 순서 역전할당 순서: A→B→C, 해제: A→B→C해제 역순: C→B→A수명주기
12cancel_work 자기 문맥워크큐 핸들러 내 cancel_work_sync(self)cancel_work_sync는 외부에서만 호출수명주기
15refcount 0에서 getkref_get() 이미 0인 객체에 호출kref_get_unless_zero() + 반환값 검사수명주기
19module_put 언밸런스try_module_get 없이 module_putget/put 쌍 유지수명주기

문맥 위반 함정

#함정 이름잘못된 코드올바른 수정분류
4IRQ 내 mutexhardirq 핸들러에서 mutex_lock()spin_lock_irqsave() 또는 워크큐 defer문맥 위반
7spin_lock 내 sleepspin_lock() 보유 중 kmalloc(GFP_KERNEL)GFP_ATOMIC 사용 또는 락 밖 할당문맥 위반
8WRITE_ONCE 게시 오해WRITE_ONCE(data, val); WRITE_ONCE(flag, 1);WRITE_ONCE(data, val); smp_store_release(&flag, 1);순서/가시성
11tasklet 긴 작업tasklet 콜백에서 수백 µs 처리workqueue로 이관문맥 위반
14RCU read 내 sleeprcu_read_lock()mutex_lock()SRCU 사용 또는 구조 변경문맥 위반
16irqsave flags 전달flags를 다른 함수에 전달 후 restore같은 함수 내에서 save/restore 쌍 유지문맥 위반
18GFP_KERNEL in softirqsoftirq 컨텍스트에서 kmalloc(GFP_KERNEL)GFP_ATOMIC 사용문맥 위반

반환값/순서 함정

#함정 이름잘못된 코드올바른 수정분류
1ERR_PTR NULL 혼동if (!ptr)ERR_PTR 놓침if (IS_ERR(ptr))반환값
3copy_from_user 반환값 무시copy_from_user(dst, src, n);if (copy_from_user(...)) return -EFAULT;반환값
10dma_map 에러 무시dma_map_single() 후 검사 없이 사용if (dma_mapping_error(...)) return -EIO;반환값
13snprintf 반환값 혼동len = snprintf(buf, sz, ...) → len > sz 미검사scnprintf 사용 또는 잘림 검사반환값
17posted write 미완료writel(val, reg) 후 즉시 종료writel(val, reg); (void)readl(reg); 으로 flush순서/가시성
20ARRAY_SIZE 포인터 적용포인터에 ARRAY_SIZE 사용크기를 별도 인자로 전달반환값
/* 함정 #2 krealloc 원본 보호 패턴 */
void *tmp = krealloc(buf, new_size, GFP_KERNEL);
if (!tmp) {
    /* buf는 여전히 유효 — 기존 데이터 보존 */
    pr_warn("realloc failed, keeping old buffer\n");
    return -ENOMEM;
}
buf = tmp;

/* 함정 #9 올바른 unwind 순서 */
static int demo_init(void)
{
    int ret;

    ret = step_a_init();     /* 1번째 */
    if (ret)
        return ret;

    ret = step_b_init();     /* 2번째 */
    if (ret)
        goto undo_a;

    ret = step_c_init();     /* 3번째 */
    if (ret)
        goto undo_b;

    return 0;

undo_b:
    step_b_exit();           /* 역순: 2번 해제 */
undo_a:
    step_a_exit();           /* 역순: 1번 해제 */
    return ret;
}

/* 함정 #8 올바른 게시 순서 */
WRITE_ONCE(shared_data, new_value);
smp_store_release(&data_ready, 1);

/* 읽기 측 */
if (smp_load_acquire(&data_ready))
    use(READ_ONCE(shared_data));

운영 | 보안 취약점 대응 플레이북

취약점 대응은 탐지 속도보다 재현 가능성과 완화 우선순위가 중요합니다. 아래 표는 커널 취약점 대응의 실전 기준 절차입니다.

단계핵심 액션사용 API/도구실수 패턴완화 포인트
탐지이상 징후 수집audit_log*, tracepoint, dmesg단일 로그 소스만 의존증거 다중 수집
분류취약점 유형 판정(UAF/OOB/경합)KASAN/KCSAN/UBSAN재현 전 단정유형별 재현 템플릿 사용
재현최소 입력으로 재현 시나리오 고정syzkaller repro, kselftest환경 차이 방치커널 config/커밋 고정
영향도권한 상승/정보노출/DoS 범위 평가LSM hook 추적, capability 검토로컬/원격 경계 혼동CVSS/운영 맥락 병행
원인 추적문제 커밋/경로 식별git bisect, ftrace증상 커밋만 수정invariant 붕괴 지점 확인
즉시 완화공격 표면 축소sysctl, module blacklist서비스 영향 미검토가역적 완화 우선
패치 설계근본 원인 수정 + 방어코드refcount_t, READ_ONCE증상 우회 코드수명주기/동기화 재설계
검증회귀/성능/안정성 확인KUnit, kselftest, perf정상 경로만 테스트실패 경로 자동화 포함
배포백포트/릴리스 노트 반영stable queue, CVE 공지영향 버전 누락수정 대상 버전 명확화
사후탐지 룰/가드레일 강화Coccinelle, CI policy단발성 대응 종료재발 방지 룰 추가
/* UAF 완화 예시: refcount로 수명주기 보호 */
struct demo_obj {
    refcount_t refcnt;
    spinlock_t lock;
    struct rcu_head rcu;
};

static bool demo_get(struct demo_obj *o)
{
    return refcount_inc_not_zero(&o->refcnt);
}

static void demo_put(struct demo_obj *o)
{
    if (refcount_dec_and_test(&o->refcnt))
        kfree_rcu(o, rcu);
}

운영 | 커널 패닉(Kernel Panic)/크래시 즉시 대응 절차

패닉 대응은 "재부팅"보다 "증거 보존"이 먼저입니다. 아래 절차는 운영 현장에서의 우선순위를 반영합니다.

시점즉시 조치수집 항목도구/명령주의점
T0자동 재부팅 정책 확인panic timeout, watchdogsysctl kernel.panic증거 수집 전 재부팅 방지
T0+콘솔 출력 확보Oops/Panic traceserial console, netconsole스크린샷만 남기지 말 것
T1메모리 덤프 경로 확인vmcorekdump/crashkernel덤프 공간 부족 점검
T1+pstore 수집ramoops 레코드/sys/fs/pstore재부팅 시 덮어쓰기 주의
T2락업 유형 분류softlockup/hardlockup/RCU stallwatchdog trace증상 명칭 혼동 금지
T2+모듈/커널 정보 고정빌드 ID, taint, configuname -a, /proc/sys/kernel/tainted버전 추정 금지
T3재현 조건 최소화입력/로드/시점stress, repro script동시 변경 최소화
T3+콜트레이스 해석faulting RIP/stackcrash, gdb, addr2lineinlined frame 누락 주의
T4완화 패치 적용hotfix 결과livepatch/quick patch근본 수정과 분리 관리
T5사후 리포트 작성원인/영향/재발방지incident template타임라인 정확성 유지

운영 | 성능 튜닝 체크리스트 (워크로드별)

성능 튜닝은 워크로드 유형별 병목(Bottleneck)이 다르므로 단일 "최적화 옵션"이 없습니다. 측정 지표를 먼저 고정한 뒤 조정해야 합니다.

워크로드주요 병목1차 측정튜닝 포인트회귀 체크
고QPS 네트워크irq/NAPI 경합, skb 할당perf top, /proc/softirqsRPS/XPS, napi budget, page_poolp99 지연, drop율
저지연 트레이딩스케줄 지터, irq-off 시간osnoise, trace_irqsoffCPU isolation, NO_HZ_FULLworst-case latency
대용량 스토리지큐 깊이, flush 비용iostat, blktracemq-deadline/bfq, io_uring iopolltail latency, write amp
DB OLTPlock 경합, page cache missperf c2c, vmstatNUMA binding, dirty ratio, hugepageTPS, 99p commit time
로그 수집/스트리밍context switch, copy overheadperf sched, bpftracebatching, zero-copy, busy-pollCPU/throughput 균형
컨테이너 멀티테넌트cgroup 경쟁, PSI 상승psi, cgroup statcpu.weight, memory.high, io.maxnoisy-neighbor 억제율
GPU 추론DMA-BUF 동기화, IRQ burstdrm trace, irqstatfence batching, cpuset 분리frame time, jitter
미디어 인코딩V4L2 queue underflowvb2 stats, ftracebuffer depth, dma burst 조정drop frame 비율
가상화 호스트VM-exit 폭증, dirty loggingperf kvm stathugepage, posted interrupt, halt pollguest steal time
파일 서버dentry/inode cache thrashslabtop, vfsstatreadahead, writeback, nfsd threadops/sec, cache hitrate
튜닝 원칙: 설정을 한 번에 많이 바꾸지 말고, 한 번에 하나의 변수만 바꾸세요. 변경마다 기준 지표(p50/p99, CPU, 에러율)를 같은 부하로 재측정해야 의미 있는 결론을 얻을 수 있습니다.

운영 | 적용 순서 해설

문서를 읽고 실제 코드/시스템에 반영할 때는 아래 순서를 추천합니다. 이 순서는 장애 위험을 낮추고 롤백(Rollback)을 쉽게 만듭니다.

  1. 관측 먼저
    로그 레벨, tracepoint, 기본 메트릭을 먼저 켭니다. 관측 없는 최적화는 회귀를 숨깁니다.
  2. 안전성 다음
    반환값 규약, 락 순서, 해제 경로를 고정합니다. 성능 이전에 crash/UAF를 먼저 제거합니다.
  3. 성능 마지막
    핫패스에 한정해서 튜닝합니다. 모든 경로를 동시에 건드리면 원인 분리가 불가능해집니다.
  4. 롤백 준비
    변경 전/후 수치를 저장하고 즉시 되돌릴 스위치(sysctl/module param)를 남깁니다.

운영 | 실전 체크리스트

이 체크리스트는 문서 전반의 규약을 배포 직전 점검 항목으로 재구성한 것입니다. 각 항목은 단독 통과보다 항목 간 정합성이 중요하며, 하나라도 누락되면 장애 전파 경로가 열릴 수 있습니다. 운영 반영 전에는 기능 테스트와 별도로 이 목록을 독립 검증해야 합니다.

실무 체크포인트: 체크리스트 점검 결과를 릴리스 노트에 남겨 회귀 시점 추적이 가능하도록 관리하세요.

다음 학습:

상세 | 커널 API 분류 체계

커널 API는 기능 도메인별로 분류되며, 각 도메인은 고유한 규약 체계를 가집니다. 아래 다이어그램은 메모리, 프로세스, 파일시스템, 네트워크, 동기화, 디바이스의 6대 영역과 이들 사이의 공통 기반(에러 처리, GFP, 참조 카운팅)을 보여줍니다. API 선택 시 도메인 내부 규약뿐 아니라 도메인 간 교차점(예: 네트워크 + 메모리의 GFP_ATOMIC 제약, 파일시스템 + 동기화의 mmap_lock 계층)도 함께 고려해야 합니다.

실무 체크포인트: 새 기능 구현 전에 관련 도메인의 규약 표를 먼저 교차 확인하고, 도메인 경계에서의 문맥 전환(프로세스↔IRQ)을 설계 문서에 명시하세요.

커널 API 분류 체계 공통 기반 계층 ERR_PTR | GFP 플래그 | refcount container_of | list_head | RCU 메모리 관리 kmalloc | vmalloc | slab folio | alloc_pages | DMA 프로세스/스케줄링 kthread | wait_event | completion schedule | cond_resched 파일시스템/VFS inode | dentry | super_block seq_file | iomap | bio 네트워크 sk_buff | NAPI | XDP netfilter | socket 동기화 spinlock | mutex | rwsem RCU | seqlock | atomic 디바이스 모델 platform_driver | cdev | misc devm_* | device tree | sysfs 관측성: printk | tracepoint | ftrace | perf | eBPF
6대 API 도메인은 공통 기반 계층(에러 처리, GFP, 참조 카운팅)을 공유하며, 관측성 계층이 전체를 횡단합니다.

상세 | 메모리 할당 API 상세 비교

메모리 할당 API는 크기, 연속성, 문맥, 해제 방식이 모두 다릅니다. 아래 표와 다이어그램은 각 API의 적합 시나리오를 한눈에 비교하고, 잘못 선택했을 때의 위험을 보여줍니다.

메모리 할당 API — 크기/문맥별 사용 시나리오 1B 4KB (PAGE_SIZE) 128KB 4MB+ kmalloc / kzalloc 물리 연속, 슬랩 캐시, 빠름 | 해제: kfree vmalloc / vzalloc 가상 연속, 페이지 테이블 설정 비용 | 해제: vfree kvmalloc / kvzalloc 작으면 kmalloc, 크면 vmalloc fallback | 해제: kvfree kmem_cache_alloc 고정 크기 반복 객체, ctor 지원 | 해제: kmem_cache_free alloc_pages / __get_free_pages order 기반 물리 연속 페이지 | 해제: __free_pages / free_pages GFP_KERNEL (sleep OK) GFP_ATOMIC (IRQ OK) devm_kzalloc (자동 정리)
할당 크기와 문맥에 따라 적합한 API가 달라집니다. kvmalloc은 크기 불확실 시 안전한 선택입니다.
API최대 크기물리 연속IRQ 문맥해제 API주 용도
kmalloc~128KB (KMALLOC_MAX)GFP_ATOMIC 가능kfree일반 소형 객체
kzalloc~128KBGFP_ATOMIC 가능kfree0 초기화 필요 시
vmalloc가상 주소 공간 한계아니오불가vfree대형 버퍼, 모듈 코드
kvmalloc가상 주소 공간 한계시도 후 fallback불가kvfree크기 불확실 할당
kmem_cache_alloc슬랩 객체 크기GFP_ATOMIC 가능kmem_cache_free고빈도 동일 크기
alloc_pages2^MAX_ORDER 페이지GFP_ATOMIC 가능__free_pagesDMA, 페이지 캐시
devm_kzalloc~128KB불가자동디바이스 드라이버
/* kmalloc vs vmalloc vs kvmalloc 선택 패턴 */

/* 1. 소형 고정 크기 — kmalloc */
struct demo_ctx *ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
if (!ctx)
    return -ENOMEM;

/* 2. 대형 버퍼 (연속 불필요) — vmalloc */
void *big_buf = vzalloc(2 * 1024 * 1024);  /* 2MB */
if (!big_buf)
    return -ENOMEM;
/* ... 사용 ... */
vfree(big_buf);

/* 3. 크기 불확실 — kvmalloc (자동 fallback) */
void *buf = kvzalloc(user_requested_size, GFP_KERNEL);
if (!buf)
    return -ENOMEM;
/* ... 사용 ... */
kvfree(buf);  /* kmalloc/vmalloc 어느 쪽이든 안전 해제 */

/* 4. 슬랩 캐시 — 동일 크기 반복 할당 */
static struct kmem_cache *demo_cache;

static int __init demo_cache_init(void)
{
    demo_cache = kmem_cache_create("demo_obj",
                    sizeof(struct demo_obj), 0,
                    SLAB_HWCACHE_ALIGN, NULL);
    return demo_cache ? 0 : -ENOMEM;
}

struct demo_obj *obj = kmem_cache_zalloc(demo_cache, GFP_KERNEL);
/* ... */
kmem_cache_free(demo_cache, obj);

/* 5. 페이지 단위 할당 — DMA/고차 페이지 */
struct page *pg = alloc_pages(GFP_KERNEL | __GFP_ZERO, 2);  /* 4 페이지 */
if (!pg)
    return -ENOMEM;
void *va = page_address(pg);
/* ... 사용 ... */
__free_pages(pg, 2);  /* 반드시 같은 order로 해제 */

상세 | 동기화 API 선택 의사결정 트리

동기화 메커니즘 선택은 성능 최적화가 아니라 정확성 보장이 1차 목표입니다. 아래 의사결정 트리는 문맥(IRQ/프로세스), sleep 가능 여부, 읽기/쓰기 비율, 임계영역 길이를 순서대로 판단해 적합한 동기화 도구를 결정합니다. 잘못된 동기화 선택은 데드락, 성능 저하, RCU stall 등 진단이 어려운 문제를 유발하므로, 처음부터 올바른 도구를 선택하는 것이 중요합니다.

실무 체크포인트: 새 락을 도입할 때는 반드시 기존 락과의 획득 순서를 문서화하고, lockdep 활성화 상태에서 테스트하세요. 락 변경 패치에는 최소 하나의 경합 지표와 회귀 기준을 함께 첨부합니다.

기술 문서: 스핀락, 뮤텍스, 읽기-쓰기 락, Seqlock, Lockdep 문서에서 각 동기화 도구의 내부 구현과 디버깅 기법을 확인하세요.
동기화 API 선택 의사결정 트리 공유 데이터를 보호해야 하는가? IRQ 핸들러와 공유하는가? 프로세스 문맥만 사용하는가? spin_lock_irqsave 짧은 임계영역 threaded_irq + mutex 긴 처리 필요 시 mutex 기본 상호배제 읽기 우세한가? 읽기/쓰기 비율 확인 rw_semaphore R/W 분리, sleep OK RCU 읽기 압도적, 락리스 seqlock 짧은 스냅샷 읽기 percpu_rwsem CPU 로컬 fast path 필수 점검 사항 lockdep 활성화 | 락 순서 문서화 | 임계영역 최소화 | 교착 시나리오 검증 단순 카운팅만 필요 → refcount_t / atomic_t (락 불필요) Yes Yes 중간 압도적
동기화 선택의 첫 분기는 IRQ 공유 여부이며, 이후 sleep 가능성과 읽기/쓰기 비율로 세분화합니다.
/* 동기화 선택별 대표 패턴 비교 */

/* 패턴 A: IRQ 공유 → spin_lock_irqsave */
static DEFINE_SPINLOCK(hw_lock);
static u32 hw_counter;

static irqreturn_t hw_irq(int irq, void *dev)
{
    spin_lock(&hw_lock);          /* IRQ 내에서는 _irqsave 불필요 */
    hw_counter++;
    spin_unlock(&hw_lock);
    return IRQ_HANDLED;
}

static u32 hw_read_counter(void)
{
    unsigned long flags;
    u32 val;
    spin_lock_irqsave(&hw_lock, flags);  /* 프로세스 문맥: 필수 */
    val = hw_counter;
    spin_unlock_irqrestore(&hw_lock, flags);
    return val;
}

/* 패턴 B: 프로세스 전용 → mutex */
static DEFINE_MUTEX(cfg_lock);

static int cfg_update(const char *new_val)
{
    mutex_lock(&cfg_lock);
    /* sleep 가능 — 파일 I/O, 메모리 할당 안전 */
    kfree(current_cfg);
    current_cfg = kstrdup(new_val, GFP_KERNEL);
    mutex_unlock(&cfg_lock);
    return current_cfg ? 0 : -ENOMEM;
}

/* 패턴 C: 읽기 우세 → RCU */
static struct config __rcu *global_cfg;

static struct config *cfg_read(void)
{
    struct config *c;
    rcu_read_lock();
    c = rcu_dereference(global_cfg);
    /* c를 사용 — 이 구간에서 c는 유효 보장 */
    rcu_read_unlock();
    return c;
}

static void cfg_update_rcu(struct config *new_cfg)
{
    struct config *old;
    old = rcu_replace_pointer(global_cfg, new_cfg, true);
    synchronize_rcu();
    kfree(old);
}

상세 | 에러 코드 빠른 참조

커널 에러 코드(errno)는 실패 원인을 호출자에게 전달하는 표준 계약입니다. 자주 사용되는 에러 코드의 의미와 적합한 사용 상황을 정리합니다. 에러 코드 선택은 호출자의 분기 로직에 직접 영향을 주므로, "실패를 알리는 것"이 아니라 "호출자가 올바른 복구 경로를 선택하게 하는 것"이 목적입니다.

실무 체크포인트: 새 함수에서 에러를 반환할 때는 -EINVAL을 남용하지 말고, 실패 원인에 가장 정확한 코드를 선택하세요. 특히 -EPROBE_DEFER는 드라이버 probe에서만 의미가 있고 일반 함수에서는 사용하면 안 됩니다.

에러 코드의미주 사용 장면
-ENOMEM-12메모리 부족kmalloc/kzalloc 실패
-EINVAL-22잘못된 인수범위 초과, 잘못된 파라미터
-EFAULT-14잘못된 주소copy_from_user/copy_to_user 실패
-ENODEV-19장치 없음probe 시 하드웨어 미감지
-EBUSY-16자원 사용 중이미 활성화된 리소스 재요청
-ETIMEDOUT-110시간 초과하드웨어 응답 대기 초과
-EIO-5I/O 오류하드웨어 통신 실패
-ENOSPC-28공간 부족파일시스템/버퍼 공간 고갈
-EPERM-1작업 불허권한 부족 (capability 미보유)
-EACCES-13접근 거부파일/리소스 접근 권한 부족
-ENOENT-2항목 없음요청한 파일/객체 미존재
-ENOTTY-25부적절한 ioctl지원하지 않는 ioctl 명령
-ERESTARTSYS-512시그널 중단wait_event_interruptible 중단
-ERANGE-34범위 초과kstrtoul 등 변환 결과 오버플로
-EPROBE_DEFER-517프로브 지연의존 리소스 미준비, 나중에 재시도
/* 에러 코드 실전 사용 패턴 */
static int demo_validate_and_apply(struct device *dev,
                                    const char __user *ubuf,
                                    size_t len)
{
    char *kbuf;
    unsigned long val;
    int ret;

    if (len > 64)
        return -EINVAL;    /* 잘못된 인수: 크기 초과 */

    kbuf = kmalloc(len + 1, GFP_KERNEL);
    if (!kbuf)
        return -ENOMEM;   /* 메모리 부족 */

    if (copy_from_user(kbuf, ubuf, len)) {
        kfree(kbuf);
        return -EFAULT;   /* 잘못된 사용자 주소 */
    }
    kbuf[len] = '\0';

    ret = kstrtoul(kbuf, 0, &val);
    kfree(kbuf);
    if (ret)
        return ret;         /* kstrtoul이 -EINVAL/-ERANGE 반환 */

    if (val > HW_MAX_FREQ)
        return -ERANGE;   /* 범위 초과 */

    writel(val, dev_priv->base + FREQ_REG);
    dev_info(dev, "freq set to %lu\n", val);
    return 0;
}

상세 | 워크큐/태스크릿/타이머 API 실전

비동기 실행 메커니즘은 실행 문맥, 지연 특성, 취소 안전성이 각각 다릅니다. 워크큐는 프로세스 문맥(sleep 가능), 태스크릿은 softirq 문맥(sleep 불가), 타이머는 softirq에서 콜백을 실행합니다. 선택의 핵심은 "콜백에서 sleep이 필요한가?"이며, 필요하면 워크큐, 불필요하면 태스크릿 또는 타이머를 사용합니다.

실무 체크포인트: 워크큐 플래그(WQ_UNBOUND, WQ_MEM_RECLAIM, WQ_HIGHPRI)는 워크로드 특성에 맞게 선택하세요. 특히 WQ_MEM_RECLAIM은 메모리 회수 경로에서 사용하는 워크큐에 필수입니다.

기술 문서: 워크큐, 태스크릿, 타이머 문서에서 각 메커니즘의 내부 구현과 고급 사용법을 확인하세요.
비동기 실행 메커니즘 비교 워크큐 (Workqueue) 실행: 프로세스 문맥 sleep: 가능 초기화: INIT_WORK 스케줄: schedule_work 취소: cancel_work_sync 태스크릿 (Tasklet) 실행: softirq 문맥 sleep: 불가 초기화: tasklet_setup 스케줄: tasklet_schedule 취소: tasklet_kill 타이머 (Timer) 실행: softirq 문맥 sleep: 불가 초기화: timer_setup 스케줄: mod_timer 취소: del_timer_sync 적합: 긴 처리, 파일 I/O 메모리 할당, 락 필요 작업 적합: 짧은 하반부 처리 직렬 실행 보장 (CPU별) 적합: 주기적 폴링 타임아웃, 재시도 지연 INIT_DELAYED_WORK + queue_delayed_work 지연 실행 + 프로세스 문맥 = 지연 워크큐 hrtimer_init + hrtimer_start 나노초 정밀도 필요 시 timer_setup 대신 사용 필수: 객체 해제 전 cancel_work_sync / del_timer_sync / tasklet_kill 호출
비동기 메커니즘은 실행 문맥(프로세스/softirq)과 sleep 가능 여부로 구분하며, 해제 전 반드시 동기 취소가 필요합니다.
/* 워크큐 완전 패턴 — INIT_WORK + 전용 큐 + 해제 */
struct demo_hw {
    struct work_struct rx_work;
    struct delayed_work poll_dwork;
    struct workqueue_struct *wq;
    bool running;
};

static void demo_rx_handler(struct work_struct *work)
{
    struct demo_hw *hw = container_of(work, struct demo_hw, rx_work);
    /* 프로세스 문맥 — sleep, 메모리 할당 안전 */
    mutex_lock(&hw->data_lock);
    process_received_data(hw);
    mutex_unlock(&hw->data_lock);
}

static void demo_poll_handler(struct work_struct *work)
{
    struct demo_hw *hw = container_of(work, struct demo_hw, poll_dwork.work);

    if (READ_ONCE(hw->running)) {
        check_hw_status(hw);
        /* 100ms 후 재스케줄 */
        queue_delayed_work(hw->wq, &hw->poll_dwork,
                           msecs_to_jiffies(100));
    }
}

static int demo_hw_init(struct demo_hw *hw)
{
    INIT_WORK(&hw->rx_work, demo_rx_handler);
    INIT_DELAYED_WORK(&hw->poll_dwork, demo_poll_handler);

    hw->wq = alloc_workqueue("demo_wq",
                              WQ_UNBOUND | WQ_MEM_RECLAIM, 0);
    if (!hw->wq)
        return -ENOMEM;

    WRITE_ONCE(hw->running, true);
    queue_delayed_work(hw->wq, &hw->poll_dwork, 0);
    return 0;
}

static void demo_hw_exit(struct demo_hw *hw)
{
    WRITE_ONCE(hw->running, false);
    cancel_delayed_work_sync(&hw->poll_dwork);
    cancel_work_sync(&hw->rx_work);
    destroy_workqueue(hw->wq);
}

/* 타이머 + 타스크릿 패턴 */
static struct tasklet_struct demo_tasklet;

static void demo_tasklet_fn(struct tasklet_struct *t)
{
    /* softirq 문맥 — sleep 불가, GFP_ATOMIC만 */
    pr_debug("tasklet executed on cpu %d\n", smp_processor_id());
}

static int __init demo_tasklet_init(void)
{
    tasklet_setup(&demo_tasklet, demo_tasklet_fn);
    tasklet_schedule(&demo_tasklet);
    return 0;
}

static void __exit demo_tasklet_exit(void)
{
    tasklet_kill(&demo_tasklet);  /* 완료 대기 후 해제 */
}

상세 | 디바이스 모델 API 실전

리눅스 디바이스 모델은 struct device, struct class, struct bus_type을 계층적으로 조합합니다. 플랫폼 드라이버(Platform Driver)는 Device Tree 바인딩과 probe/remove 수명주기를 따르며, devm_* API로 리소스를 자동 관리합니다.

디바이스 모델 계층 구조 struct bus_type platform / PCI / I2C / SPI / USB struct device_driver probe / remove / shutdown struct device devres 스택 | of_node | class platform_driver module_platform_driver() 매크로 devm_* 리소스 메모리 | IRQ | clk | ioremap 사용자 인터페이스 cdev | misc | sysfs | debugfs Device Tree (.dts) → of_match_table → probe 자동 호출
bus_type이 driver와 device를 매칭하고, platform_driver가 Device Tree 바인딩으로 자동 probe됩니다.
/* 플랫폼 드라이버 완전 템플릿 */
struct demo_priv {
    void __iomem *base;
    struct clk *clk;
    int irq;
    u32 hw_version;
};

static int demo_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    struct demo_priv *priv;

    priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
    if (!priv)
        return -ENOMEM;

    /* MMIO 매핑 — ERR_PTR 반환 */
    priv->base = devm_platform_ioremap_resource(pdev, 0);
    if (IS_ERR(priv->base))
        return PTR_ERR(priv->base);

    /* 클럭 획득+활성화 */
    priv->clk = devm_clk_get_enabled(dev, "core");
    if (IS_ERR(priv->clk))
        return PTR_ERR(priv->clk);

    /* IRQ */
    priv->irq = platform_get_irq(pdev, 0);
    if (priv->irq < 0)
        return priv->irq;

    int ret = devm_request_irq(dev, priv->irq, demo_irq_handler,
                               0, dev_name(dev), priv);
    if (ret)
        return ret;

    /* HW 버전 읽기 */
    priv->hw_version = readl(priv->base + VERSION_REG);
    platform_set_drvdata(pdev, priv);
    dev_info(dev, "probe ok, hw v%u\n", priv->hw_version);
    return 0;
}

static void demo_remove(struct platform_device *pdev)
{
    /* devm이 모든 리소스를 역순 해제 — 추가 정리 불필요 */
    dev_info(&pdev->dev, "removed\n");
}

static const struct of_device_id demo_of_match[] = {
    { .compatible = "vendor,demo-v1" },
    { .compatible = "vendor,demo-v2" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, demo_of_match);

static struct platform_driver demo_driver = {
    .probe  = demo_probe,
    .remove = demo_remove,
    .driver = {
        .name = "demo",
        .of_match_table = demo_of_match,
    },
};
module_platform_driver(demo_driver);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Demo platform driver");
MODULE_AUTHOR("Example");

상세 | proc/sysfs/debugfs 인터페이스 실전 패턴

가상 파일시스템 인터페이스는 커널 상태를 사용자 공간에 노출하는 표준 방법입니다. 각 인터페이스의 ABI 안정성 요구 수준이 다르므로, 목적에 맞는 인터페이스를 선택해야 유지보수 비용을 관리할 수 있습니다.

/* === sysfs 읽기/쓰기 속성 완전 패턴 === */
static ssize_t threshold_show(struct device *dev,
                              struct device_attribute *attr,
                              char *buf)
{
    struct demo_dev *d = dev_get_drvdata(dev);
    return sysfs_emit(buf, "%u\n", READ_ONCE(d->threshold));
}

static ssize_t threshold_store(struct device *dev,
                               struct device_attribute *attr,
                               const char *buf, size_t count)
{
    struct demo_dev *d = dev_get_drvdata(dev);
    u32 val;
    int ret;

    ret = kstrtou32(buf, 0, &val);
    if (ret)
        return ret;
    if (val > 1000)
        return -ERANGE;

    WRITE_ONCE(d->threshold, val);
    return count;
}
static DEVICE_ATTR_RW(threshold);

/* 속성 그룹 일괄 등록 */
static struct attribute *demo_attrs[] = {
    &dev_attr_threshold.attr,
    NULL,
};
ATTRIBUTE_GROUPS(demo);

/* driver.dev_groups = demo_groups 로 자동 등록 */
/* === procfs seq_file 완전 패턴 === */
struct demo_stat {
    char name[32];
    u64 count;
    struct list_head node;
};

static void *demo_seq_start(struct seq_file *m, loff_t *pos)
{
    mutex_lock(&demo_list_lock);
    return seq_list_start(&demo_stat_list, *pos);
}

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

static void demo_seq_stop(struct seq_file *m, void *v)
{
    mutex_unlock(&demo_list_lock);
}

static int demo_seq_show(struct seq_file *m, void *v)
{
    struct demo_stat *s = list_entry(v, struct demo_stat, node);
    seq_printf(m, "%-20s %12llu\n", s->name, s->count);
    return 0;
}

static const struct seq_operations demo_seq_ops = {
    .start = demo_seq_start,
    .next  = demo_seq_next,
    .stop  = demo_seq_stop,
    .show  = demo_seq_show,
};

static int __init demo_proc_init(void)
{
    proc_create_seq("demo_stats", 0444, NULL, &demo_seq_ops);
    return 0;
}

static void __exit demo_proc_exit(void)
{
    remove_proc_entry("demo_stats", NULL);
}
/* === debugfs 커스텀 파일 패턴 === */
static struct dentry *demo_dbgdir;
static u32 demo_reg_offset;

static int demo_regdump_show(struct seq_file *s, void *unused)
{
    struct demo_priv *p = s->private;
    u32 val;
    int i;

    for (i = 0; i < 16; i++) {
        val = readl(p->base + i * 4);
        seq_printf(s, "[0x%02x] = 0x%08x\n", i * 4, val);
    }
    return 0;
}
DEFINE_SHOW_ATTRIBUTE(demo_regdump);

static void demo_debugfs_setup(struct demo_priv *p)
{
    demo_dbgdir = debugfs_create_dir("demo_hw", NULL);
    debugfs_create_file("regdump", 0444, demo_dbgdir,
                        p, &demo_regdump_fops);
    debugfs_create_u32("reg_offset", 0644, demo_dbgdir,
                       &demo_reg_offset);
    debugfs_create_bool("trace_en", 0644, demo_dbgdir,
                        &p->trace_enabled);
}

static void demo_debugfs_teardown(void)
{
    debugfs_remove_recursive(demo_dbgdir);
}

상세 | 리스트/해시/rbtree/XArray 실전 패턴

커널 자료구조는 표준 라이브러리 대신 커널 전용 매크로와 인라인 함수로 구현됩니다. 각 자료구조는 삽입/삭제/순회 시 고유한 안전성 규약을 가지며, 특히 동시성 환경에서의 안전 순회(safe iteration)와 RCU 연동이 핵심입니다.

커널 자료구조 선택 가이드 컬렉션에 데이터를 저장해야 한다 순서가 중요한가? 삽입 순서 유지 → list_head 키 기반 탐색? O(1) 해시 탐색 → DEFINE_HASHTABLE 정렬된 범위 탐색? O(log n) 삽입/탐색 → rbtree (rb_root) 정수 인덱스? 희소 배열, RCU safe → XArray / IDR 순회 중 삭제 주의 list: _safe 변형 필수 | hash: _safe 변형 필수 RCU 연동 변형 list_add_rcu | hlist_add_head_rcu | xa_load (RCU safe) 공통: 동시성 보호 필수 (mutex / spinlock / RCU) + container_of로 부모 구조체 접근
자료구조 선택은 접근 패턴(순서/키/범위/인덱스)으로 분류하고, 동시성 보호와 안전 순회 규칙을 함께 결정합니다.
/* rbtree 패턴 — 정렬된 범위 탐색 */
struct demo_interval {
    struct rb_node rb;
    u64 start;
    u64 end;
};

static struct rb_root demo_tree = RB_ROOT;

static void demo_rb_insert(struct demo_interval *new)
{
    struct rb_node **link = &demo_tree.rb_node;
    struct rb_node *parent = NULL;

    while (*link) {
        struct demo_interval *entry =
            rb_entry(*link, struct demo_interval, rb);
        parent = *link;

        if (new->start < entry->start)
            link = &(*link)->rb_left;
        else
            link = &(*link)->rb_right;
    }
    rb_link_node(&new->rb, parent, link);
    rb_insert_color(&new->rb, &demo_tree);
}

static struct demo_interval *demo_rb_find(u64 addr)
{
    struct rb_node *node = demo_tree.rb_node;

    while (node) {
        struct demo_interval *entry =
            rb_entry(node, struct demo_interval, rb);

        if (addr < entry->start)
            node = node->rb_left;
        else if (addr >= entry->end)
            node = node->rb_right;
        else
            return entry;  /* 범위 내 */
    }
    return NULL;
}

/* IDR 패턴 — 동적 ID 할당/해제 */
static DEFINE_IDR(demo_idr);

static int demo_register_obj(struct demo_obj *obj)
{
    int id;

    idr_lock(&demo_idr);
    id = idr_alloc(&demo_idr, obj, 0, 0, GFP_KERNEL);
    idr_unlock(&demo_idr);

    if (id < 0)
        return id;

    obj->id = id;
    return 0;
}

static void demo_unregister_obj(struct demo_obj *obj)
{
    idr_remove(&demo_idr, obj->id);
}

상세 | 실전 코딩 패턴 모음

커널 개발에서 반복적으로 사용되는 코딩 패턴을 모았습니다. 각 패턴은 초기화/정리, ioctl 디스패치, probe/remove 쌍, 에러 전파 등 실무에서 가장 자주 작성하는 코드 구조입니다. 이 패턴들은 "복붙(Copy-Paste) 템플릿"이 아니라 "제어 흐름 설계 참고서"로 사용해야 합니다. 변수명보다 할당-실패-해제의 흐름 구조를 먼저 이식하고, 프로젝트 고유 요구사항에 맞게 조정하세요.

실무 체크포인트: 새 모듈/드라이버 작성 시 아래 패턴 중 적합한 것을 골라 실패 경로부터 먼저 코드를 작성하세요. 정상 경로는 실패 경로가 닫힌 것을 확인한 후 채웁니다.

패턴 적용 순서: ① 수명주기 뼈대(init/exit 또는 probe/remove) 확정 → ② 에러 처리 경로 설계(goto label) → ③ 인터페이스(fops/sysfs) 연결 → ④ 동시성 보호(락/RCU) 추가 → ⑤ 관측성(로그/tracepoint) 배치
실전 코딩 패턴 분류 수명주기 패턴 init/exit probe/remove open/release 인터페이스 패턴 ioctl 디스패치 read/write fops sysfs show/store 에러 처리 패턴 goto cleanup ERR_PTR 전파 devm 자동 정리 동시성 패턴 RCU 갱신/조회 워크큐 defer per-CPU 통계 공통 규칙 1. 실패 경로를 먼저 설계 → 2. 해제는 할당 역순 → 3. devm과 수동 혼용 금지 → 4. 반환값 반드시 검사 모듈 init/exit 뼈대 module_init(demo_init) module_exit(demo_exit) MODULE_LICENSE / DESCRIPTION / AUTHOR file_operations 뼈대 .owner = THIS_MODULE .open / .release / .read / .write .unlocked_ioctl / .poll / .mmap
모든 커널 코딩 패턴은 수명주기, 인터페이스, 에러 처리, 동시성의 4가지 축으로 분류되며, 공통 규칙을 따릅니다.
/* === file_operations + ioctl 완전 뼈대 === */
struct demo_file_ctx {
    struct mutex lock;
    u32 mode;
    u32 buf_size;
    void *buf;
};

static int demo_fop_open(struct inode *inode, struct file *filp)
{
    struct demo_file_ctx *ctx;

    ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
    if (!ctx)
        return -ENOMEM;

    mutex_init(&ctx->lock);
    ctx->buf_size = PAGE_SIZE;
    ctx->buf = kzalloc(ctx->buf_size, GFP_KERNEL);
    if (!ctx->buf) {
        kfree(ctx);
        return -ENOMEM;
    }

    filp->private_data = ctx;
    return 0;
}

static int demo_fop_release(struct inode *inode, struct file *filp)
{
    struct demo_file_ctx *ctx = filp->private_data;

    mutex_destroy(&ctx->lock);
    kfree(ctx->buf);
    kfree(ctx);
    return 0;
}

static long demo_fop_ioctl(struct file *filp,
                           unsigned int cmd,
                           unsigned long arg)
{
    struct demo_file_ctx *ctx = filp->private_data;
    int ret = 0;

    if (_IOC_TYPE(cmd) != DEMO_IOC_MAGIC)
        return -ENOTTY;

    mutex_lock(&ctx->lock);

    switch (cmd) {
    case DEMO_IOC_GET_MODE:
        if (put_user(ctx->mode, (u32 __user *)arg))
            ret = -EFAULT;
        break;

    case DEMO_IOC_SET_MODE: {
        u32 val;
        if (get_user(val, (u32 __user *)arg)) {
            ret = -EFAULT;
            break;
        }
        if (val > DEMO_MODE_MAX) {
            ret = -EINVAL;
            break;
        }
        ctx->mode = val;
        break;
    }
    case DEMO_IOC_RESET:
        memset(ctx->buf, 0, ctx->buf_size);
        ctx->mode = 0;
        break;

    default:
        ret = -ENOTTY;
    }

    mutex_unlock(&ctx->lock);
    return ret;
}

static const struct file_operations demo_fops = {
    .owner          = THIS_MODULE,
    .open           = demo_fop_open,
    .release        = demo_fop_release,
    .unlocked_ioctl = demo_fop_ioctl,
};
/* === 모듈 init/exit 완전 템플릿 (4단계 unwind) === */
static struct class *demo_class;
static struct cdev demo_cdev;
static dev_t demo_devno;
static struct workqueue_struct *demo_wq;

static int __init demo_module_init(void)
{
    int ret;

    /* 1단계: 워크큐 생성 */
    demo_wq = alloc_workqueue("demo", WQ_UNBOUND, 0);
    if (!demo_wq)
        return -ENOMEM;

    /* 2단계: chrdev 번호 할당 */
    ret = alloc_chrdev_region(&demo_devno, 0, 1, "demo");
    if (ret)
        goto err_wq;

    /* 3단계: cdev 등록 */
    cdev_init(&demo_cdev, &demo_fops);
    ret = cdev_add(&demo_cdev, demo_devno, 1);
    if (ret)
        goto err_region;

    /* 4단계: class + device */
    demo_class = class_create("demo");
    if (IS_ERR(demo_class)) {
        ret = PTR_ERR(demo_class);
        goto err_cdev;
    }
    device_create(demo_class, NULL, demo_devno, NULL, "demo0");

    pr_info("demo module loaded\n");
    return 0;

err_cdev:
    cdev_del(&demo_cdev);
err_region:
    unregister_chrdev_region(demo_devno, 1);
err_wq:
    destroy_workqueue(demo_wq);
    return ret;
}

static void __exit demo_module_exit(void)
{
    /* 해제 역순: 4→3→2→1 */
    device_destroy(demo_class, demo_devno);
    class_destroy(demo_class);
    cdev_del(&demo_cdev);
    unregister_chrdev_region(demo_devno, 1);
    destroy_workqueue(demo_wq);
    pr_info("demo module unloaded\n");
}

module_init(demo_module_init);
module_exit(demo_module_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Demo kernel module with full init/exit pattern");
MODULE_AUTHOR("Example");

참고자료

공식 커널 문서

커널 소스 브라우저

LWN.net 기사

커뮤니티 리소스

서적 및 권위 있는 가이드

  • GNU Assembler (as) 완전 가이드 — GAS 섹션·심볼·재배치 지시자, 매크로/조건 조립, CFI 언와인드 정보, 아키텍처별 문
  • 크래시 분석 — panic/oops/call trace 해석, crashkernel/kdump vmcore
  • perf 서브시스템 — 샘플링 기반 병목 분석과 커널 심볼 해석
  • ELF & GNU Binutils — readelf/objdump/nm/objcopy 중심으로 ELF 섹션·심볼·재배치 해석과 링커 스크립트 구조 분석