嵌入式Linux内核代码风格
这是一个简短的文档,描述了linux内核的首选代码风格。代码风格是因人而异的,而且我 不愿意把我的观点强加给任何人,不过这里所讲述的是我必须要维护的代码所遵守的风格, 并且我也希望绝大多数其他代码也能遵守这个风格。请在写代码时至少考虑一下本文所述的 风格。 首先,我建议你打印一份GNU代码规范,然后不要读它。烧了它,这是一个具有重大象征性 意义的动作。 不管怎样,现在我们开始: 第一章:缩进 制表符是8个字符,所以缩进也是8个字符。有些异端运动试图将缩进变为4(乃至2)个字符 深,这几乎相当于尝试将圆周率的值定义为3。 理由:缩进的全部意义就在于清楚的定义一个控制块起止于何处。尤其是当你盯着你的屏幕 连续看了20小时之后,你将会发现大一点的缩进会使你更容易分辨缩进。 现在,有些人会抱怨8个字符的缩进会使代码向右边移动的太远,在80个字符的终端屏幕上 就很难读这样的代码。这个问题的答案是,如果你需要3级以上的缩进,不管用何种方式你 的代码已经有问题了,应该修正你的程序。 简而言之,8个字符的缩进可以让代码更容易阅读,还有一个好处是当你的函数嵌套太深的 时候可以给你警告。留心这个警告。 在switch语句中消除多级缩进的首选的方式是让“switch”和从属于它的“case”标签对齐于同 一列,而不要“两次缩进”“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; do_something_everytime; 也不要在一行里放多个赋值语句。内核代码风格超级简单。就是避免可能导致别人误读的表 达式。 除了注释、文档和Kconfig之外,不要使用空格来缩进,前面的例子是例外,是有意为之。 选用一个好的编辑器,不要在行尾留空格。 第二章:把长的行和字符串打散 代码风格的意义就在于使用平常使用的工具来维持代码的可读性和可维护性。 每一行的长度的限制是80列,我们强烈建议您遵守这个惯例。 长于80列的语句要打散成有意义的片段。每个片段要明显短于原来的语句,而且放置的位置 也明显的靠右。同样的规则也适用于有很长参数列表的函数头。长字符串也要打散成较短的 字符串。唯一的例外是超过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; } 第三章:大括号和空格的放置 C语言风格中另外一个常见问题是大括号的放置。和缩进大小不同,选择或弃用某种放置策 略并没有多少技术上的原因,不过首选的方式,就像Kernighan和Ritchie展示给我们的,是 把起始大括号放在行尾,而把结束大括号放在行首,所以: if (x is true) { we do y } 这适用于所有的非函数语句块(if、switch、for、while、do)。比如: switch (action) { case KOBJ_ADD: return "add"; case KOBJ_REMOVE: return "remove"; case KOBJ_CHANGE: return "change"; default: return NULL; } 不过,有一个例外,那就是函数:函数的起始大括号放置于下一行的开头,所以: int function(int x) { body of function } 全世界的异端可能会抱怨这个不一致性是……呃……不一致的,不过所有思维健全的人都知道( a)K&R是_正确的_,并且(b)K&R是正确的。此外,不管怎样函数都是特殊的(在C语言中 ,函数是不能嵌套的)。 注意结束大括号独自占据一行,除非它后面跟着同一个语句的剩余部分,也就是do语句中的 “while”或者if语句中的“else”,像这样: do { body of do-loop } while (condition); 和 if (x == y) { .. } else if (x > y) { ... } else { .... } 理由:K&R。 也请注意这种大括号的放置方式也能使空(或者差不多空的)行的数量最小化,同时不失可 读性。因此,由于你的屏幕上的新行是不可再生资源(想想25行的终端屏幕),你将会有更 多的空行来放置注释。 当只有一个单独的语句的时候,不用加不必要的大括号。 if (condition) action(); 这点不适用于本身为某个条件语句的一个分支的单独语句。这时需要在两个分支里都使用大 括号。 if (condition) { do_this(); do_that(); } else { otherwise(); } 3.1:空格 Linux内核的空格使用方式(主要)取决于它是用于函数还是关键字。(大多数)关键字后 要加一个空格。值得注意的例外是sizeof、typeof、alignof和__attribute__,这些关键字 某些程度上看起来更像函数(它们在Linux里也常常伴随小括号而使用,尽管在C语言里这样 的小括号不是必需的,就像“struct fileinfo info”声明过后的“sizeof info”)。 所以在这些关键字之后放一个空格: if, switch, case, for, do, while 但是不要在sizeof、typeof、alignof或者__attribute__这些关键字之后放空格。例如, s = sizeof(struct file); 不要在小括号里的表达式两侧加空格。这是一个反例: s = sizeof( struct file ); 当声明指针类型或者返回指针类型的函数时,“*”的首选使用方式是使之靠近变量名或者函 数名,而不是靠近类型名。例子: char *linux_banner; unsigned long long memparse(char *ptr, char **retptr); char *match_strdup(substring_t *s); 在大多数二元和三元操作符两侧使用一个空格,例如下面所有这些操作符: = + - < > * / % | & ^ <= >= == != ? : 但是一元操作符后不要加空格: & * + - ~ ! sizeof typeof alignof __attribute__ defined 后缀自加和自减一元操作符前不加空格: ++ -- 前缀自加和自减一元操作符后不加空格: ++ -- “.”和“->”结构体成员操作符前后不加空格。 不要在行尾留空白。有些可以自动缩进的编辑器会在新行的行首加入适量的空白,然后你 就可以直接在那一行输入代码。不过假如你最后没有在那一行输入代码,有些编辑器就不 会移除已经加入的空白,就像你故意留下一个只有空白的行。包含行尾空白的行就这样产 生了。 当git发现补丁包含了行尾空白的时候会警告你,并且可以应你的要求去掉行尾空白;不过 如果你是正在打一系列补丁,这样做会导致后面的补丁失败,因为你改变了补丁的上下文。 第四章:命名 C是一个简朴的语言,你的命名也应该这样。和Modula-2和Pascal程序员不同,C程序员不使 用类似ThisVariableIsATemporaryCounter这样华丽的名字。C程序员会称那个变量为“tmp” ,这样写起来会更容易,而且至少不会令其难于理解。 不过,虽然混用大小写的名字是不提倡使用的,但是全局变量还是需要一个具描述性的名字 。称一个全局函数为“foo”是一个难以饶恕的错误。 全局变量(只有当你真正需要它们的时候再用它)需要有一个具描述性的名字,就像全局函 数。如果你有一个可以计算活动用户数量的函数,你应该叫它“count_active_users()”或者 类似的名字,你不应该叫它“cntuser()”。 在函数名中包含函数类型(所谓的匈牙利命名法)是脑子出了问题——编译器知道那些类型而 且能够检查那些类型,这样做只能把程序员弄糊涂了。难怪微软总是制造出有问题的程序。 本地变量名应该简短,而且能够表达相关的含义。如果你有一些随机的整数型的循环计数器 ,它应该被称为“i”。叫它“loop_counter”并无益处,如果它没有被误解的可能的话。类似 的,“tmp”可以用来称呼任意类型的临时变量。 如果你怕混淆了你的本地变量名,你就遇到另一个问题了,叫做函数增长荷尔蒙失衡综合症 。请看第六章(函数)。 第五章:Typedef 不要使用类似“vps_t”之类的东西。 对结构体和指针使用typedef是一个错误。当你在代码里看到: vps_t a; 这代表什么意思呢? 相反,如果是这样 struct virtual_container *a; 你就知道“a”是什么了。 很多人认为typedef“能提高可读性”。实际不是这样的。它们只在下列情况下有用: (a) 完全不透明的对象(这种情况下要主动使用typedef来隐藏这个对象实际上是什么)。 例如:“pte_t”等不透明对象,你只能用合适的访问函数来访问它们。 注意!不透明性和“访问函数”本身是不好的。我们使用pte_t等类型的原因在于真的是 完全没有任何共用的可访问信息。 (b) 清楚的整数类型,如此,这层抽象就可以帮助消除到底是“int”还是“long”的混淆。 u8/u16/u32是完全没有问题的typedef,不过它们更符合类别(d)而不是这里。 再次注意!要这样做,必须事出有因。如果某个变量是“unsigned long“,那么没有必要 typedef unsigned long myflags_t; 不过如果有一个明确的原因,比如它在某种情况下可能会是一个“unsigned int”而在 其他情况下可能为“unsigned long”,那么就不要犹豫,请务必使用typedef。 (c) 当你使用sparse按字面的创建一个新类型来做类型检查的时候。 (d) 和标准C99类型相同的类型,在某些例外的情况下。 虽然让眼睛和脑筋来适应新的标准类型比如“uint32_t”不需要花很多时间,可是有些 人仍然拒绝使用它们。 因此,Linux特有的等同于标准类型的“u8/u16/u32/u64”类型和它们的有符号类型是被 允许的——尽管在你自己的新代码中,它们不是强制要求要使用的。 当编辑已经使用了某个类型集的已有代码时,你应该遵循那些代码中已经做出的选择。 (e) 可以在用户空间安全使用的类型。 在某些用户空间可见的结构体里,我们不能要求C99类型而且不能用上面提到的“u32” 类型。因此,我们在与用户空间共享的所有结构体中使用__u32和类似的类型。 可能还有其他的情况,不过基本的规则是永远不要使用typedef,除非你可以明确的应用上 述某个规则中的一个。 总的来说,如果一个指针或者一个结构体里的元素可以合理的被直接访问到,那么它们就不 应该是一个typedef。 第六章:函数 函数应该简短而漂亮,并且只完成一件事情。函数应该可以一屏或者两屏显示完(我们都知 道ISO/ANSI屏幕大小是80x24),只做一件事情,而且把它做好。 一个函数的最大长度是和该函数的复杂度和缩进级数成反比的。所以,如果你有一个理论上 很简单的只有一个很长(但是简单)的case语句的函数,而且你需要在每个case里做很多很 小的事情,这样的函数尽管很长,但也是可以的。 不过,如果你有一个复杂的函数,而且你怀疑一个天分不是很高的高中一年级学生可能甚至 搞不清楚这个函数的目的,你应该严格的遵守前面提到的长度限制。使用辅助函数,并为之 取个具描述性的名字(如果你觉得它们的性能很重要的话,可以让编译器内联它们,这样的 效果往往会比你写一个复杂函数的效果要好。) 函数的另外一个衡量标准是本地变量的数量。此数量不应超过5-10个,否则你的函数就有 问题了。重新考虑一下你的函数,把它分拆成更小的函数。人的大脑一般可以轻松的同时跟 踪7个不同的事物,如果再增多的话,就会糊涂了。即便你聪颖过人,你也可能会记不清你2 个星期前做过的事情。 在源文件里,使用空行隔开不同的函数。如果该函数需要被导出,它的EXPORT*宏应该紧贴 在它的结束大括号之下。比如: int system_is_up(void) { return system_state == SYSTEM_RUNNING; } EXPORT_SYMBOL(system_is_up); 在函数原型中,包含函数名和它们的数据类型。虽然C语言里没有这样的要求,在Linux里这 是提倡的做法,因为这样可以很简单的给读者提供更多的有价值的信息。 第七章:集中的函数退出途径 虽然被某些人声称已经过时,但是goto语句的等价物还是经常被编译器所使用,具体形式是 无条件跳转指令。 当一个函数从多个位置退出并且需要做一些通用的清理工作的时候,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; } 第八章:注释 注释是好的,不过有过度注释的危险。永远不要在注释里解释你的代码是如何运作的:更好 的做法是让别人一看你的代码就可以明白,解释写的很差的代码是浪费时间。 一般的,你想要你的注释告诉别人你的代码做了什么,而不是怎么做的。也请你不要把注释 放在一个函数体内部:如果函数复杂到你需要独立的注释其中的一部分,你很可能需要回到 第六章看一看。你可以做一些小注释来注明或警告某些很聪明(或者槽糕)的做法,但不要 加太多。你应该做的,是把注释放在函数的头部,告诉人们它做了什么,也可以加上它做这 些事情的原因。 当注释内核API函数时,请使用kernel-doc格式。请看 Documentation/kernel-doc-nano-HOWTO.txt和scripts/kernel-doc以获得详细信息。 Linux的注释风格是C89“/* ... */”风格。不要使用C99风格“// ...”注释。 长(多行)的首选注释风格是: /* * 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. */ 注释数据也是很重要的,不管是基本类型还是衍生类型。为了方便实现这一点,每一行应只 声明一个数据(不要使用逗号来一次声明多个数据)。这样你就有空间来为每个数据写一段 小注释来解释它们的用途了。 第十一章:数据结构 如果一个数据结构,在创建和销毁它的单线执行环境之外可见,那么它必须要有一个引用计 数器。内核里没有垃圾收集(并且内核之外的垃圾收集慢且效率低下),这意味着你绝对需 要记录你对这种数据结构的使用情况。 引用计数意味着你能够避免上锁,并且允许多个用户并行访问这个数据结构——而不需要担心 这个数据结构仅仅因为暂时不被使用就消失了,那些用户可能不过是沉睡了一阵或者做了一 些其他事情而已。 |
相关文章:

