A good example of what I'm talking about is a program that I was peripherally involved with about 15 years ago. The lead wanted to abstract the mundane details from the users (on the ground), so they would just "register intent" with the spacecraft, and it would figure out how to do what was wanted. The lead also wanted to eliminate features such as "memory dump", which is critical to the anomaly resolution process. If I had been on that team, I would have raised hell, but I wasn't, and at the time, I needed that team lead as an ally.
I mean, even when I have the codebase readily accessible and testable in front of my eyes, I never trust the tests to be enough ? I often spot forgotten edge cases and bugs of various sort in C/embedded projects BECAUSE I run the program, can debug and spot mem issues and whole a lot of other things for which you NEED to gather the most informations you can in order to find solutions ?
So does being able to download a new version of software that uses different memory addresses. The point is if you are able to patch software, you are able to patch memory maps.
Or maybe I would use an MMU but drive it with a kernel written in the old fashioned way with no allocation. It would depend on what hardware I had available and what faults I wanted to survive.
(I’m not an aerospace software developer.)
You could have two copies of the OS mapped to different memory regions. The CPU would boot with the first copy, if it fails watchdog would trigger and the CPU could try to boot the second copy.
What does the OS do? I don’t know about aerospace specifically, but plenty of embedded microcontroller systems don’t have an OS, and I would assume that having an OS is a massive risk against any mission assurance goals, no?
The main purpose of the OS is to centralize, schedule, and manage the resources needed for the mission. It's usually pretty lightweight. Different philosophies are used on different missions. The OS risks can be mitigated. Usually there's a backup "golden copy" OS that can boot if needed. There's also "Safe Mode", which prioritizes communications with the ground, so anomalies can be worked.
This seems like a rather manual way to go about things for which an automated solution can be devised. Such as create special ECC memory where you also account for entire cell failure with Reed-Solomon coding or some boot process which blacklists bad cells etc.
This is what make remote debugging possible. It is impossible to do interactive remote debugging over a ultra low bandwidth link. If everything have static address and deterministic static, you can have a exact copy on ground and debug there.
A local exact replica with deterministic state save lots of time.
You can also have deterministic dynamic - the satellite could transmit its dynamic state (a few bits signifying which memory cells failed) and then you proceed deterministically on the ground.
Where do you place the variables then? as global variables? and how do you detect if a memory cell has gone bad?
That may be a perfectly good solution in many embedded environments, but in most other context's global variables are considered bad design or very limiting and impractical.
For instance, how would better tooling help with storing a TCP buffer in global memory?
The first will be a real pain, as you now have 3 global variables, and the second will look pretty much like multi-threaded Rust running on a normal OS, but with some extra logic to handle the buffer growing too big.
You can probably squeeze more performance out of the C code, specially if you know your system in-depth, but (from experience) it's very easy to lose track of the program's state and end up shooting your foot.
So it's mostly about the absence of abstraction, in the C example? C++ would offer the same convenience (with std::mutex and std::array globals), but in C it's more of a hassle. Gotcha.
One more question because I'm curious - where would you anticipate C would be able to squeeze out more performance in above example?
Global mutable variables, and they usually tend to be grouped into singletons (solving initialization issues, and fewer people bat an eye)
Your mention of STL makes it sound like you're talking about C++. But I don't know of any C++ compiler that lets you completely avoid use of the stack, even if you disable the usual suspects (RTTI and exceptions). Sure, you'd have to avoid local variables, defined within a function's body (or at block scope), but that's nowhere near enough.
* The compiler would need to statically allocate space for every function's parameters and return address. That's actually how early compilers did work, but today it would be inefficient because there are surely so many functions defined in a program's binary compared to the number being executed at any given time. (Edit: I suppose you already need the actual code for those functions, so maybe allocating room for their parameters is not so bad.)
* It would also mean that recursion would not work, even mutual recursion (so you'd need runtime checks because this would be hard to detect at compile/link time), although I suspect this is less of a problem than it sounds, but I'm not aware of a C++ compiler that supports it.
* You'd also need to avoid creating any temporary variables at all e.g. y = a + b + c would not be allowed if a,b,c are non-trivial types. (y = a + b would be OK because the temporary could be constructed directly into y's footprint, or stored temporarily in the return space of the relevant operator+(), which again would be statically allocated).
Is that really what you meant? I suspect not, but without all that your point about avoiding the stack doesn't make any sense.
In most other surviving CPU ISAs the return address is saved in a register and it is easy to arrange in a compiler to use only procedure arguments that are passed in registers, the only price being paid for this being a reasonable upper limit for the number of parameters of a function, e.g. 12 or 24, depending on the number of general-purpose registers (e.g. 16 or 32). For the very rare case when a programmer would want more parameters, some of them should be grouped into a structure.
With this convention, which normally should not be a problem, there is no need for a call stack. There can be software managed stacks, which can be used even for implementing recursion, when that is desired.
The use of static memory for passing function arguments was necessary only in the very early computers, which were starved in registers.
What do you get by doing it like this?
Also, in your described structure, how do you handle nested function calls? I'm sure there exists a convoluted scheme that does this, but not sure with the current call assumptions.
You also lose ABI compatibility with a bunch of stuff.
And regardless, I mostly program in Risc-v and ARM -most compiles like to pass arguments on the registers, but use the stack anyway for local context.
I don't think it's too hard to imagine a compiler that does that, although it would obviously be very limited in functionality (nesting would be disallowed as you note, or I guess limited to the number of registers you're willing to waste on it...).
1. Where do you save the current value of the return address register before calling a function?
2. When parameters are "grouped into a structure" and the structure is passed as an argument to a function, where do you store that structure?
1) You don't... hence, my question about no nested function calls. If you push it anywhere else, you can call it whatever you want, but you just re-invented the stack. I _guess_ you could do some wierd stuff to technically not get a stack, but... again, it's wierd. And for what, again?
2) Some fixed address. If you have for example:
```c
typeRealBigStructure foo;
void baz(typeRealBigStructure * struct){
// Do whatever to struct
}void bar(void){
baz(&foo);
}```
The foo will probably end up in the BSS and will take up that space for the whole lifetime of the program. That's not the heap, not the stack, just... a fixed location in memory where the linker placed it.
I guess on big PC's stuff is very dynamic and you use malloc for a lot of stuff, but in embedded C, it's a very common pattern.
> If every variable has a fixed address, and one of those addresses goes bad, a patch can be loaded to move that address and the mission can continue.
You can and do put the stack and heap pool at fixed memory ranges, so you can always do this. I'm not sold at all with this reasoning.
The main issue is mission assurance. Using the stack or the heap means your variables aren't always at the same memory address. This can be bad if a particular memory cell has failed. If every variable has a fixed address, and one of those addresses goes bad, a patch can be loaded to move that address and the mission can continue.