运行时断言

Zephyr提供了一些宏来执行运行时断言,这些断言可能是有条件编译的。它们的定义可以在inclus/zephyr/sys/__assert.h中找到。
通过将__ASSERT_ON预处理器符号设置为非零值来启用断言。有两种方法可以做到这一点:

  • 使用CONFIG_ASSERTCONFIG_ASSERT_LEVEL的 kconfig 选项来配置。
  • -D__ASSERT_ON=<level>添加到项目的CFLAGS

如果同时使用kconfig选项CFLAGS的定义,则CFLAGS的定义优先于kconfig选项。
断言级别指定为1会导致编译器发出警告,指出内核包含调试类型语句。发出此提醒是因为断言代码通常不存在于最终产品中。指定断言级别2会禁止显示这些警告。
有关在遇到失败的断言时要执行的操作的策略由assert_post_action()的实现控制。Zephyr提供了一个具有弱链接的默认实现,如果断言失败的线程在用户模式下运行,则会调用内核oops,否则会调用内核崩溃。

__ASSERT()

__ASSERT()宏可以在内核和应用程序代码中使用,以执行可选的运行时检查,如果检查未通过,这将引发致命错误。该宏采用一个字符串消息,该消息将被打印以提供断言的上下文。此外,内核将打印已计算的表达式代码的文本表示形式,以及在其中找到断言的文件和行号。
例如:

  1. __ASSERT(foo == 0xF0CACC1A, "Invalid value of foo, got 0x%x", foo);

如果在运行时foo具有某些意外值,则生成的错误可能如下所示:

  1. ASSERTION FAIL [foo == 0xF0CACC1A] @ ZEPHYR_BASE/tests/kernel/fatal/src/main.c:367
  2. Invalid value of foo, got 0xdeadbeef
  3. [00:00:00.000,000] <err> os: r0/a1: 0x00000004 r1/a2: 0x0000016f r2/a3: 0x00000000
  4. [00:00:00.000,000] <err> os: r3/a4: 0x00000000 r12/ip: 0x00000000 r14/lr: 0x00000a6d
  5. [00:00:00.000,000] <err> os: xpsr: 0x61000000
  6. [00:00:00.000,000] <err> os: Faulting instruction address (r15/pc): 0x00009fe4
  7. [00:00:00.000,000] <err> os: >>> ZEPHYR FATAL ERROR 4: Kernel panic
  8. [00:00:00.000,000] <err> os: Current thread: 0x20000414 (main)
  9. [00:00:00.000,000] <err> os: Halting system

__ASSERT_EVAL()

__ASSERT_EVAL()宏还可以在内核和应用程序代码中使用,并具有用于评估其参数的特殊语义。
它利用__ASSERT()宏,但具有一些额外的灵活性。它允许开发人员根据是否启用__ASSERT()宏来指定不同的操作。这对于防止编译器生成有关变量的注释(错误、警告或备注)特别有用,这些变量仅在赋值时使用,但在禁用__ASSERT()宏时未使用。
请考虑以下示例:

  1. int x;
  2. x = foo();
  3. __ASSERT(x != 0, "foo() returned zero!");

如果__ASSERT()禁用,则为x分配一个值,但从不使用。可以使用__ASSERT_EVAL()宏解决此类情况。

  1. __ASSERT_EVAL ((void) foo(),
  2. int x = foo(),
  3. x != 0,
  4. "foo() returned zero!");

第一个参数告诉如果__ASSERT禁用,该怎么办。第二个参数指示如果__ASSERT启用,该怎么办。第三个和第四个参数是它传递给__ASSERT()的参数。

__ASSERT_NO_MSG()

该宏可用于执行断言,该断言报告失败的测试及其位置,但缺少用于帮助用户诊断问题的其他调试信息。不鼓励使用它。

编译时断言

BUILD_ASSERT()

BUILD_ASSERT()printf()不同,消息必须是静态字符串,没有类似格式的代码或额外的参数。
例如:

  1. BUILD_ASSERT(FOO == 2000, "Invalid value of FOO");

对于 GCC,输出类似于:

  1. tests/kernel/fatal/src/main.c: In function 'test_main':
  2. include/toolchain/gcc.h:28:37: error: static assertion failed: "Invalid value of FOO"
  3. #define BUILD_ASSERT(EXPR, MSG) _Static_assert(EXPR, "" MSG)
  4. ^~~~~~~~~~~~~~
  5. tests/kernel/fatal/src/main.c:370:2: note: in expansion of macro 'BUILD_ASSERT'
  6. BUILD_ASSERT(FOO == 2000,
  7. ^~~~~~~~~~~~~~~~

内核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()的实现来自由实现自己的错误处理策略。