| ▲ | anonymousiam a day ago |
| The same is true for the software that runs many satellites. Use of the STL is prohibited. 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. |
|
| ▲ | 0xffff2 6 hours ago | parent | next [-] |
| For what it's worth, I am an active developer of space flight software. This might be true somewhere, but it's not true anywhere I've ever encountered. The contortions required to avoid using the stack would be insane and cause far more bugs than it could ever prevent. I'm pretty confident asserting that this is simply not a thing. Even heap allocation is very often allowed, but restricted to program initialization only. Furthermore, these rules are relaxing all the time. I am aware of at least one mission currently in space that is flying the C++14 STL with no restrictions on heap allocation and exceptions enabled. Unmodified `std::map` is currently flying in space with no ill effects. |
|
| ▲ | quietbritishjim 15 hours ago | parent | prev | next [-] |
| > Using the stack or the heap means your variables aren't always at the same memory address. 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. |
| |
| ▲ | RealityVoid 13 hours ago | parent [-] | | Your points are correct, but recursion is banned anyway in safety critical applications. The main issue is determinism. The fact you have to use the stack for call stacks is correct OP seems misinformed. | | |
| ▲ | adrian_b 12 hours ago | parent [-] | | You have to use the stack for procedure calls on x86/x86-64 CPUs, where the hardware enforces this. 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. | | |
| ▲ | RealityVoid 12 hours ago | parent | next [-] | | I believe it's possible to do what you've described, but I am not aware of any compiler that does this. 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. | | |
| ▲ | jcalvinowens 12 hours ago | parent [-] | | You can do it on x86 too, just use jmp instead of call and invent your own arbitrary register scheme for it. This x86 program has no stack: https://github.com/jcalvinowens/asmhttpd 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...). |
| |
| ▲ | 0xffff2 6 hours ago | parent | prev [-] | | I honestly can't tell if you know a lot more than me or a lot less than me about how computers work... A couple of honest questions: 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? | | |
| ▲ | quietbritishjim an hour ago | parent | next [-] | | The sibling comment already answered your question, but just to add: As I mentioned earlier, this was actually how old programming languages worked. Famously(ish), Dijkstra secretly snuck recursive functions into the ALGOL 60 standard, thus forcing compiler authors to use a stack! https://news.ycombinator.com/item?id=10131664 | |
| ▲ | RealityVoid 6 hours ago | parent | prev [-] | | Not OP, but presumably, the answers are: 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. | | |
| ▲ | 0xffff2 6 hours ago | parent [-] | | Ah, you're right, the struct case is actually pretty straightforward (especially since recursion is likely forbidden anyway), I just have trouble contorting my brain to such a different viewpoint. |
|
|
|
|
|
|
| ▲ | coppsilgold a day ago | parent | prev | next [-] |
| > 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. 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. |
| |
| ▲ | j16sdiz 19 hours ago | parent [-] | | It is more than that. 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. | | |
| ▲ | coppsilgold 7 hours ago | parent | next [-] | | > If everything have static address and deterministic static, you can have a exact copy on ground and debug there. 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. | |
| ▲ | varjag 17 hours ago | parent | prev [-] | | Interactive debugging is apparently possible and was reportedly done on Deep Space One mission. One of developers involved frequents HN I believe. | | |
| ▲ | unloader6118 15 hours ago | parent [-] | | Kind of. That took hours of not days. A local exact replica with deterministic state save lots of time. |
|
|
|
|
| ▲ | 5d41402abc4b 20 hours ago | parent | prev | next [-] |
| > Using the stack or the heap means your variables aren't always at the same memory address Where do you place the variables then? as global variables? and how do you detect if a memory cell has gone bad? |
| |
| ▲ | cminmin 18 hours ago | parent [-] | | your programs have a data segment. its not the heap nor the stack... and it can be (depending on loader/linker) a source of reliable object->address mappings as its not dynamically populated. | | |
| ▲ | repelsteeltje 17 hours ago | parent [-] | | That sounds like your answer is: "Yes, global variables". 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. | | |
| ▲ | 14 hours ago | parent | next [-] | | [deleted] | |
| ▲ | TuxSH 15 hours ago | parent | prev | next [-] | | > global variables are considered bad design Global mutable variables, and they usually tend to be grouped into singletons (solving initialization issues, and fewer people bat an eye) | | |
| ▲ | RealityVoid 6 hours ago | parent [-] | | Even global mutable variables are a problem only because they lend to spaghetti code and messy state handling if you use them in multiple places. But you could just... make a global variable and then handle it like you would a variable init with malloc. No functional issue there. |
| |
| ▲ | izacus 16 hours ago | parent | prev [-] | | They're considered impractical mostly because language tooling doesn't support them appropriately. | | |
| ▲ | repelsteeltje 16 hours ago | parent [-] | | Can you elaborate? For instance, how would better tooling help with storing a TCP buffer in global memory? | | |
| ▲ | tatjam 11 hours ago | parent | next [-] | | As a quick example, compare doing embedded work with a C static uint8_t[MAX_BUFFER_SIZE] alongside a FreeRTOS semaphore and counter for the number of bytes written, vs using Rust's heapless::Vec<u8, MAX_BUFFER_SIZE>, behind a embassy Mutex. 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. | | |
| ▲ | repelsteeltje 8 hours ago | parent [-] | | Okay, fair enough. 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? |
| |
| ▲ | 15 hours ago | parent | prev [-] | | [deleted] |
|
|
|
|
|
|
| ▲ | RealityVoid 14 hours ago | parent | prev | next [-] |
| You can definitely have local variables on the stack. I don't know where you got that you don't use the stack. Heap is kind of a no-no, although memory pools are used. > 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. |
|
| ▲ | menaerus 18 hours ago | parent | prev | next [-] |
| So none of the functions you implement have in/out parameters? |
| |
| ▲ | cminmin 18 hours ago | parent [-] | | if you use few you can have em all in the registers perhaps (not sure what arch they rollin?) |
|
|
| ▲ | Thaxll a day ago | parent | prev [-] |
| Can't this be done at runtime? Like the underlying calls can black list hardware address on read/write faults? |
| |
| ▲ | amluto a day ago | parent [-] | | If you have memory to spare and are using hardware with an MMU, you can remap your logical address to a different page. Linux can do this, but only for user memory. | | |
| ▲ | anonymousiam a day ago | parent [-] | | This assumes that the operating system can run. If the memory corruption impacts the OS, then it may be impossible to recover. As the systems (and software) have become more complex, keeping these Mission Assurance best practices becomes more important, but the modern generation of developers sometimes loses sight of this. 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. | | |
| ▲ | dahart 10 hours ago | parent | next [-] | | Do satellite embedded satellite systems usually have an OS these days? Is this a custom made OS, or do you have any examples of an OS that honors the no stack/heap and fixed address requirements you mentioned? 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? | | |
| ▲ | anonymousiam 7 hours ago | parent [-] | | It's a mixed bag. Some programs use Green Hills Integrity, some use Wind River VxWorks, some roll their own. I've done all of the above. 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. |
| |
| ▲ | charcircuit a day ago | parent | prev | next [-] | | >This assumes that the operating system can run. 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. | |
| ▲ | amluto a day ago | parent | prev | next [-] | | Oh, to be clear, I would not do this if I needed that degree of reliability. 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.) | |
| ▲ | 5d41402abc4b 20 hours ago | parent | prev | next [-] | | >This assumes that the operating system can run. 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. | |
| ▲ | d-lisp a day ago | parent | prev [-] | | Wow, but how did they deal with anomalies ? 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 ? |
|
|
|