커널 코딩 스타일 가이드

Linux 커널 코딩 스타일: checkpatch.pl, 들여쓰기, 네이밍, 주석, goto, 매크로 작성 규칙 완벽 가이드

전제 조건: 빌드 시스템커널 모듈 문서를 먼저 읽으세요. 코딩 스타일은 패치 제출 전 필수 검증 단계이며, 빌드 과정과 모듈 구조 이해가 선행되어야 합니다.
일상 비유: 이 주제는 건축 설계 규범과 비슷합니다. 건물마다 구조는 다르지만 안전 규격과 표준은 동일하게 적용되듯이, 커널 코드는 일관된 스타일로 유지보수성을 보장합니다.

핵심 요약

  • 자동 검증 — checkpatch.pl로 패치 제출 전 스타일 검사
  • 8칸 탭 — 들여쓰기는 8칸 탭, 공백 사용 금지
  • 80칸 제한 — 가독성을 위한 줄 길이 제한 (예외 허용)
  • 명확한 네이밍 — 함수는 동사, 변수는 명사, 매크로는 대문자
  • goto는 도구 — 에러 처리 경로 정리에 적극 사용

단계별 이해

  1. checkpatch.pl 설치
    커널 소스의 scripts/ 디렉토리에 위치, 패치 제출 전 필수 실행
  2. 기본 규칙 적용
    들여쓰기, 중괄호, 공백 등 기계적으로 확인 가능한 규칙부터 학습
  3. 네이밍 습관화
    기존 커널 코드를 읽으며 네이밍 패턴 익히기
  4. 코드 리뷰 참여
    메일링 리스트에서 다른 개발자의 피드백 관찰

Linux 커널 개발에서 일관된 코딩 스타일은 수천 명의 개발자가 협업하는 환경에서 코드 가독성과 유지보수성을 보장하는 핵심입니다. Documentation/process/coding-style.rst 기반으로 checkpatch.pl 사용법, 들여쓰기, 네이밍, 주석, goto, 매크로 등 모든 규칙을 실전 예제와 함께 설명합니다.

참고 문서: Documentation/process/coding-style.rst, Documentation/process/submitting-patches.rst — 커널 스타일 가이드의 공식 문서입니다. 종합 목록은 참고자료 — 표준 & 규격 섹션을 참고하세요.

checkpatch.pl 사용법

checkpatch.pl은 커널 소스 트리의 scripts/ 디렉토리에 있는 Perl 스크립트로, 패치 제출 전 코딩 스타일을 자동 검증합니다. 메일링 리스트에 제출된 패치의 약 30%가 스타일 문제로 거부되므로, 제출 전 필수 실행이 권장됩니다.

설치 및 기본 실행

커널 소스 트리가 있으면 별도 설치 없이 바로 사용 가능합니다:

# 단일 파일 검사
./scripts/checkpatch.pl -f drivers/net/dummy.c

# 패치 파일 검사 (가장 일반적인 용도)
./scripts/checkpatch.pl my-patch.patch

# git 커밋 검사
git format-patch -1 HEAD --stdout | ./scripts/checkpatch.pl -

