While `yield` is the logically interesting function I think this the technically most interesting. When we spawn a new task we first check if there are any available tasks (tasks in `Parked` state). If we run out of tasks we panic in this scenario but there are several (better) ways to handle that. We keep things simple for now. When we find an available task we get the stack length and a pointe
(&mut self, f: fn())
| 179 | /// |
| 180 | /// Lastly we set the state as `Ready` which means we have work to do and is ready to do it. |
| 181 | pub fn spawn(&mut self, f: fn()) { |
| 182 | let available = self |
| 183 | .tasks |
| 184 | .iter_mut() |
| 185 | .find(|t| t.state == State::Available) |
| 186 | .expect("no available task."); |
| 187 | |
| 188 | println!("RUNTIME: spawning task {}\n", available.id); |
| 189 | let size = available.stack.len(); |
| 190 | unsafe { |
| 191 | let s_ptr = available.stack.as_mut_ptr().offset(size as isize); |
| 192 | |
| 193 | // make sure our stack itself is 8 byte aligned - it will always |
| 194 | // offset to a lower memory address. Since we know we're at the "high" |
| 195 | // memory address of our allocated space, we know that offsetting to |
| 196 | // a lower one will be a valid address (given that we actually allocated) |
| 197 | // enough space to actually get an aligned pointer in the first place). |
| 198 | let s_ptr = (s_ptr as usize & !7) as *mut u8; |
| 199 | |
| 200 | available.ctx.x1 = guard as u64; //ctx.x1 is old return address |
| 201 | available.ctx.nx1 = f as u64; //ctx.nx2 is new return address |
| 202 | available.ctx.x2 = s_ptr.offset(-32) as u64; //cxt.x2 is sp |
| 203 | } |
| 204 | available.state = State::Ready; |
| 205 | } |
| 206 | } |
| 207 | |
| 208 | /// This is our guard function that we place on top of the stack. All this function does is set the |