Multiple tasks/processes on core1?

I'm experimenting with multicore, and I got one (async) task running on core1, but is it possible to run more?

When I try, I get:

error[E0382]: use of moved value: `p.CORE1`
   --> src/drive-by-wire.rs:168:9
    |
107 |         p.CORE1,
    |         ------- value moved here
...
168 |         p.CORE1,
    |         ^^^^^^^ value used here after move
    |
    = note: move occurs because `p.CORE1` has type `Peri<'_, CORE1>`, which does not implement the `Copy` trait

For more information about this error, try `rustc --explain E0382`.
error: could not compile `drive-by-wire` (bin "drive-by-wire") due to 1 previous error

First p.CORE1 is from the first (successful) spawn of a task on core1. The other is the second one..

Please provide more details about your code, otherwise we can't do much to help you.

2 Likes

Sorry, I have a different question elsewhere, where I was writing a small novel :slight_smile: .

Issues are somewhat related, but what I'm doing here is spawning off one of the tasks that's going to require as much time to run as possible.

It's not at all memory or CPU intensive (I haven't actually started that one yet, but it's next on the list), I just can't risk losing a message. It's not 101% real-time, but I think the closer I get, the better for the rest of the apps..

    //  5. Initialize the CAN bus. Needs to come third, so we can talk to the IC.
    spawn_core1(
        p.CORE1,
        unsafe { &mut *core::ptr::addr_of_mut!(CORE1_STACK) },
        move || {
            let executor1 = EXECUTOR1.init(Executor::new());
            executor1.run(|spawner| {
                spawner.spawn(unwrap!(read_can()))
            })
        }
    );

Then a few lines further down, I want another task on CORE1 - I paid £5 for the darn device, I should be able to utilize it fully!! :smiley: :smiley::

    // Actuator works. Spawn off the actuator control task.
    spawn_core1(
        p.CORE1,
        unsafe { &mut *core::ptr::addr_of_mut!(CORE1_STACK) },
        move || {
            let executor0 = EXECUTOR0.init(Executor::new());
            executor0.run(|spawner| {
                spawner.spawn(unwrap!(actuator_control(
                    CHANNEL_ACTUATOR.receiver(),
                    flash,
                    actuator
                )))
            });
        },
    );

That's when I hit that error. How DO you run multiple tasks on CORE1?

Is "it" (Embassy I guess, which is my framework of choice) smart enough to understand that if I spawn a "main" task/process (not main(), but a "main for CORE1") on CORE1, then spawn off more tasks from that, that those would then run on the same core as the "main" task?

For example, I spawn off a total of 12 (!) tasks in total (two of those above) from main(). So in my main(), in addition to those two above, I also use:

    spawner.spawn(unwrap!(feed_watchdog(
        CHANNEL_WATCHDOG.receiver(),
        watchdog
    )));
    [...]
    spawner.spawn(unwrap!(write_can(CHANNEL_CANWRITE.receiver())));

Etcetera. They all now (except read_can() which I was successful to spawn off on CORE1) run on CORE0. But it would be nice if I could split them up on the core "half-evenly" to utilize the hardware as much as possible.

Let me put it this way: When you have a compilation error, it's more important if you provide us with the types of the code involved, preferably with a reproducible example; explaining us the logic is less relevant, to put it mildly.

3 Likes

That's what I thought I did..

The code base is quite large. Over 5k lines.. How do I know what to include?

This works fine (complete program):

#![no_std]
#![no_main]

use defmt::{info, unwrap};
use embassy_executor::{Executor, Spawner};
use embassy_rp::multicore::{Stack, spawn_core1};
use static_cell::StaticCell;
use {defmt_rtt as _, panic_probe as _};

static mut CORE1_STACK: Stack<4096> = Stack::new();
static EXECUTOR1: StaticCell<Executor> = StaticCell::new();