# 전체 디렉토리 재귀 검사
./scripts/checkpatch.pl -f --terse drivers/staging/example/*.c

주요 옵션

옵션설명
-f파일 모드 (패치가 아닌 소스 파일 검사)
--strict엄격 모드 (추가 스타일 검사 활성화)
--terse간결한 출력 (파일명:줄번호: 메시지)
--no-tree커널 트리 외부에서 실행 시 사용
--fix자동 수정 가능한 항목 표시
--fix-inplace자동 수정 직접 적용 (주의 필요)
--ignore TYPE특정 경고 유형 무시
--show-types경고 유형 코드 표시

출력 해석

ERROR: trailing whitespace
#42: FILE: drivers/net/dummy.c:123:
+       return 0; $

WARNING: line over 80 characters
#58: FILE: drivers/net/dummy.c:139:
+       pr_info("This is a very long message that exceeds the recommended 80 character limit");

CHECK: Alignment should match open parenthesis
#65: FILE: drivers/net/dummy.c:146:
+       printk(KERN_INFO "message",
+               arg1, arg2);

total: 1 errors, 1 warnings, 1 checks, 120 lines checked
심각도 수준:
  • ERROR — 반드시 수정 (패치 거부 가능성 높음)
  • WARNING — 수정 권장 (정당한 이유 있으면 예외 허용)
  • CHECK — 선택적 개선 사항 (--strict 모드에서 표시)

실전 워크플로우

# 1. 코드 작성
vim drivers/example/mydriver.c

# 2. 로컬 커밋
git add drivers/example/mydriver.c
git commit -s -m "Add new feature to example driver"

# 3. checkpatch.pl 실행
git format-patch -1 HEAD --stdout | ./scripts/checkpatch.pl --strict -

# 4. 오류 수정 후 amend
vim drivers/example/mydriver.c
git add drivers/example/mydriver.c
git commit --amend --no-edit

# 5. 최종 확인
git format-patch -1 HEAD --stdout | ./scripts/checkpatch.pl -

# 6. 패치 생성
git format-patch -1 HEAD

들여쓰기 규칙

8칸 탭 원칙

커널은 공백(space)이 아닌 탭(tab)으로 들여쓰기합니다. 탭 너비는 8칸입니다. 이는 과도한 중첩을 방지하는 심리적 장벽 역할을 합니다.

절대 금지: 탭과 공백을 혼용하지 마세요. 에디터 설정에서 탭을 공백으로 변환하는 옵션을 비활성화해야 합니다.
/* ❌ 잘못된 예: 공백 사용 */
int bad_function(void)
{
    if (condition) {
        return 1;
    }
}

/* ✅ 올바른 예: 8칸 탭 사용 */
int good_function(void)
{
	if (condition) {
		return 1;
	}
}

정렬 규칙

함수 인자나 조건문이 여러 줄에 걸칠 때, 후속 줄은 여는 괄호에 맞춰 정렬합니다. 이 경우에만 탭 + 공백 조합이 허용됩니다.

/* ✅ 올바른 정렬 */
void netdev_info(const struct net_device *dev,
		  const char *fmt, ...)
{
	/* ... */
}

/* ✅ 조건문 정렬 */
if (very_long_variable_name == some_value &&
    another_long_name > threshold) {
	do_something();
}

80칸 제한

한 줄은 80칸을 넘지 않는 것이 원칙입니다. 다만 최근 커널에서는 100칸까지 허용하는 경향이 있으며, 다음 경우는 예외를 인정합니다:

/* ✅ 예외: 문자열은 분리하지 않음 */
pr_err("This is a very long error message that should not be split for greppability");

/* ❌ 잘못된 예: 문자열 분리 */
pr_err("This is a very long error message "
       "that was incorrectly split");

switch 문 들여쓰기

switch 문에서 case 레이블은 switch와 같은 깊이에 위치합니다:

switch (action) {
case ACTION_READ:
	read_data();
	break;
case ACTION_WRITE:
	write_data();
	break;
default:
	return -EINVAL;
}

중괄호 배치

함수 중괄호

함수의 여는 중괄호는 다음 줄에 위치합니다 (K&R 스타일과 다름):

/* ✅ 올바른 예 */
int function(int x)
{
	/* body */
}

/* ❌ 잘못된 예 */
int function(int x) {
	/* body */
}

제어문 중괄호

if, for, while, do 등 제어문의 여는 중괄호는 같은 줄에 위치합니다 (K&R 스타일):

/* ✅ 올바른 예 */
if (condition) {
	do_this();
	do_that();
} else {
	otherwise();
}

/* ❌ 잘못된 예 */
if (condition)
{
	do_this();
}

단일 문장 예외

단일 문장만 있는 경우 중괄호를 생략할 수 있습니다. 다만 한 분기에만 중괄호가 필요하면 모든 분기에 사용합니다:

/* ✅ 단일 문장 - 중괄호 생략 가능 */
if (condition)
	return 0;

/* ✅ 여러 문장 - 중괄호 필수 */
if (condition) {
	do_this();
	return 0;
}

/* ✅ 한 분기가 여러 줄이면 모두 중괄호 사용 */
if (condition) {
	do_this();
	do_that();
} else {
	otherwise();
}

