kernel: add faulting state to process lifecycle - #4948
Conversation
The monolithic process object doesn't check process state every time it is accessed. Rather, we check process state at the beginning of the process event loop and operate assuming the process remains in that state until the next loop. This means a process can't safely transition out of a runnable state in the middle of the loop (e.g., a command cannot directly restart or terminate a process, see #4872). Instead, we mark a running process as `Faulting` while within an execution such that the next iteration through the loop the fault and its response can be safely handled.
bradjc
left a comment
There was a problem hiding this comment.
My sense is this would address the issue with a process faulting itself inside of a capsule but outside of the exit syscall.
However, I think I would prefer this to be integrated into the scheduler rather than the process state machine. That is, just like the scheduler can decide to run a process, it could decide to handle a faulted process, or handle a process that wants to restart. I would also like to see that scheduler include starting processes, verifying processes, or really anything that a process might want to do that would consume system-wide resources (e.g., time, energy). This would also be useful for processes that are stored in external nonvolatile storage and need to be loaded in to memory to run. Having this in the scheduler would allow us to write policies on when those operations should happen, and to be able to prioritize already running processes.
| // This is a bit brittle, but is a limitation of the way that process | ||
| // state is managed and the executor loop runs currently. | ||
| match process.get_state() { | ||
| process::State::Running => { |
There was a problem hiding this comment.
I'm not sure, we currently call process.set_fault_state(); within this match statement and it isn't a problem. Also in yield we call set_syscall_return_value() which changes the process's state. I think in this part of the state machine we can update a process's state.
There was a problem hiding this comment.
Sometimes we safely can, yes. But within that same match is self.handle_syscall(resources, process, syscall);, which is the code path that eventually broke #4872.
| ForcedTerminate, | ||
|
|
||
| /// Catch-all for unknown faults. | ||
| Unknown, |
There was a problem hiding this comment.
What about ProcessFault? It is unknown with respect to what the process did, but not unknown that it wasn't initiated by a process manager.
There was a problem hiding this comment.
If we liked this approach I was going to go through and eliminate Unknown if we can; there's a countable number of things (12 from a quick grep) that call set_faulting_state, but enough that I wasn't going to handle all the cases on first draft.
I thought about that, and actually started there, but our current scheduler interface is a bit awkward for this. In particular, it's probably the case that most schedulers would want to do the same thing with a "faulting" app, i.e., "handle it" the next time it would be scheduled to run. But currently that would require modifying every scheduler implementation. There's not a great place to put generic scheduling things except within the kernel's |
I do see the ideal of having the scheduler decide when is a good time to do system-wide work. But in practice, the scheduler doesn't know enough about the inner workings of applications and their priorities to determine the best possible time. Even if all the applications are currently yielded, the scheduler doesn't know if the next action a process is going to take is going to be time-sensitive (acknowledging a packet arrival for instance) or fudgeable (timer for printing some debug output). So if the scheduler can't determine when is really the best time given the process workload, all it can use to determine is whether there is something to do or not. In which case it would presumably just do the work immediately once it's available. That would essentially be identical to putting it in the kernel loop. Do you have an example where the scheduler would have insight into when to time system-wide work? |
| process::State::Faulting(reason) => { | ||
| return_reason = process::StoppedExecutingReason::StoppedFaulted; | ||
| process.resolve_faulting_state(reason); | ||
| break; | ||
| } |
There was a problem hiding this comment.
There's not a great place to put generic scheduling things except within the kernel's
do_processloop.
I was actually a little surprised to find this added to do_process and not to execute_kernel_work. I thought of process management as work the kernel needs to do, rather than something occurring the next time we go to schedule the process. I definitely think extensions like verifying process and starting new processes would be in the execute_kernel_work domain.
There was a problem hiding this comment.
The problem is that execute_kernel_work is gated behind a scheduler decision of whether to do kernel work or process work: https://github.com/tock/tock/blob/master/kernel/src/kernel.rs#L362-L370
We can't put process lifecycle management in there, otherwise a scheduler could continually schedule process work for a faulting process, and the kernel would never actually get to handle the fault.
There was a problem hiding this comment.
We can't put process lifecycle management in there, otherwise a scheduler could continually schedule process work for a faulting process, and the kernel would never actually get to handle the fault.
But isn't that already true today? A scheduler could only do process work or sleep, and never actually handle interrupts or run capsules?
There was a problem hiding this comment.
I figured we'd just add "a faulting process exists" as part of the do_kernel_work_now check.
I think an easy answer could be "at night". Like "install this OS update at 2am tonight" kind of thing. Our basic schedulers today aren't going to handle this in any interesting way. But opening the door by adding the API allows people to experiment, like Marshall's paper on batch scheduling for lower power Tock boards. If you had a board that required many frequent app loads (e.g., a platform without executable flash), I'm guessing you would be more interested in when you block for ~1000 ms to verify a new app. |
I think that's orthogonal from the process scheduler though. I would imagine something like "install this update" to be something that some system management app / capsule is responsible for, which already has mechanisms for setting alarms at specific times etc. I think of the Scheduler as much lower-level; I don't think the same code that's picking between round robin or managing MLFQ is doing high-level process lifetime work. I do agree that it's a bit awkward that we don't have a canonical thing that manages the system. I believe the analog in Linux would be the I would imagine once we have something like a system manager, stuff like the default fault policy goes away, and rather the manager is alerted that a process has faulted and then it deals with it from there. But that feels a much more substantial design/arch change, beyond addressing the immediate bug that's blocking the ENTS work |
|
Hmm.. okay, I'm circling back on myself and wondering if something similar to |
But our scheduler isn't just a process scheduler. It chooses whether to run processes at all, and can choose to prioritize processes or the kernel. The proposed extensions from a while ago would have even allowed the scheduler to choose which interrupts to handle first. |
The very fast way to move beyond this is to just set a deferred call in the capsule so the fault downcall doesn't happen in response to an app's command. |
Pull Request Overview
The monolithic process object doesn't check process state every time it is accessed. Rather, we check process state at the beginning of the process event loop and operate assuming the process remains in that state until the next loop.
This means a process can't safely transition out of a runnable state in the middle of the loop (e.g., a command cannot directly restart or terminate a process, see #4872).
Instead, we mark a running process as
Faultingwhile within an execution such that the next iteration through the loop the fault and its response can be safely handled.This implements "path #2" from #4872 (comment)
Testing Strategy
This pull request was tested by compiling.
TODO or Help Wanted
Is this an agreeable solution to the problem?
Checklist
make prepush.PR Contents
Documentation
/docsand the Book.AI Use