编码风格——linux内核开发的coding style

总结linux内核开发的coding style, 便于以后写代码时参考.

下面只是罗列一些规则, 具体说明可以参考: 内核源码(Documentation/CodingStyle)

01 - 缩进
缩进用 Tab, 并且Tab的宽度为8个字符
swich 和 case对齐, 不用缩进
switch (suffix) {
case ‘G‘:
case ‘g‘:
mem <<= 30;
break;
case ‘M‘:
case ‘m‘:
mem <<= 20;
break;
case ‘K‘:
case ‘k‘:
mem <<= 10;
/* fall through */
default:
break;
}

一行只有一个表达式
if (condition) do_this; /* bad example */

不要用空格来缩进 (除了注释或文档)

02 - 代码行长度控制在80个字符以内
长度过长的行截断时, 注意保持易读性
void fun(int a, int b, int c)
{
if (condition)
printk(KERN_WARNING "Warning this is a long printk with "
"3 parameters a: %u b: %u "
"c: %u \n", a, b, c);
else next_statement;
}

03 - 括号和空格的位置
函数的大括号另起一行
int function(int x)
{ /* 这个大括号 { 另起了一行 */ body of function
}

非函数的语句块(if, switch, for, while, do)不用另起一行
if (x is true) { /* 这个大括号 { 不用另起一行 */ we do y
}

只有一行的语句块不用大括号
if (condition)
action();

如果if用了大括号, 那么else即使只有一行也要用大括号
if (condition) {
do_this();
do_that();
} else {
otherwise();
}

下列 keywords 后面追加一个空格
if, switch, case, for, do, while

下列 keywords 后面 *不要* 追加一个空格
sizeof, typeof, alignof, __attribute
s = sizeof(struct file); /* good */s = sizeof( struct file ); /* bad */

定义指针时, * 紧靠函数名或者变量名
char *linux_banner;
unsigned long long memparse(char *ptr, char **retptr);
char *match_strdup(substring_t *s);

下面的二元和三元操作符左右要留一个空格
= + - < > * / % | & ^ <= >= == != ? :

下面的一元操作符后面 *不要* 留空格
& * + - ~ ! sizeof typeof alignof __attribute__ defined

后缀操作符(++ --)前面不要留空格

前缀操作符(++ --)后面不要留空格

结构体成员操作符(. ->)前后都不要留空格

每行代码之后不要有多余的空格

04 - 命名
全局变量或函数(在确实需要时才使用)要有个描述性的名称
count_active_users() /* good */cntusr() /* cnt */

局部变量名称要简洁(这个规则比较抽象, 只能多看看内核代码中其他人的命名方式)

05 – Typedefs
尽量不要使用 typedef, 使用typedef主要为了下面的用途:
1. 完全不透明的类型(访问这些类型也需要对应的访问函数)
ex. pid_t, uid_t, pte_t ... 等等
2. 避免整型数据的困扰
比如int, long类型的长度在不同体系结构中不一致等等, 使用 u8/u16/u32 来代替整型定义
3. 当使用kernel的sparse工具做变量类型检查时, 可以typedef一个类型.
4. 定义C99标准中的新类型
5. 为了用户空间的类型安全
内核空间的结构体映射到用户空间时使用typedef, 这样即使内核空间的数据结构有变化, 用户空间也能正常运行

06 - 函数
函数要简短,一个函数只做一件事情

函数长度一般不超过2屏(1屏的大小是 80x24), 也就是48行

如果函数中的 switch 有很多简单的 case语句, 那么超出2屏也可以

函数中局部变量不能超过 5~10 个

函数与函数之间空一行, 但是和EXPORT* 之间不用空
int one_func(void)
{
return 0;
}

int system_is_up(void)
{
return system_state == SYSTEM_RUNNING;
}
EXPORT_SYMBOL(system_is_up);

07 - 函数退出
将函数的退出集中在一起, 特别有需要清理内存的时候.(goto 并不是洪水猛兽, 有时也很有用)
int fun(int a)
{
int result = 0;
char *buffer = kmalloc(SIZE);

if (buffer == NULL)
return -ENOMEM;

if (condition1) {
while (loop1) {
...
}
result = 1;
goto out;
}
...
out:
kfree(buffer);
return result;
}

08 - 注释
注释code做了什么, 而不是如何做的

使用C89的注释风格(/* ... */), 不要用C99的注释风格(// ...)

注释定义的数据, 不管是基本类型还是衍生的类型

09 - 控制缩进的方法
用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-common-hook
(lambda ()
;; Add kernel style
(c-add-style
"linux-tabs-only"
‘("linux" (c-offsets-alist
(arglist-cont-nonempty
c-lineup-gcc-asm-reg
c-lineup-arglist-tabs-only))))))