嵌入式Linux内核代码风格
这是一个简短的文档,描述了linux内核的首选代码风格。代码风格是因人而异的,而且我 不愿意把我的观点强加给任何人,不过这里所讲述的是我必须要维护的代码所遵守的风格, 并且我也希望绝大多数其他代码也能遵守这个风格。请在写代码…...

Andorid:关于Binder几个面试问题
1.简单介绍下binderbinder是一种进程间通讯的机制进程间通讯需要了解用户空间和内核空间每个进程拥有自己的独立虚拟机,系统为他们分配的地址空间都是互相隔离的。如两个进程需要进行通讯,则需要使用到内核空间做载体,内核空间是所有进程共享…...

【剑指Offer-Java】包含min函数的栈?
题目 定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 min、push 及 pop 的时间复杂度都是 O(1)。 MinStack minStack new MinStack(); minStack.push(-2); minStack.push(0); minStack.push(-3); minStack.min()…...

红队APT——邮件钓鱼攻击SwaksOffice漏洞RLO隐藏压缩释放
目录 (一)采用自己搭建Ewomail配合Swaks 0x01 搭建过程 0x02 配置转发信息 (二)网页钓鱼-克隆修改...

【Java|基础篇】超详细讲解运算符
文章目录1. 什么是运算符2. 算术运算符隐式类型转换强制类型转换字符串的拼接字符相加自增和自减运算符3.赋值运算符4. 关系运算符5. 逻辑运算符短路与(&&)和短路或(||)6.三目运算符7. 位运算符8. 移位运算1. 什么是运算符 运算符用于执行程序代码运算,会针…...

