커널 개발 도구 (Development Tools)

Linux 커널 개발에 사용되는 모든 도구: GCC/Clang 툴체인, 정적 분석, checkpatch, Git 워크플로, QEMU 테스트, 크로스 컴파일을 종합적으로 다룹니다.

개발 환경 구축

커널 개발에 필요한 최소 패키지:

# Debian/Ubuntu
sudo apt install build-essential bc bison flex libelf-dev \
    libssl-dev libncurses-dev dwarves python3 git

# Fedora/RHEL
sudo dnf install gcc make bc bison flex elfutils-libelf-devel \
    openssl-devel ncurses-devel dwarves python3 git

# 커널 소스 가져오기
git clone https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
cd linux
git checkout v6.8  # 특정 버전 체크아웃

GCC (GNU Compiler Collection)

커널은 최소 GCC 버전을 요구합니다 (커널 6.x는 GCC 5.1+, 실무적으로 GCC 8+ 권장).

# 커널 빌드에서 사용되는 주요 GCC 옵션
-Wall -Wundef -Werror=strict-prototypes -Wno-trigraphs
-fno-strict-aliasing -fno-common
-fno-delete-null-pointer-checks
-O2                                # 최적화 레벨
-fstack-protector-strong           # 스택 보호
-mgeneral-regs-only                # ARM64: FP/SIMD 레지스터 사용 금지
-mindirect-branch=thunk-extern     # Spectre 완화

# GCC 플러그인 (CONFIG_GCC_PLUGINS=y)
-fplugin=randomize_layout          # 구조체 랜덤화 (RANDSTRUCT)
-fplugin=stackleak_plugin          # 스택 리크 방지
-fplugin=latent_entropy             # 잠재적 엔트로피 수집

Clang/LLVM

ClangBuiltLinux 프로젝트 덕분에 Clang으로도 커널을 완전히 빌드할 수 있습니다:

# Clang으로 커널 빌드
make CC=clang LLVM=1 defconfig
make CC=clang LLVM=1 -j$(nproc)

# Clang 전용 기능
CONFIG_CC_IS_CLANG=y
CONFIG_CFI_CLANG=y          # Control-Flow Integrity
CONFIG_LTO_CLANG_THIN=y     # Link-Time Optimization (Thin LTO)
CONFIG_SHADOW_CALL_STACK=y  # 섀도 콜 스택 (ARM64)
특성GCCClang
빌드 속도보통약간 빠름
진단 메시지보통우수 (색상, 제안)
LTO제한적Thin LTO 지원
CFI미지원지원
플러그인GCC 플러그인 다수LLVM pass
아키텍처모든 아키텍처x86, ARM, ARM64 위주

코드 분석 도구

sparse

Linus Torvalds가 작성한 정적 분석 도구. 주소 공간 혼합(__user, __kernel), endian 오류, lock 문제를 검출합니다:

# sparse로 커널 빌드 검사
make C=1    # 변경된 파일만 검사
make C=2    # 모든 파일 검사

# 특정 파일만 검사
make C=1 drivers/net/ethernet/intel/e1000e/
/* sparse 어노테이션 */
void my_ioctl(void __user *ubuf, void __iomem *mmio)
{
    /* sparse 경고: __user 포인터 역참조는 copy_from_user 사용 필요 */
    /* sparse 경고: __iomem 포인터는 readl/writel 사용 필요 */
}

Coccinelle

시맨틱 패치(Semantic Patch) 도구. 코드 변환 패턴을 SmPL 언어로 정의합니다:

# Coccinelle 시맨틱 패치 실행
make coccicheck MODE=report  # 보고만
make coccicheck MODE=patch   # 패치 적용

# 특정 스크립트만 실행
make coccicheck COCCI=scripts/coccinelle/api/kfree.cocci

checkpatch.pl

커널 코딩 스타일 검사 도구. 패치 제출 전 반드시 실행해야 합니다:

# 패치 파일 검사
scripts/checkpatch.pl 0001-my-patch.patch

# 파일 직접 검사
scripts/checkpatch.pl --file drivers/mydriver/myfile.c

# 엄격 모드
scripts/checkpatch.pl --strict 0001-my-patch.patch

# 주요 경고 유형
# WARNING: line length exceeds 100 columns
# WARNING: Missing a blank line after declarations
# ERROR: trailing whitespace
# ERROR: code indent should use tabs where possible
# CHECK: Alignment should match open parenthesis

커널 코딩 스타일 (Kernel Coding Style)

리눅스 커널은 Documentation/process/coding-style.rst에 정의된 고유한 코딩 스타일을 따릅니다. 이 스타일은 Linus Torvalds가 제정하였으며, 모든 패치 제출 시 반드시 준수해야 합니다.

들여쓰기 (Indentation)

탭 문자를 사용하며, 탭 크기는 8칸입니다. 스페이스로 들여쓰기를 하면 안 됩니다:

/* 올바른 들여쓰기: 탭(8칸) 사용 */
static int my_function(struct device *dev)
{
	if (condition) {
		do_something();
		return 0;
	}
	return -EINVAL;
}

/* 잘못된 예: 스페이스 4칸 들여쓰기 — 커널에서 금지 */

왜 8칸 탭인가? 들여쓰기가 3단계 이상 깊어지면 코드를 읽기 어려워집니다. 8칸 탭은 깊은 중첩을 자연스럽게 억제하여 함수 분리를 유도합니다. "3단계 이상 들여쓰기가 필요하다면, 코드를 고쳐야 한다." — coding-style.rst

정렬(alignment)에는 스페이스를 사용할 수 있지만, 들여쓰기와 정렬은 구분해야 합니다:

/* 정렬에 스페이스 사용 (탭 뒤 스페이스로 정렬) */
unsigned long	address;
unsigned int	flags;
char		*name;

/* 함수 인자가 길 때: 여는 괄호에 맞춰 정렬 */
void some_function(struct device *dev, const char *name,
		       unsigned long flags, int count)

줄 길이

한 줄의 최대 길이는 80열을 권장하며, 100열을 초과하면 안 됩니다. 긴 줄은 적절히 나누어야 합니다:

/* 긴 조건문: 연산자를 다음 줄 앞에 배치 */
if (condition_a && condition_b
    && condition_c) {
	do_something();
}

/* 긴 함수 호출: 여는 괄호 뒤에서 나눔 */
pr_info("Device %s registered with IRQ %d, base 0x%lx\n",
	dev->name, dev->irq, dev->base_addr);