(add-hook ‘c-mode-hook
(lambda ()
(let ((filename (buffer-file-name)))
;; Enable kernel mode for the appropriate files
(when (and filename
(string-match (expand-file-name "~/src/linux-trees")
filename))
(setq indent-tabs-mode t)
(c-set-style "linux-tabs-only")))))
使用 indent 脚本来保证缩进. (脚本位置: scripts/Lindent)

10 - Kconfig配置文件
"config" 下一行要缩进一个Tab, "help" 下则缩进2个空格
config AUDIT
bool "Auditing support" depends on NET
help
Enable auditing infrastructure that can be used with another
kernel subsystem, such as SELinux (which requires this for logging of avc messages output). Does not do system-call
auditing without CONFIG_AUDITSYSCALL.

不稳定的特性要加上 *EXPERIMENTAL*
config SLUB
depends on EXPERIMENTAL && !ARCH_USES_SLAB_PAGE_STRUCT
bool "SLUB (Unqueued Allocator)" ...

危险的特性要加上 *DANGEROUS*
config ADFS_FS_RW
bool "ADFS write support (DANGEROUS)" depends on ADFS_FS
...

Kconfig的说明可以参见: Documentation/kbuild/kconfig-language.txt

11 - 数据结构
结构体要包含一个引用计数字段 (内核中没有自动垃圾收集, 需要手动释放内存)

需要保证结构体数据一致性时要加锁

结构体中有时甚至会需要多层的引用计数
ex. struc mm_struct, struct super_block

12 - 宏, 枚举类型和RTL
宏定义常量后者标签时使用大写字母
#define CONSTANT 0x12345

宏定义多行语句时要放入 do - while 中, 此时宏的名称用小写
#define macrofun(a, b, c) \
do { \
if (a == 5) \
do_this(b, c); \
} while (0)

宏定义表达式时要放在 () 中
#define CONSTANT 0x4000
#define CONSTEXP (CONSTANT | 3)

枚举类型 用来定义多个有联系的常量

13 - 打印内核消息
保持打印信息的简明清晰
比如, 不要用 "dont", 而是使用 "do not" 或者 "don‘t"

内核信息不需要使用 "." 结尾

打印 "(%d)" 之类的没有任何意义, 应该避免

选择合适的打印级别(调试,还是警告,错误等等)

14 - 分配内存
分配内存时sizeof(指针) 而不是 sizeof(结构体)
p = kmalloc(sizeof(*p), ...);
这样的话, 当p指向其他结构体时, 上面的代码仍然可用

分配内存后返回值转为 void指针是多余的, C语言本身会完成这个步骤

15 - 内联的弊端
如果一个函数有3行以上, 不要使用 inline 来标记它

16 - 函数返回值和名称
如果函数功能是一个动作或者一个命令时, 返回 int型的 error-code
比如, add_work() 函数执行成功时返回 0, 失败时返回对应的error-code(ex. -EBUSY)

如果函数功能是一个判断时, 返回 "0" 表示失败, "1" 表示成功

所有Exported函数, 公用的函数都要上述2条要求

所有私有(static)函数, 不强制要求, 但最好能满足上面2条要求

如果函数返回真实计算结果, 而不是是否成功时, 通过返回计算结果范围外的值来表示失败
比如一个返回指针的函数, 通过返回 NULL 来表示失败

17 - 不要重复发明内核宏
内核定义的宏在头文件 include/linux/kernel.h 中, 想定义新的宏时, 首先看看其中是否已有类似的宏可用

18 - 编辑器模式行和其他
不要在代码中加入特定编辑器的内容或者其他工具的配置,
比如 emacs 的配置
-*- mode: c -*-or
/*Local Variables:
compile-command: "gcc -DMAGIC_DEBUG_FLAG foo.c"
End:
*/
vim 的配置
/* vim:set sw=8 noet */

每个人使用的开发工具可能都不一样, 这样的配置对有些人来说就是垃圾

内核代码格式化工具介绍
checkpatch.pl - 检查source或者patch是否符合规范
Lindent - 格式化代码的缩进

这2个工具都在内核代码树中的 scripts 文件夹中。

时间: 2024-08-10 17:17:01

编码风格——linux内核开发的coding style的相关文章

如何参与Linux内核开发(转)

本文来源于linux内核代码的Document文件夹下的Hoto文件.Chinese translated version of Documentation/HOWTO If you have any comment or update to the content, please contact theoriginal document maintainer directly.  However, if you have a problemcommunicating in English yo

Linux内核开发进阶书籍推荐(不适合初学者)