Promise-异步回调
1.理解Promise promise是ES6提出的异步编程的新的解决方案,通过链式调用解决ajax回调地狱 从语法上看,promise是一个构造函数,自己身上有all、reject、resolve方法,原型上有then、catch方法 从功能上看,Promise对象用…...

【设计模式之美 设计原则与思想:设计原则】21 | 理论七:重复的代码就一定违背DRY吗?如何提高代码的复用性?
在上一节课中,我们讲了 KISS 原则和 YAGNI 原则,KISS 原则可以说是人尽皆知。今天,我们再学习一个你肯定听过的原则,那就是 DRY 原则。它的英文描述为:Don’t Repeat Yourself。中文直译为:不要重复自己。将…...

深度学习 | 入个Pytorch的小门
本文主要参考 1’ 2’ 3 更新:2023 / 3 / 1 深度学习 | 入个Pytorch的小门 - 1. 常见数据操作创建操作算术操作加法索引形状查询形状改变形状广播机制广播条件运算数据类型转换Tensor转NumPyNumPy转Tensor线性回归线性回归的基本要素1. 模型2. 数据集3. 损失函数4.…...

应用上云指导
应用上云指导方法论。应用上云指传统应用迁移到云上,云上应用采用K8S部署。本文旨在提供一种方法、流程,指导应用上云,以求优化上云工作,提供应用上云效率。主要包含以下内容:应用上云工作角色、分工应用上云标准流程及…...

