C 언어 완전 가이드 & 커널 C 관용어

리눅스 커널 코드베이스가 실제로 사용하는 C 관용어를 표준 문법 설명을 넘어 실전 규약으로 정리합니다. GNU 확장(`__attribute__`, `typeof`, statement expression), 타입 안정 매크로(`container_of`, `BUILD_BUG_ON`), 메모리 모델 보조 매크로(`READ_ONCE`, `WRITE_ONCE`, 배리어), 오류 포인터/정리 경로 패턴, sparse 어노테이션과 lockless 코드 주석 규칙까지 유지보수와 리뷰 관점에서 상세히 다룹니다.

전제 조건:
  • 기본 C 프로그래밍 경험 (포인터, 구조체, 매크로)
  • GCC 컴파일러 사용 경험
  • Linux 커널 소스 탐색 경험 (선택)
일상 비유로 이해하기:
C 언어는 외교 문서의 언어와 같습니다. ISO 표준(C89/C99/C11/C23)은 모든 나라가 합의한 공식 문법이고, GNU C 확장은 "리눅스 왕국"이 추가로 사용하는 방언입니다. 커널은 이 방언을 적극 활용해 성능과 안전성을 극대화합니다 — __attribute__((section(".init")))처럼 메모리 배치까지 컴파일러에게 지시합니다.

핵심 요약

  • 커널은 C11 표준 + GNU 확장(-std=gnu11)을 사용합니다 — VLA 금지, RTTI 금지.
  • __attribute__는 GCC/Clang 전용 메타데이터로 30+개 속성이 커널 전체에 쓰입니다.
  • typeof/__auto_type는 타입 안전한 min()/max() 매크로 구현의 핵심입니다.
  • Statement expressions ({ ... })container_of 같은 복잡한 매크로를 부작용 없이 작성하게 합니다.
  • container_of(ptr, type, member)는 멤버 포인터로 상위 구조체를 역추적하는 커널의 핵심 관용어입니다.
  • likely()/unlikely()__builtin_expect로 분기 예측 힌트를 CPU에 전달합니다.
  • sparse 어노테이션(__user/__iomem/__rcu)은 포인터의 "주소 공간"을 정적 분석 도구에 알립니다.
  • BUILD_BUG_ON(cond)/static_assert는 컴파일 타임 조건 검사로 런타임 오버헤드가 없습니다.
  • GNU 인라인 어셈블리(asm volatile)는 출력/입력 제약 조건(=r, r, m)과 clobber 목록("memory", "cc")으로 레지스터/메모리 사용을 컴파일러에게 정확히 알립니다.
  • __builtin_add_overflow(a, b, &res)와 커널 래퍼 check_add_overflow()는 정수 오버플로우를 안전하게 처리합니다 — size_add()/array_size()도 같은 계열입니다.
  • ERR_PTR/IS_ERR/PTR_ERR: 포인터 마지막 페이지(-4095~-1)를 에러 코드로 활용합니다 — NULL 대신 에러 인코딩 포인터 반환이 커널 관용어입니다.
  • scoped_guard(mutex, &lock) (Linux 6.4+): __attribute__((cleanup)) 기반 자동 락 해제 — 블록 종료 시 자동 mutex_unlock()이 보장됩니다.
  • module_init(fn): device_initcall 레벨 6에 등록 — initcall 0~7 순서 체계에서 대부분의 드라이버는 레벨 6에 위치합니다.

단계별 이해

  1. C 표준 역사 파악 — C89→C99→C11→C23 변천사와 커널이 gnu11을 선택한 이유를 이해합니다.
  2. __attribute__ 익히기noinline, always_inline, packed, aligned, section 속성을 커널 소스에서 찾아봅니다.
  3. typeof와 statement expressionsinclude/linux/minmax.h에서 min() 구현을 분석합니다.
  4. container_of 분해include/linux/container_of.h에서 offsetof와 포인터 산술을 추적합니다.
  5. 분기 힌트 실습likely()/unlikely()가 있는 코드를 -O2로 컴파일, objdump로 분기 레이아웃을 비교합니다.
  6. sparse 어노테이션 실습make C=2로 커널 빌드 시 sparse가 __user 오용을 어떻게 잡는지 확인합니다.
  7. 커널 타입 시스템include/linux/types.h에서 u8/u32/u64, __be32/__le32를 찾고 엔디안 변환 API와 연결합니다.
  8. 전처리기 패턴 마스터IS_ENABLED(), do { ... } while(0), x-macro를 직접 작성하며 패턴을 내재화합니다.
  9. 인라인 어셈블리 기초asm volatile("" ::: "memory")barrier()가 어떻게 구현되는지 분석하고, x86 cpuid 제약 조건 예제를 직접 작성합니다.
  10. 오버플로우 안전 연산 실습__builtin_add_overflowcheck_add_overflow()를 사용해 커널 메모리 할당 크기 계산을 안전하게 구현합니다.
  11. 에러 처리 패턴 실습IS_ERR/PTR_ERR/goto cleanup 체인을 직접 작성하고, devm_* 변환 연습을 합니다.
  12. asm goto 실습net/core/filter.cstatic_branch_unlikelyinclude/linux/jump_label.h에서 JUMP_LABEL 구현을 확인합니다.

개요 — C 언어 철학과 커널의 선택

C 언어는 1972년 Dennis Ritchie가 Unix 운영체제를 재작성하기 위해 만들었습니다. "프로그래머를 신뢰한다"는 철학 아래 하드웨어에 가까운 제어권을 제공하면서도 이식성을 보장합니다. 리눅스 커널은 이 언어의 가장 규모 있는 C 프로젝트 중 하나로, 약 3천만 줄의 C 코드로 이루어져 있습니다.

K&R C 1978 C89/C90 ANSI/ISO C99 inline/VLA C11 _Atomic/_Generic C17 수정판 C23 nullptr/constexpr 커널: -std=gnu11 (C11+GNU)

커널이 C를 고집하는 이유

C89/C90 — ANSI/ISO 최초 표준화

1989년 ANSI, 1990년 ISO에서 C를 공식 표준화했습니다. K&R C의 모호함을 제거하고 이식성을 보장했습니다.

주요 도입 기능

기능설명커널 활용
void *범용 포인터 타입kmalloc() 반환 타입
함수 프로토타입인수 타입 선언 필수화모든 내부 API
const / volatile읽기전용 / 최적화 금지HW 레지스터: volatile u32 __iomem *reg
표준 헤더<stddef.h>, <limits.h>offsetof(), NULL 정의
열거형 enum정수 상수 그룹화오류 코드, 상태 머신
/* C89: volatile은 컴파일러 최적화를 막아 실제 읽기/쓰기 보장 */
volatile u32 *reg = (volatile u32 *)MMIO_BASE;
*reg = 0x1;          /* 최적화로 제거되지 않음 */
u32 val = *reg;      /* 캐시 없이 실제 읽기 */

C99 — inline, stdint.h, restrict

1999년 ISO C99는 C 언어에 현대적 기능을 대거 도입했습니다. 특히 inline, <stdint.h>, restrict, 지정 초기화자(designated initializer)가 커널에 광범위하게 활용됩니다.

주요 도입 기능

기능설명커널 활용
inline함수 인라인 힌트static inline 헬퍼 함수
<stdint.h>크기 고정 정수 uint32_t커널은 자체 u8/u32 타입 사용
_Bool논리형 (0/1)bool: <linux/types.h> 정의
지정 초기화자.field = value구조체 / file_operations 초기화
// 주석한 줄 주석커널 코드 전반
restrict포인터 별칭 없음 힌트memcpy() 등 최적화
VLA (가변 길이 배열)스택 동적 배열⛔ 커널 완전 금지 (스택 오버플로우)
__func__현재 함수명 문자열pr_debug() 내부

restrict — 포인터 앨리어싱 힌트

restrict는 포인터가 가리키는 메모리에 다른 포인터로는 접근하지 않는다는 것을 컴파일러에게 약속합니다. 이를 통해 컴파일러는 추가적인 최적화(레지스터 캐싱, 벡터화)를 적용할 수 있습니다.

/* memcpy: src와 dst는 겹치지 않음 → 컴파일러가 벡터화 가능 */
void *memcpy(void * restrict dst,
             const void * restrict src, size_t n);

/* restrict 없으면: 컴파일러가 dst와 src 앨리어싱 가정 → 비최적화 */
/* restrict 있으면: 루프마다 src 재읽기 불필요 → 성능 향상 */

복합 리터럴(Compound Literal) — C99 임시 객체

C99 복합 리터럴 (type){ initializer }은 임시 구조체/배열 객체를 표현식 안에서 직접 생성합니다. 함수에 임시 구조체를 전달하거나 초기화에 활용합니다 (자세한 내용은 복합 리터럴 전용 섹션 참조).

/* 복합 리터럴: 함수 호출에 임시 구조체 전달 */
setup_timer(&timer, callback,
    (struct timer_args){ .flags = 0, .delay = 100 });

/* __func__: 현재 함수명 문자열 (C99 표준화) */
#define pr_debug_fn(fmt, ...) \
    pr_debug("%s: " fmt, __func__, ##__VA_ARGS__)
/* → pr_debug("my_init: starting\n") 형태로 출력 */
/* 지정 초기화자: 순서 무관, 명시적, 나머지는 0 */
static const struct file_operations my_fops = {
    .open    = my_open,
    .read    = my_read,
    .release = my_release,
    /* .write = NULL (명시 안 해도 0으로 초기화) */
};

/* VLA — 커널에서 절대 사용하지 않는다 */
/* int buf[n]; ← CONFIG_VLA 비활성화로 컴파일 오류 */
/* 이유: 스택 검사 불가, 스택 오버플로우 위험 */
코드 설명
  • 지정 초기화자C99 지정 초기화자(.field = value)는 구조체 필드를 이름으로 초기화합니다. 필드 순서와 무관하며, 명시하지 않은 필드는 0/NULL로 초기화됩니다. 커널의 file_operations, net_device_ops 등 대형 ops 구조체 초기화에 필수적입니다.
  • VLA 금지가변 길이 배열(Variable Length Array)은 스택에 동적 크기 배열을 할당합니다. 커널은 스택 크기가 제한적(4~8KB)이고 스택 사용량을 컴파일 타임에 검사할 수 없어 CONFIG_VLA=n으로 전면 금지합니다.

C11 — _Atomic, _Generic, _Static_assert

ISO C11(2011)은 멀티스레딩 지원(_Atomic, <threads.h>), 제네릭 선택(_Generic), 컴파일 타임 어서션(_Static_assert)을 도입했습니다.

기능설명커널 관련성
_Atomic원자 타입 / 연산커널은 자체 atomic_t 사용 (lock-free)
_Static_assert컴파일 타임 조건 검사static_assert() 래퍼로 활용
_Generic타입 기반 컴파일 타임 선택타입 안전 매크로에 활용 가능
_Thread_local스레드 로컬 스토리지커널: __percpu 변수 별도 사용
_Noreturn반환하지 않는 함수noreturn / __attribute__((noreturn))
익명 구조체/공용체이름 없는 내부 struct/unionstruct task_struct 내부 여러 곳

_Generic — 타입 기반 컴파일 타임 선택

C11 _Generic은 인수 타입에 따라 컴파일 타임에 다른 표현식을 선택합니다. C++의 함수 오버로딩과 유사한 타입 안전 API를 만들 수 있습니다.

/* _Generic으로 타입별 format 문자열 선택 */
#define print_val(x) printf(                     \
    _Generic((x),                               \
        int:    "%d\n",                          \
        long:   "%ld\n",                         \
        float:  "%f\n",                          \
        default: "%p\n"), (x))

/* 커널 응용: u8/u32/u64별 endian 변환 선택 */
#define cpu_to_le(x) _Generic((x),         \
    u8:  (x),                               \
    u16: cpu_to_le16(x),                   \
    u32: cpu_to_le32(x),                   \
    u64: cpu_to_le64(x))

_Atomic vs 커널 atomic_t

구분C11 _Atomic커널 atomic_t
표준ISO C11 표준리눅스 커널 전용
선언_Atomic int counter;atomic_t counter = ATOMIC_INIT(0);
연산atomic_fetch_add(&counter, 1)atomic_inc(&counter)
아키텍처 지원컴파일러 의존모든 커널 아키텍처 보장
커널 채택 여부미채택 (자체 구현 사용)전체 커널 사용
refcount 안전없음refcount_t (포화 카운터)
/* C11 _Static_assert: 조건이 거짓이면 컴파일 오류 */
_Static_assert(sizeof(unsigned long) == 8, "64비트 필요");

/* C11 익명 공용체: 멤버를 직접 접근 */
struct my_value {
    union {
        u32 raw;
        struct { u16 lo; u16 hi; };  /* 익명 struct */
    };
};
struct my_value v;
v.lo = 0x1234;  /* 직접 접근 가능 */

C17/C18 — 수정판과 기능 테스트 매크로

ISO C17(ISO/IEC 9899:2018, 별칭 C18)은 C11의 버그 수정판으로 새 기능 없이 명세 오류를 수정했습니다. 커널 개발에서는 _POSIX_C_SOURCE/_GNU_SOURCE 기능 테스트 매크로가 더 중요합니다.

기능 테스트 매크로

/* _GNU_SOURCE: GNU/Linux 전용 확장 활성화 */
#define _GNU_SOURCE
#include <string.h>   /* strchrnul(), memmem() 등 GNU 확장 포함 */

/* 커널 헤더는 자체 __KERNEL__ 가드로 userspace 혼용 방지 */
#ifdef __KERNEL__
/* 커널 전용 코드 */
#else
/* 사용자 공간 코드 */
#endif

