커널 코딩 스타일 가이드
Linux 커널 코딩 스타일: checkpatch.pl, 들여쓰기, 네이밍, 주석, goto, 매크로 작성 규칙 완벽 가이드
핵심 요약
- 자동 검증 — checkpatch.pl로 패치 제출 전 스타일 검사
- 8칸 탭 — 들여쓰기는 8칸 탭, 공백 사용 금지
- 80칸 제한 — 가독성을 위한 줄 길이 제한 (예외 허용)
- 명확한 네이밍 — 함수는 동사, 변수는 명사, 매크로는 대문자
- goto는 도구 — 에러 처리 경로 정리에 적극 사용
단계별 이해
- checkpatch.pl 설치
커널 소스의 scripts/ 디렉토리에 위치, 패치 제출 전 필수 실행 - 기본 규칙 적용
들여쓰기, 중괄호, 공백 등 기계적으로 확인 가능한 규칙부터 학습 - 네이밍 습관화
기존 커널 코드를 읽으며 네이밍 패턴 익히기 - 코드 리뷰 참여
메일링 리스트에서 다른 개발자의 피드백 관찰
Linux 커널 개발에서 일관된 코딩 스타일은 수천 명의 개발자가 협업하는 환경에서 코드 가독성과 유지보수성을 보장하는 핵심입니다. Documentation/process/coding-style.rst 기반으로 checkpatch.pl 사용법, 들여쓰기, 네이밍, 주석, goto, 매크로 등 모든 규칙을 실전 예제와 함께 설명합니다.
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칸까지 허용하는 경향이 있으며, 다음 경우는 예외를 인정합니다:
- 문자열 리터럴 (검색 가능성을 위해 분리하지 않음)
- printk, pr_* 함수의 포맷 문자열
- 함수 선언이 80칸을 살짝 초과하는 경우
/* ✅ 예외: 문자열은 분리하지 않음 */
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;
}
레이블 네이밍
레이블명은 해제할 자원 또는 에러 상황을 명확히 표현합니다:
err_alloc,err_register— 에러 지점 명시out_free,out_unlock— 수행할 작업 명시
매크로 작성 규칙
대문자 사용
매크로는 모두 대문자로 작성하며, 함수처럼 보이는 매크로도 예외가 아닙니다 (일부 기존 코드 제외):
#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 __foobar *foobar_t; - 정수 타입의 명확한 별칭:
typedef u32 dma_addr_t; - 복잡한 함수 포인터:
typedef int (*callback_t)(void *); - 스파스(Sparse) 타입 검사:
typedef __bitwise__ u32 __be32;
금지되는 경우
/* ❌ 잘못된 예: 구조체 숨기기 */
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
}
}
추가 자료
공식 문서
- Documentation/process/coding-style.rst — 코딩 스타일 공식 문서
- Documentation/process/submitting-patches.rst — 패치 제출 가이드
- Documentation/doc-guide/kernel-doc.rst — kernel-doc 주석 형식
검증 도구
scripts/checkpatch.pl— 스타일 검사 스크립트scripts/get_maintainer.pl— 메인테이너 확인scripts/kernel-doc— kernel-doc 주석 검증make coccicheck— Coccinelle 시맨틱 패치 도구
참고 코드
다음 파일들은 모범적인 코딩 스타일을 보여줍니다:
kernel/sched/core.c— 복잡한 로직의 명확한 구조화mm/slab.c— 상세한 주석과 문서화drivers/base/core.c— 디바이스 드라이버 모범 사례lib/list_sort.c— 알고리즘 구현 예제
실무 코드 리뷰 플레이북
스타일 문서를 읽는 것만으로는 품질이 올라가지 않습니다. 리뷰 단계에서 반복 가능한 기준으로 검사해야 실제로 버그와 유지보수 비용을 줄일 수 있습니다.
| 검사 축 | 핵심 질문 | 빠른 점검 방법 |
|---|---|---|
| 가독성 | 함수가 한 번에 이해되는가? | 함수 길이, 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 영향 같은 설계 품질은 별도 리뷰 없이는 놓치기 쉽습니다.
관련 문서
- 첫 커널 모듈 튜토리얼 — 모듈 작성 실습
- 커널 소스 읽기 — 소스 탐색 방법
- 커널 개발 환경 설정 — 개발 도구 구성