Skip to content

Commit 194738f

Browse files
committed
samples: philosophers: Create
This is an initial version of the dining philosophers problem, implemented in Rust. This version is intended to be flexible, allowing for different sycnronization methods to be tested. This contains the only method implemented so far, sys::Mutex. Signed-off-by: David Brown <[email protected]>
1 parent efc1e7c commit 194738f

File tree

8 files changed

+260
-1
lines changed

8 files changed

+260
-1
lines changed

samples/philosophers/CMakeLists.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
3+
cmake_minimum_required(VERSION 3.20.0)
4+
5+
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
6+
project(philosophers)
7+
8+
rust_cargo_application()

samples/philosophers/Cargo.toml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Copyright (c) 2024 Linaro LTD
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
[package]
5+
# This must be rustapp for now.
6+
name = "rustapp"
7+
version = "3.7.0"
8+
edition = "2021"
9+
description = "A sample hello world application in Rust"
10+
license = "Apache-2.0 or MIT"
11+
12+
[lib]
13+
crate-type = ["staticlib"]
14+
15+
[dependencies]
16+
zephyr = "3.7.0"
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Copyright (c) 2024 Linaro LTD
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
# This board doesn't have a serial console, so use RTT.
5+
CONFIG_UART_CONSOLE=n
6+
CONFIG_RTT_CONSOLE=y
7+
CONFIG_USE_SEGGER_RTT=y

samples/philosophers/prj.conf

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Copyright (c) 2024 Linaro LTD
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
CONFIG_RUST=y
5+
CONFIG_RUST_ALLOC=y
6+
CONFIG_MAIN_STACK_SIZE=8192
7+
8+
# CONFIG_USERSPACE=y
9+
10+
# Some debugging
11+
CONFIG_THREAD_MONITOR=y
12+
CONFIG_THREAD_ANALYZER=y
13+
CONFIG_THREAD_ANALYZER_USE_PRINTK=y
14+
CONFIG_THREAD_ANALYZER_AUTO=n
15+
CONFIG_THREAD_ANALYZER_AUTO_INTERVAL=15
16+
17+
CONFIG_RUST_CHECK_KOBJ_INIT=y

samples/philosophers/sample.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
sample:
2+
description: Philosphers, in Rust
3+
name: philosophers rust
4+
common:
5+
harness: console
6+
harness_config:
7+
type: one_line
8+
regex:
9+
- "Child 5 thinking"
10+
tags: rust
11+
filter: CONFIG_RUST_SUPPORTED
12+
tests:
13+
sample.rust.philosopher.sysmutex:
14+
tags: introduction
15+
min_ram: 32