#[embassy_executor::main]
async fn main(_spawner: Spawner) {
    let p = embassy_rp::init(Default::default());

    spawn_core1(
        p.CORE1,
        unsafe { &mut *core::ptr::addr_of_mut!(CORE1_STACK) },
        move || {
            let executor1 = EXECUTOR1.init(Executor::new());
            executor1.run(|spawner| {
                spawner.spawn(unwrap!(core1_task1()))
            })
        }
    );
}

#[embassy_executor::task]
pub async fn core1_task1() {
    info!("Hello from core1/task1");

    loop {}
}

However, this does not:

#![no_std]
#![no_main]

use defmt::{info, unwrap};
use embassy_executor::{Executor, Spawner};
use embassy_rp::multicore::{Stack, spawn_core1};
use static_cell::StaticCell;
use {defmt_rtt as _, panic_probe as _};

static mut CORE1_STACK: Stack<4096> = Stack::new();
static EXECUTOR1: StaticCell<Executor> = StaticCell::new();
static EXECUTOR2: StaticCell<Executor> = StaticCell::new();

#[embassy_executor::main]
async fn main(_spawner: Spawner) {
    let p = embassy_rp::init(Default::default());

    spawn_core1(
        p.CORE1,
        unsafe { &mut *core::ptr::addr_of_mut!(CORE1_STACK) },
        move || {
            let executor1 = EXECUTOR1.init(Executor::new());
            executor1.run(|spawner| {
                spawner.spawn(unwrap!(core1_task1()))
            })
        }
    );

    spawn_core1(
        p.CORE1,
        unsafe { &mut *core::ptr::addr_of_mut!(CORE1_STACK) },
        move || {
            let executor2 = EXECUTOR2.init(Executor::new());
            executor2.run(|spawner| {
                spawner.spawn(unwrap!(core1_task2()))
            })
        }
    );
}

#[embassy_executor::task]
pub async fn core1_task1() {
    info!("Hello from core1/task1");

    loop {}
}

#[embassy_executor::task]
pub async fn core1_task2() {
    info!("Hello from core1/task2");

    loop {}
}

That gives aforementioned compiler error:

error[E0382]: use of moved value: `p.CORE1`
  --> src/main.rs:30:9
   |
19 |         p.CORE1,
   |         ------- value moved here
...
30 |         p.CORE1,
   |         ^^^^^^^ value used here after move
   |
   = note: move occurs because `p.CORE1` has type `Peri<'_, CORE1>`, which does not implement the `Copy` trait

For more information about this error, try `rustc --explain E0382`.
error: could not compile `embassy-multicore-test` (bin "embassy-multicore-test") due to 1 previous error

Have a look at: GitHub - FransUrbo/rust-test_0-multicore: Embassy multicore tests with multiple tasks on CORE1.

1 Like

The first thing to check here, is the type of p. If we go to the documentation for embassy_rp, we get that the init function returns a Peripherals struct. Among its fields, CORE1 is of the type Peri<'static, CORE1>.

If we go to the documentation for the Peri type, we get the following description for it:

An exclusive reference to a peripheral.

This is functionally the same as a &'a mut T.

That, and the fact that it doesn't implement traits such as Copy or Clone already tell us what can we do with it.

Now let's go back to your code and how you are using this CORE1 exclusive reference:

spawn_core1(
        p.CORE1,

You use it to spawn a function using spawn_core1, and from the documentation we get its signature is:

pub fn spawn_core1<F, const SIZE: usize>(
    _core1: Peri<'static, CORE1>,
    stack: &'static mut Stack<SIZE>,
    entry: F,
)where
    F: FnOnce() -> <fn() -> ! as HasOutput>::Output + Send + 'static,

You can see that it takes ownership of Peri<'static, CORE1>, which is telling us that we aren't supposed to run this more than once.

Now let's go to the documentation for the Executor from embassy_executor:

To spawn more tasks later, you may keep copies of the Spawner (it is Copy), for example by passing it as an argument to the initial tasks.

So that's how you are supposed to spawn multiple tasks :slight_smile:.

2 Likes

Actually, that helped quite a bit! Thanx!

I knew there was something about that p.CORE1. The compiler said so in quite clear text :slight_smile:.

At least until the last section - "may keep copies of the Spawner" and "by passing it as an argument to the initial tasks"..

I have a not-to-completely-useless understanding of ownership and borrowing (don't like borrowing money, so I understand the concept :smiley:). It's when it starts with lifetimes and mut's etc, that's when I'm lost.

Looking at the section of that doc, it say it should be possible to save the Executor instance in a StaticCell (I've done that elsewhere, so have a vague idea how to do that). But that is instantiated (?) inside the move (which I don't understand at all).

Would it be to much to ask that you explain those two parts I quoted in a bit more detail?

No worries. It means that you should do it like this:

// core1_task expects a copy of the spawner as a parameter, so that you can spawn more tasks from within.

#[embassy_executor::task]
pub async fn core1_task(spawner: Spawner) {
    info!("Hello from core1/task1");
    
    // Here you can spawn more tasks with spawner.spawn(...)
   
    loop {}
}

#[embassy_executor::main]
async fn main(_spawner: Spawner) {
    let p = embassy_rp::init(Default::default());

    spawn_core1(
        p.CORE1,
        unsafe { &mut *core::ptr::addr_of_mut!(CORE1_STACK) },
        move || {
            let executor1 = EXECUTOR1.init(Executor::new());
            executor1.run(|spawner| {
                // Here we pass `spawner` to `core1_task` as an argument.
                spawner.spawn(unwrap!(core1_task(spawner)))
            })
        }
    );
1 Like

Brilliant, that worked perfectly! Thanx!

As always, I over complicate things :smiley: .

I did find that core1_task() died to soon, don't think the processor or Executor had enough time to spawn the second task.

But adding a Timer in the loop, then it worked:

#![no_std]
#![no_main]

use defmt::{info, unwrap};
use embassy_executor::{Executor, Spawner};
use embassy_rp::multicore::{Stack, spawn_core1};
use embassy_time::Timer;
use static_cell::StaticCell;
use {defmt_rtt as _, panic_probe as _};

static mut CORE1_STACK: Stack<4096> = Stack::new();
static EXECUTOR1: StaticCell<Executor> = StaticCell::new();

#[embassy_executor::main]
async fn main(_spawner: Spawner) {
    let p = embassy_rp::init(Default::default());

    spawn_core1(
        p.CORE1,
        unsafe { &mut *core::ptr::addr_of_mut!(CORE1_STACK) },
        move || {
            let executor1 = EXECUTOR1.init(Executor::new());
            executor1.run(|spawner| {
                spawner.spawn(unwrap!(core1_task1(spawner)))
            })
        }
    );
}

#[embassy_executor::task]
pub async fn core1_task1(spawner: Spawner) {
    info!("Hello from core1/task1");

    spawner.spawn(unwrap!(core1_task2()));

    info!("Hello from core1/task1 (again)");
    loop {
        Timer::after_secs(600).await;
    }
}

#[embassy_executor::task]
pub async fn core1_task2() {
    info!("Hello from core1/task2");

    loop {
        Timer::after_secs(600).await;
    }
}

Which yielded the following output:

0.010369 INFO  Hello from core1/task1
0.010720 INFO  Hello from core1/task1 (again)
0.011265 INFO  Hello from core1/task2

But.. I wonder.. Is there a (sure!) way to find out what core a process is running on? Just to double check :slight_smile:.

Not from the executor. The only info. I could find you could get from it is its id.

You could, of course, keep a map of all the executors and which core each one is running on, and then infer on which core did a given task run.

Yeah, I just wanted to make sure that core1_task2() was actually running on CORE1, so that spawner.spawn() didn't just spin it up on CORE0 by default, even though it was doing it from CORE1..

Ah, well.. I just have to assume, and hope, that they know what they were doing :smiley:.

Again, thanx a mill!

The runtime is single-threaded. You can be sure that this will not happen.

1 Like

Ah, did not know that. Thanx!