内核里常用宏BUG_ON/WARN_ON/WARN_ONCE
一、背景
内核代码里经常能看到一些用于检查一些条件的宏,如BUG_ON,WARN_ON等,这些宏除了直观的说明条件应该或者不应该被满足以外,还能方便我们进行规范的打印,让代码更加简洁,也同时满足的一些基本的要求,如在内核代码里进行打印时不建议直接使用printk,需要使用dev_printk或者至少得用pr_xx这样的打印宏。你若使用printk来进行打印输出的时候,如果用./scripts/checkpatch.pl来扫描你的改动的patch的话,如果发现是直接使用printk的话,checkpatch.pl的检查脚本会报出一个警告的。
这篇博客里,我们一次介绍一下这些基本的宏。
二、BUG/BUG_ON宏
我们搜索BUG_ON宏可以搜到,它是分为可以是ARCH代码来实现,也可以不用ARCH代码用公共的代码来实现:

如上图可以看到BUG_ON就是判断入参的条件,如果入参的条件是true,就调用BUG宏来触发panic。
CONFIG_BUG默认情况下就是打开的:

在没有定义arch的BUG_ON函数时,就用公共代码的BUG_ON实现。
可以看到是有有些arch下实现自己的BUG_ON的,搜索HAVE_ARCH_BUG_ON就可以搜到:

如mips平台的BUG_ON实现:

三、WARN_ON,WARN_ON_ONCE,WARN_ONCE宏
3.1 WARN_ON的实现
WARN_ON的下面的定义也是被包在一般都开的CONFIG_BUG宏里的:

常见的arm64平台和x86平台也都是用的上图里的定义。
我们进一步看__WARN宏的实现,__WARN宏根据有没有定义__WARN_FLAGS的情况,

来进行的定义:

而__WARN_FLAGS在x86和arm64平台都有相关定义:


我们追一下arm64平台的也就是上图里的__BUG_FLAGS是如何实现的,如上图看到__BUG_FLAGS使用了ASM_BUG_FLAGS,ASM_BUG_FLAGS定义如下:

可以看到上面图里红色框出的部分,也就是用了brk汇编指令,传入的是BUG_BRK_IMM,定义及如下:

3.2 WARN_ON_ONCE和WARN_ONCE的区别
WARN_ON_ONCE和WARN_ONCE在内核代码里大量使用,它们俩是有区别的,虽然区别并不复杂,但是却很有用,且容易被忽视。
WARN_ON_ONCE的定义如下:

它是无法输入自定义的字符串的。
而WARN_ONCE则不同,可以输入自定义的字符串:

这个区别很关键,有时候就需要定义自己的日志内容。
3.3 WARN_ONCE如何确保只打印一次
接着上面分析的WARN_ONCE宏来看一下WARN_ONCE宏所调用的DO_ONCE_LITE_IF宏是如何实现的:

如上图可以看到DO_ONCE_LITE_IF宏使用了__ONCE_LITE_IF宏来判断是否已经打印了一次。
而__ONCE_LITE_IF宏声明了一个static的bool类型的变量,这样就可以保留之前是否已经打印一次的状态了。
3.4 如何清楚打印了一次的标记
可以从上面 3.3 的分析里可以看到,在声明用于判断是否打印一次的变量时,用得是__section(".data.once")的段,这个为什么要用这个段呢,因为这个段可以让系统针对这些WARN_ONCE的标记的变量统一进行一些批处理。
下图里的clear_warn_once_set函数就是对该.data.once段的所有标记位进行清除:

而clear_warn_once_set被集成进clear_warn_once_fops后,

最后暴露出debugfs的用户态可以进行控制的节点:

有关的可以清楚WARN_ONCE标记的命令如下:
echo 1 > /sys/kernel/debug/clear_warn_once