/* ❌ 일관성 없는 중괄호 사용 */
if (condition) {
	do_this();
	do_that();
} else
	otherwise();

do-while 특수 규칙

do-while 문의 while은 닫는 중괄호와 같은 줄에 위치합니다:

do {
	process_data();
} while (condition);

네이밍 규칙

함수명

함수명은 소문자와 언더스코어를 사용하며, 동사로 시작하는 설명적 이름을 선호합니다:

/* ✅ 올바른 예 */
int get_user_pages(unsigned long start, int nr_pages);
void free_pages(unsigned long addr, unsigned int order);
static void update_rq_clock(struct rq *rq);

/* ❌ 잘못된 예: camelCase */
int getUserPages(unsigned long start, int nr_pages);

/* ❌ 잘못된 예: 의미 불명확 */
int foo(int x, int y);
네임스페이스 관행: 서브시스템 또는 드라이버명을 접두사로 사용하여 충돌을 방지합니다 (예: usb_alloc_urb, pci_register_driver).

변수명

변수명은 짧고 명확하게 작성합니다. 지역 변수는 축약 가능하지만, 전역 변수는 설명적이어야 합니다:

/* ✅ 지역 변수 - 짧게 가능 */
int count_pages(void)
{
	int i, cnt = 0;
	for (i = 0; i < nr_pages; i++)
		cnt++;
	return cnt;
}

/* ✅ 전역 변수 - 설명적 */
unsigned long total_memory_pages;

/* ❌ 잘못된 예: 전역 변수가 모호함 */
int tmp;

매크로명

매크로는 모두 대문자로 작성하며, 단어는 언더스코어로 구분합니다:

/* ✅ 올바른 예 */
#define MAX_BUFFER_SIZE 1024
#define IS_ALIGNED(x, a) (((x) & ((a) - 1)) == 0)

/* ❌ 잘못된 예: 소문자 사용 */
#define max_buffer_size 1024
예외: 함수처럼 사용되는 매크로 중 일부는 소문자를 사용할 수 있습니다 (예: list_for_each_entry). 하지만 이는 기존 코드와의 일관성이 있을 때만 허용됩니다.

구조체 및 타입명

구조체 태그는 소문자와 언더스코어를 사용합니다. typedef는 최소화하고, 사용 시 _t 접미사를 붙입니다:

/* ✅ 올바른 예 */
struct file_operations {
	ssize_t (*read)(struct file *, char __user *, size_t, loff_t *);
};

/* ✅ typedef 사용 시 */
typedef unsigned long pgoff_t;

/* ❌ 잘못된 예: 불필요한 typedef */
typedef struct {
	int x;
} myStruct;

주석 작성법

블록 주석

여러 줄 주석은 다음 형식을 따릅니다. 네트워크 서브시스템은 다른 스타일을 사용하므로 기존 코드를 참고하세요:

/*
 * This is the preferred multi-line comment style.
 * Each line starts with a space, an asterisk, and another space.
 * The closing marker is on a separate line.
 */

/* 네트워크 서브시스템 스타일 (net/) */
/* This is a multi-line comment in networking subsystem.
 * Note the different opening line style.
 */

한 줄 주석

