#logging #cortex-m #arm #log

no-std cortex-m-funnel

ARM Cortex-M 架构的无锁、无等待、无阻塞的日志记录器

1 个不稳定版本

0.1.0-alpha.12019 年 11 月 27 日

#527#log

MIT/Apache

30KB
440

cortex-m-funnel

ARM Cortex-M 架构的无锁、无等待、无阻塞的日志记录器

(无锁是指在日志记录时不阻塞中断处理程序;无等待是指在获取句柄时没有自旋(例如 CAS 循环);无阻塞是指在日志记录器不会等待 I/O 传输(例如 ITM、UART 等)完成的情况下)

状态:☢️ 实验性 ☢️(alpha 预发布版)

文档

许可证

根据您的要求,许可协议可以是以下之一

任选其一。

贡献

除非您明确说明,否则根据 Apache-2.0 许可证定义的,您有意提交的任何贡献,均可根据上述协议双重许可,而无需任何附加条款或条件。


lib.rs:

ARM Cortex-M 架构的无锁、无等待、无阻塞的日志记录器

(无锁是指在日志记录时不阻塞中断处理程序;无等待是指在获取句柄时没有自旋(例如 CAS 循环);无阻塞是指在日志记录器不会等待 I/O 传输(例如 ITM、UART 等)完成的情况下)

状态:☢️ 实验性 ☢️(alpha 预发布版)

非常重要 在多线程环境中使用此包将导致程序不稳定!已提醒您!另外,还没有考虑多核支持,因此在多核上下文中使用时可能不正确。

工作原理

每个优先级都有一个环形缓冲区。来自中断/异常处理程序的日志将简单地将这些消息写入其中一个环形缓冲区。因此,日志记录实际上是“无 I/O”的,并且与 memcpy 一样快。只有“线程处理器”(也称为 RTFM 应用程序中的 mainidle)可以将这些环形缓冲区中的内容排入适当的 I/O 汇聚器(例如 ITM)。

在这个世界上,没有什么是没有权衡的;这个日志记录器使用大量的静态内存(即 RAM)来换取快速和可预测的日志性能。此外,与直接进行 I/O 的日志记录器相比,这个日志记录器在总体上可能会花费更多的 CPU 周期来记录相同数量的数据,但大部分工作将在最低优先级完成,使得在中断处理程序中的日志记录更快。

示例

常规设置

应用程序包

// aligned = "0.3.2"
use aligned::Aligned;
use cortex_m::itm;

use funnel::{Drain, funnel, info, trace};

// `NVIC_PRIO_BITS` is the number of priority bits supported by the device
//
// The `NVIC_PRIO_BITS` value can be a literal integer (e.g. `3`) or a path to a constant
// (`stm32f103xx::NVIC_PRIO_BITS`)
//
// This macro call can only appear *once* in the dependency graph and *must* appear if any
// of the `funnel` macros or the `Logger::get()` API is used anywhere in the dependency graph
funnel!(NVIC_PRIO_BITS = 3, {
     // syntax: $logical_priority : $ring_buffer_size_in_bytes
     // to get better performance use sizes that are a power of 2
     1: 32,
     2: 64,

     // not listing a priority here disables logging at that priority level
     // entering the wrong NVIC_PRIO_BITS value will disable most loggers
});

#[entry]
fn main() -> ! {
    // ..
    let mut itm: ITM = /* .. */;

    let drains = Drain::get_all();

    let mut buf = Aligned([0; 32]); // 4-byte aligned buffer
    loop {
        for (i, drain) in drains.iter().enumerate() {
            'l: loop {
                let n = drain.read(&mut buf).len();

                // this drain is empty
                if n == 0 {
                    break 'l;
                }

                // we need this coercion or the slicing below won't do the right thing
                let buf: &Aligned<_, [_]> = &buf;

                // will send data in 32-bit chunks
                itm::write_aligned(&mut itm.stim[i], &buf[..n]);
            }
        }
    }
}

// logical_priority = 1 (nvic_priority = 224)
#[interrupt]
fn GPIOA() {
    info!("GPIOA");
    foo(0);
    // ..
}

// logical_priority = 2 (nvic_priority = 192)
#[interrupt]
fn GPIOB() {
    info!("GPIOB");
    foo(1);
    // ..
}

fn foo(x: i32) {
    // this macro can appear in libraries
    trace!("foo({})", x);
    // ..
}

日志记录器

可以使用 Logger 上的 uwrite! 宏之一来减少每个宏调用带来的开销。只能使用 Logger::get() 构造函数来获取 Logger

use funnel::{Logger, log_enabled};

#[interrupt]
fn GPIOC() {
    if let Some(mut logger) = Logger::get() {
         if log_enabled!(Info) {
             uwriteln!(logger, "{}", 100).ok();
             uwriteln!(logger, "{:?}", some_value).ok();
         }
    }
}

日志级别

funnel 支持五种日志级别:跟踪、调试、信息、警告和错误,按照严重程度递增排序。每个日志级别都有一个相关的日志宏:trace!debug!info!warn!error!

可以使用这些 Cargo 功能静态地禁用 较轻 严重程度的日志。

  • max_level_trace
  • max_level_debug
  • max_level_info
  • max_level_warn
  • max_level_error
  • max_level_off
  • release_max_level_trace
  • release_max_level_debug
  • release_max_level_info
  • release_max_level_warn
  • release_max_level_error
  • release_max_level_off

启用 max_level_info 功能将禁用 DebugTrace 日志级别;max_level_off 将禁用所有日志级别。`release_` 相关功能适用于使用 'release' 配置文件编译的应用程序;其他功能适用于使用 'dev' 配置文件时。要检查代码中是否启用了或禁用了某个日志级别,请使用 log_enabled! 宏。

基准测试

在频率为 8 MHz 的 Cortex-M3 内核上运行,并配置了 0 个闪存等待周期。

代码 周期
info!("") 36
uwriteln!(logger, "") 15
drain("") 27
info!("{}",S) 331-369
uwriteln!(logger, "{}",S) 308-346
drain(S) 863-916
iprintln!(_, "{}",S) 1652
info!("{}",N) 348-383
uwriteln!(logger, "{}",N) 329-364
drain(N) 217-230

其中 S 是一个 45 字节长的字符串,N = usize::max_value(),`drain` 函数正在 ptr::read_volatile 每个字节,并且 ITM 的时钟频率为 2 MHz。

潜在改进/替代方案

除了在最低优先级下清除环形缓冲区之外,还可以使用类似 SEGGER 的实时传输 的机制通过调试器清除缓冲区。实现需要更改以正确支持这种并行清除形式。

依赖项

~1.5MB
~38K SLoC