C23 — nullptr, constexpr, BitInt, typeof 표준화

ISO C23(ISO/IEC 9899:2024)은 C++의 현대적 기능을 C에 도입했습니다. 커널은 아직 C23을 공식 채택하지 않았지만, GNU C 확장으로 유사 기능을 이미 사용하고 있습니다.

C23 기능설명GNU C 선행 기능
nullptr타입 안전 NULL 포인터 상수NULL (기존)
_BitInt(N)임의 비트 폭 정수__int128 (부분)
constexpr컴파일 타임 상수const + __attribute__((const))
typeof(x)typeof 표준화__typeof__(x) (GNU 확장)
#embed이진 파일 포함링커 스크립트 INCBIN
[[attribute]]표준 속성 문법__attribute__(())
이진 리터럴 0b1010이진수 리터럴GCC 이미 지원

nullptr vs NULL — 타입 안전 널 포인터

C23의 nullptr은 C++11 nullptr과 동일한 타입 안전 널 포인터 상수입니다. 기존 NULL 매크로는 정수 0 또는 ((void*)0)으로 정의되어 오버로딩/가변 인수에서 문제가 있었습니다.

/* C23: nullptr은 nullptr_t 타입의 상수 */
typedef decltype(nullptr) nullptr_t;

/* NULL 문제점: 정수 0과 포인터 혼동 */
#define NULL ((void *)0)  /* 또는 0 */
printf("%p\n", NULL);    /* 구현 정의 동작 */

/* nullptr: 모든 포인터 타입으로 암시적 변환 */
int *p = nullptr;
void (*fp)(void) = nullptr;
printf("%p\n", nullptr);  /* 정의된 동작 */

/* 커널은 NULL 사용 유지 (gnu11 기준) */
#define NULL ((void *)0)

_BitInt(N) — 임의 비트 폭 정수

C23의 _BitInt(N)은 N비트 정수를 선언합니다. N은 1 이상이어야 하며, 하드웨어 레지스터 매핑, 비트 필드 대체, 프로토콜 구현에 활용합니다.

/* C23: 임의 비트 폭 정수 */
_BitInt(7)  flags;     /* -64 ~ 63 */
unsigned _BitInt(12) addr;  /* 0 ~ 4095 */

/* 레지스터 비트 필드 대체 */
struct hw_reg {
    unsigned _BitInt(3)  mode;
    unsigned _BitInt(5)  status;
    unsigned _BitInt(24) counter;
};  /* sizeof = 4 (패딩 없음) */

/* 프로토콜 헤더 (12비트 필드) */
struct packet_hdr {
    unsigned _BitInt(4)  version;
    unsigned _BitInt(12) length;  /* 0~4095 */
    unsigned _BitInt(16) checksum;
};

/* GNU 대안: __int128 (128비트만 지원) */
__int128 big_val = (__int128)1 << 100;

#embed — 이진 파일 포함

C23의 #embed 지시어는 컴파일 타임에 이진 파일을 배열로 포함합니다. 기존에는 링커 스크립트나 외부 도구가 필요했습니다.

/* C23: 이진 파일 직접 포함 */
static const unsigned char firmware[] = {
#embed "firmware.bin"
};

/* 크기 제한 */
static const unsigned char small_data[] = {
#embed "data.bin" limit(1024)
};

/* 커널 기존 방식: 링커 스크립트 + .incbin */
/* .section .rodata
 * .incbin "firmware.bin"
 */

[[attribute]] — C23 표준 속성 문법

C23은 C++ 표준 속성 문법 [[attr]]을 도입했습니다. GNU __attribute__((...))보다 이식성이 높습니다.

/* C23 표준 속성 */
[[noreturn]] void panic(const char *msg);
[[deprecated("use new_func()")]] void old_func(void);
[[fallthrough]];  /* switch 의도적 fall-through */
[[maybe_unused]] static int debug_flag = 1;
[[nodiscard]] int get_value(void);  /* 반환값 무시 경고 */

/* GNU 속성 (여전히 더 강력) */
__attribute__((noinline, cold)) void error_path(void);
__attribute__((section(".init.text"))) int init_fn(void);

/* 비교 */
/* [[noreturn]] ≈ __attribute__((noreturn)) */
/* [[deprecated]] ≈ __attribute__((deprecated)) */
/* [[fallthrough]] ≈ __attribute__((fallthrough)) */
/* GNU 전용: section, packed, aligned, cold, hot... */

true/false 키워드화 & #elifdef

/* C23: true/false가 키워드 (매크로 아님) */
_Bool b = true;   /* true는 1로 정의된 키워드 */

/* 기존 매크로와 호환성 */
#ifdef true
#undef true   /* C23에서는 불필요 */
#endif

/* #elifdef / #elifndef (C23) */
#ifdef CONFIG_X86
    /* x86 코드 */
#elifdef CONFIG_ARM64
    /* ARM64 코드 */
#elifndef CONFIG_NO_ARCH
    /* 기타 아키텍처 */
#endif

/* 기존 방식 */
#ifdef CONFIG_X86
#elif defined(CONFIG_ARM64)
#elif !defined(CONFIG_NO_ARCH)
#endif

GNU C — 커널이 -std=gnu11을 선택한 이유

리눅스 커널은 -std=gnu11로 컴파일됩니다. -std=c11(순수 ISO)이 아닌 GNU 확장을 활성화하는 이유는 다음과 같습니다.

이유필요한 GNU 확장사용 예
인라인 어셈블리asm volatile(...) 확장 문법아키텍처 코드, barrier
타입 안전 매크로typeof(x), statement expressionsmin(), max(), container_of()
컴파일러 힌트__attribute__(())noinline, always_inline, packed
분기 예측__builtin_expect()likely(), unlikely()
계산된 goto&&label, goto *ptr해석기 dispatch table
제로 길이 배열struct { int data[0]; }가변 길이 구조체 끝 표시
/* -std=gnu11 vs -std=c11 차이 확인 */
#ifdef __GNUC__
/* GNU C 컴파일러 (GCC 또는 Clang) */
#endif

/* 제로 길이 배열: 가변 크기 구조체 (C99 flexible array와 유사) */
struct sk_buff {
    u32  len;
    u8   cb[48];
    u8   data[0];  /* GNU: 0-length, C99: [] */
};

__attribute__ — GCC/Clang 속성 완전 참조

__attribute__((...))는 GCC와 Clang이 제공하는 메타데이터 시스템입니다. 함수, 변수, 타입에 컴파일러 동작을 지시합니다.

__attribute__(()) 함수 속성 변수 속성 타입 속성 noinline / always_inline noreturn / pure / const cold / hot / flatten format / sentinel visibility / weak constructor / destructor section("name") aligned(N) / packed section("name") unused / used cleanup(fn) __percpu / __initdata packed / aligned(N) transparent_union may_alias deprecated / unavailable 커널 핵심 매크로 매핑 noinline → __noinline | always_inline → __always_inline | packed → __packed section → __section(x) | noreturn → __noreturn | cold → __cold

함수 속성 완전 참조

속성커널 매크로의미
noinlinenoinline인라인 전개 금지 — 디버그/스택 추적용
always_inline__always_inline항상 인라인 전개 — 성능 임계 경로
noreturn__noreturn반환하지 않는 함수 (panic(), BUG())
pure직접 사용부작용 없음, 반환값만 있음 (메모이제이션 가능)
const직접 사용인수에만 의존, 전역 미참조 (pure보다 강)
cold__cold드물게 호출 — 오류 경로, 별도 섹션 배치
hot__hot자주 호출 — 핫 경로 최적화
flatten직접 사용호출하는 모든 함수를 인라인
format(printf,m,n)__printf(m,n)printf 형식 문자열 검사
visibility("hidden")직접 사용ELF 심볼 가시성 제어
weak__weak약한 심볼 — 재정의 가능
constructor(priority)직접 사용main() 이전 실행
section("name")__section(x)링커 섹션 지정 (.init.text 등)
used__used미참조라도 심볼 유지
unused__maybe_unused미사용 경고 억제

변수/타입 속성

속성커널 매크로의미
aligned(N)__aligned(N)N바이트 정렬 (캐시라인, SIMD 요구사항)
packed__packed패딩 없이 최소 크기 배치 (프로토콜 헤더)
may_alias직접 사용엄격한 앨리어싱 규칙 예외 (__be32 변환)
cleanup(fn)__free(fn)스코프 이탈 시 자동 해제 (scope guard) → RAII 패턴 상세
alloc_size(N)직접 사용할당 함수의 크기 인수 지정 — 버그 탐지
malloc직접 사용메모리 할당 함수 — 별칭 분석 최적화
returns_nonnull직접 사용NULL 반환 안 함 — 호출자 NULL 체크 제거
error("msg")직접 사용호출 시 컴파일 오류 — deprecated API 완전 차단
warning("msg")직접 사용호출 시 컴파일 경고 — 레거시 API 사용 알림
target("isa")직접 사용함수별 ISA 지정 — target("avx2")
target_clones(...)직접 사용다중 ISA 버전 자동 생성 — 멀티버저닝
ifunc("resolver")직접 사용런타임 함수 선택 — CPU 특화 구현
noipa직접 사용IPA 최적화 완전 금지 — 디버깅/LTO
no_stack_protector직접 사용스택 카나리 비활성화 — 부트/인터럽트 코드
no_sanitize_address__no_sanitize_addressKASAN 검사 제외
no_sanitize_undefined직접 사용UBSAN 검사 제외
no_sanitize("thread")직접 사용TSAN 검사 제외
no_kcsan__no_kcsanKCSAN 데이터 레이스 검사 제외

캐시라인 정렬 & 구조체 패딩 최적화

현대 CPU의 캐시 계층 구조에서 데이터 정렬은 성능에 결정적 영향을 미칩니다. False sharing 방지와 캐시 효율을 위한 정렬 기법을 다룹니다.

/* 캐시라인 정렬: 64바이트 (일반적) */
#define ____cacheline_aligned __attribute__((__aligned__(SMP_CACHE_BYTES)))

/* False sharing 방지: Per-CPU 데이터 */
struct my_percpu_data {
    u64 counter ____cacheline_aligned;  /* 다른 캐시라인 */
    u64 events  ____cacheline_aligned;
};

/* 구조체 패딩 최적화: 큰 필드 → 작은 필드 순서 */
struct bad_layout {       /* sizeof = 24 */
    char a;                /* 1 + 7 패딩 */
    u64  b;                /* 8 */
    char c;                /* 1 + 7 패딩 */
};

struct good_layout {      /* sizeof = 16 */
    u64  b;                /* 8 */
    char a;                /* 1 */
    char c;                /* 1 + 6 패딩 */
};

/* __aligned 최대값: 컴파일러/아키텍처 제한 있음 */
/* x86-64: 64바이트까지 권장 (캐시라인) */
/* 페이지 정렬: __aligned(PAGE_SIZE) */
정렬 대상용도
SMP_CACHE_BYTES64 (일반적)L1 캐시라인 정렬
PAGE_SIZE4096 (일반적)페이지 경계 정렬
__alignof__(type)타입 기본 정렬표준 매크로 alignof
__alignof_max__최대 정렬모든 타입 정렬 중 최대

cleanup() — 스코프 가드 (RAII 패턴)

GCC/Clang의 __attribute__((cleanup(fn)))는 변수가 스코프를 이탈할 때 자동으로 fn을 호출합니다. 커널은 이를 __free(fn) 매크로로 래핑합니다.

/* cleanup 헬퍼 함수: 포인터 해제 */
static inline void kfree_cleanup(void *p)
{
    kfree(*(void **)p);
}

/* __free(kfree): 스코프 이탈 시 kfree 자동 호출 */
void my_function(void)
{
    char *__free(kfree) buf = kmalloc(256, GFP_KERNEL);
    if (!buf)
        return;
    /* ... 작업 ... */
    /* 함수 종료 시 자동으로 kfree(buf) 호출 — goto 없이 깔끔 */
}

/* 커널 4.20+ __cleanup() 매크로 실제 선언 */
#define __free(free_fn) __attribute__((__cleanup__(free_fn##_cleanup)))

nonnull + access — GCC 9/10 포인터 안전성

/* nonnull: 인수가 NULL이면 UB → 컴파일러가 경고/최적화 허용 */
void __attribute__((__nonnull__(1, 2)))
copy_data(void *dst, const void *src, size_t len);

/* GCC 10+ access: 포인터 인수가 읽기/쓰기 접근 범위 명시 */
/* access(read_only, ptr_arg, size_arg): size_arg 바이트 읽기 */
int __attribute__((__access__(read_only, 1, 2)))
my_read(const void *buf, size_t len);
/* __section: 링커가 함수를 특정 섹션에 배치 */
static int __init my_driver_init(void)  /* __section(".init.text") */
{
    return 0;
}

/* __packed: Ethernet 헤더 — 패딩 없이 와이어 포맷 그대로 */
struct __packed ethhdr {
    u8  h_dest[6];
    u8  h_source[6];
    __be16 h_proto;
};  /* sizeof = 14 (패딩 없음) */

/* __cold: 오류 경로 — 캐시 친화적 배치에서 분리 */
static void __cold handle_fatal_error(void) { BUG(); }

typeof — 타입 안전 매크로의 핵심

typeof(x)는 GNU C 확장으로 표현식 x의 타입을 컴파일 타임에 추론합니다. C23에서 표준화되었습니다.

/* min() 매크로 커널 구현 (include/linux/minmax.h) */
#define min(x, y) ({                          \
    typeof(x) _x = (x);                        \
    typeof(y) _y = (y);                        \
    (void)(&_x == &_y);  /* 타입 일치 검사 */  \
    _x < _y ? _x : _y;                         \
})