samples/philosophers/src/lib.rs

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
// Copyright (c) 2023 Linaro LTD
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
#![no_std]
5+
6+
extern crate alloc;
7+
8+
use alloc::boxed::Box;
9+
use alloc::vec::Vec;
10+
use zephyr::time::{Duration, sleep, Tick};
11+
use zephyr::{
12+
printkln,
13+
kobj_define,
14+
sys::uptime_get,
15+
sync::Arc,
16+
};
17+
18+
use crate::sysmutex::SysMutexSync;
19+
20+
mod sysmutex;
21+
22+
/// How many philosophers. There will be the same number of forks.
23+
const NUM_PHIL: usize = 6;
24+
25+
/// How much stack should each philosopher thread get. Worst case I've seen is riscv64, with 3336
26+
/// bytes, when printing messages. Make a bit larger to work.
27+
const PHIL_STACK_SIZE: usize = 4096;
28+
29+
// The dining philosophers problem is a simple example of cooperation between multiple threads.
30+
// This implementation use one of several different underlying mechanism to support this cooperation.
31+
32+
// This example uses dynamic dispatch to allow multiple implementations. The intent is to be able
33+
// to periodically shut down all of the philosphers and start them up with a differernt sync
34+
// mechanism. This isn't implemented yet.
35+
36+
/// The philosophers use a fork synchronization mechanism. Essentially, this is 6 locks, and will be
37+
/// implemented in a few different ways to demonstrate/test different mechanmism in Rust. All of
38+
/// them implement The ForkSync trait which provides this mechanism.
39+
trait ForkSync: core::fmt::Debug + Sync + Send {
40+
/// Take the given fork. The are indexed the same as the philosopher index number. This will
41+
/// block until the fork is released.
42+
fn take(&self, index: usize);
43+
44+
/// Release the given fork. Index is the same as take.
45+
fn release(&self, index: usize);
46+
}
47+
48+
#[no_mangle]
49+
extern "C" fn rust_main() {
50+
printkln!("Hello world from Rust on {}",
51+
zephyr::kconfig::CONFIG_BOARD);
52+
printkln!("Time tick: {}", zephyr::time::SYS_FREQUENCY);
53+
54+
let syncers = get_syncer();
55+
56+
printkln!("Pre fork");
57+
for (i, syncer) in (0..PHIL_THREAD.len()).zip(syncers.into_iter()) {
58+
/*
59+
let child_syncer = syncer.clone();
60+
*/
61+
// The Rust borrow checker doesn't seem quite smart enough to realize that we are moving
62+
// these out individually. Best would be to rewrite this to iterate over the queue, but for
63+
// now, the clone isn't terribly costly.
64+
/*
65+
let child_syncer = ChannelSync::new(cq_send.clone(), reply_queues[i].clone());
66+
let child_syncer = Arc::new(child_syncer);
67+
*/
68+
// let child_stat = stats.clone();
69+
let thread = PHIL_THREAD[i].spawn(PHIL_STACK[i].token(), move || {
70+
phil_thread(i, syncer /*, child_stat*/);
71+
});
72+
thread.start();
73+
}
74+
75+
let delay = Duration::secs_at_least(10);
76+
loop {
77+
// Periodically, printout the stats.
78+
zephyr::time::sleep(delay);
79+
// stats.lock().unwrap().show();
80+
}
81+
}
82+
83+
fn get_syncer() -> Vec<Arc<dyn ForkSync>> {
84+
// Simple mutex version.
85+
let syncer = Box::new(SysMutexSync::new())
86+
as Box<dyn ForkSync>;
87+
let syncer: Arc<dyn ForkSync> = Arc::from(syncer);
88+
let mut result = Vec::new();
89+
for _ in 0..NUM_PHIL {
90+
result.push(syncer.clone());
91+
}
92+
result
93+
}
94+
95+
fn phil_thread(n: usize, syncer: Arc<dyn ForkSync> /*, stats: Arc<Mutex<Stats>>*/) {
96+
printkln!("Child {} started: {:?}", n, syncer);
97+
98+
// Determine our two forks.
99+
let forks = if n == NUM_PHIL - 1 {
100+
// Per Dijkstra, the last phyilosopher needs to reverse forks, or we deadlock.
101+
(0, n)
102+
} else {
103+
(n, n+1)
104+
};
105+
106+
loop {
107+
{
108+
// printkln!("Child {} hungry", n);
109+
// printkln!("Child {} take left fork", n);
110+
syncer.take(forks.0);
111+
// printkln!("Child {} take right fork", n);
112+
syncer.take(forks.1);
113+
114+
let delay = get_random_delay(n, 25);
115+
printkln!("Child {} eating ({} ms)", n, delay);
116+
sleep(delay);
117+
// stats.lock().unwrap().record_eat(n, delay);
118+
119+
// Release the forks.
120+
// printkln!("Child {} giving up forks", n);
121+
syncer.release(forks.1);
122+
syncer.release(forks.0);
123+
124+
let delay = get_random_delay(n, 25);
125+
printkln!("Child {} thinking ({} ms)", n, delay);
126+
sleep(delay);
127+
// stats.lock().unwrap().record_think(n, delay);
128+
}
129+
}
130+
}
131+
132+
/// Get a random delay, based on the ID of this user, and the current uptime.
133+
fn get_random_delay(id: usize, period: usize) -> Duration {
134+
let tick = (uptime_get() & (usize::MAX as i64)) as usize;
135+
let delay = (tick / 100 * (id + 1)) & 0x1f;
136+
137+
// Use one greater to be sure to never get a delay of zerp.
138+
Duration::millis_at_least(((delay + 1) * period) as Tick)
139+
}
140+
141+
kobj_define! {
142+
static PHIL_THREAD: [StaticThread; NUM_PHIL];
143+
static PHIL_STACK: [ThreadStack<PHIL_STACK_SIZE>; NUM_PHIL];
144+
}

samples/philosophers/src/sysmutex.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Copyright (c) 2024 Linaro LTD
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
//! # sys::Mutex implementation of ForkSync
5+
//!
6+
//! This is a simple implementation of the Fork synchronizer that uses underlying Zephyr `k_mutex`
7+
//! wrapped in `sys::Mutex`. The ForkSync semantics map simply to these.
8+
9+
use crate::{
10+
ForkSync,
11+
NUM_PHIL,
12+
};
13+
use zephyr::{
14+
kobj_define,
15+
};
16+
use zephyr::object::KobjInit;
17+
use zephyr::sys::sync::Mutex;
18+
use zephyr::time::Forever;
19+
20+
type SysMutexes = [Mutex; NUM_PHIL];
21+
22+
/// A simple implementation of ForkSync based on underlying Zephyr sys::Mutex, which uses explicit
23+
/// lock and release semantics.
24+
25+
#[derive(Debug)]
26+
pub struct SysMutexSync {
27+
locks: SysMutexes,
28+
}
29+
30+
impl SysMutexSync {
31+
pub fn new() -> SysMutexSync {
32+
let locks = MUTEXES.each_ref().map(|m| {
33+
m.init();
34+
m.get()
35+
});
36+
SysMutexSync { locks }
37+
}
38+
}
39+
40+
impl ForkSync for SysMutexSync {
41+
fn take(&self, index: usize) {
42+
self.locks[index].lock(Forever).unwrap();
43+
}
44+
45+
fn release(&self, index: usize) {
46+
self.locks[index].unlock().unwrap();
47+
}
48+
}
49+
50+
kobj_define! {
51+
static MUTEXES: [StaticMutex; NUM_PHIL];
52+
}

zephyr/module.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ build:
44
cmake: .
55
kconfig: Kconfig
66
samples:
7-
- samples/hello_world
7+
- samples
88
tests:
99
- tests

0 commit comments

Comments
 (0)