进程概念~
进程概念 (冯诺依曼体系结构,操作系统,进程概念,进程状态,环境变量,程序地址空间) 冯诺依曼体系结构:(计算机硬件体系结构) 输入设备,输出设备&a…...

三天吃透Java基础八股文
本文已经收录到Github仓库,该仓库包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等核心知识点,欢迎star~ Github地址:https://github.com/…...

YOLOv8训练自己的数据集(超详细)
一、准备深度学习环境本人的笔记本电脑系统是:Windows10YOLO系列最新版本的YOLOv8已经发布了,详细介绍可以参考我前面写的博客,目前ultralytics已经发布了部分代码以及说明,可以在github上下载YOLOv8代码,代码文件夹中…...

【洛谷 P1088】[NOIP2004 普及组] 火星人 题解(全排列+向量)
[NOIP2004 普及组] 火星人 题目描述 人类终于登上了火星的土地并且见到了神秘的火星人。人类和火星人都无法理解对方的语言,但是我们的科学家发明了一种用数字交流的方法。这种交流方法是这样的,首先,火星人把一个非常大的数字告诉人类科学…...

基于混合蛙跳算法优化SVM的滚动轴承故障诊断python实现
1.混合蛙跳算法(SFLA)原理 混合蛙跳算法(SFLA)是一种受自然生物模仿启示而产生的基于群体的协同搜索方法,由局部搜索和全局信息交换两部分组成。 混合蛙跳算法中,每个青蛙的位置代表了一个可行解。青蛙在沼泽中跳跃,沼泽在离散的地方有很多石头,青蛙可以跳过这些石头来找…...

