Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

メッセージ受け渡しを高速化するための間接参照の利用

メッセージ受け渡しでは、常に送信側から静的変数へ、さらに静的変数から受信側へと、ペイロードのコピーが発生します。そのため、[u8; 128] のような大きなバッファをメッセージとして送信すると、高コストな memcpy が 2 回発生します。

間接参照を使うと、メッセージ受け渡しのオーバーヘッドを最小化できます。つまり、バッファを値として送る代わりに、バッファへの所有権を持つポインタを送ることができます。

間接参照を実現するには、グローバルメモリアロケータ(alloc::Boxalloc::Rc など)を使う方法がありますが、Rust v1.37.0 時点では nightly チャネルの使用が必要です。あるいは、heapless::Pool のような静的に確保されたメモリプールを使うこともできます。

このアプローチの例は、shared と local を持つ RTIC のリソースモデルを完全に外れるため、プログラムは、この場合 heapless::pool であるメモリアロケータの正しさに依存することになります。

以下は、heapless::Pool を使って 128 バイトのバッファを「box 化」する例です。

//! examples/pool.rs

#![no_main]
#![no_std]
#![deny(warnings)]

use panic_semihosting as _;
use rtic::app;

// thumbv6-none-eabi does not support pool
// This might be better worked around in the build system,
// but for proof of concept, let's try having one example
// being different for different backends
// https://docs.rs/heapless/0.8.0/heapless/pool/index.html#target-support
cfg_if::cfg_if! {
    if #[cfg(feature = "thumbv6-backend")] {
        // Copy of the smallest.rs example
        #[app(device = lm3s6965)]
        mod app {
            use cortex_m_semihosting::debug;

            #[shared]
            struct Shared {}

            #[local]
            struct Local {}

            #[init]
            fn init(_: init::Context) -> (Shared, Local) {
                debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator
                (Shared {}, Local {})
            }
        }
    } else {
        // Run actual pool example
        use heapless::{
            box_pool,
            pool::boxed::{Box, BoxBlock},
        };

        // Declare a pool containing 8-byte memory blocks
        box_pool!(P: u8);

        const POOL_CAPACITY: usize = 512;

        #[app(device = lm3s6965, dispatchers = [SSI0, QEI0])]
        mod app {
            use crate::{Box, BoxBlock, POOL_CAPACITY};
            use cortex_m_semihosting::debug;
            use lm3s6965::Interrupt;

            // Import the memory pool into scope
            use crate::P;

            #[shared]
            struct Shared {}

            #[local]
            struct Local {}

            const BLOCK: BoxBlock<u8> = BoxBlock::new();

            #[init(local = [memory: [BoxBlock<u8>; POOL_CAPACITY] = [BLOCK; POOL_CAPACITY]])]
            fn init(cx: init::Context) -> (Shared, Local) {
                for block in cx.local.memory {
                    // Give the 'static memory to the pool
                    P.manage(block);
                }

                rtic::pend(Interrupt::I2C0);

                (Shared {}, Local {})
            }

            #[task(binds = I2C0, priority = 2)]
            fn i2c0(_: i2c0::Context) {
                // Claim 128 u8 blocks
                let x = P.alloc(128).unwrap();

                // .. send it to the `foo` task
                foo::spawn(x).ok().unwrap();

                // send another 128 u8 blocks to the task `bar`
                bar::spawn(P.alloc(128).unwrap()).ok().unwrap();
            }

            #[task]
            async fn foo(_: foo::Context, _x: Box<P>) {
                // explicitly return the block to the pool
                drop(_x);

                debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator
            }

            #[task(priority = 2)]
            async fn bar(_: bar::Context, _x: Box<P>) {
                // this is done automatically so we can omit the call to `drop`
                // drop(_x);
            }
        }
    }
}
$ cargo xtask qemu --verbose --example pool