/* printk 등의 문자열 리터럴은 절대 자르지 않음 (grep 가능해야 함) */
pr_warn("This is a long warning message that should not be broken into multiple lines\n");

중괄호 배치 (Brace Placement)

K&R 스타일을 따릅니다. 여는 중괄호는 같은 줄에, 닫는 중괄호는 새 줄에 배치합니다. 단, 함수 정의만 예외로 여는 중괄호를 다음 줄에 배치합니다:

/* 함수 정의: 여는 중괄호를 다음 줄에 */
static int device_probe(struct platform_device *pdev)
{
	struct my_data *data;
	int ret;

	/* if/for/while/switch: 같은 줄에 여는 중괄호 */
	if (condition) {
		do_a();
		do_b();
	}

	for (i = 0; i < count; i++) {
		process(items[i]);
	}

	/* else / else if: 닫는 중괄호와 같은 줄에 */
	if (err) {
		handle_error();
	} else if (retry) {
		try_again();
	} else {
		success();
	}

	return 0;
}

/* 단일 문장의 if/for/while: 중괄호 생략 가능 */
if (condition)
	do_something();

/* 단, if-else 중 하나라도 여러 문장이면 양쪽 모두 중괄호 사용 */
if (condition) {
	do_a();
	do_b();
} else {
	do_c();    /* 단일 문장이지만 if 블록에 중괄호가 있으므로 */
}

네이밍 규칙 (Naming Conventions)

커널은 소문자 + 언더스코어(snake_case)를 사용합니다. CamelCase는 금지입니다:

/* 올바른 네이밍 */
static int device_open(struct inode *inode, struct file *filp);
static ssize_t my_driver_read(struct file *filp, char __user *buf,
				    size_t count, loff_t *f_pos);
unsigned long total_free_pages;

/* 잘못된 네이밍 — CamelCase, 헝가리안 금지 */
/* int DeviceOpen(...)        ← 금지 */
/* unsigned long ulTotalFree  ← 금지 */
/* char *pszName              ← 금지 */

/* 전역 변수/함수: 서브시스템 접두사 사용 */
extern int netdev_register(struct net_device *dev);
extern void blk_mq_start_request(struct request *rq);

/* 지역 변수/파일 스코프: 짧고 명확하게 */
int i, ret, err;
struct page *page;
struct device *dev;
char *buf, *name;

매크로와 열거형 상수대문자 + 언더스코어를 사용합니다: MAX_RETRIES, PAGE_SIZE, DEVICE_STATE_RUNNING. 그러나 매크로를 함수처럼 사용하는 경우 소문자도 허용됩니다 (예: container_of()).

typedef 사용 규칙

커널에서 typedef는 원칙적으로 금지합니다. 구조체, 공용체, 열거형에 typedef를 쓰면 실제 타입을 숨기게 됩니다:

/* 잘못된 예: typedef로 구조체를 숨기지 말 것 */
typedef struct {
	unsigned long flags;
	int count;
} my_data_t;   /* ← 커널에서 금지 */

/* 올바른 예: struct 키워드를 직접 사용 */
struct my_data {
	unsigned long flags;
	int count;
};

typedef가 허용되는 경우:

허용 사례예시이유
완전 불투명(opaque) 타입pid_t, uid_t, gfp_t내부 구현이 바뀔 수 있는 추상 타입
고정 크기 정수u8, u16, u32, u64__u8 등의 커널 고정 크기 타입
함수 포인터typedef void (*irq_handler_t)(int, void *)가독성 향상
sparse 체크 타입__le32, __be16, __bitwise정적 분석 어노테이션

함수 작성 규칙

함수는 짧고 한 가지 일만 해야 합니다. 화면 한두 개(최대 48줄 권장)를 넘으면 분리를 고려하세요:

/* 함수 선언 순서: 반환타입, 한정자, 함수명, 인자 */
static inline int is_valid_page(struct page *page)
{
	return page && !PagePoisoned(page);
}

/* 지역 변수 선언: 함수 시작부에 모아서 선언 (역 크리스마스 트리) */
static int my_init(struct device *dev, const char *name)
{
	struct my_private_data *priv;    /* 긴 것부터 */
	struct resource *res;
	unsigned long flags;
	int ret;                         /* 짧은 것은 마지막 */

	/* ... */
}
💡

역 크리스마스 트리 (Reverse Christmas Tree): 지역 변수 선언은 긴 줄부터 짧은 줄 순서로 배치합니다. 네트워크 서브시스템에서 특히 엄격하게 적용됩니다.

goto를 이용한 에러 처리

커널에서 goto에러 정리(cleanup) 패턴에서 적극적으로 사용됩니다. 이는 중첩된 if 문 대신 선형적이고 깔끔한 에러 처리를 가능하게 합니다:

static int my_probe(struct platform_device *pdev)
{
	struct my_data *data;
	struct resource *res;
	int ret;

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

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	data->base = devm_ioremap_resource(&pdev->dev, res);
	if (IS_ERR(data->base)) {
		ret = PTR_ERR(data->base);
		goto err_free;
	}

	data->clk = devm_clk_get(&pdev->dev, NULL);
	if (IS_ERR(data->clk)) {
		ret = PTR_ERR(data->clk);
		goto err_unmap;
	}

	ret = clk_prepare_enable(data->clk);
	if (ret)
		goto err_unmap;

	platform_set_drvdata(pdev, data);
	return 0;

/* 레이블은 역순으로 정리 (할당의 역순) */
err_unmap:
	iounmap(data->base);
err_free:
	kfree(data);
	return ret;
}

에러 레이블 네이밍: err_ 접두사를 사용하고, 해당 레이블에서 되돌리는(undo) 작업 또는 이동 시점을 나타내는 이름을 붙입니다. 예: err_free_irq, err_put_device, err_disable_clk.

주석 작성 규칙

/* 커널은 C89 스타일 주석만 사용 (C99 // 주석 금지) */

/*
 * 여러 줄 주석 형식:
 * 첫 줄은 /* 만 쓰고,
 * 각 줄은 *로 시작하며,
 * 마지막 줄은 */ 로 닫습니다.
 */

/* "무엇을 하는가"가 아니라 "왜 하는가"를 설명 */
/*
 * IRQ 핸들러 등록 전에 디바이스를 리셋해야 합니다.
 * 이전 상태의 인터럽트가 spurious IRQ를 발생시킬 수 있기 때문입니다.
 */
device_reset(dev);
request_irq(irq, handler, flags, name, dev);