한 줄 주석은 C99 스타일(//)보다 전통적인 /* */ 스타일이 선호됩니다:

/* ✅ 선호되는 스타일 */
int ret;  /* return value */

/* ⚠️ 허용되나 비권장 */
int ret;  // return value

함수 설명 주석

공개 함수는 kernel-doc 형식으로 문서화합니다:

/**
 * fget_light - 빠른 파일 디스크립터 변환
 * @fd: 파일 디스크립터 번호
 * @fput_needed: 출력 플래그 (fput 필요 여부)
 *
 * 현재 프로세스의 파일 디스크립터 테이블에서 struct file을 가져옵니다.
 * 싱글 스레드 프로세스의 경우 참조 카운트 증가를 생략하여 성능을 향상시킵니다.
 *
 * Return: struct file 포인터, 실패 시 NULL
 */
struct file *fget_light(unsigned int fd, int *fput_needed)
{
	/* ... */
}

데이터 구조 주석

구조체 멤버에는 인라인 주석을 추가합니다:

struct task_struct {
	volatile long state;	/* -1 unrunnable, 0 runnable, >0 stopped */
	void *stack;		/* kernel stack pointer */
	unsigned int flags;	/* per process flags, defined below */
};

goto 문 사용

에러 처리 패턴

커널에서 goto는 에러 처리 경로를 정리하는 권장 패턴입니다. 중첩된 if 문과 중복된 cleanup 코드를 방지합니다:

/* ✅ 올바른 goto 사용 */
int setup_device(struct device *dev)
{
	int ret;
	void *buffer;

	buffer = kmalloc(SIZE, GFP_KERNEL);
	if (!buffer) {
		ret = -ENOMEM;
		goto err_alloc;
	}

	ret = register_device(dev);
	if (ret)
		goto err_register;

	ret = enable_interrupts(dev);
	if (ret)
		goto err_irq;

	return 0;

err_irq:
	unregister_device(dev);
err_register:
	kfree(buffer);
err_alloc:
	return ret;
}
/* ❌ 잘못된 예: goto 없이 중복 cleanup */
int setup_device_bad(struct device *dev)
{
	void *buffer = kmalloc(SIZE, GFP_KERNEL);
	if (!buffer)
		return -ENOMEM;

	if (register_device(dev)) {
		kfree(buffer);
		return -EIO;
	}

	if (enable_interrupts(dev)) {
		unregister_device(dev);
		kfree(buffer);  /* 중복! */
		return -EIO;
	}

	return 0;
}

레이블 네이밍

레이블명은 해제할 자원 또는 에러 상황을 명확히 표현합니다:

해제 순서: 레이블은 할당의 역순으로 배치합니다. 가장 마지막에 할당한 자원을 가장 먼저 해제합니다.

매크로 작성 규칙

대문자 사용

매크로는 모두 대문자로 작성하며, 함수처럼 보이는 매크로도 예외가 아닙니다 (일부 기존 코드 제외):

#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
#define min(x, y) ((x) < (y) ? (x) : (y))

괄호 규칙

매크로 인자는 항상 괄호로 감싸서 연산자 우선순위 문제를 방지합니다:

/* ❌ 잘못된 예 */
#define SQUARE(x) x * x
/* SQUARE(a + 1) = a + 1 * a + 1 = 2a + 1 (잘못됨!) */

/* ✅ 올바른 예 */
#define SQUARE(x) ((x) * (x))
/* SQUARE(a + 1) = ((a + 1) * (a + 1)) (올바름) */

do-while(0) 패턴

여러 문장을 포함하는 매크로는 do { ... } while (0)으로 감쌉니다. 이는 세미콜론 필수화와 if 문 호환성을 보장합니다:

/* ❌ 잘못된 예 */
#define FREE_BOTH(x, y) kfree(x); kfree(y)

/* if (condition) FREE_BOTH(a, b); else foo();
 * → if (condition) kfree(a); kfree(y); else foo();
 * kfree(y)가 if 밖으로 나가고 else가 컴파일 에러 */

/* ✅ 올바른 예 */
#define FREE_BOTH(x, y) do { \
	kfree(x); \
	kfree(y); \
} while (0)

/* if (condition) FREE_BOTH(a, b); else foo();
 * → 정상 작동 */

typeof 활용

매크로에서 타입 안전성을 위해 typeof를 사용합니다 (GCC 확장):

#define min_t(type, x, y) ({ \
	type __x = (x); \
	type __y = (y); \
	__x < __y ? __x : __y; \
})

#define container_of(ptr, type, member) ({ \
	const typeof(((type *)0)->member) *__mptr = (ptr); \
	(type *)((char *)__mptr - offsetof(type, member)); \
})

typedef 사용 제한

커널은 typedef를 최소화합니다. 구조체를 숨기는 것은 캡슐화가 아니라 혼란을 초래한다는 철학입니다:

허용되는 경우

금지되는 경우

/* ❌ 잘못된 예: 구조체 숨기기 */
typedef struct {
	int x, y;
} point_t;

