运行时断言
Zephyr提供了一些宏来执行运行时断言,这些断言可能是有条件编译的。它们的定义可以在inclus/zephyr/sys/__assert.h
中找到。
通过将__ASSERT_ON
预处理器符号设置为非零值来启用断言。有两种方法可以做到这一点:
- 使用
CONFIG_ASSERT
和CONFIG_ASSERT_LEVEL
的 kconfig 选项来配置。 - 将
-D__ASSERT_ON=<level>
添加到项目的CFLAGS
。
如果同时使用kconfig选项
和CFLAGS
的定义,则CFLAGS
的定义优先于kconfig
选项。
将断言级别
指定为1会导致编译器发出警告,指出内核包含调试类型语句。发出此提醒是因为断言代码通常不存在于最终产品中。指定断言级别2
会禁止显示这些警告。
有关在遇到失败的断言时要执行的操作的策略由assert_post_action()
的实现控制。Zephyr提供了一个具有弱链接的默认实现,如果断言失败的线程在用户模式下运行,则会调用内核oops,否则会调用内核崩溃。
__ASSERT()
__ASSERT()
宏可以在内核和应用程序代码中使用,以执行可选的运行时检查,如果检查未通过,这将引发致命错误。该宏采用一个字符串消息,该消息将被打印以提供断言的上下文。此外,内核将打印已计算的表达式代码的文本表示形式,以及在其中找到断言的文件和行号。
例如:
__ASSERT(foo == 0xF0CACC1A, "Invalid value of foo, got 0x%x", foo);
如果在运行时foo
具有某些意外值,则生成的错误可能如下所示:
ASSERTION FAIL [foo == 0xF0CACC1A] @ ZEPHYR_BASE/tests/kernel/fatal/src/main.c:367
Invalid value of foo, got 0xdeadbeef
[00:00:00.000,000] <err> os: r0/a1: 0x00000004 r1/a2: 0x0000016f r2/a3: 0x00000000
[00:00:00.000,000] <err> os: r3/a4: 0x00000000 r12/ip: 0x00000000 r14/lr: 0x00000a6d
[00:00:00.000,000] <err> os: xpsr: 0x61000000
[00:00:00.000,000] <err> os: Faulting instruction address (r15/pc): 0x00009fe4
[00:00:00.000,000] <err> os: >>> ZEPHYR FATAL ERROR 4: Kernel panic
[00:00:00.000,000] <err> os: Current thread: 0x20000414 (main)
[00:00:00.000,000] <err> os: Halting system
__ASSERT_EVAL()
__ASSERT_EVAL()
宏还可以在内核和应用程序代码中使用,并具有用于评估其参数的特殊语义。
它利用__ASSERT()
宏,但具有一些额外的灵活性。它允许开发人员根据是否启用__ASSERT()
宏来指定不同的操作。这对于防止编译器生成有关变量的注释(错误、警告或备注)特别有用,这些变量仅在赋值时使用,但在禁用__ASSERT()
宏时未使用。
请考虑以下示例:
int x;
x = foo();
__ASSERT(x != 0, "foo() returned zero!");
如果__ASSERT()
禁用,则为x
分配一个值,但从不使用。可以使用__ASSERT_EVAL()
宏解决此类情况。
__ASSERT_EVAL ((void) foo(),
int x = foo(),
x != 0,
"foo() returned zero!");
第一个参数告诉如果__ASSERT
禁用,该怎么办。第二个参数指示如果__ASSERT
启用,该怎么办。第三个和第四个参数是它传递给__ASSERT()
的参数。
__ASSERT_NO_MSG()
该宏可用于执行断言,该断言报告失败的测试及其位置,但缺少用于帮助用户诊断问题的其他调试信息。不鼓励使用它。
编译时断言
BUILD_ASSERT()
BUILD_ASSERT()
与printf()
不同,消息必须是静态字符串
,没有类似格式的代码或额外的参数。
例如:
BUILD_ASSERT(FOO == 2000, "Invalid value of FOO");
对于 GCC,输出类似于:
tests/kernel/fatal/src/main.c: In function 'test_main':
include/toolchain/gcc.h:28:37: error: static assertion failed: "Invalid value of FOO"
#define BUILD_ASSERT(EXPR, MSG) _Static_assert(EXPR, "" MSG)
^~~~~~~~~~~~~~
tests/kernel/fatal/src/main.c:370:2: note: in expansion of macro 'BUILD_ASSERT'
BUILD_ASSERT(FOO == 2000,
^~~~~~~~~~~~~~~~
内核OOPS
内核oops
是由k_oops()
调用的软件触发的致命错误。这应该用于指示应用程序逻辑中的不可恢复条件。
生成的致命错误原因代码将为K_ERR_KERNEL_OOPS
。
内核崩溃
内核崩溃是由k_panic()
调用的软件触发的致命错误。这应该用于指示Zephyr内核处于不可恢复状态。如果内核遇到崩溃情况,则会调用k_sys_fatal_error_handler()
的实现,需要重置整个系统。
在用户模式下运行的线程不允许调用k_panic()
,这样做会生成一个内核oops。生成的致命错误原因码将为K_ERR_KERNEL_PANIC
。
致命错误处理
遇到致命错误时要执行的操作的策略由k_sys_fatal_error_handler()
函数的实现。此函数具有弱链接的默认实现,该实现调用LOG_PANIC()
转储所有挂起的日志记录消息,然后使用k_fatal_halt()
无条件地暂停系统。
应用程序可以通过重写k_sys_fatal_error_handler()
的实现来自由实现自己的错误处理策略。