/* TODO/FIXME/XXX/HACK 는 허용되지만, 반드시 설명 포함 */
/* TODO: 추후 DMA coherent 모드 지원 추가 필요 */
/* FIXME: 32비트 시스템에서 오버플로 가능성 있음 */

switch 문 스타일

/* case 레이블은 switch와 같은 열에 정렬 */
switch (action) {
case KOBJ_ADD:
	return "add";
case KOBJ_REMOVE:
	return "remove";
case KOBJ_CHANGE:
	return "change";
default:
	return NULL;
}

/* fall through 주석: GCC/Clang 경고 방지 */
switch (level) {
case 3:
	do_level3();
	fallthrough;    /* fallthrough 매크로 사용 (5.4+) */
case 2:
	do_level2();
	fallthrough;
case 1:
	do_level1();
	break;
}

매크로 작성 규칙

/* 여러 문장 매크로: do { } while (0)로 감싸야 안전 */
#define DEVICE_INIT(dev, name) \
	do { \
		(dev)->name = (name); \
		(dev)->state = DEV_STATE_INIT; \
		spin_lock_init(&(dev)->lock); \
	} while (0)

/* 인자는 반드시 괄호로 감싸기 */
#define MAX(a, b)  ((a) > (b) ? (a) : (b))
/* 부작용 주의: MAX(x++, y++) 는 위험 → 가능하면 max() 매크로 사용 */

/* 커널 제공 안전 매크로를 우선 사용 */
max(a, b)            /* 타입 체크 포함 */
min(a, b)
clamp(val, lo, hi)
ARRAY_SIZE(arr)      /* 배열 요소 수 */
container_of(ptr, type, member)
list_for_each_entry(pos, head, member)

/* 매크로보다 static inline 함수 선호 */
/* 타입 안전성, 디버깅 정보, 부작용 없음 */
static inline int clamp_val(int val, int lo, int hi)
{
	return clamp(val, lo, hi);
}

반환값 규칙

커널 함수의 에러 반환 규칙:

/* 정수 반환 함수: 성공 시 0, 실패 시 음수 errno */
static int my_init(void)
{
	if (no_memory)
		return -ENOMEM;
	if (bad_arg)
		return -EINVAL;
	if (not_found)
		return -ENODEV;
	return 0;        /* 성공 */
}

/* 포인터 반환 함수: 실패 시 ERR_PTR() 사용 */
static struct device *create_device(int id)
{
	struct device *dev;

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

	return dev;
}

/* 호출 측 검사 */
dev = create_device(id);
if (IS_ERR(dev))
	return PTR_ERR(dev);

/* bool 반환 함수: is_/has_/can_ 접두사 */
static bool is_valid_address(unsigned long addr);
static bool has_capability(struct device *dev, int cap);

공백(Space) 규칙

/* 키워드 뒤에 공백 */
if (condition)          /* ○ if 뒤 공백 */
for (i = 0; ...)       /* ○ for 뒤 공백 */
while (running)         /* ○ while 뒤 공백 */
switch (action)         /* ○ switch 뒤 공백 */
return 0;              /* ○ return 뒤 공백 */

/* 함수명/매크로명 뒤에는 공백 없음 */
printk("hello");       /* ○ */
printk ("hello");      /* × 함수명 뒤 공백 금지 */

/* 이항 연산자 양쪽에 공백 */
x = a + b;              /* ○ */
x = a * b + c / d;      /* ○ */
if (a == b && c != d)  /* ○ */

/* 단항 연산자 뒤에는 공백 없음 */
i++;  !valid;  ~mask;  -err;  *ptr;  &dev;

/* 구조체 멤버 접근 연산자 양쪽에 공백 없음 */
dev->irq    obj.field   /* ○ */

/* 후행 공백(trailing whitespace) 절대 금지 */

kernel-doc 주석 형식

외부에 공개되는 API 함수에는 kernel-doc 형식의 구조화된 주석을 작성합니다:

/**
 * devm_ioremap_resource - managed ioremap of a resource
 * @dev: generic device to handle the resource for
 * @res: resource to be handled
 *
 * Checks that a resource is a valid memory region, requests
 * the memory region and ioremaps it. All operations are managed
 * and will be undone on driver detach.
 *
 * Usage example:
 *
 *     res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 *     base = devm_ioremap_resource(&pdev->dev, res);
 *     if (IS_ERR(base))
 *         return PTR_ERR(base);
 *
 * Return: a pointer to the remapped memory or an ERR_PTR() encoded
 * error code on failure.
 */
void __iomem *devm_ioremap_resource(struct device *dev,
				       const struct resource *res);

메모리 할당 패턴

/* sizeof(*ptr) 사용 — 타입명 대신 변수를 참조 */
data = kmalloc(sizeof(*data), GFP_KERNEL);  /* ○ */
data = kmalloc(sizeof(struct my_data), GFP_KERNEL);  /* △ 비권장 */

/* 0으로 초기화된 할당 */
data = kzalloc(sizeof(*data), GFP_KERNEL);

/* 배열 할당: 오버플로 방지를 위해 kcalloc/kmalloc_array 사용 */
items = kcalloc(count, sizeof(*items), GFP_KERNEL);  /* ○ */
items = kmalloc(count * sizeof(*items), GFP_KERNEL);  /* × 오버플로 위험 */

/* NULL 검사 시 캐스팅 불필요 */
if (!data)
	return -ENOMEM;

/* devm_ 리소스 관리 API: probe/remove에서 자동 해제 */
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);

구조체 초기화 패턴

커널에서는 C99 지정 초기화(designated initializer)를 필수로 사용합니다. 위치 기반 초기화는 금지됩니다:

/* 올바른 예: 지정 초기화 (designated initializer) */
static const struct file_operations my_fops = {
	.owner		= THIS_MODULE,
	.open		= my_open,
	.read		= my_read,
	.write		= my_write,
	.release	= my_release,
	.unlocked_ioctl = my_ioctl,
};

/* 잘못된 예: 위치 기반 초기화 — 구조체 변경 시 깨짐 */
/* static const struct file_operations my_fops = {     */
/*     THIS_MODULE, my_open, my_read, ...  ← 금지     */
/* };                                                   */

ops 패턴: 함수 포인터 테이블 구조체(file_operations, net_device_ops, block_device_operations 등)는 커널에서 다형성(polymorphism)을 구현하는 핵심 패턴입니다. 반드시 const로 선언하고, static 스코프를 사용하세요.

/* 정렬: = 기호를 맞추면 가독성 향상 */
static const struct net_device_ops my_netdev_ops = {
	.ndo_open		= my_ndo_open,
	.ndo_stop		= my_ndo_stop,
	.ndo_start_xmit		= my_ndo_start_xmit,
	.ndo_set_mac_address	= my_ndo_set_mac,
	.ndo_get_stats64	= my_ndo_get_stats64,
};

/* 구조체 변수 초기화: 0이 아닌 필드만 명시 */
struct my_config cfg = {
	.timeout	= HZ * 5,
	.retries	= 3,
	.flags		= MY_FLAG_ENABLE | MY_FLAG_DEBUG,
	/* 나머지 필드는 0/NULL로 자동 초기화 */
};

조건부 컴파일 (#ifdef 규칙)

커널에서 #ifdef는 코드 가독성을 크게 떨어뜨리므로 최소화해야 합니다. 가능하면 IS_ENABLED() 매크로를 사용하여 일반 C 코드로 작성하세요:

/* 잘못된 예: #ifdef 남발 */
#ifdef CONFIG_SMP
	smp_call_function(func, info, 1);
#endif

/* 올바른 예: IS_ENABLED()로 컴파일러가 dead code 제거 */
if (IS_ENABLED(CONFIG_SMP))
	smp_call_function(func, info, 1);

/* IS_ENABLED는 bool 컨텍스트에서 사용 가능 */
if (IS_ENABLED(CONFIG_NET) && dev->netdev)
	register_netdev(dev->netdev);

IS_ENABLED() 한계: IS_ENABLED()는 함수 호출에만 사용할 수 있습니다. #ifdef가 반드시 필요한 경우: (1) 타입/구조체 정의가 CONFIG에 의존할 때, (2) 헤더 파일 선택적 include, (3) 인라인 어셈블리, (4) 모듈 전용 매크로(MODULE_*).

/* 헤더에서: stub 함수로 #ifdef 격리 */
#ifdef CONFIG_HOTPLUG_CPU
int cpu_hotplug_disable(void);
void cpu_hotplug_enable(void);
#else
static inline int cpu_hotplug_disable(void) { return 0; }
static inline void cpu_hotplug_enable(void) { }
#endif

/* .c 파일에서는 #ifdef 없이 호출 가능 */
cpu_hotplug_disable();  /* CONFIG 없으면 no-op */

/* #ifdef 중첩 금지: 최대 1단계까지만 */
/* #ifdef A             */
/*   #ifdef B  ← 금지  */
/*   #endif             */
/* #endif               */

헤더 파일 규칙

커널 헤더 파일은 이중 포함 방지(include guard)최소 의존성 원칙을 따릅니다:

/* include guard: #ifndef + #define 형식 (pragma once 금지) */
#ifndef _LINUX_MY_DRIVER_H
#define _LINUX_MY_DRIVER_H

#include <linux/types.h>       /* 필요한 헤더만 포함 */
#include <linux/list.h>

/* 전방 선언(forward declaration)으로 헤더 의존성 최소화 */
struct device;
struct net_device;
struct sk_buff;

struct my_driver_data {
	struct device *dev;         /* 포인터만 사용하면 전방 선언으로 충분 */
	struct list_head list;      /* 값으로 포함하면 헤더 include 필요 */
	unsigned long flags;
};

int my_driver_init(struct device *dev);
void my_driver_exit(struct device *dev);

#endif /* _LINUX_MY_DRIVER_H */

#include 순서 (커널 소스 관례):

/* 1. 자기 자신의 헤더 (모듈 헤더) */
#include "my_driver.h"

/* 2. linux/ 시스템 헤더 (알파벳 순) */
#include <linux/device.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>

/* 3. 서브시스템 헤더 */
#include <linux/pci.h>
#include <linux/platform_device.h>

/* 4. 아키텍처 헤더 */
#include <asm/io.h>

/* 5. UAPI 헤더 (사용자 공간 인터페이스) */
#include <uapi/linux/my_driver.h>
💡

자기 충족성(self-contained): 모든 헤더 파일은 단독으로 컴파일 가능해야 합니다. 헤더가 사용하는 타입과 매크로에 필요한 다른 헤더를 직접 include해야 하며, 암묵적 의존성에 기대면 안 됩니다.

printk와 로깅 규칙

커널에서는 printf() 대신 printk() 계열 함수를 사용합니다. 로그 레벨은 메시지의 중요도를 나타냅니다:

/* 로그 레벨별 pr_* 매크로 (include/linux/printk.h) */
pr_emerg("System is unusable\n");       /* 레벨 0: 시스템 사용 불가 */
pr_alert("Action must be taken\n");     /* 레벨 1: 즉시 조치 필요 */
pr_crit("Critical condition\n");        /* 레벨 2: 치명적 상태 */
pr_err("Error condition\n");            /* 레벨 3: 에러 */
pr_warn("Warning condition\n");         /* 레벨 4: 경고 */
pr_notice("Normal but significant\n");   /* 레벨 5: 주의 */
pr_info("Informational\n");             /* 레벨 6: 정보 */
pr_debug("Debug-level message\n");      /* 레벨 7: 디버그 (CONFIG_DYNAMIC_DEBUG) */

pr_fmt() 매크로: 파일 상단에 pr_fmt()를 정의하면 해당 파일의 모든 pr_*() 출력에 접두사가 자동 추가됩니다. 서브시스템/드라이버 식별에 필수입니다:

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
/* struct device가 있으면 dev_* 계열 사용 (자동으로 디바이스명 포함) */
dev_err(&pdev->dev, "failed to map registers: %d\n", ret);
dev_warn(dev, "firmware version %u is outdated\n", ver);
dev_info(dev, "device registered successfully\n");
dev_dbg(dev, "register 0x%x = 0x%08x\n", reg, val);

/* 네트워크 디바이스: netdev_* 계열 */
netdev_err(netdev, "TX timeout\n");
netdev_info(netdev, "link up at %d Mbps\n", speed);

/* 조건부 에러 출력: 코드 간결화 */
ret = devm_request_irq(dev, irq, handler, 0, "my-irq", priv);
if (ret)
	return dev_err_probe(dev, ret, "failed to request IRQ %d\n", irq);
/* dev_err_probe()는 에러 출력 + ret 반환을 한 줄로 (EPROBE_DEFER 자동 처리) */

커널 전용 포맷 지정자 (printk 확장):

지정자용도예시 출력
%pS심볼명 (함수 포인터)my_function+0x10/0x80
%ps심볼명 (오프셋 없이)my_function
%pI4IPv4 주소192.168.1.1
%pI6IPv6 주소fe80::1
%pMMAC 주소00:11:22:33:44:55
%pR리소스 (struct resource)[mem 0x10000000-0x1000ffff]
%pUbUUID/GUID01020304-0506-...
%pddentry 이름my_file.txt
%*phhex 바이트열00 11 22 33
%paphys_addr_t0x000000001f000000
/* 커널 전용 포맷 사용 예시 */
pr_info("callback: %pS\n", callback);           /* 함수 심볼 */
pr_info("IP: %pI4, MAC: %pM\n", &ip, mac);     /* 네트워크 주소 */
pr_info("resource: %pR\n", res);                /* MMIO 리소스 */
pr_info("raw: %*ph\n", 8, buf);                 /* 8바이트 hex dump */

/* 절대 금지: 포인터를 %lx로 출력 (주소 노출은 보안 위협) */
pr_info("ptr: %p\n", ptr);   /* %p는 해시된 주소 출력 (보안) */
pr_info("ptr: %px\n", ptr);  /* %px는 실제 주소 (디버그 전용) */

데이터 타입 규칙

커널에서는 표준 C 타입 대신 커널 고유 타입을 사용하며, sparse 어노테이션으로 타입 안전성을 강화합니다:

범주커널 타입표준 C 타입비고
고정 크기 (커널 내부)u8, s8uint8_tunsigned char 대신
u16, s16uint16_t
u32, s32uint32_t
u64, s64uint64_t
고정 크기 (UAPI)__u32, __s32-사용자 공간 인터페이스에서 사용
__le32, __be32-엔디안 명시 (sparse 검사)
어노테이션__user-사용자 공간 포인터
__iomem-I/O 메모리 영역
__kernel-커널 공간 포인터
/* address space 어노테이션: sparse가 경고 */
static ssize_t my_read(struct file *filp,
			char __user *buf,       /* 사용자 공간 버퍼 */
			size_t count, loff_t *ppos)
{
	char kbuf[256];

	/* copy_to_user(): __user 포인터에 직접 접근 금지 */
	if (copy_to_user(buf, kbuf, count))
		return -EFAULT;

	/* 직접 역참조 금지 — sparse 경고 발생 */
	/* *buf = kbuf[0];  ← __user 포인터 직접 접근 금지 */

	return count;
}

/* I/O 메모리: __iomem 포인터는 readl/writel로만 접근 */
void __iomem *base;
u32 val = readl(base + REG_STATUS);
writel(val | FLAG_ENABLE, base + REG_CONTROL);

/* 엔디안 변환: 네트워크/디스크 I/O에서 필수 */
__be32 net_val = cpu_to_be32(host_val);
__le16 disk_val = cpu_to_le16(host_val);
u32 host = be32_to_cpu(net_val);

sparse 검사: make C=1로 sparse를 실행하면 __user, __iomem, __le32 등의 address space / 엔디안 어노테이션을 검증합니다. 패치 제출 전 make C=2(모든 파일 검사)로 확인하세요.

const 정확성 (const Correctness)

커널에서 const읽기 전용 데이터를 보호하고 의도를 명확히 합니다. 함수 포인터 테이블, 문자열 상수, 변경하지 않는 매개변수에 적극 사용하세요:

/* 함수 포인터 테이블: 반드시 const (rodata 섹션 배치) */
static const struct file_operations my_fops = { ... };
static const struct attribute_group my_attr_group = { ... };

/* 문자열 배열: const char * const (포인터와 문자열 모두 불변) */
static const char * const state_names[] = {
	[STATE_IDLE]    = "idle",
	[STATE_RUNNING] = "running",
	[STATE_STOPPED] = "stopped",
};

/* 함수 매개변수: 변경하지 않는 포인터에 const */
static int validate_config(const struct my_config *cfg)
{
	if (cfg->timeout > MAX_TIMEOUT)
		return -EINVAL;
	return 0;
}

/* 모듈 매개변수 설명 문자열 */
static int debug_level = 0;
module_param(debug_level, int, 0644);
MODULE_PARM_DESC(debug_level, "Debug verbosity (0=off, 1=minimal, 2=verbose)");

조건 검사 패턴

커널에서는 간결하고 일관된 조건 검사 스타일을 사용합니다:

/* 포인터 NULL 검사: 암시적 비교 (== NULL 생략) */
if (!ptr)               /* ○ NULL인 경우 */
if (ptr)                /* ○ NULL이 아닌 경우 */
if (ptr == NULL)       /* △ 비권장: 장황함 */
if (ptr != NULL)       /* △ 비권장 */

/* bool 값: 직접 사용 (== true/false 금지) */
if (is_valid)           /* ○ */
if (!is_valid)          /* ○ */
if (is_valid == true)  /* × 금지 */

/* 정수 비교: 0과의 비교는 명시적으로 */
if (ret)                /* ○ 에러 검사 (0이 아닌 경우) */
if (!count)             /* ○ 0인 경우 */
if (ret < 0)           /* ○ 음수 에러 검사 */

/* ERR_PTR 패턴: IS_ERR() + PTR_ERR() 조합 */
clk = devm_clk_get(dev, NULL);
if (IS_ERR(clk))
	return PTR_ERR(clk);

/* IS_ERR_OR_NULL: NULL도 에러로 취급할 때 */
if (IS_ERR_OR_NULL(ptr))
	return PTR_ERR_OR_ZERO(ptr);

/* likely/unlikely: 분기 예측 힌트 (핫 경로에서만) */
if (unlikely(!dev)) {          /* 발생 빈도가 매우 낮은 경로 */
	pr_err("device is NULL\n");
	return -ENODEV;
}

if (likely(skb->len >= hdr_len)) {  /* 거의 항상 참인 경로 */
	process_packet(skb);
}
💡

likely/unlikely 남용 금지: 분기 예측 힌트는 성능에 민감한 핫 경로에서만 사용하세요. 일반적인 에러 처리에서는 불필요하며, 컴파일러와 CPU의 자체 분기 예측이 충분합니다. 잘못된 힌트는 오히려 성능을 저하시킵니다.

커밋 메시지 형식

커널 커밋 메시지는 엄격한 형식을 따릅니다. git log과 메일링 리스트에서 일관된 형식을 유지하기 위함입니다:

subsystem: short summary in imperative mood (max 72 chars)

Detailed explanation of the change. Explain WHAT was changed and
WHY it was necessary. The body should provide enough context for
a reviewer who is not familiar with the recent history.

Wrap at 72 characters per line.

If fixing a bug, describe the symptoms and root cause:
- What was the observable problem?
- What was the underlying cause?
- How does this change fix it?

If adding a feature, explain the motivation:
- Why is this change needed?
- What use case does it address?

Link: https://lore.kernel.org/...    (관련 메일링 리스트 논의)
Fixes: abc123def456 ("original commit subject")  (버그 수정 시 원인 커밋)
Cc: stable@vger.kernel.org   (안정 브랜치 백포트 필요 시)
Reported-by: Reporter Name <reporter@email.com>
Tested-by: Tester Name <tester@email.com>
Reviewed-by: Reviewer Name <reviewer@email.com>
Signed-off-by: Your Name <your@email.com>
태그의미필수 여부
Signed-off-byDeveloper Certificate of Origin (DCO) 동의필수
Fixes수정 대상 원인 커밋 (12자 해시 + 제목)버그 수정 시 필수
Cc: stable안정 브랜치 백포트 요청안정 버전 수정 시
Reviewed-by코드 리뷰 완료 확인선택
Tested-by테스트 완료 확인선택
Reported-by버그 보고자버그 수정 시 권장
Acked-by서브시스템 메인테이너 승인선택
Link관련 논의/버그 링크선택
Suggested-by변경 제안자선택
실제 예시:

net: ethernet: fix use-after-free in tx completion

The TX completion handler accesses the skb after it has been freed
by napi_consume_skb(). This can cause a kernel panic under heavy
network load.

Move the skb access before the free call and save the needed values
(bytes, packets) in local variables.

The bug was introduced when the driver switched from dev_kfree_skb()
to napi_consume_skb() for better NAPI performance.

Fixes: a1b2c3d4e5f6 ("net: mydrv: use napi_consume_skb for TX")
Cc: stable@vger.kernel.org
Reported-by: Jane Doe <jane@example.com>
Signed-off-by: John Developer <john@example.com>

서브시스템 접두사: 커밋 제목의 접두사는 해당 서브시스템의 관례를 따르세요. git log --oneline -- path/to/files로 기존 커밋들의 접두사 패턴을 확인할 수 있습니다. 예: net:, drm/i915:, mm:, scsi:, btrfs:

코딩 스타일 요약

항목규칙비고
들여쓰기탭 (8칸)스페이스 금지
줄 길이80열 권장, 100열 한계문자열 리터럴은 예외
중괄호K&R 스타일함수 정의만 다음 줄에 여는 중괄호
네이밍snake_caseCamelCase, 헝가리안 금지
매크로/상수UPPER_CASE함수형 매크로는 소문자 허용
typedef원칙적 금지불투명 타입, 고정 크기 정수, 함수 포인터만 허용
함수 길이48줄 이내 권장화면 1~2개 분량
변수 선언함수 시작부, 역 크리스마스 트리긴 선언부터 짧은 순서
에러 처리goto cleanup 패턴역순 해제 레이블
반환값성공 0, 실패 음수 errno포인터는 ERR_PTR()
주석C89 스타일 /* */// 주석 금지
공백키워드 뒤 공백, 함수명 뒤 공백 없음후행 공백 금지
할당sizeof(*ptr) 사용배열은 kcalloc()
구조체 초기화지정 초기화(designated init) 필수위치 기반 초기화 금지
#ifdefIS_ENABLED() 우선, 최소화헤더에서 stub 함수로 격리
헤더 파일#ifndef guard, 전방 선언알파벳 순 include
로깅dev_*() / pr_*()pr_fmt()으로 접두사 설정
데이터 타입u32 등 커널 타입, sparse 어노테이션__user, __iomem, __le32
constops 테이블, 불변 매개변수에 필수rodata 섹션 배치
조건 검사!ptr (NULL), IS_ERR()== true/== NULL 금지
커밋 메시지서브시스템 접두사 + 명령형Signed-off-by 필수, 72열 줄바꿈
검사 도구checkpatch.pl --strict패치 제출 전 필수 실행
💡

에디터 설정: 대부분의 에디터에서 커널 코딩 스타일을 자동 적용할 수 있습니다. 커널 소스 트리의 scripts/Lindent 스크립트는 indent 유틸리티로 커널 스타일 자동 포맷팅을 수행합니다. .editorconfig 또는 .clang-format (커널 소스 루트에 포함)을 사용하면 VS Code, Vim 등에서 자동 적용됩니다.

pahole과 BTF

pahole은 구조체의 메모리 레이아웃을 분석합니다. hole(패딩)을 찾아 구조체를 최적화할 수 있습니다:

# 구조체 레이아웃 분석
pahole -C task_struct vmlinux | head -50
# struct task_struct {
#     struct thread_info         thread_info;  /*     0    24 */
#     unsigned int               __state;      /*    24     4 */
#     /* ... */
#     /* size: 9792, cachelines: 153, members: 259 */
#     /* sum members: 9600, holes: 14, sum holes: 192 */

# BTF 생성 (BPF Type Format)
CONFIG_DEBUG_INFO_BTF=y
# pahole --btf_encode_detached btf_vmlinux vmlinux

Git 워크플로

# 커널 패치 생성
git format-patch -1 HEAD                      # 마지막 커밋을 패치로
git format-patch -3 --cover-letter            # 3개 커밋 + 커버 레터

# 패치 전송
git send-email --to=subsystem@vger.kernel.org \
    --cc=maintainer@kernel.org \
    0001-my-patch.patch

# 메인테이너 찾기
scripts/get_maintainer.pl 0001-my-patch.patch

# git bisect로 버그 커밋 찾기
git bisect start
git bisect bad                  # 현재 커밋은 버그 있음
git bisect good v6.5            # v6.5는 정상
# ... 커널 빌드 & 테스트 반복 ...
git bisect good                 # 또는 git bisect bad
git bisect reset                # 완료 후 리셋

# b4 도구 (메일링 리스트 패치 관리)
b4 am 20231115120000.12345-1-developer@kernel.org  # 패치 시리즈 적용
b4 shazam                                           # 현재 lore URL의 패치 적용

QEMU 테스트 환경

# 최소 커널 + initramfs로 QEMU 부팅
make x86_64_defconfig
make -j$(nproc)

# 최소 initramfs 생성
mkdir -p rootfs/bin
cp /path/to/busybox rootfs/bin/busybox
cd rootfs/bin && ln -s busybox sh && cd ../..

cat > rootfs/init << 'EOF'
#!/bin/sh
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t debugfs debugfs /sys/kernel/debug
echo "Boot OK"
exec /bin/sh
EOF
chmod +x rootfs/init
cd rootfs && find . | cpio -o -H newc | gzip > ../initramfs.gz && cd ..

# QEMU 실행
qemu-system-x86_64 \
    -kernel arch/x86/boot/bzImage \
    -initrd initramfs.gz \
    -append "console=ttyS0 nokaslr" \
    -nographic -m 1G -smp 4 \
    -enable-kvm

# 9p 파일시스템 공유 (호스트 디렉토리를 게스트에 마운트)
qemu-system-x86_64 ... \
    -fsdev local,id=shared,path=/path/to/share,security_model=none \
    -device virtio-9p-pci,fsdev=shared,mount_tag=hostshare
# 게스트에서: mount -t 9p hostshare /mnt

크로스 컴파일

# ARM64 크로스 컴파일
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-
make defconfig
make -j$(nproc)

# RISC-V 크로스 컴파일
export ARCH=riscv
export CROSS_COMPILE=riscv64-linux-gnu-
make defconfig
make -j$(nproc)

# 크로스 컴파일 툴체인 설치
# Debian/Ubuntu:
sudo apt install gcc-aarch64-linux-gnu gcc-riscv64-linux-gnu
# Fedora:
sudo dnf install gcc-aarch64-linux-gnu gcc-riscv64-linux-gnu

IDE/에디터 설정

# compile_commands.json 생성 (clangd/ccls용)
scripts/clang-tools/gen_compile_commands.py

# 또는 bear 사용
bear -- make -j$(nproc)

# cscope/ctags 생성
make cscope tags

# VS Code 설정: .vscode/c_cpp_properties.json
# "compileCommands": "${workspaceFolder}/compile_commands.json"

# Vim + cscope
# :cscope add cscope.out
# :cs find g function_name    (정의 찾기)
# :cs find c function_name    (호출처 찾기)
# :cs find s symbol_name      (심볼 참조 찾기)

KUnit과 kselftest

# KUnit: 커널 단위 테스트 프레임워크
./tools/testing/kunit/kunit.py run --arch=x86_64

# 특정 테스트만 실행
./tools/testing/kunit/kunit.py run --kunitconfig=lib/kunit/.kunitconfig

# kselftest: 커널 셀프 테스트
make -C tools/testing/selftests run_tests
make -C tools/testing/selftests TARGETS=net run_tests  # 네트워크 테스트만

커널 문서화

# kernel-doc 형식의 함수 주석
# HTML 문서 빌드
make htmldocs
# 결과: Documentation/output/

# 특정 서브시스템 문서만
make SPHINXDIRS=driver-api htmldocs
/**
 * kmalloc - allocate kernel memory
 * @size: how many bytes of memory are required
 * @flags: GFP allocation flags
 *
 * kmalloc is the normal method of allocating memory
 * for objects smaller than page size in the kernel.
 *
 * Return: pointer to allocated memory, or NULL on failure
 */
void *kmalloc(size_t size, gfp_t flags);

개발 워크플로 다이어그램

커널 개발 워크플로 코드 수정 checkpatch sparse 빌드 & 테스트 QEMU 부팅 KUnit git format-patch git send-email 리뷰 피드백 반영 LKML / 서브시스템 메일링 리스트
코드 수정 → 검사 → 빌드/테스트 → QEMU → 패치 전송 → 리뷰 반영 사이클
💡

참고 자료: 커널 패치 제출 가이드, ClangBuiltLinux, Documentation/dev-tools/

커널 개발 도구 상세 실행 가이드

GCC 커널 빌드 주요 옵션

# 커널이 기본 사용하는 GCC 플래그 (top-level Makefile)
-Wall -Wundef -Werror=strict-prototypes -Werror=implicit-function-declaration
-Wno-trigraphs -fno-strict-aliasing -fno-common
-fno-delete-null-pointer-checks -fno-stack-protector
-Wdeclaration-after-statement -Wno-pointer-sign

# 아키텍처 의존 플래그 (x86_64)
-mno-sse -mno-mmx -mno-sse2 -mno-3dnow -mno-avx
# ↑ 커널 코드에서 SIMD 레지스터 사용 금지 (kernel_fpu_begin 없이)
-mno-red-zone    # Red Zone 비활성화 (인터럽트 핸들러 안전성)
-mcmodel=kernel  # 커널 주소 공간 (상위 2GB) 코드 모델
-fno-pie         # PIE 비활성화 (KASLR은 별도 메커니즘)
-mstack-protector-guard=global  # Stack Canary

# 디버그 빌드
make KCFLAGS="-g -O0" menuconfig    # 최적화 제거 (디버깅 시)
make KCFLAGS="-fsanitize=undefined" # UBSAN (Undefined Behavior)

# 빌드 시간 최적화
make -j$(nproc)                     # 병렬 빌드 (CPU 코어 수)
make CC="ccache gcc" -j$(nproc)     # ccache로 재빌드 가속

GDB 커널 디버깅 상세

# QEMU + GDB 커널 디버깅 세션

# 1. QEMU 시작 (디버그 포트 열기)
qemu-system-x86_64 \
    -kernel arch/x86/boot/bzImage \
    -initrd initramfs.cpio.gz \
    -append "console=ttyS0 nokaslr" \   # nokaslr: 디버깅 시 심볼 주소 고정
    -nographic \
    -s -S         # -s: gdb 포트 1234, -S: CPU 시작 대기

# 2. GDB 연결
gdb vmlinux                          # 심볼 포함 커널 ELF
(gdb) target remote :1234            # QEMU에 연결
(gdb) break start_kernel             # 커널 시작점에 브레이크포인트
(gdb) continue                       # 실행 시작

# 유용한 GDB 커널 디버깅 명령
(gdb) lx-dmesg                       # 커널 로그 출력 (scripts/gdb/)
(gdb) lx-ps                          # 프로세스 목록
(gdb) lx-lsmod                       # 로드된 모듈 목록
(gdb) p/x *(struct task_struct *)$lx_current()   # 현재 태스크
(gdb) p jiffies                      # 전역 변수 확인

# 커널 모듈 디버깅
(gdb) add-symbol-file drivers/my/my.ko 0xffffffffa0000000
(gdb) break my_module_init

ftrace 상세 사용법

# ftrace 기본 설정 경로
FTRACE=/sys/kernel/debug/tracing

# 사용 가능한 트레이서 목록
cat $FTRACE/available_tracers
# nop function function_graph irqsoff preemptoff preemptirqsoff wakeup

# function 트레이서: 함수 호출 추적
echo function > $FTRACE/current_tracer
echo tcp_sendmsg > $FTRACE/set_ftrace_filter  # 특정 함수만
echo 1 > $FTRACE/tracing_on
cat $FTRACE/trace_pipe  # 실시간 출력

# function_graph 트레이서: 함수 호출 트리
echo function_graph > $FTRACE/current_tracer
echo vfs_write > $FTRACE/set_graph_function  # 진입점
echo 5 > $FTRACE/max_graph_depth             # 최대 깊이
cat $FTRACE/trace

# 이벤트 기반 트레이싱
echo 1 > $FTRACE/events/sched/sched_switch/enable   # 스케줄러 이벤트
echo 1 > $FTRACE/events/irq/irq_handler_entry/enable # IRQ 이벤트
echo 1 > $FTRACE/events/kmem/kmalloc/enable           # 메모리 할당

# 트리거: 특정 함수 호출 시 스택 트레이스
echo 'stacktrace' > $FTRACE/set_ftrace_filter
echo '__alloc_pages:stacktrace' >> $FTRACE/set_ftrace_filter

# 히스토그램 (5.x+)
echo 'hist:keys=common_pid:vals=hitcount' > \
    $FTRACE/events/sched/sched_switch/trigger

perf 상세 사용법

# perf stat: CPU 성능 카운터 통계
perf stat -e cycles,instructions,cache-misses,branch-misses ./program
perf stat -e 'syscalls:sys_enter_*' -a sleep 5  # 시스템 콜 통계

# perf record + report: 프로파일링
perf record -g -p $(pidof myapp) -- sleep 10    # 콜스택 포함
perf report --stdio                              # 텍스트 출력
perf report --hierarchy                          # 계층적 출력

# Flame Graph 생성
perf record -g -a -- sleep 30
perf script | stackcollapse-perf.pl | flamegraph.pl > flame.svg

# perf top: 실시간 프로파일링
perf top -g                        # 시스템 전체
perf top -p $(pidof myapp)         # 특정 프로세스

# perf probe: 동적 트레이스 포인트
perf probe --add 'tcp_sendmsg size'            # 함수 인자 추적
perf record -e probe:tcp_sendmsg -a -- sleep 5
perf probe --del tcp_sendmsg

# perf sched: 스케줄러 분석
perf sched record -- sleep 5
perf sched latency             # 스케줄링 지연 통계
perf sched timehist            # 시간순 컨텍스트 스위치

# perf mem: 메모리 접근 분석
perf mem record -- ./program
perf mem report
💡

perf annotate, perf c2c, perf diff, perf bench, 콜 그래프 방법 비교, Intel PEBS/AMD IBS 등 고급 기능은 성능 최적화 — perf 종합 가이드를 참고하세요.

bpftrace 상세 사용법

# bpftrace: eBPF 기반 동적 트레이싱 (awk-like 문법)

# 시스템 콜 빈도 (5초간)
bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[comm] = count(); }' -d 5

# 프로세스별 read 크기 히스토그램
bpftrace -e 'tracepoint:syscalls:sys_exit_read /args->ret > 0/ {
    @bytes[comm] = hist(args->ret);
}'

# 디스크 I/O 지연 분석
bpftrace -e 'tracepoint:block:block_rq_issue {
    @start[args->dev, args->sector] = nsecs;
}
tracepoint:block:block_rq_complete /@start[args->dev, args->sector]/ {
    @usecs = hist((nsecs - @start[args->dev, args->sector]) / 1000);
    delete(@start[args->dev, args->sector]);
}'

# TCP retransmit 추적
bpftrace -e 'kprobe:tcp_retransmit_skb {
    @[kstack] = count();
}'

# 커널 함수 인자 접근
bpftrace -e 'kprobe:vfs_read {
    @bytes[comm] = hist(arg2);  /* arg2 = count */
}'

KASAN / KMSAN / UBSAN 상세

# KASAN (Kernel Address Sanitizer): 메모리 오류 감지
# .config에 설정:
CONFIG_KASAN=y
CONFIG_KASAN_GENERIC=y    # 범용 (느리지만 정확)
# 또는
CONFIG_KASAN_SW_TAGS=y    # ARM64 태그 기반 (빠름)
# 또는
CONFIG_KASAN_HW_TAGS=y    # ARM MTE 하드웨어 태깅