/* __auto_type: C++11 auto와 유사 (GCC 4.9+) */
#define swap(a, b) ({            \
    __auto_type _tmp = (a);      \
    (a) = (b);                   \
    (b) = _tmp;                  \
})

/* typeof 활용: 포인터 제거 */
#define DEREF_TYPE(ptr)  typeof(*(ptr))

/* 사용 예 */
int a = 5, b = 3;
int result = min(a, b);     /* _x, _y: int 타입 — 부작용 없음 */
/* min(a++, b++) → a++가 두 번 평가되지 않음 */
코드 설명
  • typeof(x) _x = (x)x의 타입을 추론해 임시 변수 _x에 복사합니다. 이렇게 하면 xa++처럼 부작용 있는 표현식이어도 한 번만 평가됩니다.
  • (void)(&_x == &_y)포인터 비교를 통해 xy의 타입이 호환되는지 컴파일러가 확인합니다. 타입이 다르면 경고가 발생합니다. 결과는 버려집니다((void)).

Statement Expressions — 블록을 값으로

GNU C 확장 ({ ... })는 여러 문(statement)을 하나의 표현식처럼 사용할 수 있게 합니다. 마지막 표현식의 값이 전체 블록의 값이 됩니다.

/* 기본 형태 */
int result = ({
    int tmp = compute();
    tmp * tmp;  /* 이 값이 result에 대입 */
});

/* container_of에서의 사용 (include/linux/container_of.h) */
#define container_of(ptr, type, member) ({              \
    void *__mptr = (void *)(ptr);                     \
    static_assert(__same_type(*(ptr), ((type *)0)->member) \
        || __same_type(*(ptr), void),                 \
        "pointer type mismatch in container_of()");     \
    ((type *)(__mptr - offsetof(type, member)));       \
})

/* 커널 list.h에서의 사용 패턴 */
struct list_head *pos;
list_for_each(pos, &head) {
    struct my_struct *obj = list_entry(pos, struct my_struct, list);
    /* obj->data 접근 */
}

Computed Goto — 디스패치 테이블

GNU C 확장 &&label은 레이블의 주소를 void 포인터로 얻습니다. goto *ptr로 해당 레이블로 점프합니다. 해석기(interpreter)의 바이트코드 디스패치에 활용됩니다.

/* Dispatch table 패턴 — if/switch보다 빠른 디스패치 */
static const void *dispatch_table[] = {
    [0] = &&op_add,
    [1] = &&op_sub,
    [2] = &&op_mul,
    [3] = &&op_halt,
};

goto *dispatch_table[opcode];  /* 직접 점프 */

op_add:
    result = a + b;
    goto *dispatch_table[next_opcode()];
op_sub:
    result = a - b;
    goto *dispatch_table[next_opcode()];
op_halt:
    return result;

커널 사용 예: eBPF JIT 컴파일러, BPF 해석기(kernel/bpf/core.c)에서 computed goto로 각 BPF 명령어를 디스패치합니다.

__builtin_* — 컴파일러 내장 함수

__builtin_* 함수는 컴파일러가 직접 구현하는 내장 함수로, 아키텍처 특화 명령어로 최적화됩니다.

내장 함수커널 래퍼설명
__builtin_expect(e, v)likely()/unlikely()분기 예측 힌트
__builtin_clz(x)__fls(x)선행 0 비트 수 (Count Leading Zeros)
__builtin_ctz(x)__ffs(x)후행 0 비트 수 (Count Trailing Zeros)
__builtin_popcount(x)hweight32(x)1 비트 수 (Population Count)
__builtin_unreachable()unreachable()도달 불가 코드 표시 → 최적화
__builtin_constant_p(x)직접 사용컴파일 타임 상수 여부 확인
__builtin_bswap32(x)swab32(x)32비트 바이트 순서 뒤집기
__builtin_frame_address(n)task_pt_regs()스택 프레임 주소
__builtin_types_compatible_p(T1,T2)__same_type(a,b)두 타입 동일 여부 (컴파일 타임)
__builtin_choose_expr(e,t,f)직접 사용컴파일 타임 삼항 연산
__builtin_add_overflow(a,b,res)check_add_overflow()덧셈 오버플로우 감지 (1이면 오버플로우)
__builtin_sub_overflow(a,b,res)check_sub_overflow()뺄셈 오버플로우 감지
__builtin_mul_overflow(a,b,res)check_mul_overflow()곱셈 오버플로우 감지
__builtin_cpu_supports("avx2")직접 사용런타임 CPU 기능 플래그 확인
__builtin_expect_with_probability직접 사용확률값(0.0~1.0) 기반 분기 힌트 (GCC 9+)
__builtin_prefetch(addr, rw, locality)prefetch()캐시 프리페치 힌트 — 0=읽기, 1=쓰기, locality 0~3
__builtin_parity(x)직접 사용비트 패리티 계산 (1 비트 개수 홀수면 1)
__builtin_parityl(x)직접 사용long 버전 패리티
__builtin_parityll(x)직접 사용long long 버전 패리티
__builtin_ffs(x)__ffs()와 유사최하위 1비트 위치 (1부터 시작, 0이면 0)
__builtin_extract_return_addr(addr)직접 사용반환 주소에서 실제 명령 주소 추출
__builtin_call_with_static_chain(call, chain)직접 사용정적 체인으로 중첩 함수 호출
__builtin_alloca(size)⛔ 커널 금지스택 동적 할당 — 스택 오버플로우 위험
__builtin_object_size(ptr, type)__builtin_object_size객체 크기 추정 — FORTIFY_SOURCE용
__builtin_popcountll(x)hweight64(x)64비트 1 비트 수
__builtin_ctzl(x)__ffs()long 버전 후행 0 비트 수
__builtin_clzl(x)__fls()long 버전 선행 0 비트 수
/* __builtin_constant_p: 상수 인수에 따른 최적화 경로 선택 */
#define test_bit(nr, addr)                          \
    (__builtin_constant_p(nr) ?                    \
     constant_test_bit(nr, addr) :                 \
     variable_test_bit(nr, addr))

/* __builtin_clz: 최상위 비트 위치 계산 */
static inline int fls(unsigned int x)
{
    return x ? sizeof(x) * 8 - __builtin_clz(x) : 0;
}

/* __builtin_unreachable: switch default에서 컴파일러 최적화 허용 */
switch (state) {
case STATE_A: ...; break;
case STATE_B: ...; break;
default: unreachable();  /* 도달 불가 — 범위 검사 최적화 */
}

GNU 인라인 어셈블리 — asm volatile 완전 가이드

GNU C는 C 코드 안에 어셈블리 명령을 삽입하는 확장 asm volatile을 제공합니다. 커널은 메모리 배리어, 아키텍처 특화 명령(cpuid, rdtsc), 원자 연산 구현에 광범위하게 활용합니다.

기본 문법

asm [volatile] (
    "어셈블리 명령 템플릿"
    : 출력 제약 조건 (output operands)    /* 선택적 */
    : 입력 제약 조건 (input operands)     /* 선택적 */
    : clobber 목록                        /* 선택적 */
);
asm volatile ( "template" : outputs : inputs : clobbers ); volatile → 컴파일러가 이 명령을 제거/이동하지 못하도록 강제 출력 제약 조건 "=r" 레지스터 쓰기 "=m" 메모리 쓰기 "+r" 읽기&쓰기 "=&r" 조기 클로버 입력 제약 조건 "r" 레지스터 읽기 "m" 메모리 읽기 "i" 즉치값 "0" 출력과 공유 Clobber 목록 "memory" 메모리 변경 "cc" 플래그 변경 "rax" 레지스터 "rcx","rdx" 등 %0, %1... 또는 %[name] 으로 operand 참조 | "=r"(out) → %0에 결과 저장

커널 실전 예제 3가지

/* 1. barrier(): 컴파일러 재배치 방지 — "memory" clobber */
#define barrier() __asm__ volatile("" ::: "memory")
/* template: 빈 문자열 (명령 없음)
 * outputs/inputs: 없음
 * clobbers: "memory" → 컴파일러가 메모리 읽기/쓰기를 이 지점에서 완료 */

/* 2. x86 cpuid: 출력 4개, 입력 1개, clobber "cc" */
static inline void native_cpuid(u32 *eax, u32 *ebx,
                                  u32 *ecx, u32 *edx)
{
    asm volatile("cpuid"
        : "=a"(*eax), "=b"(*ebx),  /* 출력: eax, ebx */
          "=c"(*ecx), "=d"(*edx)  /* 출력: ecx, edx */
        : "0"(*eax), "2"(*ecx)   /* 입력: 출력 0,2와 공유 */
    );
}

/* 3. x86 rdtsc: TSC 읽기 (부작용 → volatile 필수) */
static inline u64 rdtsc(void)
{
    u32 lo, hi;
    asm volatile("rdtsc"
        : "=a"(lo), "=d"(hi));  /* EDX:EAX에 결과 */
    return ((u64)hi << 32) | lo;
}
자세한 인라인 어셈블리: 아키텍처별 패턴, ARM64 mrs/msr, RISC-V csrr 예제는 인라인 어셈블리 완전 가이드를 참조하세요.

asm goto — 점프 레이블과 정적 분기 패치

asm goto는 GNU C 확장으로 인라인 어셈블리에서 C 레이블로 점프할 수 있게 합니다. 리눅스 커널의 JUMP_LABEL 시스템은 이를 활용해 조건 분기의 오버헤드를 사실상 0으로 만듭니다 — 대부분의 경우 NOP 명령 1개이며, 런타임에 JMP 명령으로 패치됩니다.

JUMP_LABEL 런타임 패치 원리 (x86-64) ① 일반 if() 분기 CMP reg, 0 JNE slow_path fast_path: ... 빠른 경로 ... ~15 사이클 오버헤드 (분기 예측 미스 시) 메모리 읽기 + 비교 필요 파이프라인 플러시 위험 ② NOP 상태 (false 기본값) 0x90 0x90 0x90 0x90 0x90 (NOP ×5 = 5바이트) fast_path: (계속 실행) ≈ 0 사이클 오버헤드 NOP은 1사이클 디코딩 분기 예측기 부하 없음 코드 캐시 효율 최대 DEFINE_STATIC_KEY_FALSE ③ JMP 상태 (true 활성화) 0xE9 rel32 (JMP 5바이트) (NOP→JMP 원자 패치) slow_path: (점프 대상) static_branch_enable() → stop_machine() → text_poke_bp() → 5바이트 원자 교체 빈도: 부팅/모듈 로드 시 DEFINE_STATIC_KEY_TRUE static_branch_enable() 호출 시 NOP → JMP 패치

asm goto 기본 문법

asm goto는 인라인 어셈블리의 4번째 콜론(:) 이후에 C 레이블 목록을 선언합니다. 어셈블리에서 그 레이블로 점프하면 C 실행 흐름이 해당 레이블로 분기합니다.

/* 기본 asm goto 문법 */
asm goto(
    "testl %[flag], %[flag]\n\t"   /* flag 테스트 */
    "jnz %l[label_true]\n\t"       /* 비제로면 label_true로 점프 */
    :                               /* 출력 없음 */
    : [flag] "r"(my_flag)           /* 입력 */
    : "cc"                          /* clobber: 플래그 레지스터 */
    : label_true                    /* 4번째 ':' 이후: 점프 가능 레이블 목록 */
);
/* 여기는 flag == 0 경우 (fall-through) */
return 0;

label_true:
return 1;

/* asm volatile vs asm goto 비교 */
/* asm volatile: 부작용 있는 일반 인라인 어셈블리 */
/* asm goto: C 레이블로 분기 가능 (출력 제약 조건 불가) */

JUMP_LABEL 시스템 — DEFINE_STATIC_KEY

커널 JUMP_LABEL 시스템(include/linux/jump_label.h)은 asm goto를 기반으로 조건 분기를 런타임 패치 가능한 NOP/JMP로 변환합니다.

/* 정적 키 선언 — 기본값 false (NOP 상태) */
static DEFINE_STATIC_KEY_FALSE(net_debug_key);

/* 사용: 대부분의 경우 NOP으로 실행 */
void process_packet(struct sk_buff *skb)
{
    if (static_branch_unlikely(&net_debug_key)) {
        /* 이 블록은 net_debug_key가 활성화될 때만 진입 */
        dump_packet(skb);
    }
    /* 기본 경로: NOP 5개 → 0사이클 오버헤드 */
}

/* 런타임에 활성화: NOP → JMP 패치 (stop_machine 필요) */
static_branch_enable(&net_debug_key);   /* → JMP slow_path */
static_branch_disable(&net_debug_key);  /* → NOP ×5 */

/* 기본값 true로 선언 (JMP 상태) */
static DEFINE_STATIC_KEY_TRUE(rcu_expedited_key);
if (static_branch_likely(&rcu_expedited_key)) {
    /* 기본으로 실행되는 빠른 경로 */
}

static_branch_likely vs static_branch_unlikely

API기본 상태NOP/JMP 배치최적화 대상
static_branch_unlikely(&key)DEFINE_STATIC_KEY_FALSE기본 NOP (fall-through)거의 비활성화 경로 (디버그, 통계)
static_branch_likely(&key)DEFINE_STATIC_KEY_TRUE기본 JMP 후 fall-through가 빠른 경로거의 활성화 경로 (기능 플래그)
static_branch_enable()NOP → JMP 패치런타임 활성화
static_branch_disable()JMP → NOP 패치런타임 비활성화
static_branch_inc()참조 카운트 증가공유 기능 플래그
static_branch_dec()참조 카운트 감소마지막 dec 시 NOP 전환