Linux内核开发进阶书籍推荐(不适合初学者) 很早之前就想写一篇文章总结一下Linux Kernel开发的相关资料,项目的原因,再加上家里的一些事情,一直没能找到闲暇,今天终于有些时间,希望可以完成这篇文章吧.首先需要说明的是不是所有的人都需要搞内核开发,因为据笔者了解,大部分的软件工程师的工作都在用户态开发,学习内核开发事倍功半,没什么实际意义,另外,初学编程的人也不太适合搞内核开发,因为信息量相对比较大,而且枯燥晦涩,很容易让初学者还没有入门,就已经放弃了,再有就是不想投入时间,总是试图走

linux内核开发程序风格

变量命名法 这里是linux不是windows,所以匈牙利命名法是不允许使用的,在内核中,局部变量只要可以明确表达自己的意思,可以使用idx,i这种名字的id, 全局函数和变量需要有表达性的名字例如get_active_name 之类的都是允许使用的. 空格的使用 在关键字周围使用空格 关于注释的使用 函数的注释应该表达这个函数做了什么,为什么做,而不是像以前一样写一大段这个程序怎么写的(这个问题我以前在写注释的时候出现过,实在是不应该) 关于测试内核 可以利用kgdb 来测试,这是一个补丁,运

Linux 内核开发—内核简单介绍

内核简单介绍 Linux 构成 Linux 为什么被划分为系统空间和内核空间 隔离核心程序和应用程序,实现对核心程序和数据的保护. 什么内核空间,用户空间 内核空间和用户空间是程序执行的两种不同的状态,Linux对自身软件系统进行了划分,一部分核心的软件独立于普通的软件,拥有特权级别,可以訪问平台的全部硬件和资源,称为"内核空间".而普通的软件执行在"用户空间",它仅仅拥有有限的系统资源,不能直接訪问内核空间和硬件资源. 将系统分为"内核空间"和

浅谈 Linux 内核开发之网络设备驱动

网络设备介绍 网络设备是计算机体系结构中必不可少的一部分,处理器如果想与外界通信,通常都会选择网络设备作为通信接口.众所周知,在 OSI(Open Systems Interconnection,开放网际互连)中,网络被划分为七个层次,从下到上分别是物理层.数据链路层.网络层.传输层.会话层.表示层和应用层.我们所讲的网络设备也包括两个层次,一层叫做 MAC(Media Access Control)层,对应于 OSI 的数据链路层:另一层叫做 PHY(Physical Layer)层,对应于物

Linux 内核开发——内核简介

内核简介 Linux 构成 Linux 为什么被划分为系统空间和内核控件 隔离核心程序和应用程序,实现对核心程序的保护,如保护操作系统本身的保护. 什么内核空间,用户空间 内核空间和用户空间是程序运行的两种不同的状态,Linux对自身软件系统进行了划分,一部分核心的软件独立于普通的软件,拥有特权级别,能够访问平台的所有硬件和资源,称为"内核空间".而普通的软件运行在"用户空间",它只拥有有限的系统资源,不能直接访问内核空间和硬件资源. 将系统分为"内核空间

Linux内核开发 — 进程调度

本节简单介绍内核开发中,进程调度的相关知识点. 什么是进程调度 进程调度就是在已经就绪的进程中选择一个最合适的进程执行的过程. 进程调度策略 实时类调度策略 非实时类调度策略 进程调度时机 主动式抢占 当前进程因为需要等待资源等原因主动放弃执行权限,主动要求切换到下一个进程执行. 主动放弃CPU的例子: current->state = TASK_INTERRUPTIBLE;               schedule(); 被动式抢占 当前进程因为优先级.进程属性等原因被迫让出CPU执行权限

Linux内核开发基础

1.Linux内核简介 1.1.Linux系统如何构成 内核空间(Kernel Space)+用户空间(User Space) 用户空间 = 用户程序 + C语言库(例如:GNC C Library) 内核空间 = Kernel + 系统调用接口(System Call Interface) + 体系结构相关代码 Linux系统被划分用户空间 内核空间 原因? 现在CPU实现不同的工作模式,以ARM为例 A.用户模式(usr) B.系统模式(sys) C.外部中断模式(irq) D.管理模式(s

嵌入式系统Linux内核开发工程师必须掌握的三十道题 (转)

1)      Linux中主要有哪几种内核锁?2)      Linux中的用户模式和内核模式是什么含意?3)      怎样申请大块内核内存?4)      用户进程间通信主要哪几种方式?5)      通过伙伴系统申请内核内存的函数有哪些?6)      通过slab分配器申请内核内存的函数有?7)      Linux的内核空间和用户空间是如何划分的(以32位系统为例)?8)      vmalloc()申请的内存有什么特点?9)      用户程序使用malloc()申请到的内存空间在