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.
switchvàcasecùng cột (không indentcase):
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.
else/else if/whilecủado-whileđặt cùng dòng với}:
- Không dùng
{}cho câu lệnh đơn, trừ khi một nhánh khác của if-else cần{}:
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:
icho loop counter,tmpcho 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
staticdù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 (xemDocumentation/process/coding-style.rst).
6. Typedef¶
Kernel hạn chế dùng typedef:
- Không
typedefcho 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).
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 inlinedài trong header, cân nhắc bỏinline- đừng lạm dụnginline(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:
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ềboolhoặc 0/1 với 1 = đúng. - Hàm trả về con trỏ: trả
NULLhoặc dùngERR_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ùngerr1,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):
- 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 inlinefunction thay cho macro khi có thể (có type check). - Ưu tiên
enumcho tập hằng số liên quan.
11. In message của kernel¶
- Dùng
pr_*()hoặcdev_*()(dev_err,dev_warn,dev_info,dev_dbg) thay vìprintk()trần. Với driver, ưu tiêndev_*()vì tự gắn tên device. - Không dùng
printkcho debug mặc định; dùngdev_dbg/pr_debug(bật qua Dynamic Debug). - Định dạng đúng:
%zuchosize_t,%pad/%pacho địa chỉ vật lý,%pScho symbol.
12. Cấp phát bộ nhớ¶
- Dạng ưu tiên:
p = kmalloc(sizeof(*p), GFP_KERNEL);- dùngsizeof(*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ế
#ifdeftrong thân hàm; ưu tiên đặt trong header và cung cấp stub:
- Dùng
IS_ENABLED(CONFIG_FOO)trong code C thay cho#ifdefkhi 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#ifdà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:
- 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ìnhlinux-kerneltrong 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ủaif/for/whilecùng dòng - Tên
snake_case, macro/enum viết HOA - Không
typedefstruct/pointer - Hàm ngắn, trả 0 /
-errno - Cleanup bằng
gotocó label mô tả, theo thứ tự ngược - Comment kiểu
/* */, giải thích "tại sao" - Dùng
dev_*()thayprintk,sizeof(*p)khi cấp phát - SPDX license ở dòng đầu,
checkpatch.plsạch