x86 ALTERNATIVE 매크로

ALTERNATIVE는 부팅 시 CPU 기능 플래그를 확인하여 코드를 패치합니다. JUMP_LABEL과 달리 런타임 변경이 아닌 부팅 1회 패치입니다.

/* ALTERNATIVE: 부팅 시 CPU 기능 기반 코드 교체 */
/* arch/x86/include/asm/alternative.h */

/* 기본 코드: 구버전 방식 */
/* AVX2 지원 시: 최적화 버전으로 교체 */
ALTERNATIVE(
    "call legacy_memcpy",
    "call avx2_memcpy",
    X86_FEATURE_AVX2
);

/* ALTERNATIVE_2: 두 단계 패치 */
ALTERNATIVE_2(
    "call scalar_fn",    /* 기본 */
    "call sse4_fn", X86_FEATURE_SSE4_2,
    "call avx2_fn", X86_FEATURE_AVX2
);

/* HAVE_ALTERNATIVE: 여러 CPU별 최적 경로를 런타임에 선택 */
/* 동작: apply_alternatives() → 부팅 시 .altinstructions 섹션 순회 */
/*       → CPU feature 확인 → text_poke()로 코드 교체 */

성능 임팩트 비교

패턴런타임 비용패치 비용적합한 사용처
일반 if (flag)~1~15 사이클 (예측 미스 시)없음빈번히 변경되는 조건
static_branch_unlikely (NOP)~0 사이클 (NOP 디코딩)stop_machine 필요거의 꺼져 있는 디버그/통계
static_branch_likely (JMP)~1 사이클 (예측 가능 JMP)stop_machine 필요거의 켜져 있는 기능 플래그
ALTERNATIVE~0 (패치된 코드 직접 실행)부팅 1회만CPU 기능 선택 (AVX2, CRC32)

커널 실제 활용 예

/* net/core/filter.c — eBPF JIT 활성화 여부 */
static DEFINE_STATIC_KEY_FALSE(bpf_jit_enable_key);

/* kernel/sched/core.c — 스케줄러 통계 수집 */
static DEFINE_STATIC_KEY_FALSE(sched_schedstats);

void update_sched_clock(void)
{
    if (static_branch_unlikely(&sched_schedstats))
        update_sched_statistics();  /* 통계 수집: 거의 꺼져 있음 */
}

/* /proc/sys/kernel/sched_schedstats = 1 → static_branch_enable() */

/* arch/x86/lib/copy_user_64.S — ALTERNATIVE 활용 */
/* ERMS(Enhanced REP MOVSB) 지원 CPU에서 rep movsb 사용 */
ALTERNATIVE "jmp copy_user_generic_string",
             "jmp copy_user_enhanced_fast_string", X86_FEATURE_ERMS
소스 참조: include/linux/jump_label.h, arch/x86/include/asm/jump_label.h, arch/x86/include/asm/alternative.h에서 구현을 확인하세요.

오버플로우 안전 정수 연산 — __builtin_add_overflow

C의 정수 오버플로우는 미정의 동작(UB)입니다. 커널은 __builtin_add_overflow 계열과 include/linux/overflow.h의 래퍼로 이를 안전하게 처리합니다. 특히 메모리 할당 크기 계산(kmalloc(n * sizeof(T)))에서 필수적입니다.

check_add_overflow(a, b, &res) a + b > TYPE_MAX 또는 a + b < TYPE_MIN? YES return true NO *res = a + b return false 커널 래퍼 계층 check_add_overflow() → __builtin_add_overflow() size_add(a,b) → check_add_overflow() + -E2BIG

__builtin_add_overflow 계열

/* GCC/Clang 내장: 오버플로우 시 true 반환, 결과는 *res에 저장 */
bool ret = __builtin_add_overflow(a, b, &result);  /* a + b */
bool ret = __builtin_sub_overflow(a, b, &result);  /* a - b */
bool ret = __builtin_mul_overflow(a, b, &result);  /* a * b */

/* 타입 추론: a, b, *res 타입은 독립적으로 선택 가능 */
u32 a = 0xFFFFFFFF;
u32 result;
if (__builtin_add_overflow(a, 1U, &result))
    handle_overflow();  /* result = 0 (래핑), 오버플로우 감지 */

커널 래퍼: include/linux/overflow.h

/* check_add_overflow: 오버플로우 시 true */
if (check_add_overflow(count, extra, &total))
    return -EOVERFLOW;

/* size_add / size_mul: 오버플로우 시 SIZE_MAX 반환 → 할당 실패 유도 */
size_t total = size_add(hdr_size, data_len);
void *buf = kmalloc(total, GFP_KERNEL);  /* SIZE_MAX면 실패 */

/* array_size(n, size): n * size 오버플로우 안전 버전 */
void *arr = kmalloc_array(n, sizeof(struct foo), GFP_KERNEL);
/* 내부: array_size(n, sizeof(struct foo)) = size_mul(n, sizeof) */

/* struct_size(ptr, member, n): 가변 구조체 크기 계산 */
struct flex_buf {
    u32 count;
    u8  data[];  /* flexible array member */
};
size_t sz = struct_size(p, data, n);  /* sizeof(*p) + n * sizeof(p->data[0]) */
void *p = kmalloc(sz, GFP_KERNEL);
함수동작오버플로우 반환
check_add_overflow(a,b,res)a + b → *restrue
check_sub_overflow(a,b,res)a - b → *restrue
check_mul_overflow(a,b,res)a * b → *restrue
size_add(a, b)a + b (size_t)SIZE_MAX
size_mul(a, b)a × b (size_t)SIZE_MAX
array_size(n, size)n × sizeSIZE_MAX
struct_size(p, member, n)sizeof(*p) + n×sizeof(멤버)SIZE_MAX

에러 처리 패턴 — ERR_PTR, IS_ERR, goto cleanup

리눅스 커널은 에러 처리를 위한 독특한 관용어를 사용합니다. ERR_PTR/IS_ERR/PTR_ERR 삼각 관계와 goto cleanup 패턴은 모든 드라이버 코드에서 반복되는 핵심 패턴입니다.

64비트 포인터 주소 공간 — ERR_PTR 에러 영역 정상 포인터 영역 (0x0000... ~ 0xFFFF...FFEF000) 에러 영역 -4095 ~ -1 MAX_ERRNO=4095 -1 (= -EPERM) IS_ERR(ptr) (ulong)ptr > (ulong)-MAX_ERRNO true (에러) PTR_ERR(ptr) → long errno false (정상) 포인터 사용 ptr->field 접근 타입 변환 시 ERR_CAST(ptr) → void* 에러 유지 PTR_ERR_OR_ZERO IS_ERR? errno : 0 IS_ERR_OR_NULL(ptr) IS_ERR(ptr) || ptr == NULL ERR_PTR 인코딩 원리 ERR_PTR(-ENOMEM) = (void *)(long)(-ENOMEM) = 0xFFFFFFFFFFFFFFFC 유효 포인터는 항상 PAGE_SIZE(4096) 이상 → 마지막 페이지(-4095~-1)는 에러 예약

ERR_PTR / IS_ERR / PTR_ERR 삼각 관계

커널은 포인터 반환 함수에서 NULL 대신 에러 코드를 인코딩한 포인터를 반환합니다. 64비트 주소 공간의 마지막 페이지(-4095 ~ -1)는 절대 유효한 포인터가 될 수 없으므로 에러 코드 저장에 활용합니다.

/* ERR_PTR: errno를 포인터로 인코딩 */
struct file *open_file(const char *name)
{
    struct file *f = kmalloc(sizeof(*f), GFP_KERNEL);
    if (!f)
        return ERR_PTR(-ENOMEM);  /* NULL 대신 에러 포인터 반환 */

    if (do_open(f, name) < 0) {
        kfree(f);
        return ERR_PTR(-ENOENT);
    }
    return f;  /* 성공: 유효 포인터 반환 */
}

/* 호출자: IS_ERR로 검사 후 PTR_ERR로 코드 추출 */
struct file *f = open_file("/dev/null");
if (IS_ERR(f)) {
    int err = PTR_ERR(f);  /* -ENOMEM 또는 -ENOENT */
    pr_err("open failed: %d\n", err);
    return err;
}
/* 여기서는 f가 유효 포인터임이 보장됨 */
use(f);

에러 포인터 함수 6종 비교

함수/매크로동작사용 시점
ERR_PTR(errno)errno → void* 인코딩에러를 포인터로 반환할 때
IS_ERR(ptr)포인터가 에러인지 검사반환된 포인터 검사
PTR_ERR(ptr)에러 포인터 → long errnoIS_ERR() 후 코드 추출
ERR_CAST(ptr)에러 포인터 타입 변환 (errno 유지)void* → 구체 타입 변환 시
PTR_ERR_OR_ZERO(ptr)IS_ERR? PTR_ERR(ptr) : 0int 반환 함수에서 처리
IS_ERR_OR_NULL(ptr)IS_ERR(ptr) || ptr == NULLNULL도 에러로 처리할 때

goto cleanup 패턴 — 역순 해제 원칙

드라이버 probe() 함수에서 여러 자원을 순서대로 획득하고, 실패 시 역순으로 해제하는 패턴입니다. goto 레이블은 역순 해제 순서를 명확히 표현합니다.

static int my_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
    struct my_dev *dev;
    int ret;

    /* 1단계: 구조체 할당 */
    dev = kzalloc(sizeof(*dev), GFP_KERNEL);
    if (!dev)
        return -ENOMEM;  /* 해제할 것 없음 → 직접 반환 */

    /* 2단계: PCI 영역 요청 */
    ret = pci_request_regions(pdev, "my_driver");
    if (ret)
        goto err_free;  /* dev 해제 후 리턴 */

    /* 3단계: MMIO 매핑 */
    dev->regs = pci_iomap(pdev, 0, 0);
    if (!dev->regs) {
        ret = -EIO;
        goto err_regions;  /* regions + dev 해제 */
    }

    /* 4단계: IRQ 요청 */
    ret = request_irq(pdev->irq, my_irq, 0, "my", dev);
    if (ret)
        goto err_unmap;  /* unmap + regions + dev 해제 */

    pci_set_drvdata(pdev, dev);
    return 0;  /* 성공 */

    /* 역순 해제 — 레이블은 획득 역순 */
err_unmap:
    pci_iounmap(pdev, dev->regs);
err_regions:
    pci_release_regions(pdev);
err_free:
    kfree(dev);
    return ret;
}

devm_* — goto 없는 자동 해제

Linux 3.1+의 devm_* 관리 자원 함수는 디바이스 해제 시 자동으로 정리됩니다. probe()에서 goto가 필요 없어집니다.

static int my_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
    struct my_dev *dev;
    int ret;

    /* devm_kzalloc: 디바이스 해제 시 자동 kfree */
    dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
    if (!dev)
        return -ENOMEM;

    /* devm_pci_iomap: 자동 iounmap */
    dev->regs = devm_pci_iomap(&pdev->dev, pdev, 0, 0);
    if (!dev->regs)
        return -EIO;  /* goto 없이 직접 반환 가능 */

    /* devm_request_irq: 자동 free_irq */
    ret = devm_request_irq(&pdev->dev, pdev->irq,
                            my_irq, 0, "my", dev);
    if (ret)
        return ret;

    pci_set_drvdata(pdev, dev);
    return 0;
    /* remove()에서 devm 자원 자동 해제 */
}

에러 처리 안티패턴 체크리스트

안티패턴문제올바른 방법
if (ptr == NULL) ERR_PTR 체크IS_ERR 에러 포인터 누락if (IS_ERR(ptr)) 사용
if (!ptr)로 ERR_PTR 체크에러 포인터는 NULL이 아님 — 역참조 위험IS_ERR(ptr)
goto 레이블 순서 오류자원 이중 해제 또는 누락획득 역순으로 레이블 배치
IS_ERR_OR_NULL 남용설계 문제 숨김 (NULL과 에러 혼동)API 반환 타입 재설계
에러 코드 무시상위에 0 반환 → 잘못된 성공 판정__must_check, PTR_ERR_OR_ZERO
ERR_PTR(-err) 부호 오류양수 값 인코딩 → IS_ERR 실패항상 음수 errno 전달

C99 복합 리터럴 & Flexible Array Member

C99의 두 가지 강력한 기능 — 복합 리터럴과 Flexible Array Member는 커널에서 임시 구조체 전달과 가변 크기 구조체 패턴에 광범위하게 사용됩니다.

복합 리터럴 (Compound Literal)

(type){ initializer-list } 형태로 임시 객체를 생성합니다. C++의 임시 객체와 유사하지만 자동 수명을 가집니다.

/* 기본 형태: (type){ .field = value, ... } */
struct point origin = (struct point){ .x = 0, .y = 0 };

/* 함수 인수로 임시 구조체 직접 전달 */
setup_irq(irq, &(struct irqaction){
    .handler = my_irq_handler,
    .flags   = IRQF_SHARED,
    .name    = "my_device",
});

/* 배열 복합 리터럴: 임시 배열 */
memcpy(dst, (u8[]){ 0xAA, 0xBB, 0xCC }, 3);

/* 커널 활용: 임시 skb_shared_info */
const struct ethhdr hdr = (struct ethhdr){
    .h_proto = htons(ETH_P_IP),
};

Flexible Array Member (C99 표준)

/* C99 표준: 마지막 필드로 빈 배열 선언 */
struct variable_buf {
    u32  count;
    u32  flags;
    char data[];    /* Flexible Array Member (FAM) */
};