如何让AI帮你干活-娱乐(2)
背景:好容易完成朋友的任务,帮忙给小朋友绘画比赛生成一些创意参考图片。他给我个挑战更高的问题,是否可以帮他用AI生成一些视频。这个乍一听以现在AI技术根本不太可能完成。奈何他各种坚持,无奈被迫营业。苦脸接受了这个不可能完…...

文件异步多备常用方案
业务需求上经常存在需要对同一个文件进行双上传,上传到不同云存储桶,以防出现某一个云厂商因各种意外导致自身服务出现不可用的情况,当然,还有其他措施可以避免,现在只针对通过程序业务代码而双写存储的这个场景。 业务…...

java面试八股文之------Redis夺命连环25问
java面试八股文之------Redis夺命连环25问👨🎓1.为什么redis这么快👨🎓2.redis的应用场景,为什么要用👨🎓3.redis6.0之前为什么一直不使用多线程,6.0为甚么又使用多线程了&…...

【数据结构】AVL平衡二叉树底层原理以及二叉树的演进之多叉树
1.AVL平衡二叉树底层原理 背景 二叉查找树左右子树极度不平衡,退化成为链表时候,相当于全表扫描,时间复杂度就变为了O(n) 插入速度没影响,但是查询速度变慢,比单链表都慢,每次都要判断左右子树是否为空 需…...

K8S篇-安装nfs插件
前言 有关k8s的搭建可以参考:http://t.csdn.cn/H84Zu 有关过程中使用到的nfs相关的nas,可以参考: http://t.csdn.cn/ACfoT http://t.csdn.cn/tPotK http://t.csdn.cn/JIn27 安装nfs存储插件 NFS-Subdir-External-Provisioner是一个自动配置…...

xmu 离散数学 卢杨班作业详解【4-7章】
文章目录第四章 二元关系和函数4.6.2911121618.120.222.1232834第五章 代数系统的一般概念2判断二元运算是否封闭348111214第六章 几个典型的代数系统1.5.6.7.11.12151618第七章 图的基本概念12479111215第四章 二元关系和函数 4. A{1,2,3} 恒等关系 IA{<1,1>,<2,2…...

多重背包问题中的二进制状态压缩
1.多重背包问题 经典的多重背包问题和01背包问题的相似之处在于二者的一维遍历顺序都是从右侧往左侧遍历。 同时多重背包的一维写法不比二维写法降低时间复杂度。 2.多重背包标准写法:(平铺展开形式) class Solution {public int maxValue(int N, int C, int[] s…...

汇编语言程序设计(四)之汇编指令
系列文章 汇编语言程序设计(一) 汇编语言程序设计(二)之寄存器 汇编语言程序设计(三)之汇编程序 汇编指令 1. 数据传输指令 指令包括:MOV、XCHG、XLAT、LEA、LDS、LES、PUSH、POP、PUSHF、LA…...

Vant2 源码分析之 vant-sticky
前言 原打算借鉴 vant-sticky 源码,实现业务需求的某个功能,第一眼看以为看懂了,拿来用的时候,才发现一知半解。看第二遍时,对不起,是我肤浅了。这里侧重分析实现原理,其他部分不拓展开来&…...

【自然语言处理】【大模型】大语言模型BLOOM推理工具测试
相关博客 【自然语言处理】【大模型】大语言模型BLOOM推理工具测试 【自然语言处理】【大模型】GLM-130B:一个开源双语预训练语言模型 【自然语言处理】【大模型】用于大型Transformer的8-bit矩阵乘法介绍 【自然语言处理】【大模型】BLOOM:一个176B参数…...

云桌面技术初识:VDI,IDV,VOI,RDS
VDI(Virtual Desktop Infrastucture,虚拟桌面架构),俗称虚拟云桌面 VDI构架采用的“集中存储、集中运算”构架,所有的桌面以虚拟机的方式运行在服务器硬件虚拟化层上,桌面以图像传输的方式发送到客户端。 …...

基于本地centos构建gdal2.4.4镜像
1.前言 基于基础镜像构建gdal环境一般特别大,一般少则1.6G,多则2G甚至更大,这对于镜像的迁移造成了极大的不便。究其原因在于容器中有大量的源码文件以及编译中间过程文件,还要大量编译需要的yum库。本文主要通过在centos系统上先…...

生产环境线程问题排查
线程状态的解读RUNNABLE线程处于运行状态,不一定消耗CPU。例如,线程从网络读取数据,大多数时间是挂起的,只有数据到达时才会重新唤起进入执行状态。只有Java代码显式调用sleep或wait方法时,虚拟机才可以精准获取到线程…...

Day908.joinsnljdist和group问题和备库自增主键问题 -MySQL实战
join&snlj&dist和group问题和备库自增主键问题 Hi,我是阿昌,今天学习记录的是关于join&snlj&dist和group问题和备库自增主键问题的内容。 一、join 的写法 join 语句怎么优化?中,在介绍 join 执行顺序的时候&am…...

算法 - 剑指Offer 丑数
题目 我们把只包含质因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。 解题思路 这题我使用最简单方法去做, 首先我们可以获取所有2n,3n,5*n的丑数,只是我们这里暂时无法排序,并且可能…...

【ONE·C || 文件操作】
总言 C语言:文件操作。 文章目录总言1、文件是什么?为什么需要文件?1.1、为什么需要文件?1.2、文件是什么?2、文件的打开与关闭2.1、文件指针2.2、文件打开和关闭:fopen、fclose2.3、文件使用方式3、文…...