Skip to content

Linux Kernel Coding Style

Tóm tắt từ tài liệu chính thức Documentation/process/coding-style.rst trong source Linux kernel (online). Tài liệu này là bắt buộc khi gửi patch lên kernel, và cũng là chuẩn nên dùng cho kernel module / driver.

Quote

Coding style is very personal, and I won't force my views on anybody, but this is what goes for anything that I have to be able to maintain.

1. Indentation

  • Dùng tab, độ rộng 8 ký tự. Không dùng space để indent.
  • Tab 8 ký tự giúp thấy rõ cấu trúc khối; nếu nesting quá 3 cấp thì nên refactor.
  • switch và case cùng cột (không indent case):
switch (suffix) {
case 'G':
case 'g':
    mem <<= 30;
    break;
case 'M':
case 'm':
    mem <<= 20;
    break;
default:
    break;
}
  • Không đặt nhiều câu lệnh trên một dòng, không có trailing whitespace.

2. Độ dài dòng

  • Giới hạn 80 cột (checkpatch hiện cảnh báo ở 100 cột).
  • Không ngắt chuỗi thông báo người dùng (printk) để có thể grep được.
  • Khi ngắt dòng, căn các tham số tiếp theo thẳng với dấu ( của dòng trước.

3. Vị trí dấu ngoặc {}

Kiểu K&R:

  • Với câu lệnh điều khiển (if, for, while, switch): { ở cuối dòng, } ở dòng riêng.
  • Với hàm: { ở dòng mới.
if (x is true) {
    we do y
}

int function(int x)
{
    body of function
}
  • else/else if/while của do-while đặt cùng dòng với }:
if (x == y) {
    ..
} else if (x > y) {
    ...
} else {
    ....
}

do {
    body of do-loop
} while (condition);
  • Không dùng {} cho câu lệnh đơn, trừ khi một nhánh khác của if-else cần {}:
if (condition)
    action();

if (condition) {
    do_this();
    do_that();
} else {
    otherwise();
}

4. Khoảng trắng (Spaces)

  • Có space sau các keyword: if, switch, case, for, do, while.
  • Không có space sau sizeof, typeof, alignof, __attribute__.
  • Không có space bên trong ngoặc: s = sizeof(struct file);.
  • Dấu * của con trỏ dính với tên biến/hàm: char *linux_banner;, unsigned long long memparse(char *ptr, char **retptr);.
  • Có space hai bên toán tử hai ngôi và ba ngôi: = + - < > * / % | & ^ <= >= == != ? :.
  • Không có space sau toán tử một ngôi: & * + - ~ ! sizeof typeof alignof __attribute__ defined, và ++/-- (cả tiền tố lẫn hậu tố).
  • Không có space quanh . và ->.
  • Không để trailing whitespace ở cuối dòng.

5. Đặt tên (Naming)

  • Ngắn gọn, snake_case, chữ thường. Không dùng CamelCase hay notation Hungarian.
  • Biến cục bộ ngắn: i cho loop counter, tmp cho biến tạm. Biến global và hàm export phải có tên mô tả rõ ràng (count_active_users() thay vì cntusr()).
  • Không encode kiểu vào tên (int vps_attr_val ✗).
  • Hàm/biến static dùng nội bộ thì tên có thể ngắn hơn, nhưng vẫn nên có prefix của module/driver.
  • Macro / enum constant viết HOA: #define CONSTANT 0x12345.
  • Dùng từ trung tính: primary/secondary, controller/device... thay cho các thuật ngữ cũ không phù hợp (xem Documentation/process/coding-style.rst).

6. Typedef

Kernel hạn chế dùng typedef:

  • Không typedef cho struct/pointer - che giấu thông tin kiểu thật.
  • Chỉ dùng khi:
    • Đối tượng hoàn toàn opaque (chỉ truy cập qua hàm).
    • Kiểu số nguyên đảm bảo kích thước, tránh nhầm lẫn (u8/u16/u32/u64, size_t...).
    • Kiểu mới phục vụ sparse (kiểm tra kiểu).
    • Tương thích với kiểu chuẩn C99 (uint32_t) hoặc ABI user-space (__u32).
/* Tốt */
struct my_dev *dev;

/* Tránh */
typedef struct my_dev *my_dev_t;

7. Hàm (Functions)

  • Hàm ngắn, làm một việc. Một hàm nên vừa một-hai màn hình (khoảng 24 dòng x 80 cột) - hàm phức tạp thì ngắn hơn nữa.
  • Số biến cục bộ tối đa khoảng 5-10.
  • Giữa các hàm cách một dòng trống.
  • Với hàm static inline dài trong header, cân nhắc bỏ inline - đừng lạm dụng inline (chỉ nên cho hàm vài dòng).
  • Kiểu trả về và tên hàm: tách riêng dòng khi định nghĩa trong file C, { ở dòng mới:
static int foo_probe(struct platform_device *pdev)
{
    ...
}
  • EXPORT_SYMBOL() đặt ngay sau hàm được export, không có dòng trống ở giữa.
  • Prototype nên có tên tham số.

Giá trị trả về

  • Hàm "hành động"/"lệnh" trả về 0 khi thành công, số âm (errno) khi lỗi (-EINVAL, -ENOMEM...).
  • Hàm "kiểm tra" (predicate, tên dạng is_*, has_*) trả về bool hoặc 0/1 với 1 = đúng.
  • Hàm trả về con trỏ: trả NULL hoặc dùng ERR_PTR()/IS_ERR()/PTR_ERR().

8. Centralized exiting of functions - goto

Dùng goto để cleanup tập trung khi hàm có nhiều điểm thoát và cần giải phóng tài nguyên:

int fun(int a)
{
    int result = 0;
    char *buffer;

    buffer = kmalloc(SIZE, GFP_KERNEL);
    if (!buffer)
        return -ENOMEM;

    if (condition1) {
        while (loop1) {
            ...
        }
        result = 1;
        goto out_free_buffer;
    }
    ...
out_free_buffer:
    kfree(buffer);
    return result;
}
  • Đặt tên label mô tả việc nó làm (out_free_buffer:, err_unregister:), không dùng err1, err2.
  • Giải phóng theo thứ tự ngược với lúc cấp phát.
  • Tránh "one err bug": chỉ giải phóng những gì đã thực sự được cấp phát ở label tương ứng.

9. Comment

  • Comment cho biết cái gì và tại sao, không giải thích như thế nào (code tự nói điều đó).
  • Tránh comment bên trong thân hàm; nếu cần giải thích, hãy viết lại cho rõ hoặc đặt comment ở đầu hàm.
  • Dùng kiểu C /* ... */. Không dùng // (trừ khi file đã dùng sẵn).
  • Multi-line comment:
/*
 * This is the preferred style for multi-line
 * comments in the Linux kernel source code.
 * Please use it consistently.
 *
 * Description:  A column of asterisks on the left side,
 * with beginning and ending almost-blank lines.
 */
  • Comment dữ liệu (struct member, biến global) nên mô tả ý nghĩa; mỗi khai báo một dòng để dễ comment.
  • Dùng kernel-doc cho API công khai:
/**
 * foo_read() - read data from the foo device
 * @dev: the foo device
 * @buf: destination buffer
 * @len: number of bytes to read
 *
 * Return: number of bytes read, or a negative errno on failure.
 */

10. Macro và enum

  • Tên macro/hằng số viết HOA.
  • Macro nhiều câu lệnh phải bọc trong do { ... } while (0):
#define macrofun(a, b, c)           \
    do {                    \
        if (a == 5)         \
            do_this(b, c);      \
    } while (0)
  • Tham số macro nên đặt trong ngoặc () khi dùng trong biểu thức.
  • Tránh macro có side effect, macro phụ thuộc biến cục bộ "ẩn", và macro dạng return/goto ẩn.
  • Ưu tiên static inline function thay cho macro khi có thể (có type check).
  • Ưu tiên enum cho tập hằng số liên quan.

11. In message của kernel

  • Dùng pr_*() hoặc dev_*() (dev_err, dev_warn, dev_info, dev_dbg) thay vì printk() trần. Với driver, ưu tiên dev_*() vì tự gắn tên device.
  • Không dùng printk cho debug mặc định; dùng dev_dbg/pr_debug (bật qua Dynamic Debug).
  • Định dạng đúng: %zu cho size_t, %pad/%pa cho địa chỉ vật lý, %pS cho symbol.

12. Cấp phát bộ nhớ

  • Dạng ưu tiên: p = kmalloc(sizeof(*p), GFP_KERNEL); - dùng sizeof(*p) thay vì sizeof(struct foo).
  • Không cast kết quả kmalloc (và void * nói chung).
  • Mảng: dùng kmalloc_array() / kcalloc() để tránh overflow khi nhân.
  • Với driver, ưu tiên devm_* để tự động giải phóng khi device bị gỡ.

13. Điều kiện biên dịch (#ifdef)

  • Hạn chế #ifdef trong thân hàm; ưu tiên đặt trong header và cung cấp stub:
#ifdef CONFIG_FOO
int foo_init(void);
#else
static inline int foo_init(void) { return 0; }
#endif
  • Dùng IS_ENABLED(CONFIG_FOO) trong code C thay cho #ifdef khi có thể, để compiler vẫn kiểm tra cú pháp nhánh bị tắt.
  • Chú thích #endif /* CONFIG_FOO */ cho khối #if dài.

14. Cấu trúc file và header

  • Thứ tự thông thường: license (SPDX-License-Identifier) → #include → macro/định nghĩa → struct → hàm static → hàm public → module_init/module_exit.
  • Dòng đầu tiên của file là SPDX tag:
// SPDX-License-Identifier: GPL-2.0
  • Header dùng include guard #ifndef _LINUX_FOO_H ... #endif.
  • Include <linux/...> trước, header cục bộ của driver sau; sắp xếp theo alphabet.

15. Cấu hình editor

  • Tab = 8, indent bằng tab thật. Trong .editorconfig:
[*.{c,h}]
indent_style = tab
indent_size = 8
max_line_length = 80
trim_trailing_whitespace = true
insert_final_newline = true
  • Vim: set noexpandtab tabstop=8 shiftwidth=8.
  • Emacs: dùng c-set-style "linux" (hoặc cấu hình linux-kernel trong tài liệu).

16. Công cụ kiểm tra

# Kiểm tra patch trước khi gửi
./scripts/checkpatch.pl --strict -g HEAD
./scripts/checkpatch.pl --strict -f drivers/foo/foo.c

# Static analysis của kernel
make C=1 drivers/foo/          # sparse
make W=1 drivers/foo/          # thêm cảnh báo compiler
make coccicheck                # Coccinelle

# Định dạng tự động (cần xem lại kết quả, kernel có file .clang-format)
clang-format -i drivers/foo/foo.c

Warning

clang-format không thể thay thế hoàn toàn việc đọc lại style; luôn chạy checkpatch.pl trước khi gửi patch.

Checklist nhanh

  • Indent bằng tab 8, dòng ≤ 80 cột
  • { hàm ở dòng mới, { của if/for/while cùng dòng
  • Tên snake_case, macro/enum viết HOA
  • Không typedef struct/pointer
  • Hàm ngắn, trả 0 / -errno
  • Cleanup bằng goto có label mô tả, theo thứ tự ngược
  • Comment kiểu /* */, giải thích "tại sao"
  • Dùng dev_*() thay printk, sizeof(*p) khi cấp phát
  • SPDX license ở dòng đầu, checkpatch.pl sạch

Tham khảo