Linux官文转载-- Linux 内核代码风格
Warning
此文件的目的是为让中文读者更容易阅读和理解,而不是作为一个分支。 因此, 如果您对此文件有任何意见或更新,请先尝试更新原始英文文件。
这是一个简短的文档,描述了 linux 内核的首选代码风格。代码风格是因人而异的, 而且我不愿意把自己的观点强加给任何人,但这就像我去做任何事情都必须遵循的原则 那样,我也希望在绝大多数事上保持这种的态度。请 (在写代码时) 至少考虑一下这里 的代码风格。
首先,我建议你打印一份 GNU 代码规范,然后不要读。烧了它,这是一个具有重大象征 性意义的动作。
不管怎样,现在我们开始:
1) 缩进
制表符是 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;fallthrough; default:break; }
不要把多个语句放在一行里,除非你有什么东西要隐藏:
if (condition) do_this;do_something_everytime;
不要使用逗号来避免使用大括号:
if (condition)do_this(), do_that();
使用大括号包裹多语句:
if (condition) {do_this();do_that(); }
也不要在一行里放多个赋值语句。内核代码风格超级简单。就是避免可能导致别人误读 的表达式。
除了注释、文档和 Kconfig 之外,不要使用空格来缩进,前面的例子是例外,是有意为 之。
选用一个好的编辑器,不要在行尾留空格。
2) 把长的行和字符串打散
代码风格的意义就在于使用平常使用的工具来维持代码的可读性和可维护性。
每一行的长度的限制是 80 列,我们强烈建议您遵守这个惯例。
长于 80 列的语句要打散成有意义的片段。除非超过 80 列能显著增加可读性,并且不 会隐藏信息。
子片段要明显短于母片段,并明显靠右。一种非常常用的样式是将子体与函数左括号对齐。
这同样适用于有着很长参数列表的函数头。
然而,绝对不要打散对用户可见的字符串,例如 printk 信息,因为这样就 很难对它们 grep。
3) 大括号和空格的放置
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(); elsedo_that();
这并不适用于只有一个条件分支是单语句的情况;这时所有分支都要使用大括号:
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 发现补丁包含了行尾空白的时候会警告你,并且可以应你的要求去掉行尾空白; 不过如果你是正在打一系列补丁,这样做会导致后面的补丁失败,因为你改变了补丁的 上下文。
4) 命名
C 是一个简朴的语言,你的命名也应该这样。和 Modula-2 和 Pascal 程序员不同, C 程序员不使用类似 ThisVariableIsATemporaryCounter 这样华丽的名字。C 程序员会 称那个变量为 tmp
,这样写起来会更容易,而且至少不会令其难于理解。
不过,虽然混用大小写的名字是不提倡使用的,但是全局变量还是需要一个具描述性的 名字。称一个全局函数为 foo
是一个难以饶恕的错误。
全局变量 (只有当你 真正 需要它们的时候再用它) 需要有一个具描述性的名字,就 像全局函数。如果你有一个可以计算活动用户数量的函数,你应该叫它 count_active_users()
或者类似的名字,你不应该叫它 cntuser()
。
在函数名中包含函数类型 (所谓的匈牙利命名法) 是脑子出了问题——编译器知道那些类 型而且能够检查那些类型,这样做只能把程序员弄糊涂了。
本地变量名应该简短,而且能够表达相关的含义。如果你有一些随机的整数型的循环计 数器,它应该被称为 i
。叫它 loop_counter
并无益处,如果它没有被误解的 可能的话。类似的, tmp
可以用来称呼任意类型的临时变量。
如果你怕混淆了你的本地变量名,你就遇到另一个问题了,叫做函数增长荷尔蒙失衡综 合征。请看第六章 (函数)。
对于符号名称和文档,避免引入新的“master/slave”(或独立于“master”的“slave”) 和“blacklist/whitelist”。
“master/slave”推荐替换为:
‘{primary,main} / {secondary,replica,subordinate}’ ‘{initiator,requester} / {target,responder}’ ‘{controller,host} / {device,worker,proxy}’ ‘leader/follower’ ‘director/performer’
“blacklist/whitelist”推荐替换为:
‘denylist/allowlist’ ‘blocklist/passlist’
引入新用法的例外情况是:维护用户空间ABI/API,或更新现有(截至2020年)硬件或 协议规范的代码时要求这些术语。对于新规范,尽可能将术语的规范用法转换为内核 编码标准。
Warning
以上主从、黑白名单规则不适用于中文文档,请勿更改中文术语!
5) Typedef
不要使用类似 vps_t
之类的东西。
对结构体和指针使用 typedef 是一个 错误 。当你在代码里看到:
vps_t a;
这代表什么意思呢?
相反,如果是这样
struct virtual_container *a;
你就知道 a
是什么了。
很多人认为 typedef 能提高可读性
。实际不是这样的。它们只在下列情况下有用:
完全不透明的对象 (这种情况下要主动使用 typedef 来 隐藏 这个对象实际上 是什么)。
例如:
pte_t
等不透明对象,你只能用合适的访问函数来访问它们。Note
不透明性和“访问函数”本身是不好的。我们使用 pte_t 等类型的原因在于真 的是完全没有任何共用的可访问信息。
清楚的整数类型,如此,这层抽象就可以 帮助 消除到底是
int
还是long
的混淆。u8/u16/u32 是完全没有问题的 typedef,不过它们更符合类别 (d) 而不是这里。
Note
要这样做,必须事出有因。如果某个变量是
unsigned long
,那么没有必要typedef unsigned long myflags_t;
不过如果有一个明确的原因,比如它在某种情况下可能会是一个
unsigned int
而在其他情况下可能为unsigned long
,那么就不要犹豫,请务必使用 typedef。当你使用 sparse 按字面的创建一个 新 类型来做类型检查的时候。
和标准 C99 类型相同的类型,在某些例外的情况下。
虽然让眼睛和脑筋来适应新的标准类型比如
uint32_t
不需要花很多时间,可 是有些人仍然拒绝使用它们。因此,Linux 特有的等同于标准类型的
u8/u16/u32/u64
类型和它们的有符号 类型是被允许的——尽管在你自己的新代码中,它们不是强制要求要使用的。当编辑已经使用了某个类型集的已有代码时,你应该遵循那些代码中已经做出的选 择。
可以在用户空间安全使用的类型。
在某些用户空间可见的结构体里,我们不能要求 C99 类型而且不能用上面提到的
u32
类型。因此,我们在与用户空间共享的所有结构体中使用 __u32 和类似 的类型。
可能还有其他的情况,不过基本的规则是 永远不要 使用 typedef,除非你可以明 确的应用上述某个规则中的一个。
总的来说,如果一个指针或者一个结构体里的元素可以合理的被直接访问到,那么它们 就不应该是一个 typedef。
6) 函数
函数应该简短而漂亮,并且只完成一件事情。函数应该可以一屏或者两屏显示完 (我们 都知道 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);
6.1) 函数原型
在函数原型中包含参数名和它们的数据类型。虽然 C 语言里没有这样的要求,但在 Linux 里这是提倡的做法,因为这样可以很简单的给读者提供更多的有价值的信息。
不要在函数声明里使用 extern
关键字,因为这会导致代码行变长,并且不是严格 必需的。
写函数原型时,请保持 元素顺序规则 。 例如下列函数声明:
__init void * __must_check action(enum magic value, size_t size, u8 count,char *fmt, ...) __printf(4, 5) __malloc;
推荐的函数原型元素顺序是:
-
储存类型(下方的
static __always_inline
,注意__always_inline
技术上来讲是个属性但被当做inline
) -
储存类型属性(上方的
__init
——即节声明,但也像__cold
) -
返回类型(上方的
void *
) -
返回类型属性(上方的
__must_check
) -
函数名(上方的
action
) -
函数参数(上方的
(enum magic value, size_t size, u8 count, char *fmt, ...)
, 注意必须写上参数名) -
函数参数属性(上方的
__printf(4, 5)
) -
函数行为属性(上方的
__malloc
)
请注意,对于函数 定义 (即实际函数体),编译器不允许在函数参数之后添加函 数参数属性。在这种情况下,它们应该跟随存储类型属性(例如,与上面的 声明 示例相比,请注意下面的 __printf(4, 5)
的位置发生了变化):
static __always_inline __init __printf(4, 5) void * __must_check action(enum magic value,size_t size, u8 count, char *fmt, ...) __malloc {... }
7) 集中的函数退出途径
虽然被某些人声称已经过时,但是 goto 语句的等价物还是经常被编译器所使用,具体 形式是无条件跳转指令。
当一个函数从多个位置退出,并且需要做一些类似清理的常见操作时,goto 语句就很方 便了。如果并不需要清理操作,那么直接 return 即可。
选择一个能够说明 goto 行为或它为何存在的标签名。如果 goto 要释放 buffer
, 一个不错的名字可以是 out_free_buffer:
。别去使用像 err1:
和 err2:
这样的GW_BASIC 名称,因为一旦你添加或删除了 (函数的) 退出路径,你就必须对它们 重新编号,这样会难以去检验正确性。
使用 goto 的理由是:
-
无条件语句容易理解和跟踪
-
嵌套程度减小
-
可以避免由于修改时忘记更新个别的退出点而导致错误
-
让编译器省去删除冗余代码的工作 ;)
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; }
一个需要注意的常见错误是 单 err 错误
,就像这样:
err:kfree(foo->bar);kfree(foo);return ret;
这段代码的错误是,在某些退出路径上 foo
是 NULL。通常情况下,通过把它分离 成两个错误标签 err_free_bar:
和 err_free_foo:
来修复这个错误:
err_free_bar:kfree(foo->bar); err_free_foo:kfree(foo);return ret;
理想情况下,你应该模拟错误来测试所有退出路径。
8) 注释
注释是好的,不过有过度注释的危险。永远不要在注释里解释你的代码是如何运作的: 更好的做法是让别人一看你的代码就可以明白,解释写的很差的代码是浪费时间。
一般来说你用注释告诉别人你的代码做了什么,而不是怎么做的。也请你不要把 注释放在一个函数体内部:如果函数复杂到你需要独立的注释其中的一部分,你很可能 需要回到第六章看一看。你可以做一些小注释来注明或警告某些很聪明 (或者槽糕) 的 做法,但不要加太多。你应该做的,是把注释放在函数的头部,告诉人们它做了什么, 也可以加上它做这些事情的原因。
当注释内核 API 函数时,请使用 kernel-doc 格式。详见 如何编写内核文档 和 scripts/kernel-doc 。
长 (多行) 注释的首选风格是:
/** 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.*/
注释数据也是很重要的,不管是基本类型还是衍生类型。为了方便实现这一点,每一行 应只声明一个数据 (不要使用逗号来一次声明多个数据)。这样你就有空间来为每个数据 写一段小注释来解释它们的用途了。
9) 你已经把事情弄糟了
这没什么,我们都是这样。可能你长期使用 Unix 的朋友已经告诉你 GNU emacs
能自动帮你格式化 C 源代码,而且你也注意到了,确实是这样,不过它 所使用的默认值和我们想要的相去甚远 (实际上,甚至比随机打的还要差——无数个猴子 在 GNU emacs 里打字永远不会创造出一个好程序) (译注:Infinite Monkey Theorem)
所以你要么放弃 GNU 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)))(dir-locals-set-class-variables'linux-kernel'((c-mode . ((c-basic-offset . 8)(c-label-minimum-indentation . 0)(c-offsets-alist . ((arglist-close . c-lineup-arglist-tabs-only)(arglist-cont-nonempty .(c-lineup-gcc-asm-reg c-lineup-arglist-tabs-only))(arglist-intro . +)(brace-list-intro . +)(c . c-lineup-C-comments)(case-label . 0)(comment-intro . c-lineup-comment)(cpp-define-intro . +)(cpp-macro . -1000)(cpp-macro-cont . +)(defun-block-intro . +)(else-clause . 0)(func-decl-cont . +)(inclass . +)(inher-cont . c-lineup-multi-inher)(knr-argdecl-intro . 0)(label . -1000)(statement . 0)(statement-block-intro . +)(statement-case-intro . +)(statement-cont . +)(substatement . +)))(indent-tabs-mode . t)(show-trailing-whitespace . t)))))(dir-locals-set-directory-class(expand-file-name "~/src/linux-trees")'linux-kernel)
这会让 emacs 在 ~/src/linux-trees
下的 C 源文件获得更好的内核代码风格。
不过就算你尝试让 emacs 正确的格式化代码失败了,也并不意味着你失去了一切:还可 以用 indent
。
不过,GNU indent 也有和 GNU emacs 一样有问题的设定,所以你需要给它一些命令选 项。不过,这还不算太糟糕,因为就算是 GNU indent 的作者也认同 K&R 的权威性 (GNU 的人并不是坏人,他们只是在这个问题上被严重的误导了),所以你只要给 indent 指定选项 -kr -i8
(代表 K&R,8 字符缩进
),或使用 scripts/Lindent
这样就可以以最时髦的方式缩进源代码。
indent
有很多选项,特别是重新格式化注释的时候,你可能需要看一下它的手册。 不过记住: indent
不能修正坏的编程习惯。
请注意,您还可以使用 clang-format
工具帮助您处理这些规则,快速自动重新格 式化部分代码,并审阅整个文件以发现代码风格错误、打字错误和可能的改进。它还可 以方便地排序 #include
,对齐变量/宏,重排文本和其他类似任务。 详见 clang-format 。
10) Kconfig 配置文件
对于遍布源码树的所有 Kconfig* 配置文件来说,它们缩进方式有所不同。紧挨着 config
定义的行,用一个制表符缩进,然而 help 信息的缩进则额外增加 2 个空 格。举个例子:
config AUDITbool "Auditing support"depends on NEThelpEnable auditing infrastructure that can be used with anotherkernel subsystem, such as SELinux (which requires this forlogging of avc messages output). Does not do system-callauditing without CONFIG_AUDITSYSCALL.
而那些危险的功能 (比如某些文件系统的写支持) 应该在它们的提示字符串里显著的声 明这一点:
config ADFS_FS_RWbool "ADFS write support (DANGEROUS)"depends on ADFS_FS...
要查看配置文件的完整文档,请看 Kconfig Language 。
11) 数据结构
如果一个数据结构,在创建和销毁它的单线执行环境之外可见,那么它必须要有一个引 用计数器。内核里没有垃圾收集 (并且内核之外的垃圾收集慢且效率低下),这意味着你 绝对需要记录你对这种数据结构的使用情况。
引用计数意味着你能够避免上锁,并且允许多个用户并行访问这个数据结构——而不需要 担心这个数据结构仅仅因为暂时不被使用就消失了,那些用户可能不过是沉睡了一阵或 者做了一些其他事情而已。
注意上锁 不能 取代引用计数。上锁是为了保持数据结构的一致性,而引用计数是一 个内存管理技巧。通常二者都需要,不要把两个搞混了。
很多数据结构实际上有 2 级引用计数,它们通常有不同 类
的用户。子类计数器统 计子类用户的数量,每当子类计数器减至零时,全局计数器减一。
这种 多级引用计数
的例子可以在内存管理 (struct mm_struct
: mm_users 和 mm_count),和文件系统 (struct super_block
: s_count 和 s_active) 中找到。
记住:如果另一个执行线索可以找到你的数据结构,但这个数据结构没有引用计数器, 这里几乎肯定是一个 bug。
12) 宏,枚举和RTL
用于定义常量的宏的名字及枚举里的标签需要大写。
#define CONSTANT 0x12345
在定义几个相关的常量时,最好用枚举。
宏的名字请用大写字母,不过形如函数的宏的名字可以用小写字母。
通常如果能写成内联函数就不要写成像函数的宏。
含有多个语句的宏应该被包含在一个 do-while 代码块里:
#define macrofun(a, b, c) \do { \if (a == 5) \do_this(b, c); \} while (0)
使用宏的时候应避免的事情:
-
影响控制流程的宏:
#define FOO(x) \do { \if (blah(x) < 0) \return -EBUGGERED; \} while (0)
非常 不好。它看起来像一个函数,不过却能导致 调用
它的函数退出;不要打 乱读者大脑里的语法分析器。
-
依赖于一个固定名字的本地变量的宏:
#define FOO(val) bar(index, val)
可能看起来像是个不错的东西,不过它非常容易把读代码的人搞糊涂,而且容易导致看起 来不相关的改动带来错误。
-
作为左值的带参数的宏: FOO(x) = y;如果有人把 FOO 变成一个内联函数的话,这 种用法就会出错了。
-
忘记了优先级:使用表达式定义常量的宏必须将表达式置于一对小括号之内。带参数 的宏也要注意此类问题。
#define CONSTANT 0x4000 #define CONSTEXP (CONSTANT | 3)
-
在宏里定义类似函数的本地变量时命名冲突:
#define FOO(x) \ ({ \typeof(x) ret; \ret = calc_ret(x); \(ret); \ })
ret 是本地变量的通用名字—— __foo_ret 更不容易与一个已存在的变量冲突。
cpp 手册对宏的讲解很详细。gcc internals 手册也详细讲解了 RTL,内核里的汇编语 言经常用到它。
13) 打印内核消息
内核开发者应该看起来有文化。请一定注意内核信息的拼写,以给人良好的印象。 不要用不规范的单词比如 dont
,而要用 do not
或者 don't
。保证这些信 息简单明了、无歧义。
内核信息不必以英文句号结束。
在小括号里打印数字 (%d) 没有任何价值,应该避免这样做。
<linux/device.h> 里有一些驱动模型诊断宏,你应该使用它们,以确保信息对应于正确 的设备和驱动,并且被标记了正确的消息级别。这些宏有:dev_err(), dev_warn(), dev_info() 等等。对于那些不和某个特定设备相关连的信息,<linux/printk.h> 定义 了 pr_notice(), pr_info(), pr_warn(), pr_err() 和其他。
写出好的调试信息可以是一个很大的挑战;一旦你写出后,这些信息在远程除错时能提 供极大的帮助。然而打印调试信息的处理方式同打印非调试信息不同。其他 pr_XXX() 函数能无条件地打印,pr_debug() 却不;默认情况下它不会被编译,除非定义了 DEBUG 或设定了 CONFIG_DYNAMIC_DEBUG。实际这同样是为了 dev_dbg(),一个相关约定是在一 个已经开启了 DEBUG 时,使用 VERBOSE_DEBUG 来添加 dev_vdbg()。
许多子系统拥有 Kconfig 调试选项来开启对应 Makefile 里面的 -DDEBUG;在其他 情况下,特殊文件使用 #define DEBUG。当一条调试信息需要被无条件打印时,例如, 如果已经包含一个调试相关的 #ifdef 条件,printk(KERN_DEBUG ...) 就可被使用。
14) 分配内存
内核提供了下面的一般用途的内存分配函数: kmalloc(), kzalloc(), kmalloc_array(), kcalloc(), vmalloc() 和 vzalloc()。 请参考 API 文档以获取有关它们的详细信息: 内存分配指南 。
传递结构体大小的首选形式是这样的:
p = kmalloc(sizeof(*p), ...);
另外一种传递方式中,sizeof 的操作数是结构体的名字,这样会降低可读性,并且可能 会引入 bug。有可能指针变量类型被改变时,而对应的传递给内存分配函数的 sizeof 的结果不变。
强制转换一个 void 指针返回值是多余的。C 语言本身保证了从 void 指针到其他任何 指针类型的转换是没有问题的。
分配一个数组的首选形式是这样的:
p = kmalloc_array(n, sizeof(...), ...);
分配一个零长数组的首选形式是这样的:
p = kcalloc(n, sizeof(...), ...);
两种形式都会检查分配 n * sizeof(...) 大小时内存的溢出,如果溢出返回 NULL。
在没有 __GFP_NOWARN 的情况下使用时,这些通用分配函数都会在失败时发起堆栈转储, 因此当返回NULL时,没有必要发出额外的失败消息。
15) 内联弊病
有一个常见的误解是 内联
是 gcc 提供的可以让代码运行更快的一个选项。虽然使 用内联函数有时候是恰当的 (比如作为一种替代宏的方式,请看第十二章),不过很多情 况下不是这样。inline 的过度使用会使内核变大,从而使整个系统运行速度变慢。 因为体积大内核会占用更多的指令高速缓存,而且会导致 pagecache 的可用内存减少。 想象一下,一次 pagecache 未命中就会导致一次磁盘寻址,将耗时 5 毫秒。5 毫秒的 时间内 CPU 能执行很多很多指令。
一个基本的原则是如果一个函数有 3 行以上,就不要把它变成内联函数。这个原则的一 个例外是,如果你知道某个参数是一个编译时常量,而且因为这个常量你确定编译器在 编译时能优化掉你的函数的大部分代码,那仍然可以给它加上 inline 关键字。 kmalloc() 内联函数就是一个很好的例子。
人们经常主张给 static 的而且只用了一次的函数加上 inline,如此不会有任何损失, 因为没有什么好权衡的。虽然从技术上说这是正确的,但是实际上这种情况下即使不加 inline gcc 也可以自动使其内联。而且其他用户可能会要求移除 inline,由此而来的 争论会抵消 inline 自身的潜在价值,得不偿失。
16) 函数返回值及命名
函数可以返回多种不同类型的值,最常见的一种是表明函数执行成功或者失败的值。这样 的一个值可以表示为一个错误代码整数 (-Exxx=失败,0=成功) 或者一个 成功
布尔值 (0=失败,非0=成功)。
混合使用这两种表达方式是难于发现的 bug 的来源。如果 C 语言本身严格区分整形和 布尔型变量,那么编译器就能够帮我们发现这些错误... 不过 C 语言不区分。为了避免 产生这种 bug,请遵循下面的惯例:
如果函数的名字是一个动作或者强制性的命令,那么这个函数应该返回错误代 码整数。如果是一个判断,那么函数应该返回一个“成功”布尔值。
比如, add work
是一个命令,所以 add_work() 在成功时返回 0,在失败时返回 -EBUSY。类似的,因为 PCI device present
是一个判断,所以 pci_dev_present() 在成功找到一个匹配的设备时应该返回 1,如果找不到时应该返回 0。
所有 EXPORTed 函数都必须遵守这个惯例,所有的公共函数也都应该如此。私有 (static) 函数不需要如此,但是我们也推荐这样做。
返回值是实际计算结果而不是计算是否成功的标志的函数不受此惯例的限制。通常 他们通过返回一些正常值范围之外的结果来表示出错。典型的例子是返回指针的函数, 他们使用 NULL 或者 ERR_PTR 机制来报告错误。
17) 使用布尔类型
Linux内核布尔(bool)类型是C99 _Bool类型的别名。布尔值只能为0或1,而对布尔的 隐式或显式转换将自动将值转换为true或false。在使用布尔类型时 不需要 构造, 它会消除一类错误。
使用布尔值时,应使用true和false定义,而不是1和0。
布尔函数返回类型和堆栈变量总是可以在适当的时候使用。鼓励使用布尔来提高可读性, 并且布尔值在存储时通常比“int”更好。
如果缓存行布局或值的大小很重要,请不要使用布尔,因为其大小和对齐方式根据编译 的体系结构而不同。针对对齐和大小进行优化的结构体不应使用布尔。
如果一个结构体有多个true/false值,请考虑将它们合并为具有1比特成员的位域,或使 用适当的固定宽度类型,如u8。
类似地,对于函数参数,多个true/false值可以合并为单个按位的“标志”参数,如果调 用点具有裸true/false常量,“标志”参数通常是更具可读性的替代方法。
总之,在结构体和参数中有限地使用布尔可以提高可读性。
18) 不要重新发明内核宏
头文件 include/linux/kernel.h 包含了一些宏,你应该使用它们,而不要自己写一些 它们的变种。比如,如果你需要计算一个数组的长度,使用这个宏
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
类似的,如果你要计算某结构体成员的大小,使用
#define sizeof_field(t, f) (sizeof(((t*)0)->f))
还有可以做严格的类型检查的 min() 和 max() 宏,如果你需要可以使用它们。你可以 自己看看那个头文件里还定义了什么你可以拿来用的东西,如果有定义的话,你就不应 在你的代码里自己重新定义。
19) 编辑器模式行和其他需要罗嗦的事情
有一些编辑器可以解释嵌入在源文件里的由一些特殊标记标明的配置信息。比如,emacs 能够解析被标记成这样的行:
-*- mode: c -*-
或者这样的:
/* Local Variables: compile-command: "gcc -DMAGIC_DEBUG_FLAG foo.c" End: */
Vim 能够解析这样的标记:
/* vim:set sw=8 noet */
不要在源代码中包含任何这样的内容。每个人都有他自己的编辑器配置,你的源文件不 应该覆盖别人的配置。这包括有关缩进和模式配置的标记。人们可以使用他们自己定制 的模式,或者使用其他可以产生正确的缩进的巧妙方法。
20) 内联汇编
在特定架构的代码中,你可能需要内联汇编与 CPU 和平台相关功能连接。需要这么做时 就不要犹豫。然而,当 C 可以完成工作时,不要平白无故地使用内联汇编。在可能的情 况下,你可以并且应该用 C 和硬件沟通。
请考虑去写捆绑通用位元 (wrap common bits) 的内联汇编的简单辅助函数,别去重复 地写下只有细微差异内联汇编。记住内联汇编可以使用 C 参数。
大型,有一定复杂度的汇编函数应该放在 .S 文件内,用相应的 C 原型定义在 C 头文 件中。汇编函数的 C 原型应该使用 asmlinkage
。
你可能需要把汇编语句标记为 volatile,用来阻止 GCC 在没发现任何副作用后就把它 移除了。你不必总是这样做,尽管,这不必要的举动会限制优化。
在写一个包含多条指令的单个内联汇编语句时,把每条指令用引号分割而且各占一行, 除了最后一条指令外,在每个指令结尾加上 \n\t
,让汇编输出时可以正确地缩进 下一条指令:
asm ("magic %reg1, #42\n\t""more_magic %reg2, %reg3": /* outputs */ : /* inputs */ : /* clobbers */);
21) 条件编译
只要可能,就不要在 .c 文件里面使用预处理条件 (#if, #ifdef);这样做会让代码更难 阅读并且更难去跟踪逻辑。替代方案是,在头文件中用预处理条件提供给那些 .c 文件 使用,再给 #else 提供一个空桩 (no-op stub) 版本,然后在 .c 文件内无条件地调用 那些 (定义在头文件内的) 函数。这样做,编译器会避免为桩函数 (stub) 的调用生成 任何代码,产生的结果是相同的,但逻辑将更加清晰。
最好倾向于编译整个函数,而不是函数的一部分或表达式的一部分。与其放一个 ifdef 在表达式内,不如分解出部分或全部表达式,放进一个单独的辅助函数,并应用预处理 条件到这个辅助函数内。
如果你有一个在特定配置中,可能变成未使用的函数或变量,编译器会警告它定义了但 未使用,请把它标记为 __maybe_unused 而不是将它包含在一个预处理条件中。(然而, 如果一个函数或变量总是未使用,就直接删除它。)
在代码中,尽可能地使用 IS_ENABLED 宏来转化某个 Kconfig 标记为 C 的布尔 表达式,并在一般的 C 条件中使用它:
if (IS_ENABLED(CONFIG_SOMETHING)) {... }
编译器会做常量折叠,然后就像使用 #ifdef 那样去包含或排除代码块,所以这不会带 来任何运行时开销。然而,这种方法依旧允许 C 编译器查看块内的代码,并检查它的正 确性 (语法,类型,符号引用,等等)。因此,如果条件不满足,代码块内的引用符号就 不存在时,你还是必须去用 #ifdef。
在任何有意义的 #if 或 #ifdef 块的末尾 (超过几行的),在 #endif 同一行的后面写下 注解,注释这个条件表达式。例如:
#ifdef CONFIG_SOMETHING ... #endif /* CONFIG_SOMETHING */
附录 I) 参考资料
The C Programming Language, 2nd Edition 作者:Brian W. Kernighan 和 Denni M. Ritchie. Prentice Hall, Inc., 1988. ISBN 0-13-110362-8 (平装), 0-13-110370-9 (精装).
Note
《C程序设计语言(第2版)》 作者:[美] Brian W. Kernighan / [美] Dennis M. Ritchie 译者:徐宝文 / 李志 / 尤晋元(审校) 出版社:机械工业出版社,2019 ISBN:9787111617945
The Practice of Programming 作者:Brian W. Kernighan 和 Rob Pike. Addison-Wesley, Inc., 1999. ISBN 0-201-61586-X.
Note
《程序设计实践》 作者:[美] Brian W. Kernighan / [美] Rob Pike 出版社:机械工业出版社,2005 ISBN:9787111091578
《程序设计实践》 作者:[美] Brian W. Kernighan / Rob Pike 译者:裘宗燕 出版社:机械工业出版社,2000 ISBN:9787111075738
GNU 手册 - 遵循 K&R 标准和此文本 - cpp, gcc, gcc internals and indent, 都可以从 GNU Manuals Online- GNU Project - Free Software Foundation 找到
WG14 是 C 语言的国际标准化工作组,URL: ISO/IEC JTC1/SC22/WG14 - C
内核文档 Linux kernel coding style, 作者 greg@kroah.com 发表于 OLS 2002: MagicPoint presentation foils
0voice · GitHub
相关文章:
Linux官文转载-- Linux 内核代码风格
Warning 此文件的目的是为让中文读者更容易阅读和理解,而不是作为一个分支。 因此, 如果您对此文件有任何意见或更新,请先尝试更新原始英文文件。 这是一个简短的文档,描述了 linux 内核的首选代码风格。代码风格是因人而异的&a…...
通过无障碍服务(AccessibilityService)实现Android设备全局水印显示
一、无障碍功能简介 首先我们先来了解下无障碍功能的官方介绍: 无障碍服务仅应用于帮助残障用户使用 Android 设备和应用。它们在后台运行,并在触发 AccessibilityEvents 时接收系统的回调。此类事件表示用户界面中的某些状态转换,例如焦点已…...
Dockerfile基础指令
1.FROM 基于基准镜像(建议使用官方提供的镜像作为基准镜像,相对安全一些) 举例: 制作基准镜像(基于centos:lastest) FROM cenots 不依赖于任何基准镜像 FROM scratch 依赖于9.0.22版本的tomcat镜像 FROM…...
rocketmq5源码系列--(二)--生产者发送消息
这是broker源码系列第一篇。还是和往常一样,建议copy到本地阅读 broker是基于netty的 rocketmq队列分物理队列和逻辑队列,物理队列只有一个而逻辑队列有很多个 rocketmq 物理队列,一个物理队列对应一个文件,一个物理队列可以对…...
uniapp从入门到精通(全网保姆式教程)~ 别再说你不会开发小程序了
目录 一、介绍 二、环境搭建(hello world) 2.1 下载HBuilderX 2.2 下载微信开发者工具 2.3 创建uniapp项目 2.4 在浏览器运行 2.5 在微信开发者工具运行 2.6 在手机上运行 三、项目基本目录结构 四、开发规范概述 五、全局配置文件࿰…...
Kali 自动化换源脚本编写与使用
1. 背景与需求 在使用 Kali Linux 的过程中,软件源的配置对系统的更新与软件安装速度至关重要。 Kali 的默认官方源提供了安全且最新的软件包,但有时由于网络条件或地理位置的限制,使用官方源可能会出现速度较慢的问题。 为了解决这一问题&a…...
mysql-二进制安装方式
目录 1. 安装组件即依赖包 2. 创建用户 3. 关闭防火墙 4. 解压mysql二进制源码包 5. 创建文件夹并赋予权限 6. 编译安装mysql,安装完成最后面,会有一串英文,那是登录数据库的密码 7. 设置配置文件 8. 将mysql添加进环境变量 9. 复制…...
深入剖析Android SoundPool及其JNI实现
深入剖析Android SoundPool及其JNI实现 1. SoundPool概述 SoundPool是Android中用于管理和播放音频资源的类,特别适合播放短小的音效,如游戏中的爆炸声、按钮点击声等。与MediaPlayer相比,SoundPool具有以下优势: 低延迟:SoundPool适用于需要快速响应的音效播放。并发播…...
15_C语言 -构造类型
构造类型 数据类型 基本类型: 整型 short / short int(2字节)整型(默认) :int(4字节)长整型:long / long int (8字节 (64位系统)&am…...
Python爬虫 - 豆瓣电影排行榜数据爬取、处理与存储
文章目录 前言一、使用版本二、需求分析1. 分析要爬取的内容1.1 分析要爬取的分类1.2 分析要爬取的单个电影的数据1.3 分析如何获取单个电影数据1.3.1 预览数据1.3.2 查看请求网址、方法及请求头信息1.3.3 查看请求参数 2. 数据用途2.1 统计分析2.2 探索性数据分析 (EDA)2.3 高…...
知识碎片-环境配置
1.添加-i 下载快: pip install onnxruntime-gpu==X.X.X -i https://pypi.tuna.tsinghua.edu.cn/simple 清华源 pip install xxx -i https://pypi.tuna.tsinghua.edu.cn/simple/ 阿里源 pip install xxx -i https://mirrors.aliyun.com/pypi/simple 中科大源 pip install xxx -…...
手机实时提取SIM卡打电话的信令声音-智能拨号器的SIP线路-双卡双待单通方案
手机实时提取SIM卡打电话的信令声音 --智能拨号器的SIP线路-双卡双待单通方案 一、前言 蓝牙电话的技术方案最初是从蓝牙耳机和车机蓝牙的使用领域延伸出来的技术方式。通过蓝牙的HFP协议,把手机通话的声音和通话事件状态提取出来进行复用和处理。但中国大陆现行…...
LangChain教程 - 表达式语言 (LCEL) -构建智能链
系列文章索引 LangChain教程 - 系列文章 LangChain提供了一种灵活且强大的表达式语言 (LangChain Expression Language, LCEL),用于创建复杂的逻辑链。通过将不同的可运行对象组合起来,LCEL可以实现顺序链、嵌套链、并行链、路由以及动态构建等高级功能…...
pytorch学习笔记汇总
一.tensor创建 1.张量的定义 张量在形式上就是多维数组,例如标量就是0维张量,向量就是一维张量,矩阵就是二维张量,而三维张量就可以想象RGB图片,每个channel是一个二维的矩阵,共有三个channel࿰…...
uniapp配置文字艺术字体风格
1.vue配置 App.vue中: <style>/*每个页面公共css *//* 全局字体风格 */font-face {font-family: gufengFont;src: url(./static/font/gufeng.ttf) format(truetype);font-weight: normal;font-style: normal;} </style>页面中: .text1 {f…...
江科大学习笔记之——HAL库点亮一个LED灯
HAL三步走:1.建工程。2.设配置。3.写代码 一.建立工程 HAL库写法 点击FinIsh 2.配置时钟 2 、设置配置参数 把模块.C.h单独设置文件 生产代码 三写代码 控制GPIO就三步 1.RCC时钟使能 2.结构体配置GPIO寄存器 3.控制GPIO值 上面的步骤已经把前两步的配置完成了接下…...
java web项目软件自动生成使用初体验-帅帅软件生成平台ASoft
软件默认 登陆账号 admin 密码 123456 一、软件使用简介说 AI软件自动生成越来越成熟,但AI生成的软件代码只是片段化的,不成体系。有没有一款可以10-30分钟自动生成JAVA WEB休系的软件?我也找了好久,终于找到了,开发…...
损失函数-二分类和多分类
二分类和多分类的损失函数 二分类 损失函数 L ( y , y ^ ) − ( y l o g ( y ^ ) ) ( 1 − y ) l o g ( 1 − y ^ ) L(y,\hat{y}) -(ylog(\hat{y})) (1-y)log(1-\hat{y}) L(y,y^)−(ylog(y^))(1−y)log(1−y^) 其中真实标签表示为y(取值为 0 或 1&#…...
Springboot:后端接收数组形式参数
1、接收端写法 PermissionAnnotation(permissionName "",isCheckToken true)PostMapping("/batchDeleteByIds")public ReturnBean webPageSelf( NotNull(message "请选择要删除的单据!") Long[] ids) {for (Long string : ids) {l…...
CSS系列(46)-- Color Functions详解
前端技术探索系列:CSS Color Functions详解 🎨 致读者:探索颜色函数的艺术 👋 前端开发者们, 今天我们将深入探讨 CSS Color Functions,这个强大的颜色处理特性。 基础函数 🚀 颜色空间 /…...
MySQL数据库误删恢复_mysql 数据 误删
2、BigLog日志相关 2.1、检查biglog状态是否开启 声明: 当前为mysql版本5.7 当前为mysql版本5.7****当前为mysql版本5.7 2.1.1、Navicat工具执行 SHOW VARIABLES LIKE LOG_BIN%;OFF 是未开启状态,如果不是ON 开启状态需要开启为ON。{默认情况下就是关闭状态} 2.…...
ModiLeo交易平台:引领数字货币交易新未来
在当今数字化高速发展的时代,数字货币作为一种新兴的金融资产形式,正逐渐改变着全球金融格局。而此刻,由印度 ModiLeo 实验室联合全球顶级投行共同打造的全球领先的一站式数字货币交易平台——ModiLeo 即将上线,这无疑是数字货币领…...
57.插入区间 python
插入区间 题目题目描述示例 1:示例 2:提示: 题解解题思路python实现代码解释提交结果 题目 题目描述 给你一个 无重叠的 ,按照区间起始端点排序的区间列表 intervals,其中 intervals[i] [starti, endi] 表示第 i 个…...
使用WebRTC进行视频通信
一、WebRTC技术简介 什么是WebRTC? 是一种支持浏览器之间实时音频、视频和数据传输的开放源代码项目。它允许开发者在不需要任何第三方插件或软件的情况下实现点对点的实时通信。WebRTC已经成为现代Web应用中的关键技术,为开发者提供了强大的工具和API…...
详细讲解axios封装与api接口封装管理
一、axios封装 axios是基于promise的http客户端,用于浏览器和nodejs发送http请求 ,对它进行封装主要是为了统一管理请求配置和处理请求和响应的通用逻辑等。以下是常用的封装逻辑和要点 1:引入axios相关依赖 首先引用项目中的axios库&…...
likeAdmin架构部署(踩坑后的部署流程
1、gitee下载 https://gitee.com/likeadmin/likeadmin_java.git 自己克隆 2、项目注意 Maven:>3.8 ❤️.9 (最好不要3.9已经试过失败 node :node14 (不能是18 已经测试过包打不上去使用14的换源即可 JDK:JDK8 node 需要换源 npm c…...
算法-回文数判断
给你一个整数 x ,如果 x 是一个回文整数,返回 true ;否则,返回 false 。 回文数 是指正序(从左向右)和倒序(从右向左)读都是一样的整数。 例如,121 是回文,…...
力扣-数据结构-7【算法学习day.78】
前言 ###我做这类文章一个重要的目的还是给正在学习的大家提供方向(例如想要掌握基础用法,该刷哪些题?建议灵神的题单和代码随想录)和记录自己的学习过程,我的解析也不会做的非常详细,只会提供思路和一些关…...
计算机组成原理的学习笔记(8)-- 指令系统·其一 指令的组成以及数据寻址方式/RISK和CISK
学习笔记 前言 本文主要是对于b站尚硅谷的计算机组成原理的学习笔记,仅用于学习交流。 1. 指令 1.1 组成 操作码(Opcode):指指令中执行特定操作的部分。地址码:指令中用于指定操作数位置的部分。 1.2 扩展操作…...
Hive刷分区MSCK
一、MSCK刷分区 我们平时通常是通过alter table add partition方式增加Hive的分区的,但有时候会通过HDFS put/cp命令或flink、flum程序往表目录下拷贝分区目录,如果目录多,需要执行多条alter语句,非常麻烦。Hive提供了一个"…...
2024年12月HarmonyOS应用开发者基础认证全新题库
注意事项:切记在考试之外的设备上打开题库进行搜索,防止切屏三次考试自动结束,题目是乱序,每次考试,选项的顺序都不同,如果有两台电脑设备建议一台打开题库一台考试,如果只有一台电脑设备建议手…...
集成方案 | Docusign + 蓝凌 EKP,打造一站式合同管理平台,实现无缝协作!
本文将详细介绍 Docusign 与蓝凌 EKP 的集成步骤及其效果,并通过实际应用场景来展示 Docusign 的强大集成能力,以证明 Docusign 集成功能的高效性和实用性。 在当今数字化办公环境中,企业对于提高工作效率和提升用户体验的需求日益迫切。蓝凌…...
centos7 免安装mysql5.7及配置(支持多个mysql)
一) 下载免安装包: mysql下载地址: https://dev.mysql.com/downloads/mysql/下载时,选择以前5.7版本: image 下载第一个TAR压缩包: image 二) 定义安装路径并解压安装包 1、假设需要把MySQL放到 /usr/local…...
【若依框架】代码生成详细教程,15分钟搭建Springboot+Vue3前后端分离项目,基于Mysql8数据库和Redis5,管理后台前端基于Vue3和Element Plus,开发小程序数据后台
今天我们来借助若依来快速的搭建一个基于springboot的Java管理后台,后台网页使用vue3和 Element Plus来快速搭建。这里我们可以借助若依自动生成Java和vue3代码,这就是若依的强大之处,即便你不会Java和vue开发,只要跟着石头哥也可…...
什么是容器?
什么是容器? 容器是一种虚拟化技术,用于将应用程序及其所有依赖项打包在一起,以便在不同的计算环境中进行移植和运行。容器提供了一种隔离的运行环境,使不同应用程序能够在独立的文件系统、网络和进程空间等独立运行环境中运行&a…...
苍穹外卖——准备工作
模块介绍 后端的工程基于Maven进行项目构建,并且进行分模块开发,我们创建四个模块: sky-take-out:maven父工程,统一管理依赖版本,聚合其他子模块sky-common:子模块,存放公共类&…...
LLMs之o3:《Deliberative Alignment: Reasoning Enables Safer Language Models》翻译与解读
LLMs之o3:《Deliberative Alignment: Reasoning Enables Safer Language Models》翻译与解读 导读:2024年12月,这篇论文提出了一种名为“审慎式对齐 (Deliberative Alignment)”的新方法,旨在提高大型语言模型 (LLM) 的安全性。论…...
百度二面,MySQL 怎么做权重搜索?
考虑这样一个搜索需求,有一个 MySQL 表,表中很多个列存放着不同的内容,希望用户通过关键词进行搜索的时候,能够模糊匹配多个列,比如有 t1 列、t2 列、t3 列,同时还希望 t1 列的匹配权重最高,t3 …...
PHP:IntelliJ IDEA 配置 PHP 开发环境及导入PHP项目
在创建PHP项目之前我们需要安装PHP插件,安装步骤如下:Windows:IntelliJ IDEA Ultimate 安装 PHP 插件-CSDN博客 1、导入已有PHP项目,导入之后选择,File > Setting 选择对应CLL Interpreter,如果没有操作…...
国产数据库TiDB从入门到放弃教程
国家层面战略,安全的角度,硬件、软件国产化是趋势,鸿蒙电脑操作系统、鸿蒙手机操作系统…数据库也会慢慢国产化,国产数据库TiDB用起来比OceanBase丝滑,本身没有那么重。 从入门到放弃 1. 介绍1.1 TiDB 的主要特点1.2 T…...
Android 自定义控件
目录 Android 自定义控件 一、什么是自定义控件 二、创建自定义控件的常见方式 2.1继承现有控件(如 Button、TextView 等) 2.2直接继承 View 类 2.3组合控件 三、自定义控件的基本步骤 3.1创建一个继承自 View 或现有控件的类 3.2重写 onDraw()…...
学习笔记 --C#基础其他知识点(同步和异步)
C#中的同步和异步《一》 以下理解借鉴博客:借鉴博客地址1 异步编程(Asynchronous) 允许任务在后台执行,而不会阻塞调用线程。C#使用async和await关键字 async Task AsynchronousMethod() {// 等待异步操作完成await Task.Dela…...
药片缺陷检测数据集,8625张图片,使用YOLO,PASICAL VOC XML,COCO JSON格式标注,可识别药品是否有缺陷,是否完整
药片缺陷检测数据集,8625张图片,使用YOLO,PASICAL VOC XML,COCO JSON格式标注,可识别药品是否有缺陷,是否完整 有缺陷的标注信息: 无缺陷的标注信息 数据集下载: yolov11:https://d…...
Hive如何创建自定义函数(UDF)?
目录 1 自定义UDF函数基础 2 自定义UDF函数案例 3 创建临时函数 4 创建永久函数 1 自定义UDF函数基础 1. 内置函数:Hive 自带了一些函数...
深入理解MVCC:快照读与当前读的原理及实践
一、引言 MVCC是数据库系统中一种常见的并发控制技术,它允许多个事务同时对同一数据进行读取和修改,而不会相互干扰。在MVCC中,数据行存在多个版本,每个版本对应一个事务。本文将重点讨论MVCC中的两种读取方式:快照读…...
活动预告 |【Part1】Microsoft Azure 在线技术公开课:数据基础知识
课程介绍 参加“Azure 在线技术公开课:数据基础知识”活动,了解有关云环境和数据服务中核心数据库概念的基础知识。通过本次免费的介绍性活动,你将提升在关系数据、非关系数据、大数据和分析方面的技能。 活动时间:01 月 07 日…...
小程序笔记
1.小程序全局配置app.json {"pages":["pages/index/index","pages/logs/logs"],"window":{"backgroundTextStyle":"light","navigationBarBackgroundColor": "#fff","navigationBarTit…...
linux安装nginxs报错:openssl not found
系统: linux 版本:centOS7 nginx版本:nginx-1.20.2 linux安装nginx时 执行下面命令时报错: ./configure --with-http_stub_status_module --with-http_ssl_module --prefix/usr/local/nginxchecking for OpenSSL library ... not …...
Vite内网ip访问,两种配置方式和修改端口号教程
目录 问题 两种解决方式 结果 总结 preview.host preview.port 问题 使用vite运行项目的时候,控制台会只出现127.0.0.1(localhost)本地地址访问项目。不可以通过公司内网ip访问,其他团队成员无法访问,这是因为没…...
地理数据库Telepg面试内容整理-如何在高并发情况下保证GIS服务的高可用性?
在高并发情况下,保证 GIS 服务的高可用性是一个重要的挑战,尤其是当空间数据量巨大、请求频繁时。为了确保 GIS 服务的高可用性和稳定性,需要考虑以下几个方面: 分布式架构设计 分布式架构通过将工作负载分配到多个服务器上,能够大大提高服务的可用性和扩展性。通过设计高…...