/* 크기: sizeof(struct variable_buf) = 8 (data[] 제외) */
/* 할당: sizeof(struct) + n * sizeof(원소) */
struct variable_buf *buf =
    kmalloc(struct_size(buf, data, n), GFP_KERNEL);
buf->count = n;
buf->data[0] = 'A';   /* 정상 접근 */

/* GNU 제로 배열 (C99 FAM 이전 방식) */
struct old_buf {
    u32  count;
    char data[0];   /* GNU 확장 — C99 FAM으로 대체 권장 */
};

/* 차이: FAM은 sizeof 계산에 포함되지 않음 (C99 보장)
 *       제로 배열은 GNU 확장 (이식성 낮음) */
구분C99 FAM data[]GNU 제로 배열 data[0]
표준ISO C99 표준GNU 확장
sizeof 포함아니오 (보장)아니오 (구현 의존)
포인터 산술허용허용 (GNU)
커널 권장✅ 신규 코드⚠ 레거시 코드

Type Punning & 엄격한 앨리어싱

C의 strict aliasing 규칙은 컴파일러가 서로 다른 타입의 포인터가 동일한 메모리를 가리키지 않는다고 가정하는 것을 허용합니다. 이 규칙을 위반하면 미정의 동작(UB)입니다. 커널은 이를 피하기 위한 3가지 안전한 방법을 사용합니다.

Strict Aliasing 규칙

/* 위반 예: float의 비트 패턴을 u32로 읽기 — UB! */
float f = 3.14f;
u32   *ip = (u32 *)&f;  /* strict aliasing 위반 */
u32   bits = *ip;       /* UB: GCC -O2에서 최적화로 제거될 수 있음 */

안전한 Type Punning 3가지 방법

/* 방법 1: memcpy — 가장 안전, 컴파일러가 최적화 */
float f = 3.14f;
u32 bits;
memcpy(&bits, &f, sizeof(bits));  /* GCC -O2: mov 한 번으로 최적화 */

/* 방법 2: union (C99에서 공식 지원) */
union float_bits {
    float f;
    u32   bits;
};
union float_bits u = { .f = 3.14f };
u32 bits = u.bits;  /* C99 표준에서 허용 */

/* 방법 3: __attribute__((may_alias)) — GNU C 확장 */
typedef u32 __attribute__((__may_alias__)) aliased_u32;
float f = 3.14f;
aliased_u32 *p = (aliased_u32 *)&f;  /* OK: may_alias 허용 */
u32 bits = *p;

커널에서의 Type Punning

/* __be32 / __le32: sparse 타입 + may_alias 기반 */
/* typedef __u32 __attribute__((__may_alias__)) __be32; */

/* 엔디안 변환: union 방식 */
static inline __be32 __cpu_to_be32p(const u32 *p)
{
    return (__be32)__builtin_bswap32(*p);
}

/* 커널은 -fno-strict-aliasing 으로 컴파일
 * → 위반 코드가 오랫동안 있었기 때문에 전면 비활성화
 * → 신규 코드는 memcpy 방식 권장 */
방법표준성능커널 권장
memcpyISO C 완전 준수최적화 후 동등✅ 신규 코드
unionC99 허용동등✅ 허용
may_aliasGNU 확장동등⚠ 기존 타입(__be32)
캐스트 직접UB가장 빠름(위험)❌ 사용 금지

커널 핵심 매크로 — container_of, ARRAY_SIZE, BIT

리눅스 커널 고유의 핵심 매크로들을 분석합니다.

struct my_dev int id (offset=0) struct list_head list (offset = offsetof(my_dev, list)) char name[32] ... ← ptr (list_head *) container_of(ptr, my_dev, list) = (my_dev *)((void*)ptr - offsetof(my_dev, list)) 포인터를 구조체 시작점으로 역추적 = 구조체 base 주소 offsetof(type, member) = (size_t)&((type *)0)->member NULL에서 멤버 주소 = 오프셋 값

container_of 구현 분석

/* include/linux/container_of.h */
#define container_of(ptr, type, member) ({           \
    void *__mptr = (void *)(ptr);                   \
    static_assert(__same_type(*(ptr),               \
        ((type *)0)->member) ||                       \
        __same_type(*(ptr), void),                   \
        "pointer type mismatch in container_of()");   \
    ((type *)(__mptr - offsetof(type, member)));     \
})

/* 사용 예: list_head에서 상위 구조체 복원 */
struct my_dev {
    int id;
    struct list_head list;
    char name[32];
};

struct list_head *pos;
list_for_each(pos, &dev_list) {
    struct my_dev *dev = container_of(pos, struct my_dev, list);
    pr_info("device: %s\n", dev->name);
}

ARRAY_SIZE, BIT, GENMASK

/* ARRAY_SIZE: 배열 원소 수 (포인터에는 사용 불가) */
#define ARRAY_SIZE(arr) \
    (sizeof(arr) / sizeof((arr)[0]) + __must_be_array(arr))

/* BIT: 비트 위치 → 마스크 값 */
#define BIT(nr)  (1UL << (nr))

/* GENMASK: 연속 비트 마스크 생성 (high:low 포함) */
#define GENMASK(h, l) \
    ((~0UL - (1UL << (l)) + 1) & (~0UL >> (BITS_PER_LONG - 1 - (h))))

/* 예시 */
#define REG_ENABLE  BIT(0)       /* 0x00000001 */
#define REG_MODE    GENMASK(3, 1) /* 0x0000000E */
#define REG_STATUS  GENMASK(7, 4) /* 0x000000F0 */

u32 reg = readl(base);
u32 mode = (reg & REG_MODE) >> 1;

/* FIELD_GET/FIELD_PREP (include/linux/bitfield.h) */
u32 mode2 = FIELD_GET(REG_MODE, reg);        /* 자동 쉬프트 */
writel(FIELD_PREP(REG_MODE, 3), base);  /* 값을 마스크 위치로 */

clamp, swap, round_up, DIV_ROUND_UP

/* clamp(val, lo, hi): lo ≤ val ≤ hi 범위 클램프 */
#define clamp(val, lo, hi)  min(max(val, lo), hi)

int volume = clamp(raw_vol, 0, 100);   /* 0~100 범위 강제 */
u32 freq = clamp_t(u32, f, 1000, 4000000000U); /* 명시적 타입 */
int val = clamp_val(x, 0, 255);  /* val 타입으로 추론 */

/* swap(a, b): typeof로 타입 안전 교환 */
#define swap(a, b) \
    do { typeof(a) __t = (a); (a) = (b); (b) = __t; } while (0)

int x = 1, y = 2;
swap(x, y);  /* x=2, y=1 */

/* round_up(x, y): x를 y의 배수로 올림 (y는 2의 제곱수) */
#define round_up(x, y)  (((x) + (y) - 1) & ~((y) - 1))

size_t aligned = round_up(size, PAGE_SIZE);  /* 페이지 단위 올림 */
size_t aligned = round_up(len, 64);          /* 캐시라인 단위 올림 */

/* round_down(x, y): x를 y의 배수로 내림 */
#define round_down(x, y)  ((x) & ~((y) - 1))

/* DIV_ROUND_UP(n, d): n/d 올림 나눗셈 (페이지 수 계산 등) */
#define DIV_ROUND_UP(n, d)  (((n) + (d) - 1) / (d))

u32 pages = DIV_ROUND_UP(size, PAGE_SIZE);  /* 1바이트도 1페이지 */
u32 chunks = DIV_ROUND_UP(total, 64);       /* 64바이트 청크 수 */

/* DIV_ROUND_CLOSEST: 가장 가까운 정수 나눗셈 */
#define DIV_ROUND_CLOSEST(x, div) \
    (((x) + ((div) / 2)) / (div))

/* ALIGN(x, a): x를 a 단위로 올림 정렬 (power-of-2) */
#define ALIGN(x, a)  (((x) + (a) - 1) & ~((a) - 1))
#define IS_ALIGNED(x, a) (((x) & ((a) - 1)) == 0)

likely / unlikely — 분기 예측 힌트

likely(x)unlikely(x)__builtin_expect로 구현된 분기 예측 힌트입니다. CPU에게 "이 조건은 거의 항상 참(참 아님)"임을 알려 분기 예측기를 최적화합니다.

/* include/linux/compiler.h */
#define likely(x)   __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)

/* 사용 패턴: 오류 경로는 unlikely */
int ret = do_something();
if (unlikely(ret < 0)) {
    /* 오류 처리 — 드물게 발생 */
    return ret;
}

/* 정상 경로는 likely */
if (likely(skb->len > 0)) {
    process_packet(skb);
}

/* GCC 어셈블리 효과:
 * likely: 조건 참일 때 fall-through (직선 경로)
 * unlikely: 조건 거짓일 때 fall-through
 * → 분기 예측기 미스 감소, instruction cache 효율 향상 */

/* __builtin_expect_with_probability (GCC 9+) */
if (__builtin_expect_with_probability(cond, 1, 0.99)) {
    /* 99% 확률로 참 */
}

CPU BTB와 분기 예측의 관계

현대 CPU의 Branch Target Buffer(BTB)는 과거 분기 패턴을 기반으로 다음 분기 결과를 예측합니다. likely()/unlikely()는 GCC가 생성하는 어셈블리 코드의 분기 레이아웃을 바꿔서 BTB 미스를 줄입니다.

/* likely() 효과: GCC -O2 어셈블리 비교 */

/* C 코드 */
if (likely(x > 0)) {
    normal_path();
} else {
    error_path();
}

/* → GCC 생성 어셈블리 (x86):
 *   cmp  rdi, 0
 *   jle  .L_error      ← 거의 안 뛰는 분기 (fall-through가 정상)
 *   call normal_path   ← fall-through: BTB 미스 없음
 *   ...
 * .L_error:
 *   call error_path    ← 드문 경로를 멀리 배치 */

/* unlikely() 없으면: 컴파일러가 임의 배치 → BTB 미스 증가 */
힌트어셈블리 배치사용 시점
likely(x)참 경로가 fall-through99%+ 참인 조건 (패킷 처리 정상 경로)
unlikely(x)거짓 경로가 fall-through오류 처리, 초기화 검사, NULL 체크
__builtin_expect_with_probability(x,v,p)확률 p로 힌트프로파일링 데이터 기반 정밀 힌트 (GCC 9+)

barrier, READ_ONCE, WRITE_ONCE — 메모리 순서 제어

컴파일러와 CPU 모두 명령어를 재배치합니다. 커널은 이를 제어하기 위해 메모리 배리어와 접근 프리미티브를 제공합니다.

/* barrier(): 컴파일러 재배치 금지 (CPU는 자유) */
#define barrier() __asm__ volatile("" ::: "memory")

/* READ_ONCE: 컴파일러가 반복 읽기를 최적화하지 못하게 */
#define READ_ONCE(x) (*((volatile __typeof__(x) *)&(x)))

/* WRITE_ONCE: 원자적 쓰기 보장 (torn write 방지) */
#define WRITE_ONCE(x, val) \
    (*((volatile __typeof__(x) *)&(x)) = (val))

/* 메모리 배리어: SMP 환경 */
smp_mb();    /* Full memory barrier (read+write) */
smp_rmb();   /* Read memory barrier */
smp_wmb();   /* Write memory barrier */
smp_store_release(&x, val);  /* store + release barrier */
smp_load_acquire(&x);        /* load + acquire barrier */

/* 실전 패턴: 락 없는 플래그 공유 */
static bool shutdown_requested;

/* 스레드 A (생산자) */
WRITE_ONCE(shutdown_requested, true);

/* 스레드 B (소비자) */
while (!READ_ONCE(shutdown_requested))
    do_work();

/* data_race(): KCSAN 데이터 레이스 검출기에서 의도적 레이스 표시 */
int approx = data_race(counter);  /* 정확도 불필요한 통계 읽기 */

Acquire/Release 의미론

smp_store_release/smp_load_acquire는 LKMM(Linux Kernel Memory Model)의 핵심 원시 연산으로, C11 memory_order_release/memory_order_acquire에 대응합니다.

/* store-release: 이 저장 이전의 모든 쓰기가 완료된 후 저장 */
smp_store_release(&ready, 1);
/* → 다른 코어가 ready==1을 보면, 앞의 모든 데이터도 보임 */

/* load-acquire: 이 읽기 이후의 모든 읽기는 이 값 이후로 */
int r = smp_load_acquire(&ready);
if (r)
    use_data(data);  /* ready==1이면 data도 최신 */

/* acquire/release 쌍으로 생산자-소비자 패턴 */
/* 생산자 */
WRITE_ONCE(data, 42);
smp_store_release(&flag, 1);  /* data가 먼저 보임을 보장 */

/* 소비자 */
while (!smp_load_acquire(&flag))
    cpu_relax();
int val = READ_ONCE(data);  /* 42를 보장 */

rcu_dereference() 내부 구현

/* rcu_dereference: RCU read-side에서 포인터 역참조 */
/* 내부 구현 (include/linux/rcupdate.h) */
#define rcu_dereference(p)  rcu_dereference_check(p, 0)

#define rcu_dereference_check(p, c) \
    rcu_dereference_sparse(p, typeof(p))

/* 본질: volatile 읽기 + memory barrier (Alpha 아키텍처 대응) */
#define __rcu_dereference(p)  ({              \
    typeof(p) _________p1 = READ_ONCE(p); \
    smp_read_barrier_depends();             \
    _________p1;                             \
})

/* 올바른 RCU 패턴 */
rcu_read_lock();
struct foo *p = rcu_dereference(global_foo);
if (p)
    do_something(p->data);  /* p가 유효한 동안 안전 */
rcu_read_unlock();
/* rcu_assign_pointer(global_foo, new_p): 쓰기 쪽 */
더 자세한 메모리 배리어: SMP 메모리 모델, acquire/release 의미론, 아키텍처별 구현은 메모리 배리어 완전 가이드를 참조하세요.

함수 포인터 & 콜백 — 타입 안전 패턴

커널은 함수 포인터를 드라이버 등록, 인터럽트 핸들러, 타이머 콜백, VFS 연산 등 광범위하게 활용합니다. 타입 안전한 선언과 속성 적용이 중요합니다.

함수 포인터 타입 안전 선언

/* 기본 함수 포인터 선언 */
void (*handler)(int);  /* 가독성 낮음 */

/* typedef로 타입 정의 — 권장 */
typedef void (*irq_handler_t)(int irq, void *dev_id);
typedef int  (*cmp_func_t)(const void *, const void *);

irq_handler_t my_handler = my_irq_handler;
cmp_func_t    sort_cmp   = strcmp;

/* 구조체 멤버로 함수 포인터 */
struct my_driver {
    int  (*probe)(struct pci_dev *);
    void (*remove)(struct pci_dev *);
    int  (*suspend)(struct device *, pm_message_t);
};

커널 콜백 패턴

/* 1. 인터럽트 핸들러 */
static irqreturn_t my_irq_handler(int irq, void *dev_id)
{
    struct my_dev *dev = dev_id;
    /* ... */
    return IRQ_HANDLED;
}

request_irq(irq, my_irq_handler, IRQF_SHARED, "mydev", dev);

/* 2. 타이머 콜백 */
static void my_timer_callback(struct timer_list *t)
{
    struct my_dev *dev = from_timer(dev, t, timer);
    /* ... */
}

timer_setup(&dev->timer, my_timer_callback, 0);

/* 3. VFS file_operations */
static const struct file_operations my_fops = {
    .owner   = THIS_MODULE,
    .open    = my_open,
    .read    = my_read,
    .write   = my_write,
    .release = my_release,
};

속성 적용 — __no_sanitize_*

/* KASAN 검사 제외: 부트 코드, 인터럽트 컨텍스트 */
static void __no_sanitize_address early_init(void)
{
    /* KASAN이 아직 초기화되지 않음 */
}

/* KCSAN 검사 제외: 의도적 데이터 레이스 */
static void __no_kcsan update_stats(void)
{
    /* 정확도 불필요한 통계 — 레이스 허용 */
    stats.counter++;  /* KCSAN 경고 억제 */
}

/* UBSAN 검사 제외 */
static void __no_sanitize_undefined legacy_code(void)
{
    /* 레거시 오버플로우 코드 */
}
속성용도대표 사용처
__no_sanitize_addressKASAN 제외부트 코드, 인터럽트, KASAN 자체
__no_kcsanKCSAN 제외의도적 레이스, 통계 업데이트
__no_sanitize_undefinedUBSAN 제외레거시 코드, 특정 최적화
__no_sanitize("thread")TSAN 제외스레드 새니타이저 우회

__attribute__((cleanup)) & Linux 6.4+ RAII 패턴

GCC/Clang의 __attribute__((cleanup(fn)))는 변수가 스코프를 이탈할 때 자동으로 fn을 호출하는 C 언어의 RAII(Resource Acquisition Is Initialization) 구현입니다. Linux 6.4부터 DEFINE_CLASS/scoped_guard 매크로 시스템으로 대폭 확장되었습니다.

__free() / DEFINE_FREE() 매크로 계층

include/linux/cleanup.h__attribute__((cleanup)) 위에 사용하기 쉬운 매크로 계층을 제공합니다.

/* __free(fn): 스코프 이탈 시 fn 자동 호출 */
/* include/linux/cleanup.h */
#define __free(free_fn)  __attribute__((__cleanup__(free_fn##_p)))

/* DEFINE_FREE: 해제 함수 등록 */
#define DEFINE_FREE(_name, _type, _free) \
    static __always_inline void __free_##_name(_type *p) { _free; }

/* 미리 정의된 __free 대상 (include/linux/cleanup.h) */
DEFINE_FREE(kfree,    void *,  kfree(*p))
DEFINE_FREE(kvfree,   void *,  kvfree(*p))
DEFINE_FREE(kfree_sensitive, void *, kfree_sensitive(*p))
DEFINE_FREE(fput,     struct file *, if (*p) fput(*p))

/* 사용: 어떤 return 경로에서도 자동 kfree */
void my_function(void)
{
    char *__free(kfree) buf = kmalloc(256, GFP_KERNEL);
    if (!buf)
        return;  /* buf == NULL이면 kfree(NULL) → 안전 */

    if (some_error())
        return;  /* 자동 kfree(buf) */

    use(buf);
}   /* 자동 kfree(buf) — goto 없이 깔끔 */

DEFINE_CLASS() — 뮤텍스/스핀락 자동 RAII

DEFINE_CLASS는 락/언락을 자동화하는 클래스를 정의합니다. CLASS(mutex, lock)(&my_mutex)로 선언하면 스코프 이탈 시 자동 언락됩니다.

/* DEFINE_CLASS 정의 형식 */
#define DEFINE_CLASS(_name, _type, _exit, _init, _init_args...) \
    typedef _type class_##_name##_t;                \
    static __always_inline _type                    \
    class_##_name##_constructor(_init_args) { _init; } \
    static __always_inline void                    \
    class_##_name##_destructor(_type *p) { _exit; }

/* 미리 정의된 클래스들 (include/linux/mutex.h 등) */
DEFINE_CLASS(mutex, struct mutex *,
    mutex_unlock(*p),
    ({ mutex_lock(_T); _T; }),
    struct mutex *_T)

DEFINE_CLASS(spinlock, unsigned long,
    spin_unlock_irqrestore(_L, *p),
    ({ spin_lock_irqsave(_L, _flags); _flags; }),
    spinlock_t *_L, unsigned long _flags)

/* 사용: CLASS() 매크로 */
void my_critical_section(void)
{
    CLASS(mutex, lock)(&my_mutex);  /* mutex_lock() 자동 호출 */

    if (error_condition())
        return;  /* 자동 mutex_unlock() */

    do_work();
}   /* 자동 mutex_unlock() */

scoped_guard / guard() — 블록 단위 보호

scoped_guard는 명시적 블록을 락으로 보호합니다. guard()는 함수 전체를 보호하는 전통적 패턴입니다.

/* scoped_guard: 특정 블록만 보호 */
void update_shared_data(void)
{
    /* 락이 필요 없는 준비 작업 */
    prepare();

    scoped_guard(mutex, &shared_lock) {
        /* 이 블록만 락 보호 */
        shared_data.update();
    }   /* 자동 언락 */

    /* 락 없이 후처리 */
    cleanup();
}

/* guard(): 함수 전체를 RCU/스핀락으로 보호 */
void rcu_protected_read(void)
{
    guard(rcu)();  /* rcu_read_lock() — 함수 종료 시 rcu_read_unlock() */

    struct my_obj *obj = rcu_dereference(global_obj);
    if (obj)
        process(obj);
    /* 자동 rcu_read_unlock() */
}

/* scope_guard(): 임의 함수 자동 실행 (C++의 ScopeExit) */
void complex_init(void)
{
    enable_device();
    bool success = false;

    auto cleanup = scope_guard([&] {
        if (!success)
            disable_device();  /* 실패 시에만 비활성화 */
    });

    if (setup_phase1() < 0) return;
    if (setup_phase2() < 0) return;

    success = true;  /* 성공 시 cleanup에서 disable 건너뜀 */
}

Linux 6.4+ DEFINE_GUARD 매크로 테이블

매크로락 종류획득/해제사용 예
guard(mutex)뮤텍스mutex_lock / mutex_unlock슬립 가능 컨텍스트
guard(spinlock)스핀락spin_lock / spin_unlock인터럽트 컨텍스트
guard(spinlock_irq)스핀락+IRQspin_lock_irq / spin_unlock_irqIRQ 비활성화 필요 시
scoped_guard(rcu)RCU 읽기rcu_read_lock / rcu_read_unlockRCU 보호 포인터 읽기
scoped_guard(rwsem_read)읽기 세마포어down_read / up_read공유 읽기 잠금
scoped_guard(rwsem_write)쓰기 세마포어down_write / up_write배타적 쓰기 잠금
guard(preempt)선점 비활성화preempt_disable / preempt_enablePer-CPU 변수 접근

cleanup-attribute vs goto cleanup 비교

패턴장점단점적합한 상황
goto cleanup명시적, 디버그 용이, 커널 전통레이블 순서 오류 위험, 가독성↓복잡한 역순 해제 체인
devm_*누락 불가, probe 코드 간소화해제 시점 제어 불가디바이스 드라이버 probe/remove
__free()단순 포인터 해제에 최적복잡한 조건부 해제 어려움단일 자원 자동 해제
scoped_guard락 해제 누락 방지, 가독성↑Linux 6.4+ 전용임계 구역 보호

sparse 어노테이션 — 포인터 주소 공간 분류

sparse는 리눅스 커널 전용 정적 분석 도구입니다. __user, __iomem, __rcu 등의 어노테이션을 인식해 잘못된 포인터 사용을 컴파일 타임에 감지합니다.

sparse 포인터 주소 공간 분류 커널 주소 공간 일반 포인터 (어노테이션 없음) __iomem — I/O 메모리 매핑 __percpu — Per-CPU 변수 __rcu — RCU 보호 포인터 __kernel — 명시적 커널 공간 사용자 주소 공간 __user — 사용자 공간 포인터 직접 역참조 금지! copy_from_user() / copy_to_user() 필수 함수 어노테이션 __must_check — 반환값 무시 금지 __force — 강제 변환 (경고 억제) __acquire/__release — 락 상태 추적

주요 sparse 어노테이션

어노테이션의미사용 예
__user사용자 공간 포인터 — 직접 역참조 금지copy_from_user(kernel_buf, __user buf, len)
__iomemI/O 메모리 매핑 포인터void __iomem *regs = ioremap(paddr, size)
__percpuPer-CPU 변수 포인터DEFINE_PER_CPU(int, my_counter)
__rcuRCU 보호 포인터 — 적절한 컨텍스트에서만 접근struct foo __rcu *ptr
__must_check반환값 무시 금지int __must_check request_irq(...)
__force강제 타입 변환 (sparse 경고 억제)엔디안 변환: (__force __be32)
__acquire(x)락 획득 표시 (sparse 락 추적)락 래퍼 함수
__release(x)락 해제 표시언락 래퍼 함수
/* __user: 사용자 공간 포인터 사용 패턴 */
static ssize_t my_write(struct file *f,
                        const char __user *buf,
                        size_t count, loff_t *pos)
{
    char kbuf[256];
    /* if (!buf) return -EFAULT; sparse가 감지 */
    if (copy_from_user(kbuf, buf, min(count, sizeof(kbuf))))
        return -EFAULT;
    return count;
}

/* make C=2 M=drivers/mydrv/ 로 sparse 실행 */

__bitwise — 타입 안전 정수 (gfp_t, __be32 기반)

sparse의 __bitwise는 정수 타입에 "비트 공간 레이블"을 붙여 다른 레이블의 정수와 직접 혼용하면 경고를 발생시킵니다. gfp_t와 엔디안 타입에 활용됩니다.

/* __bitwise: sparse 전용 타입 레이블 (컴파일 런타임 영향 없음) */
#ifdef __CHECKER__   /* sparse가 정의하는 매크로 */
#define __bitwise  __attribute__((bitwise))
#else
#define __bitwise
#endif

/* gfp_t: GFP_KERNEL과 GFP_ATOMIC 혼용 감지 */
typedef unsigned int __bitwise gfp_t;
#define GFP_KERNEL ((gfp_t)0x00000CC0u)
#define GFP_ATOMIC ((gfp_t)0x00000020u)

/* 잘못된 사용: sparse 경고 발생 */
gfp_t flags = GFP_KERNEL | 0x100;  /* warning: plain integer */
gfp_t flags = (__force gfp_t)0x100; /* OK: __force로 억제 */

/* __be32/__le32: 엔디안 혼용 감지 */
__be32 net_val = cpu_to_be32(42);
u32    host_val = net_val;  /* sparse 경고: 엔디안 혼용 */
u32    host_val = be32_to_cpu(net_val);  /* 정상 */

sparse 실행 방법

# C=1: 변경된 파일만 sparse 검사
make C=1 M=drivers/net/ethernet/intel/igb/

# C=2: 모든 파일 sparse 검사 (느림)
make C=2 M=drivers/my_driver/

# 특정 파일만 검사
make C=2 drivers/char/mem.o

# CF: sparse 추가 플래그
make C=1 CF="-D__CHECK_ENDIAN__" drivers/net/

# sparse 경고 예시 출력:
# drivers/char/mem.c:123:5: warning: incorrect type in assignment
#   expected unsigned int [usertype] [bitwise] gfp_t
#   got unsigned int

커널 타입 시스템 — u8, __be32, gfp_t

커널은 include/linux/types.h에서 자체 정수 타입 시스템을 정의합니다. 크기 고정, 엔디안 안전, 컨텍스트 강제를 목적으로 합니다.

크기 고정 정수 타입

커널 타입표준 동등용도
u8 / s8uint8_t / int8_t바이트 필드, 레지스터
u16 / s16uint16_t / int16_t포트 번호, 16비트 오프셋
u32 / s32uint32_t / int32_tPID, 타임스탬프, 레지스터 값
u64 / s64uint64_t / int64_t주소, 파일 크기, 나노초 시각
ulongunsigned long아키텍처 워드 크기

엔디안 안전 타입 (sparse + __force)

/* Big-endian / Little-endian 타입 (sparse가 혼용 오류 검출) */
__be16 be_port = htons(80);  /* Network byte order */
__le32 le_val  = cpu_to_le32(0x12345678);

/* 변환 API (include/uapi/linux/byteorder/) */
u32 host = be32_to_cpu(be_val);  /* BE → 호스트 */
u32 host = le32_to_cpu(le_val);  /* LE → 호스트 */
__be32 net = cpu_to_be32(host);  /* 호스트 → BE */

컨텍스트 강제 타입

타입정의설명
gfp_ttypedef unsigned int __bitwise gfp_t메모리 할당 플래그 (GFP_KERNEL, GFP_ATOMIC)
phys_addr_ttypedef u64 phys_addr_t물리 주소 (32비트: u32)
dma_addr_ttypedef u64 dma_addr_tDMA 버스 주소
resource_size_ttypedef phys_addr_t resource_size_tI/O 리소스 크기
sector_ttypedef u64 sector_t블록 디바이스 섹터 번호
pid_ttypedef int __kernel_pid_t프로세스 ID
atomic_tstruct { int counter; }원자적 정수 (직접 접근 금지)
/* gfp_t: 잘못된 플래그 혼용을 sparse가 감지 */
void *ptr = kmalloc(size, GFP_KERNEL);   /* 슬립 가능 컨텍스트 */
void *ptr = kmalloc(size, GFP_ATOMIC);   /* 인터럽트 컨텍스트 */

/* phys_addr_t vs dma_addr_t: IOMMU가 있으면 다를 수 있음 */
phys_addr_t paddr = virt_to_phys(kaddr);
dma_addr_t  daddr = dma_map_single(dev, kaddr, size, DMA_TO_DEVICE);

refcount_t — 포화 참조 카운터

일반 atomic_t는 0에서 dec 시 언더플로우가 발생할 수 있어 use-after-free 취약점으로 이어집니다. refcount_t포화 카운터로 0에서 감소를 차단합니다.

/* refcount_t: include/linux/refcount.h */
struct my_obj {
    refcount_t refs;   /* atomic_t 대신 */
    /* ... */
};

refcount_set(&obj->refs, 1);    /* 초기화 */
refcount_inc(&obj->refs);         /* +1 (0에서 증가 금지) */
refcount_dec_and_test(&obj->refs) /* -1, 0이면 true 반환 */

if (refcount_dec_and_test(&obj->refs))
    kfree(obj);  /* 마지막 참조자가 해제 */

/* kref: 고수준 래퍼 (refcount_t 기반) */
struct my_obj {
    struct kref kref;
    /* ... */
};
kref_init(&obj->kref);          /* refs = 1 */
kref_get(&obj->kref);           /* refs++ */
kref_put(&obj->kref, release_fn); /* refs--, 0이면 release_fn() */

ktime_t — 고해상도 시각

/* ktime_t: s64 나노초 단위 (include/linux/ktime.h) */
ktime_t start = ktime_get();          /* 현재 시각 (나노초) */
/* ... 작업 ... */
ktime_t end   = ktime_get();
s64 elapsed_ns = ktime_to_ns(ktime_sub(end, start));

/* 단위 변환 */
s64 us = ktime_to_us(ktime);    /* 마이크로초 */
s64 ms = ktime_to_ms(ktime);    /* 밀리초 */

/* timespec64로 변환 */
struct timespec64 ts;
ktime_get_real_ts64(&ts);  /* 실제 시각(CLOCK_REALTIME) */

/* atomic64_t: 64비트 원자 연산 */
atomic64_t counter = ATOMIC64_INIT(0);
atomic64_inc(&counter);
atomic64_add(100, &counter);
s64 val = atomic64_read(&counter);
커널 코딩 스타일: 타입 명명 규칙, typedef 사용 지침은 커널 코딩 스타일을 참조하세요.

컴파일 타임 검사 — BUILD_BUG_ON, static_assert

런타임 오버헤드 없이 불변식을 강제하는 컴파일 타임 어서션입니다.

/* BUILD_BUG_ON: 조건이 참이면 컴파일 오류 */
BUILD_BUG_ON(sizeof(struct task_struct) > 4096);
BUILD_BUG_ON(THREAD_SIZE < 4096);

/* BUILD_BUG_ON 내부 구현 */
#define BUILD_BUG_ON(condition) \
    BUILD_BUG_ON_MSG(condition, "BUILD_BUG_ON failed: " #condition)

/* BUILD_BUG_ON_MSG */
#define BUILD_BUG_ON_MSG(cond, msg) \
    compiletime_assert(!(cond), msg)

/* compiletime_assert: 조건 거짓이면 0-사이즈 배열 오류 트릭 */
#define compiletime_assert(condition, msg)          \
    _compiletime_assert(condition, msg,             \
        __compiletime_assert_, __COUNTER__)

/* C11 static_assert (매크로 래퍼) */
static_assert(sizeof(int) == 4, "int must be 4 bytes");

/* BUILD_BUG_ON_ZERO: 컴파일 타임 0 반환 (표현식 내 사용) */
/* __must_be_array: 포인터이면 컴파일 오류 */
#define __must_be_array(a) \
    BUILD_BUG_ON_ZERO(__same_type((a), &(a)[0]))

/* 실전: 구조체 크기/오프셋 검사 */
BUILD_BUG_ON(offsetof(struct pt_regs, ip) != 16 * 8);
코드 설명
  • BUILD_BUG_ON조건이 참이면 컴파일 오류를 발생시킵니다. 런타임 비용이 전혀 없으며, 구조체 크기, 타입 크기, 오프셋 등의 불변식을 강제하는 데 사용합니다. 커널 전반에서 "이 값은 항상 N이어야 한다"는 가정을 문서화하고 강제합니다.
  • __must_be_arrayARRAY_SIZE 매크로 내부에서 사용됩니다. 배열이 아닌 포인터에 ARRAY_SIZE를 적용하면 컴파일 오류가 발생하도록 합니다. 포인터와 배열의 sizeof가 다름을 이용합니다.

커널 C 보안 코딩 — FORTIFY_SOURCE, randstruct, structleak

리눅스 커널은 컴파일 타임과 런타임 보안 강화 기법을 다층으로 적용합니다. FORTIFY_SOURCE, GCC 보안 플러그인, 스택 보호 메커니즘의 동작 원리를 이해하면 더 안전한 커널 코드를 작성할 수 있습니다.

FORTIFY_SOURCE — 경계 검사 강화

-D_FORTIFY_SOURCE=2와 커널의 CONFIG_FORTIFY_SOURCE=y__builtin_object_size를 활용해 memcpy, strcpy 등의 경계를 컴파일 타임 또는 런타임에 검사합니다.

/* FORTIFY_SOURCE 동작 원리 (include/linux/fortify-string.h) */

/* 원래 memcpy → FORTIFY 버전으로 치환 */
#define memcpy(p, q, s)  __builtin_memcpy_chk(p, q, s, \
    __builtin_object_size(p, 0))
/* __builtin_object_size(p, 0): p가 가리키는 객체 크기 반환 */
/* 컴파일 타임: 크기 알면 → BUILD_BUG_ON으로 컴파일 오류 */
/* 런타임: 크기 모르면 → __memcpy_chk()에서 크기 비교 후 panic */

/* 예: 컴파일 타임 탐지 */
char buf[8];
memcpy(buf, src, 16);  /* ← 컴파일 오류: 8바이트에 16 복사 */

/* 커널 FORTIFY: strscpy 권장 (strcpy 대신) */
/* strscpy(dst, src, size): 최대 size-1 복사, 항상 NUL 종료 */
strscpy(dev->name, src_name, sizeof(dev->name));

/* strlcpy/strcpy는 FORTIFY_SOURCE에서 경고/오류 발생 가능 */
/* 커널 6.x: strlcpy deprecated → strscpy 사용 권장 */

스택 보안 레이어

보안 기법GCC 옵션Kconfig동작
스택 카나리-fstack-protector-strongCONFIG_STACKPROTECTOR_STRONG함수 진입/복귀 시 카나리 값 검사
레지스터 초기화-fzero-call-used-regs=used-gprCONFIG_ZERO_CALL_USED_REGS함수 반환 전 사용 레지스터 0으로 초기화
스택 변수 초기화GCC plugin structleakCONFIG_GCC_PLUGIN_STRUCTLEAK구조체 변수 자동 0 초기화
스택 클래쉬 방지-fstack-clash-protectionCONFIG_SHADOW_CALL_STACK스택 프로브로 클래쉬 탐지
리턴 어드레스 보호ShadowCallStack (ARM64)CONFIG_SHADOW_CALL_STACK별도 스택에 리턴 주소 저장
CFI (제어 흐름 무결성)Clang CFICONFIG_CFI_CLANG함수 포인터 타입 검사

GCC 보안 플러그인 — structleak & randstruct

/* structleak: 미초기화 구조체 변수 자동 0 채움 */
/* CONFIG_GCC_PLUGIN_STRUCTLEAK=y 활성화 시 */
/* 컴파일러가 자동으로 memset(&var, 0, sizeof(var)) 추가 */
struct my_sensitive {
    u32 key;
    u8  secret[16];
};

void process(void)
{
    struct my_sensitive s;  /* STRUCTLEAK: 자동 0초기화 삽입 */
    do_work(&s);
    /* 초기화 전 secret이 스택 잔여 데이터를 노출하지 않음 */
}

/* randstruct: 구조체 필드 무작위 배치 */
/* CONFIG_GCC_PLUGIN_RANDSTRUCT=y */
/* 빌드마다 다른 필드 순서 → 오프셋 기반 익스플로잇 방지 */
struct cred {                    /* 커널 자격 증명 구조체 */
    uid_t  uid;
    gid_t  gid;
    u32    securebits;
    kernel_cap_t cap_effective;
    /* 빌드마다 필드 순서 다름 — 주소 예측 불가 */
} __randomize_layout;           /* 명시적 무작위화 마킹 */

/* __no_randomize_layout: 특정 구조체 제외 */
struct pt_regs {               /* ABI 고정 필요 → 무작위화 금지 */
    unsigned long r15;
    unsigned long r14;
    /* ... */
} __no_randomize_layout;

커널 C 보안 코딩 체크리스트

위험 패턴안전한 대안이유
strcpy(dst, src)strscpy(dst, src, sizeof(dst))경계 검사 없음 → 오버플로우
sprintf(buf, fmt, ...)snprintf(buf, sizeof(buf), fmt, ...)버퍼 오버플로우 위험
kmalloc(size, ...)kzalloc(size, ...)미초기화 메모리 노출 방지
memcpy(dst, __user ptr, n)copy_from_user(dst, ptr, n)사용자 포인터 직접 역참조 금지
*(int*)arbitrary_addrKASLR + SMEP/SMAP 하에서 접근 불가임의 주소 접근 → panic
n * sizeof(T) kmallockmalloc_array(n, sizeof(T), ...)정수 오버플로우 → 과소 할당
고정 크기 스택 버퍼동적 할당 또는 kvmalloc스택 오버플로우 위험
kfree(ptr); ptr→fieldptr = NULL 후 접근 금지Use-after-free → KASAN 탐지

OPTIMIZER_HIDE_VAR — 보안 변수 최적화 방지

/* 문제: 컴파일러 최적화로 보안 변수 제거 가능 */
u8 key[32];
get_key(key);
crypto_sign(msg, key);
memset(key, 0, sizeof(key));  /* ← 컴파일러가 제거 가능 (이후 미사용) */

/* 해결: OPTIMIZER_HIDE_VAR 또는 memzero_explicit */
memzero_explicit(key, sizeof(key));  /* 최적화 방지 barrier() 포함 */

/* OPTIMIZER_HIDE_VAR: 변수를 컴파일러 최적화에서 숨김 */
#define OPTIMIZER_HIDE_VAR(var)  \
    asm ("" : "=r"(var) : "0"(var))

u32 secret = get_secret();
OPTIMIZER_HIDE_VAR(secret);  /* 컴파일러가 값을 추적하지 못하게 */
use(secret);

/* kfree_sensitive: 해제 전 0 채움 (CONFIG_INIT_ON_FREE_DEFAULT_ON과 별개) */
kfree_sensitive(key_buffer);  /* memzero_explicit + kfree */

LTO (Link-Time Optimization) — 커널과 인터랙션

LTO는 링크 타임에 전체 프로그램 최적화를 수행합니다. 커널에서는 제한적으로 사용되며, noinline과 같은 속성의 중요성이 커집니다.

LTO 개요

# Full LTO: 전체 프로그램을 IR로 링크 후 최적화
gcc -flto -O2 -c a.c b.c
gcc -flto -O2 a.o b.o -o program

# Thin LTO: 병렬 처리 + 증분 빌드 (빠름)
gcc -flto=thin -O2 -c a.c b.c
gcc -flto=thin -O2 a.o b.o -o program

커널과 LTO

/* 커널 LTO 지원 (CONFIG_LTO=y) */
/* 장점: IPA 인라이닝, 데드 코드 제거, 전역 최적화 */
/* 단점: 빌드 시간 증가, 메모리 사용량 급증 */

/* LTO 하에서 noinline 중요성 */
/* LTO는 IPA(Inter-Procedural Analysis)로 모든 함수 인라인 후보 */
/* 디버깅/스택 추적용 함수는 noinline 필수 */

void __noinline debug_dump_stack(void)  /* 인라인 금지 */
{
    dump_stack();
}

/* __used: LTO가 미사용 함수 제거 방지 */
static void __used __init early_init(void)
{
    /* 링커가 제거하지 않음 */
}

