Skip to content
Snippets Groups Projects
Commit c8e32783 authored by Per Lindgren's avatar Per Lindgren
Browse files

HOME_EXAM

parent 774f64b7
Branches
No related tags found
No related merge requests found
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
Most recent changes: Most recent changes:
- 2020-12-20 Home exams: Initial commit.
- 2020-12-14 Some clarifications: - 2020-12-14 Some clarifications:
- In `array.rs`, missing `not` in "(Hint, even if we don't use the result `b`, Rust do not optimize out the call, why?)" - In `array.rs`, missing `not` in "(Hint, even if we don't use the result `b`, Rust do not optimize out the call, why?)"
......
# Home Exam D7020E January 2021
## Grading:
3) Implement the response time analysis, and overall schedulability test. (Skeleton found under `srp_analysis`.)
4) Generate report on the analysis results, this could be as a generated html/xml or however you feel like results are best reported and visualized.
5) Perform measurements on a real RTIC application and analyze with your tool developed for grades 3/4.
Both Lab 4 and 5 are mandatory for passing the course. Grades are incremental, i.e. to get a 5 you must pass grades 3 and 4.
---
## Preparation
Start by reading 1, 2 and 3:
1) [A Stack-Based Resource Allocation Policy for Realtime Processes](https://www.math.unipd.it/~tullio/RTS/2009/Baker-1991.pdf), which refers to
2) [Stack-Based Scheduling of Realtime Processes](https://link.springer.com/content/pdf/10.1007/BF00365393.pdf), journal publication based on technical report [3] of the 1991 paper. The underlying model is the same in both papers.
3) [Rate Monotonic Analysis](http://www.di.unito.it/~bini/publications/2003BinButBut.pdf), especially equation 3 is of interest to us. (It should be familiar for the real-time systems course you have taken previously.)
## Procedure
Assignments for submission will be posted on Canvas.
The procedure follows that of Lab 4/5, i.e:
The files in `srp_analysis` gives a skeleton for your solution(s). Implement your solutions for Grade 3, and optionally 4/5.
1) `Home-Exam Initial Submission`: Submit link(s) in canvas (similarly to Lab 4/5). You will get two reviews and two repositories to review. If you have done work towards grade 5 you also submit link to your `rtic_fxx_nucleo` repository.
2) `Hame-Exam Review`: Perform and submit reviews.
3) `Home-Exam Final Submission`: Address raised issues and make final submission.
(Check issues you made to fellow repositories and close those those that have been addressed to your satisfaction, both regarding labs and the home exam.)
As the final part of the examination you will individually present your solutions. Grades will be based on the fulfillment of the `Home Exam` grading goals. (We decide time/place for your presentation over Telegram.)
---
## Definitions
A task `t` is defined by:
- `P(t)` the priority of task `t`
- `D(t)` the deadline of taks `t`
- `A(t)` the inter-arrival of task `t`
A resource `r` is defined by:
- `π(r)` the resource ceiling, computed as the highest priority of any task accessing `r`. SRP allows for dynamic priorities, in our case we have static priorities only.
For SRP based analysis we assume a task to perform/execute a finite sequence of operations/instructions (aka. run-to-end or run-to-completion semantics). During execution, a task can claim resources `Rj`... in nested fashion. Sequential re-claim of resources is allowed but NOT re-claiming an already held resource (in a nested fashion, since that would violate the Rust memory aliasing rule).
E.g., a possible trace for a task can look like:
`[t:...[r1:...[r2:...]...]...[r2:...]...]`, where `[r:...]` denotes a critical section of task `t` holding the resource `r`. In this case the task starts, and at some point claims `r1` and inside the critical section claims `r2` (nested claim), at some point it exits `r2`, exits `r1` and continues executing where it executes a critical section on `r2`, and then finally executes until completion.
## Grade 3
Analysis:
### 1. Total CPU utilization
Worst Case Execution Time (WCET) for tasks and critical sections
In general determining WCET is rather tricky. In our case we adopt a measurement based technique, that spans all feasible paths for the task. Tests triggering the execution paths are automatically generated by symbolic execution. To correctly take concurrency into account resource state is treated symbolically. Thus, for a critical section, the resource is given a fresh (new) symbolic value for each critical section. Inside the critical section we are ensured exclusive access (and thus the value can be further constrained inside of the critical section).
We model hardware (peripheral register accesses) as shared resources (shared between your application and the environment). As such each *read* regenerates a new symbolic value while write operations have no side-effect.
For now, we just assume we have complete WCETs information, in terms of `start` and `end` time-stamps (`u32`) for each section `[_: ... ]`. We represent that by the `Task` and `Trace` data structures in `common.rs`.
### Total CPU request (or total load factor)
Each task `t` has a WCET `C(t)` and given (assumed) inter-arrival time `A(t)`. The CPU request (or load) inferred by a task is `L(t)` = `C(t)`/`A(t)`. Ask yourself, what is the consequence of `C(t)` > `A(t)`?
We can compute the total CPU request (or load factor), as `Ltot` = sum(`L(T)`), `T` being the set of tasks.
Ask yourself, what is the consequence of `Ltot` > 1?
Implement a function taking `Vec<Task>` and returning the load factor. (Use data structures from `common.rs` for suitable data structures and inspiration.)
### Response time (simple over-approximation)
Under SRP response time can be computed by equation 7.22 in [Hard Real-Time Computing Systems](
https://doc.lagout.org/science/0_Computer%20Science/2_Algorithms/Hard%20Real-Time%20Computing%20Systems_%20Predictable%20Scheduling%20Algorithms%20and%20Applications%20%283rd%20ed.%29%20%5BButtazzo%202011-09-15%5D.pdf).
In general the response time for a task `t` is computed as.
- `R(t)` = `B(t)` + `C(t)` + `I(t)`, where
- `B(t)` is the blocking time for task `t`, and
- `I(t)` is the interference (preemptions) to task `t`
For a task set to be schedulable under SRP we have two requirements:
- `Ltot` < 1
- `R(t)` < `D(t)`, for all tasks. (`R(t)` > `D(t)` implies a deadline miss.)
#### Blocking
SRP brings the outstanding property of single blocking. In words, a task `t` is blocked by the maximal critical section a task `l` with lower priority (`P(l)`< `P(t)`) holds a resource `l_r`, with a ceiling `π(l_r)` equal or higher than the priority of `t`.
- `B(t)` = max(`C(l_r)`), where `P(l)`< `P(t)`, `π(l_r) >= P(t)`
Implement a function that takes a `Task` and returns the corresponding blocking time.
#### Preemptions
A task is exposed to interference (preemptions) by higher priority tasks. Intuitively, during the execution of a task `t` (`Bp(t)`) each higher priority task `h` (`P(h)`>`P(t)`) may preempt us (`Bp(t)`/`A(h)` rounded upwards) times.
- `I(t)` = sum(`C(h)` * ceiling(`Bp(t)`/`A(h)`)), forall tasks `h`, `P(h)` > `P(t)`, where
- `Bp(t)` is the *busy-period*
We can over approximate the *busy period* `Bp(i)` = `D(i)` (assuming the worst allowed *busy-period*).
As a technical detail. For the scheduling of tasks of the same priority, the original work on SRP adopted a FIFO model (first arrived, first served). Under Rust RTIC, tasks are bound to hardware interrupts. Thus we can exploit the underlying hardware to do the actual scheduling for us (with zero-overhead). However the interrupt hardware, schedules interrupts of the same priority by the index in the vector table. For our case here we can make a safe over approximation by considering preemptions from tasks with SAME or higher priority (`P(h)` >= `P(t)`).
Implement a function that takes a `Task` and returns the corresponding preemption time.
Now make a function that computes the response time for a `Task`, by combing `B(t)`, `C(t)`, and `I(t)`.
Finally, make a function that iterates over the task set and returns a vector with containing:
`Vec<Task, R(t), C(t), B(t), I(t)>`. Just a simple `println!` of that vector gives the essential information on the analysis.
#### Preemptions revisited
The *busy-period* is in `7.22` computed by a recurrence equation.
Implement the recurrence relation (equation) starting from the base case `B(t) + C(t)`. The recurrence might diverge in case the `Bp(t) > D(t)`, this is a pathological case, where the task becomes non-schedulable, in that case terminate the recurrence (with an error indicating a deadline miss). You might want to indicate that a non feasible response time have been reached by using the `Result<u32, ())>` type or some other means e.g., (`Option<u32>`).
You can let your `preemption` function take a parameter indicating if the exact solution or approximation should be used.
---
## Grade 4
Your tool should be usable/documented and present a comprehensive "view" of the analysis results. Think about the usability of the tool and write a short description of use. (You may use `StructOpt` for command line parsing making your application work as a "professional" tool, a fun and easy exercise.) This you can easily implement a `-h/--help` for documenting the usage.
---
## Grade 5
Pull the latest [rtic_fxx_nucleo](https://gitlab.henriktjader.com/pln/rtic_f4xx_nucleo). Use your tool (that you developed for grades 3, 4) to analyse the task/resource set of `timing_exam.rs` based on the corresponding WCET data (per task/critical section). (See the file `examples/timing_exam.rs` for further information.) To import task-set/wcet, look at Rust `serde`.
## Resources
`common.rs` gives the basic data structures, and some helper functions.
`main.rs` gives an example on how `Tasks` can be manually constructed. This is vastly helpful for your development, when getting started. Later you likely want file input using `serde`.
## Tips
When working with Rust, the standard library documentation [std](https://doc.rust-lang.org/std/) is excellent and easy to search (just press S). For most cases, you will find examples on intended use and cross referencing to related data types is just a click away.
Use the `main` example to get started. Initially you may simplify it further by reducing the number of tasks/and or resources. Make sure you understand the helper functions given in `common.rs`, (your code will likely look quite similar). You might want to add further `common` types and helper functions to streamline your development, along the way.
Generate your own task sets to make sure your code works in the general case not only for the `Tasks` provided. Heads up, I will expose your code to some other more complex task sets.
---
## Learning outcomes, Robust and Energy Efficient Real-Time Systems
In this part of the course, we have covered.
- Embedded Programming using the RTIC framework. General tracing and debugging. Cycle accurate timing measurements.
- Software robustness. We have adopted Rust and Symbolic Execution to achieve guaranteed memory safety and defined behavior (panic free execution). With this at hand, we have a strong (and theoretically underpinned) foundation for improved robustness and reliability proven at compile time.
- Real-Time Scheduling and Analysis. SRP provides an execution model and resource management policy with outstanding properties of race-and deadlock free execution, single blocking and stack sharing. Our Rust RTIC framework provides a correct by construction implementation of SRP, exploiting zero-cost (software) abstractions. Using Rust RTIC resource management and scheduling, is done by directly by the hardware, which allows for efficiency (zero OH) and predictability.
The SRP model is amenable to static analysis, which you have now internalized through an actual implementation of the theoretical foundations. We have also covered methods for Worst Case Execution Time (WCET) analysis by cycle accurate measurements, which in combination with Symbolic Execution for test-case generation allows for high degree of automation.
- Energy Consumption is roughly proportional to the supply voltage (static leakage/dissipation), and exponential to the frequency (dynamic/switching activity dissipation). In the case of embedded systems, low-power modes allow parts of the system to be powered down while retaining sufficient functionality to wake on external (and/or internal) events. In sleep mode, both static and dynamic power dissipation is minimized typically to the order of uAmp (in comparison to mAmp in run mode).
Rust RTFM adopts an event driven approach allowing the system to automatically sleep in case no further tasks are eligible for scheduling. Moreover, leveraging on the zero-cost abstractions in Rust and the guarantees provided by the analysis framework, we do not need to sacrifice correctness/robustness and reliability in order to obtain highly efficient executables.
Robust and Energy Efficient Real-Time Systems for real, This is the Way!
[package]
name = "srp_analysis"
version = "0.1.0"
authors = ["Per Lindgren <per.lindgren@ltu.se>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
use std::collections::{HashMap, HashSet};
// common data structures
#[derive(Debug)]
pub struct Task {
pub id: String,
pub prio: u8,
pub deadline: u32,
pub inter_arrival: u32,
pub trace: Trace,
}
//#[derive(Debug, Clone)]
#[derive(Debug)]
pub struct Trace {
pub id: String,
pub start: u32,
pub end: u32,
pub inner: Vec<Trace>,
}
// useful types
// Our task set
pub type Tasks = Vec<Task>;
// A map from Task/Resource identifiers to priority
pub type IdPrio = HashMap<String, u8>;
// A map from Task identifiers to a set of Resource identifiers
pub type TaskResources = HashMap<String, HashSet<String>>;
// Derives the above maps from a set of tasks
pub fn pre_analysis(tasks: &Tasks) -> (IdPrio, TaskResources) {
let mut ip = HashMap::new();
let mut tr: TaskResources = HashMap::new();
for t in tasks {
update_prio(t.prio, &t.trace, &mut ip);
for i in &t.trace.inner {
update_tr(t.id.clone(), i, &mut tr);
}
}
(ip, tr)
}
// helper functions
fn update_prio(prio: u8, trace: &Trace, hm: &mut IdPrio) {
if let Some(old_prio) = hm.get(&trace.id) {
if prio > *old_prio {
hm.insert(trace.id.clone(), prio);
}
} else {
hm.insert(trace.id.clone(), prio);
}
for cs in &trace.inner {
update_prio(prio, cs, hm);
}
}
fn update_tr(s: String, trace: &Trace, trmap: &mut TaskResources) {
if let Some(seen) = trmap.get_mut(&s) {
seen.insert(trace.id.clone());
} else {
let mut hs = HashSet::new();
hs.insert(trace.id.clone());
trmap.insert(s.clone(), hs);
}
for trace in &trace.inner {
update_tr(s.clone(), trace, trmap);
}
}
mod common;
use common::*;
fn main() {
let t1 = Task {
id: "T1".to_string(),
prio: 1,
deadline: 100,
inter_arrival: 100,
trace: Trace {
id: "T1".to_string(),
start: 0,
end: 10,
inner: vec![],
},
};
let t2 = Task {
id: "T2".to_string(),
prio: 2,
deadline: 200,
inter_arrival: 200,
trace: Trace {
id: "T2".to_string(),
start: 0,
end: 30,
inner: vec![
Trace {
id: "R1".to_string(),
start: 10,
end: 20,
inner: vec![Trace {
id: "R2".to_string(),
start: 12,
end: 16,
inner: vec![],
}],
},
Trace {
id: "R1".to_string(),
start: 22,
end: 28,
inner: vec![],
},
],
},
};
let t3 = Task {
id: "T3".to_string(),
prio: 3,
deadline: 50,
inter_arrival: 50,
trace: Trace {
id: "T3".to_string(),
start: 0,
end: 30,
inner: vec![Trace {
id: "R2".to_string(),
start: 10,
end: 20,
inner: vec![],
}],
},
};
// builds a vector of tasks t1, t2, t3
let tasks: Tasks = vec![t1, t2, t3];
println!("tasks {:?}", &tasks);
// println!("tot_util {}", tot_util(&tasks));
let (ip, tr) = pre_analysis(&tasks);
println!("ip: {:?}", ip);
println!("tr: {:?}", tr);
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment