1 个不稳定版本
0.1.0-alpha.1 | 2019 年 11 月 27 日 |
---|
#527 在 #log
30KB
440 行
cortex-m-funnel
ARM Cortex-M 架构的无锁、无等待、无阻塞的日志记录器
(无锁是指在日志记录时不阻塞中断处理程序;无等待是指在获取句柄时没有自旋(例如 CAS 循环);无阻塞是指在日志记录器不会等待 I/O 传输(例如 ITM、UART 等)完成的情况下)
状态:☢️ 实验性 ☢️(alpha 预发布版)
文档
许可证
根据您的要求,许可协议可以是以下之一
- Apache 许可证 2.0(LICENSE-APACHE 或 https://apache.ac.cn/licenses/LICENSE-2.0)
- MIT 许可证(LICENSE-MIT 或 http://opensource.org/licenses/MIT)
任选其一。
贡献
除非您明确说明,否则根据 Apache-2.0 许可证定义的,您有意提交的任何贡献,均可根据上述协议双重许可,而无需任何附加条款或条件。
lib.rs
:
ARM Cortex-M 架构的无锁、无等待、无阻塞的日志记录器
(无锁是指在日志记录时不阻塞中断处理程序;无等待是指在获取句柄时没有自旋(例如 CAS 循环);无阻塞是指在日志记录器不会等待 I/O 传输(例如 ITM、UART 等)完成的情况下)
状态:☢️ 实验性 ☢️(alpha 预发布版)
非常重要 在多线程环境中使用此包将导致程序不稳定!已提醒您!另外,还没有考虑多核支持,因此在多核上下文中使用时可能不正确。
工作原理
每个优先级都有一个环形缓冲区。来自中断/异常处理程序的日志将简单地将这些消息写入其中一个环形缓冲区。因此,日志记录实际上是“无 I/O”的,并且与 memcpy
一样快。只有“线程处理器”(也称为 RTFM 应用程序中的 main
或 idle
)可以将这些环形缓冲区中的内容排入适当的 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
功能将禁用 Debug
和 Trace
日志级别;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