/* ✅ 올바른 예: 구조체 노출 */
struct point {
	int x, y;
};
이유: struct point p;는 타입이 구조체임을 명시하지만, point_t p;는 타입 정보를 숨깁니다. 커널 개발자는 코드를 읽을 때 타입의 본질을 즉시 파악해야 합니다.

함수 길이 및 분할

함수 길이 제한

함수는 한 화면(24-25줄) 내에 들어가는 것이 이상적입니다. 더 길어지면 가독성이 떨어지고 복잡성이 증가합니다:

경험 법칙:
  • 함수가 3개 화면을 넘으면 반드시 분할 고려
  • 지역 변수가 10개를 넘으면 논리적 단위로 분리
  • 중첩이 3단계를 넘으면 helper 함수로 추출

함수 분할 예제

/* ❌ 너무 긴 함수 */
int process_request(struct request *req)
{
	/* 50줄의 검증 로직 */
	/* 30줄의 데이터 처리 */
	/* 20줄의 결과 전송 */
}

/* ✅ 분할된 함수들 */
static int validate_request(struct request *req)
{
	/* 검증 로직만 */
}

static int handle_request_data(struct request *req)
{
	/* 데이터 처리만 */
}

static int send_response(struct request *req)
{
	/* 결과 전송만 */
}

int process_request(struct request *req)
{
	int ret;

	ret = validate_request(req);
	if (ret)
		return ret;

	ret = handle_request_data(req);
	if (ret)
		return ret;

	return send_response(req);
}

static 함수 활용

파일 내부에서만 사용되는 함수는 static으로 선언하여 심볼 네임스페이스를 오염시키지 않습니다:

/* ✅ 내부 helper 함수 */
static void cleanup_resources(struct device *dev)
{
	/* ... */
}

/* ✅ 공개 API 함수 */
int device_init(struct device *dev)
{
	/* ... */
	cleanup_resources(dev);
}
EXPORT_SYMBOL_GPL(device_init);

지역 변수 선언

선언 위치

C99 표준을 따라 변수는 사용 지점에 가깝게 선언할 수 있습니다. 다만 함수 시작 부분에 선언하는 전통적 방식도 여전히 사용됩니다:

/* ✅ 전통적 스타일 */
int function(void)
{
	int ret, i;
	struct device *dev;

	/* 로직 */
}

/* ✅ C99 스타일 (허용) */
int function(void)
{
	struct device *dev = get_device();

	for (int i = 0; i < 10; i++) {
		/* ... */
	}
}

초기화

선언과 동시에 초기화하는 것이 권장되지만, 불필요한 초기화는 피합니다:

/* ✅ 의미 있는 초기화 */
int ret = 0;
struct list_head *pos, *n;

/* ❌ 불필요한 초기화 */
int x = 0;
x = get_value();  /* 바로 덮어씌워짐 */

공백 사용 규칙

연산자 주변 공백

대부분의 이항 및 삼항 연산자는 양쪽에 공백을 넣습니다:

/* ✅ 올바른 예 */
x = y + z;
if (a == b && c != d)
result = (x > 0) ? x : -x;

/* ❌ 잘못된 예 */
x=y+z;
if(a==b&&c!=d)
예외: 단항 연산자(&, *, ++, --, !, ~)와 피연산자 사이에는 공백을 넣지 않습니다.
예: *ptr, !flag, i++

키워드와 괄호

대부분의 키워드는 여는 괄호 앞에 공백을 넣습니다. sizeof, typeof, alignof 등은 예외입니다:

/* ✅ 올바른 예 */
if (condition)
while (count > 0)
switch (value)

/* ✅ sizeof는 함수처럼 취급 (공백 없음) */
size = sizeof(struct device);

/* ❌ 잘못된 예 */
if(condition)
sizeof (struct device)

함수 호출

함수명과 여는 괄호 사이에는 공백을 넣지 않습니다:

/* ✅ 올바른 예 */
ret = function(arg1, arg2);

/* ❌ 잘못된 예 */
ret = function (arg1, arg2);

포인터 선언

포인터 타입 선언 시 *는 변수명 쪽에 붙입니다:

/* ✅ 올바른 예 */
char *name;
struct device *dev;

/* ❌ 잘못된 예 */
char* name;
char * name;

실전 예제

디바이스 드라이버 초기화

다음은 커널 스타일을 모두 적용한 완전한 예제입니다:

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/io.h>

#define DRIVER_NAME "example-device"

struct example_dev {
	void __iomem *base;	/* MMIO base address */
	int irq;			/* interrupt number */
	struct device *dev;
};

/**
 * example_hw_init - 하드웨어 초기화
 * @edev: example_dev 구조체 포인터
 *
 * Return: 성공 시 0, 실패 시 음수 에러 코드
 */
static int example_hw_init(struct example_dev *edev)
{
	u32 val;

	/* 하드웨어 리셋 */
	writel(0x1, edev->base + 0x00);
	usleep_range(100, 200);

	/* 초기화 확인 */
	val = readl(edev->base + 0x04);
	if (!(val & 0x80000000)) {
		dev_err(edev->dev, "Hardware init failed\\n");
		return -EIO;
	}

	return 0;
}

static int example_probe(struct platform_device *pdev)
{
	struct example_dev *edev;
	struct resource *res;
	int ret;

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

	edev->dev = &pdev->dev;

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

	edev->irq = platform_get_irq(pdev, 0);
	if (edev->irq < 0) {
		ret = edev->irq;
		goto err_alloc;
	}

	ret = example_hw_init(edev);
	if (ret)
		goto err_alloc;

	platform_set_drvdata(pdev, edev);
	dev_info(&pdev->dev, "Device initialized successfully\\n");

	return 0;

err_alloc:
	return ret;
}

static int example_remove(struct platform_device *pdev)
{
	dev_info(&pdev->dev, "Device removed\\n");
	return 0;
}

static const struct of_device_id example_of_match[] = {
	{ .compatible = "vendor,example-device" },
	{ }
};
MODULE_DEVICE_TABLE(of, example_of_match);

static struct platform_driver example_driver = {
	.probe = example_probe,
	.remove = example_remove,
	.driver = {
		.name = DRIVER_NAME,
		.of_match_table = example_of_match,
	},
};
module_platform_driver(example_driver);

MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Your Name <your.email@example.com>");
MODULE_DESCRIPTION("Example device driver");
코드 설명
  • 1-3행 플랫폼 디바이스 드라이버에 필요한 헤더 포함. io.h는 MMIO 접근 함수 제공.
  • 5행 드라이버명 매크로 정의. 대문자로 작성.
  • 7-11행 드라이버 프라이빗 데이터 구조체. __iomem은 스파스 타입 체크용 어노테이션.
  • 13-20행 kernel-doc 형식 함수 주석. 공개 함수는 이 형식으로 문서화.
  • 23-24행 MMIO 레지스터 읽기/쓰기. writel/readl 사용.
  • 28-31행 에러 처리 패턴. 실패 시 로그 출력 후 음수 에러 코드 반환.
  • 40-42행 devm_* API 사용으로 자동 메모리 관리. sizeof(*edev) 패턴 사용.
  • 51-53행 에러 처리 goto 패턴. IS_ERR로 포인터 에러 확인.
  • 66행 에러 레이블. 할당 역순으로 정리.
  • 77-80행 Device Tree 매칭 테이블. MODULE_DEVICE_TABLE로 모듈 로딩 자동화.
  • 82-89행 플랫폼 드라이버 구조체. 구조체 멤버 초기화 스타일.
  • 90행 드라이버 등록 매크로. module_init/exit를 내부에서 처리.

일반적인 위반 사례

공백 관련 오류

위반 사례수정 방법
if(condition) if (condition) (키워드 뒤 공백)
function (arg) function(arg) (함수명 뒤 공백 제거)
x=y+z; x = y + z; (연산자 양쪽 공백)
줄 끝 공백 (trailing whitespace) 에디터 설정으로 자동 제거
탭/공백 혼용 들여쓰기는 탭만 사용

구조 관련 오류