# 감지 가능: use-after-free, out-of-bounds, double-free, use-after-scope
# 오버헤드: 메모리 ~2x, 성능 ~2-3x 느림 (디버그 전용)

# KFENCE (Kernel Electric Fence): 프로덕션 환경 경량 감지
CONFIG_KFENCE=y
# 확률적 샘플링으로 낮은 오버헤드 (< 1%) 메모리 오류 감지
# 부트: kfence.sample_interval=100 (100ms마다 샘플링)

# UBSAN (Undefined Behavior Sanitizer)
CONFIG_UBSAN=y
# 감지: 정수 오버플로, NULL 포인터 역참조, 정렬 오류, shift 오류

# KMSAN (Kernel Memory Sanitizer): 초기화되지 않은 메모리 사용 감지
CONFIG_KMSAN=y
# Clang 컴파일러 전용 (GCC 미지원)

커널 개발 도구 종합 요약

도구분류주요 용도핵심 명령/옵션
GCC/Clang빌드커널 컴파일make CC=clang LLVM=1
GDB디버깅커널/모듈 디버깅target remote :1234, lx-dmesg
QEMU가상화빌드 커널 테스트-kernel -initrd -s -S
ftrace트레이싱함수/이벤트 추적/sys/kernel/debug/tracing/
perf프로파일링성능 분석, 카운터perf record -g, perf report
bpftrace트레이싱동적 eBPF 트레이싱bpftrace -e '...'
sparse정적 분석타입/잠금 검사make C=1 또는 C=2
Coccinelle리팩터링패턴 기반 코드 변환make coccicheck
checkpatch.pl스타일코딩 스타일 검사./scripts/checkpatch.pl -f file.c
pahole/BTF분석구조체 레이아웃, BPFpahole -C task_struct vmlinux
KASAN런타임 검사메모리 오류 감지CONFIG_KASAN=y
lockdep런타임 검사데드락 감지CONFIG_LOCKDEP=y
KUnit테스트커널 단위 테스트make KUNIT=y, kunit.py run
kselftest테스트기능 회귀 테스트make -C tools/testing/selftests run_tests
crash사후 분석vmcore 분석crash vmlinux vmcore
Bootlin Elixir브라우저소스 크로스 레퍼런스elixir.bootlin.com