embassy book

最佳实践

随着时间的推移,一些最佳实践逐渐形成。以下内容将作为开发者在使用 Rust 编写嵌入式软件时的指导方针,特别是在使用 Embassy 框架的场景下。

通过引用传递缓冲区

在编程时,我们可能会倾向于像使用 std::Vec 那样传递数组或者像 heapless::Vec 这样的包装器,或者从函数返回它们。然而,在大多数嵌入式应用中,我们并不想在分配器上消耗资源,最终不得不在栈上放置缓冲区。如果不够谨慎,这很容易导致栈溢出。

考虑以下示例:

fn process_buffer(mut buf: [u8; 1024]) -> [u8; 1024] {
    // 处理并返回新的缓冲区
    for elem in buf.iter_mut() {
        *elem = 0;
    }
    buf
}

pub fn main() -> () {
    let buf = [1u8; 1024];
    let buf_new = process_buffer(buf);
    // 使用 buf_new 做一些操作
    ()
}

当在程序中调用 process_buffer 时,传递给函数的缓冲区将被复制一份,消耗额外的 1024 字节。 处理完成后,另一个 1024 字节的缓冲区将被放置在栈上并返回给调用者。 (你可以查看汇编代码,当为 Cortex-M 处理器编译时,会有两个内存复制操作,例如 bl __aeabi_memcpy

可能的解决方案:

在输入和输出时都通过引用而不是值来传递数据。 例如,你可以返回输入缓冲区的一个切片作为输出。 通过要求输入切片和输出切片具有相同的生命周期,编译器将强制保证这个过程的内存安全。

fn process_buffer<'a>(buf: &'a mut [u8]) -> &'a mut[u8] {
    for elem in buf.iter_mut() {
        *elem = 0;
    }
    buf
}

pub fn main() -> () {
    let mut buf = [1u8; 1024];
    let buf_new = process_buffer(&mut buf);
    // 使用 buf_new 做一些操作
    ()
}