위반 사례수정 방법
함수가 100줄 초과 논리적 단위로 함수 분할
중첩 if 문 5단계 이상 early return 또는 helper 함수로 평탄화
goto를 사용하지 않은 중복 cleanup goto 에러 처리 패턴 적용
지역 변수 15개 이상 구조체로 그룹화 또는 함수 분할

네이밍 관련 오류

위반 사례수정 방법
getUserData() (camelCase) get_user_data() (snake_case)
int tmp, data, x; (의미 불명) int page_count, user_data, offset; (설명적)
#define max(a,b) (소문자 매크로) #define MAX(a, b) (대문자)
typedef struct { ... } foo; struct foo { ... }; (typedef 제거)

checkpatch.pl 빈발 경고

ERROR: trailing whitespace
WARNING: line over 80 characters
WARNING: please, no spaces at the start of a line
ERROR: space prohibited before that ',' (ctx:WxW)
WARNING: Prefer 'unsigned int' to bare use of 'unsigned'
CHECK: Alignment should match open parenthesis
WARNING: Missing a blank line after declarations
ERROR: do not use C99 // comments

에디터 설정

Vim 설정

~/.vimrc에 다음 설정 추가:

" 커널 스타일 설정
set tabstop=8
set shiftwidth=8
set noexpandtab
set textwidth=80

" 공백 문제 강조
highlight ExtraWhitespace ctermbg=red guibg=red
match ExtraWhitespace /\s\+$/

" 커널 소스 디렉토리에서 자동 적용
autocmd BufRead,BufNewFile */linux-*/*.c,*/linux-*/*.h set cindent cinoptions=:0,l1,t0,g0,(0

Emacs 설정

~/.emacs에 다음 설정 추가:

(defun c-lineup-arglist-tabs-only (ignored)
  "Line up argument lists by tabs, not spaces"
  (let* ((anchor (c-langelem-pos c-syntactic-element))
         (column (c-langelem-2nd-pos c-syntactic-element))
         (offset (- (1+ column) anchor))
         (steps (floor offset c-basic-offset)))
    (* (max steps 1) c-basic-offset)))

(add-hook 'c-mode-hook
          (lambda ()
            (let ((filename (buffer-file-name)))
              (when (and filename
                         (string-match "linux" filename))
                (setq indent-tabs-mode t)
                (setq show-trailing-whitespace t)
                (c-set-style "linux-tabs-only")))))

VS Code 설정

.vscode/settings.json에 추가:

{
  "[c]": {
    "editor.insertSpaces": false,
    "editor.tabSize": 8,
    "editor.rulers": [80, 100],
    "editor.detectIndentation": false,
    "editor.renderWhitespace": "boundary",
    "files.trimTrailingWhitespace": true
  }
}

추가 자료

공식 문서

검증 도구

참고 코드

다음 파일들은 모범적인 코딩 스타일을 보여줍니다:

실무 코드 리뷰 플레이북

스타일 문서를 읽는 것만으로는 품질이 올라가지 않습니다. 리뷰 단계에서 반복 가능한 기준으로 검사해야 실제로 버그와 유지보수 비용을 줄일 수 있습니다.

검사 축 핵심 질문 빠른 점검 방법
가독성 함수가 한 번에 이해되는가? 함수 길이, early return, 의미 있는 변수명 확인
에러 경로 실패 시 자원 정리가 완전한가? goto 레이블 순서, 누수 여부 확인
동시성 락/원자성/컨텍스트 규칙이 맞는가? sleep 가능 컨텍스트와 irq 컨텍스트 분리 확인
패치 품질 커밋 단위가 논리적으로 분리되었는가? 기능 변경과 리팩터링을 같은 커밋에 섞지 않기

리뷰 전 자동 점검 명령

# 스타일 검사
./scripts/checkpatch.pl --file drivers/foo/bar.c

# 최소 빌드 검증
make M=drivers/foo -j$(nproc)

# 경고 확대
make W=1 M=drivers/foo

# 정적 분석
make C=1 M=drivers/foo
주의: checkpatch.pl 통과는 시작점일 뿐입니다. 동시성, 에러 처리, ABI 영향 같은 설계 품질은 별도 리뷰 없이는 놓치기 쉽습니다.
이전 단계:
다음 단계:
심화 학습: