1 个不稳定版本

0.1.0-alpha.32019年10月15日
0.1.0-alpha.2 2019年6月13日
0.1.0-alpha.1 2019年5月10日

#1882 in 嵌入式开发

MIT/Apache

19KB
459

μAMP

一个用于构建裸机 AMP (非对称多处理) 应用的 (微) 框架

特性

  • 允许您编写单源多核应用程序

  • 为处理器间通信提供零成本共享内存抽象。

  • 使用内置的条件编译特性(#[cfg(core = "0")] / cfg!(core = "1"))来在不同核心之间划分您的应用程序。

计划中的特性

  • 多目标支持。

该框架目前使用 相同 的编译目标为所有核心。这限制了支持的系统为同构多核设备(所有核心使用完全相同的指令集)以及异构多核设备(核心共享最低公共指令集,例如,Cortex-M4 + Cortex-M0 设备中的两个核心都可以运行编译为 thumbv6m-none-eabi 的程序)。

我们需要设计(并测试)一个命令行标志来指定或覆盖每个核心的编译目标。

已知限制

  • 该框架目前仅支持 ARM 架构。

为了解除这个限制,我们需要在 Rust 中实现 strip 命令的功能。具体来说,这个调用需要移植到 Rust:strip -R '*' -R '!.shared' --strip-unneeded 。如果您知道任何可以做到这一点的 crate,请在问题跟踪器中告诉我!

已知问题

  • 在核心之间发送/共享函数指针或特质对象可能是不可靠的,或者至少是一个非常糟糕的想法,但在函数指针的情况下,这并没有被完全拒绝。

框架试图在编译时防止这种操作。目前所有特质对象都被拒绝,但只有具有 0 到 12 个参数的函数指针被拒绝。要拒绝所有函数指针,我们需要可变泛型 (VG) 语言特性。

示例

这是一个在均质双核设备(2x Cortex-R5 核心)上运行的程序。

#![no_main]
#![no_std]

use core::sync::atomic::{AtomicU8, Ordering};

use arm_dcc::dprintln;
use microamp::shared;
use panic_dcc as _; // panic handler
use zup_rt::entry;

// non-atomic variable
#[shared] // <- means: same memory location on all the cores
static mut SHARED: u64 = 0;

// used to synchronize access to `SHARED`
#[shared]
static SEMAPHORE: AtomicU8 = AtomicU8::new(CORE0);

// possible values for SEMAPHORE
const CORE0: u8 = 0;
const CORE1: u8 = 1;
const LOCKED: u8 = 2;

#[entry]
fn main() -> ! {
    let (our_turn, next_core) = if cfg!(core = "0") {
        (CORE0, CORE1)
    } else {
        (CORE1, CORE0)
    };

    dprintln!("START");

    let mut done = false;
    while !done {
        // try to acquire the lock
        while SEMAPHORE
            .compare_exchange(our_turn, LOCKED, Ordering::AcqRel, Ordering::Relaxed)
            .is_err()
        {
            // busy wait if the lock is held by the other core
        }

        // we acquired the lock; now we have exclusive access to `SHARED`
        unsafe {
            if SHARED >= 10 {
                // stop at some arbitrary point
                done = true;
            } else {
                dprintln!("{}", SHARED);

                SHARED += 1;
            }
        }

        // release the lock & unblock the other core
        SEMAPHORE.store(next_core, Ordering::Release);
    }

    dprintln!("DONE");

    loop {}
}

在这个示例中,我们有两个在共享内存中的静态变量,对两个核心都可见(*)。其中一个变量,SEMAPHORE,用于同步对非原子的 SHARED 变量的访问。两个核心都会在引导时执行 main 函数,但由于使用了 cfg! 宏,它们将执行略微不同的代码路径。

构建应用程序时,我们使用以下命令

$ cargo microamp --bin app --release
   Compiling zup-rtfm v0.1.0 (/tmp/firmware)
    Finished dev [unoptimized + debuginfo] target(s) in 0.32s
   Compiling zup-rtfm v0.1.0 (/tmp/firmware)
    Finished dev [unoptimized + debuginfo] target(s) in 0.12s
   Compiling zup-rtfm v0.1.0 (/tmp/firmware)
    Finished dev [unoptimized + debuginfo] target(s) in 0.12s

默认情况下,该命令会产生两个镜像,每个核心一个。

$ # image for first core
$ size -Ax target/armv7r-none-eabi/release/examples/app-0
target/armv7r-none-eabi/release/examples/app-0  :
section             size         addr
.text              0x360          0x0
.local               0x0      0x20000
.bss                 0x0   0xfffc0000
.data                0x0   0xfffc0000
.rodata             0x40   0xfffc0000
.shared             0x10   0xfffe0000

$ # image for second core
$ size -Ax target/armv7r-none-eabi/release/examples/app-1
target/armv7r-none-eabi/release/examples/app-1  :
section             size         addr
.text              0x360          0x0
.local               0x0      0x20000
.bss                 0x0   0xfffd0000
.data                0x0   0xfffd0000
.rodata             0x40   0xfffd0000
.shared             0x10   0xfffe0000

如果我们在这台核心 #0 上运行镜像,我们会看到

$ # on another terminal: load and run the program
$ CORE=0 xsdb -interactive debug.tcl amp-shared-0

$ # output of core #0
$ tail -f dcc0.log
START
0

程序停止,因为它正在等待另一个核心。现在,我们在核心 #1 上运行另一个镜像。

$ # on another terminal: load and run the program
$ CORE=1 xsdb -interactive debug.tcl amp-shared-1

$ # output of core #1
$ tail -f dcc1.log
START
1
3
5
7
9
DONE

然后我们会从核心 #0 获得新的输出。

$ # output of core #0
$ tail -f dcc0.log
START
0
2
4
6
8
DONE

(*) 重要 所有未标记为 #[shared] 的静态变量将在每个核心中 实例化,并且很可能具有不同的地址(即使它们具有相同的符号名称),这是由于编译器优化和链接脚本差异造成的。例如,非 #[shared] 变量 static mut X: u32 在一个镜像中的地址可能是 0xffe20000,而在另一个镜像中的地址可能是 0xffeb0000

要求

用户或 crate 必须为每个核心提供一个链接脚本 。cargo-microamp 工具将使用这些链接脚本为每个核心链接程序,并期望它们命名为 core0.xcore1.x 等。

cargo-microamp 将在链接每个镜像时传递一个名为 microamp-data.o 的文件。这个目标文件包含所有名为 .shared#[shared] 变量。这些变量必须放在名为 .shared 的输出部分。这个部分必须在所有镜像中位于 相同 的地址。例如

$ cat core0.x
SECTIONS
{
  /* .. */

  .shared : ALIGN(4)
  {
    KEEP(microamp-data.o(.shared));
    . = ALIGN(4);
  } > OCM0

  /* .. */
}
$ cat core1.x
SECTIONS
{
  /* .. */

  /* NOTE(NOLOAD) core 0 will initialize this shared section  */
  .shared (NOLOAD) : ALIGN(4)
  {
    KEEP(microamp-data.o(.shared));
    . = ALIGN(4);
  } > OCM0

  /* .. */
}

此外,必须注意 不要 多次初始化这个 .shared 链接部分。在上面的例子中,共享变量是在将第一个镜像加载到内存中时初始化的。

许可

所有源代码(包括代码片段)均受以下任一许可协议的许可:

您可选择其中之一。

本书中的书面文本受 Creative Commons CC-BY-SA v4.0 许可协议的条款约束(《LICENSE-CC-BY-SA》或 https://creativecommons.org/licenses/by-sa/4.0/legalcode)。

贡献

除非您明确说明,否则根据 Apache-2.0 许可证定义的,您有意提交以包含在工作中的任何贡献,应按上述方式许可,不附加任何额外条款或条件。

依赖项

~2–10MB
~91K SLoC