/* __visible: LTO가 심볼 가시성 유지 */
void __visible public_api(void);
속성/옵션LTO 효과커널 사용
noinlineIPA 인라이닝 방지디버그 함수, 스택 추적
__used미사용 함수 제거 방지링커 테이블, init/cleanup
__visible심볼 가시성 유지외부 API, 어셈블리 호출
-fltoFull LTO 활성화CONFIG_LTO
-flto=thinThin LTO (병렬)CONFIG_LTO_THIN

LTO 관련 커널 Kconfig

# arch/Kconfig
config LTO
    bool "Link-time optimization (LTO)"
    help
      전체 프로그램 최적화 활성화

config LTO_THIN
    bool "Thin LTO"
    depends on LTO
    help
      병렬 LTO — 빌드 시간 단축

모듈 초기화 패턴 — __init, initcall, module_param

리눅스 커널 모듈 시스템은 정교한 초기화 체계를 제공합니다. __init/__exit 링커 섹션, 8단계 initcall 레벨, module_param 파라미터 시스템을 이해하면 드라이버와 서브시스템 초기화 흐름을 완전히 파악할 수 있습니다.

initcall 레벨 0~7 타임라인 — 부팅 순서 부팅 완료 0 pure_initcall 가장 이른 초기화 (CPU기능, 아키텍처) 1 core_initcall 핵심 시스템 (kallsyms, workqueue) 2 postcore_initcall 코어 이후 (버스 서브시스템) 3 arch_initcall 아키텍처 초기화 (ACPI, IRQ) 4 subsys_initcall 서브시스템 (PCI, USB, 블록 레이어) 5 fs_initcall 파일시스템 (ext4, xfs, tmpfs) 6 device_initcall ← module_init() 여기에 등록! 대부분의 드라이버 (NIC, 스토리지) 7 late_initcall 최후 초기화 (watchdog, RCU)

__init / __exit / __initdata — 링커 섹션

__init은 함수를 .init.text 섹션에, __initdata는 변수를 .init.data에 배치합니다. 초기화 완료 후 free_initmem()으로 이 메모리를 해제합니다.

/* __init: .init.text 섹션에 배치 */
static int __init my_driver_init(void)
{
    pr_info("my_driver: initializing\n");
    return platform_driver_register(&my_platform_driver);
}

/* __exit: .exit.text 섹션 — 모듈 언로드 시만 실행 */
static void __exit my_driver_exit(void)
{
    platform_driver_unregister(&my_platform_driver);
}

/* module_init/exit: initcall 레벨 6(device_initcall)에 등록 */
module_init(my_driver_init);
module_exit(my_driver_exit);

/* __initdata: 초기화 데이터 — 부팅 후 해제됨 */
static int __initdata init_count = 0;

/* free_initmem(): 부팅 완료 후 .init 섹션 전체 해제 */
/* "Freeing unused kernel memory: 1234K" 메시지의 원인 */

/* __ref: __init 함수를 초기화 외 컨텍스트에서 호출 시 경고 억제 */
void __ref cpu_up(unsigned int cpu)  /* 핫플러그 CPU 추가 */
{
    __cpu_up(cpu);  /* __init 함수 호출 가능 (핫플러그 경우) */
}

initcall 레벨 8단계 전체 참조

레벨매크로섹션대표 서브시스템
0pure_initcall(fn).initcall0.initCPU 기능, KASLR 설정
1core_initcall(fn).initcall1.initkallsyms, workqueue, rcu_init
1score_initcall_sync(fn).initcall1s.init동기화 barrier
2postcore_initcall(fn).initcall2.init버스 추상화, 클럭 프레임워크
3arch_initcall(fn).initcall3.initACPI, IOMMU, IRQ 서브시스템
4subsys_initcall(fn).initcall4.initPCI, USB, 블록 레이어, 네트워킹
5fs_initcall(fn).initcall5.initVFS, ext4, xfs, btrfs, procfs
6device_initcall(fn).initcall6.initmodule_init() 기본 레벨
7late_initcall(fn).initcall7.initwatchdog, 배터리, 최후 진단

module_param — 모듈 파라미터 완전 참조

/* 기본 형식: module_param(이름, 타입, 권한) */
static int debug_level = 0;
module_param(debug_level, int, 0644);
MODULE_PARM_DESC(debug_level, "Debug verbosity level (0=off, 1=info, 2=verbose)");

static bool enable_feature = true;
module_param(enable_feature, bool, 0444);  /* 0444: 읽기 전용 */

static char *device_name = "default";
module_param(device_name, charp, 0000);  /* 0000: sysfs에 미노출 */

/* module_param_array: 배열 파라미터 */
static int irq_list[8] = { -1 };
static int irq_count = 0;
module_param_array(irq_list, int, &irq_count, 0444);
MODULE_PARM_DESC(irq_list, "IRQ numbers (comma-separated, up to 8)");
/* insmod mydrv.ko irq_list=3,7,11 → irq_list[0..2] = {3,7,11}, irq_count = 3 */

/* 지원 파라미터 타입 */
/* byte, short, ushort, int, uint, long, ulong */
/* charp (char*), bool, invbool (반전 bool) */
/* hexint (0x 접두사 허용 정수) */

/* sysfs 경로: /sys/module/<모듈명>/parameters/<파라미터명> */
/* echo 2 > /sys/module/mydrv/parameters/debug_level */

early_param / __setup — 커맨드라인 파싱

/* __setup: 커맨드라인 파라미터 등록 (메모리 초기화 후) */
static int __init parse_console(char *str)
{
    add_preferred_console("ttyS", simple_strtoul(str, &str, 10), str);
    return 1;  /* 1: 파라미터 소비 완료 */
}
__setup("console=", parse_console);
/* 커맨드라인: console=ttyS0,115200 */

/* early_param: 메모리 초기화 이전에 파싱 (더 이른 시점) */
static int __init parse_earlyprintk(char *str)
{
    setup_early_printk(str);
    return 0;
}
early_param("earlyprintk", parse_earlyprintk);
/* 커맨드라인: earlyprintk=serial,ttyS0,115200 */

/* 파싱 타이밍 비교 */
/* early_param: start_kernel() → parse_early_param() → 메모리 초기화 전 */
/* __setup:     start_kernel() → parse_args() → 메모리 초기화 후 */
/* module_param: insmod/modprobe 시 또는 부팅 시 (builtin 모듈) */

MODULE_* 메타데이터 매크로

매크로용도예시
MODULE_LICENSE("GPL v2")라이선스 선언 (non-GPL 심볼 접근 제한)"GPL", "GPL v2", "Dual MIT/GPL"
MODULE_AUTHOR("Name")작성자 정보/sys/module/*/srcversion
MODULE_DESCRIPTION("...")모듈 설명modinfo mydrv.ko
MODULE_VERSION("1.0.0")버전 문자열modinfo 출력
MODULE_ALIAS("pci:...")별칭 → udev 자동 로드pci:v00008086d...
MODULE_DEVICE_TABLE(pci, tbl)PCI ID 테이블 등록udev 핫플러그 지원
MODULE_FIRMWARE("fw.bin")필요 펌웨어 선언modinfo firmware 필드
MODULE_SOFTDEP("pre: crc32")소프트 의존성 (로드 순서)modprobe 의존성 해결

전처리기 패턴 — IS_ENABLED, do-while(0), x-macro

커널에서 자주 사용하는 C 전처리기 관용 패턴들입니다.

do { ... } while (0) 패턴

/* 문제: 멀티-문 매크로가 if-else에서 깨짐 */
#define BAD_MACRO(x)  stmt1; stmt2  /* ← 위험 */

if (cond)
    BAD_MACRO(x);   /* stmt2는 항상 실행됨! */
else
    other();

/* 해결: do-while(0) 래핑 — 단일 문으로 처리 */
#define GOOD_MACRO(x)  do { stmt1; stmt2; } while (0)

if (cond)
    GOOD_MACRO(x);  /* 올바르게 동작 */
else
    other();

/* 커널 예시: pr_err, list_add_tail, spin_lock */
#define spin_unlock_irqrestore(lock, flags) do { \
    raw_spin_unlock_irqrestore(&(lock)->rlock, flags); \
} while (0)

stringify / token-paste

/* # stringify: 인수를 문자열 리터럴로 변환 */
#define STRINGIFY(x)  #x
#define STR(x)        STRINGIFY(x)  /* 매크로 전개 후 stringify */

/* ## token-paste: 두 토큰 연결 */
#define DEFINE_MUTEX(name)  struct mutex name##_mutex = __MUTEX_INITIALIZER(name##_mutex)

/* 커널 예: __UNIQUE_ID (충돌 없는 고유 식별자) */
#define __UNIQUE_ID(prefix) __PASTE(__PASTE(__UNIQUE_ID_, prefix), __COUNTER__)

IS_ENABLED 패턴

/* IS_ENABLED: Kconfig 심볼이 y 또는 m이면 1 반환 */
#if IS_ENABLED(CONFIG_IPV6)
    /* IPv6 코드 */
#endif

/* IS_BUILTIN vs IS_MODULE 구분 */
#if IS_BUILTIN(CONFIG_NET)
    /* =y 빌트인 전용 코드 */
#elif IS_MODULE(CONFIG_NET)
    /* =m 모듈 전용 코드 */
#endif

/* IS_ENABLED 내부 구현 (include/linux/kconfig.h) */
/* CONFIG_FOO=y → #define CONFIG_FOO 1 */
/* CONFIG_FOO=m → #define CONFIG_FOO_MODULE 1 */
#define IS_ENABLED(option) \
    (IS_BUILTIN(option) || IS_MODULE(option))

가변 인수 매크로와 pr_debug

/* pr_debug: 조건부 디버그 출력 (C99 가변 인수) */
#ifdef DEBUG
#define pr_debug(fmt, ...)  printk(KERN_DEBUG fmt, ##__VA_ARGS__)
#else
#define pr_debug(fmt, ...)  no_printk(KERN_DEBUG fmt, ##__VA_ARGS__)
#endif

/* ##__VA_ARGS__: 인수 없을 때 앞 쉼표 제거 (GNU 확장) */

__VA_OPT__ — C23 가변 인수 쉼표 처리

C23의 __VA_OPT__는 가변 인수 매크로에서 인수가 있을 때만 내용을 확장합니다. GNU 확장 ##__VA_ARGS__의 표준화 버전입니다.

/* C23: __VA_OPT__ — 가변 인수 있을 때만 확장 */
#define log_debug(fmt, ...) \
    printf("[DEBUG] " fmt __VA_OPT__(,) __VA_ARGS__)

log_debug("started\n");           /* printf("[DEBUG] started\n") */
log_debug("count=%d\n", 42);    /* printf("[DEBUG] count=%d\n", 42) */

/* GNU 확장 (C23 이전): ##__VA_ARGS__ */
#define pr_info(fmt, ...) \
    printk(KERN_INFO fmt, ##__VA_ARGS__)

/* 활용: 선택적 추가 인수 */
#define ASSERT_MSG(cond, ...) \
    ((cond) ? (void)0 : panic("assertion failed" __VA_OPT__(": ") __VA_ARGS__))

__COUNTER__ — 고유 식별자 생성

__COUNTER__는 매크로가 확장될 때마다 0부터 증가하는 정수로 대체됩니다. 고유 변수명, 레이블, 식별자 생성에 활용합니다.

/* __COUNTER__: 매 확장마다 증가 */
#define UNIQUE_VAR(prefix) prefix##_##__COUNTER__

int UNIQUE_VAR(tmp) = 1;  /* int tmp_0 = 1 */
int UNIQUE_VAR(tmp) = 2;  /* int tmp_1 = 2 */

/* 커널: __UNIQUE_ID */
#define __UNIQUE_ID(prefix) __PASTE(__PASTE(__UNIQUE_ID_, prefix), __COUNTER__)

/* SCOPE_GUARD 패턴 */
#define SCOPE_GUARD(name, fn) \
    auto void __PASTE(__scope_guard_, __COUNTER__)(void *p) { fn(p); }
/* C의 auto는 다른 의미 — 실제 구현은 cleanup 속성 사용 */

매크로 디버깅 기법

/* 매크로 확장 결과 확인: gcc -E */
/* $ gcc -E test.c -o test.i */

/* 매크로 정의 확인 */
#pragma message("BUILD_BUG_ON defined as: " STRINGIFY(BUILD_BUG_ON))

/* 매크로 인수 개수 확인 */
#define NUM_ARGS(...) (sizeof((int[]){__VA_ARGS__})/sizeof(int))
int n = NUM_ARGS(a, b, c);  /* n = 3 */

/* 매크로 재귀 확장 방지 */
#define FOO  FOO  /* 무한 재귀 방지: 자기 자신으로 확장 금지 */

X-Macro 패턴

/* X-Macro: 목록을 여러 용도로 재사용 */
#define IRQ_TYPES  \
    X(IRQ_TYPE_NONE,     0,   "none")     \
    X(IRQ_TYPE_EDGE_RISING,  1,   "rising")  \
    X(IRQ_TYPE_EDGE_FALLING, 2,   "falling") \
    X(IRQ_TYPE_LEVEL_HIGH,   4,   "high")    \
    X(IRQ_TYPE_LEVEL_LOW,    8,   "low")

/* 열거형 생성 */
enum irq_type {
#define X(name, val, str)  name = val,
    IRQ_TYPES
#undef X
};

/* 이름 배열 생성 */
static const char *irq_type_names[] = {
#define X(name, val, str)  [val] = str,
    IRQ_TYPES
#undef X
};
다음 학습: