Practical Guide to Bare Metal C++
gitbook.com
gitbook.com
For instance they don't appear to use RAII at all, even in places where it's an obvious win (InterruptLock).
The first advantage for me of C++ everywhere IS RAII.
Do they really not cover it or is the search busted ?
As the maintainer of a C++14 proprietary RTOS, it's probably the most convincing argument to the C guys as to why C++ can benefit embedded codebases. Like not being able to forget to re-drop thread priority when you've temporarily increased it.
Or one of my favorite patterns is returning the object representing the temporary higher priority. If the caller doesn't do anything with the return value, the destructor is called and thread priority reverts to what it was. However, it gives you a chance for the caller to hold on to that high priority object, and get more work done essentially atomically.
In an RTOS of all places, that seems awfully expensive. One generally avoids wasted resources in embedded -- the trade-off is it requires more care during development.
I've never seen RAII work well in an embedded system. Not being able to handle exceptions or errors during destruction ruins everything.
> In an RTOS of all places, that seems awfully expensive. One generally avoids wasted resources in embedded -- the trade-off is it requires more care during development.
The compiler has no issues "optimizing out" the constructor in my experience. The asm looks just like the equivalent C in all of the cases I've seen.
> I've never seen RAII work well in an embedded system. Not being able to handle exceptions or errors during destruction ruins everything.
I mean, we disallow exceptions anyway. And we use RAII for objects where there isn't a weird failure case to be handled like my example of thread priority guards.
> I haven't found many practical guides or tutorials of how to use C++ superiority and boost development process compared to conventional approach of using “C” programming language.
... and does not mention RAII in their quest to write such a guide, they have seriously failed at their goal.
Also without exceptions you really need double initialize pattern for your class types. I.e. a ctor that cannot (and will not throw) and then a "error_code Init(...)" method that can return an error code.
I find the improved initializers in C++11 a very nice addition for such usecases. One can use a struct for the application state (with struct/std::array members etc), and initialize it statically in one statement. Nicely declarative, space/code efficient, and with good type-safety.
This right here. There is no STL in true embedded C++ apps.
It is possible to make compile time allocated templated data structures, they work just fine, but they aren't the STL by any stretch.
IMHO this type of C++ is much better to use. Having to plan ahead for worst case memory usage for all classes and statically allocate memory at compile time simplifies a LOT of code. Pointers don't disappear on you (although contents can obviously change!), and removing all the code handling alloc and de-alloc removes a huge chunk of code.
Honestly having gone from "C with classes C++" to "full on C++11", I'm missing the C with classes. Fewer ways to abstract means finding the code that is actually running becomes a lot easier.
Also having code size be an issue means people don't get to write their own set of abstraction layers that end up having only 1 or 2 concrete implementations. :/
On bare-metal, you probably want to be using std::array<> most often (Thanks C++11), which I don't believe have any errors or allocations.
Dynamic Memory is useful in application code, but under bare metal... its often less useful than carefully managed static allocations. Or perhaps... carefully managed static allocations (std::array) are easier to "prove" that edge-cases won't happen.
---------
Something like std::vector or std::unordered_map which reallocate themselves and moves all around memory seems like a huge, unnecessary complication in memory-restricted situations that warrant bare-metal coding.
I guess with a proper std::Allocator class, std::vector and std::unordered_map would be useful. But... that gets complicated. And not very many C++ programmers are aware of how to use "Placement New" or other allocator features. Its probably best to just stick to the simple std::array and avoid dynamic memory.
I mean sometimes for some apps aborting on such errors (on some embedded platforms where they must work within specific resource constraints) can be an OK strategy but in general people seem to have this special idea about "bad alloc" conditions.
The usual misconceptions are that a) it cannot happen and b) nothing can be done.
Both are false.
Ok first thing first.
Like I said for some specicial resource constrained system abort() can be a viable solution, or if you just don't want to deal with any resource allocation failures.
In general though if you have a std::vector and a push_back throws bad_alloc, that bad_alloc really only pertains to that particular vector using that particular allocator.
If you assume that a bad_alloc coming from anywhere indicates some kind of system/process wide condition you're assuming too much. UNLESS you've designed the system, i.e. in this case the allocators and such and know this assumption to be true, otherwise no.
Additionally consider a case where your code is for example reading some data off a disk in a loop and the Nth iteration and push_back throws, the stack is unwound, the vector is destroyed and the memory it consumed before is released. In a "normal" application the bad_alloc can propagate up the stack until it's handled by whatever strategy that the application uses for general resource failures.
The two takeaways here are that bad_alloc doesn't really indicate any system wide condition (unless you know that it does), so don't assume such.
Secondly bad_alloc is just like any other resource allocation failure. If your application aborts() when it cannot open a socket, that's fine. But if it doesn't, then why would it abort on memory allocation failure?
And I really want to stress here that bad_alloc doesn't mean same as OOM for example from the kernel's perspective.
It really only pertains to a particular allocator, UNLESS you KNOW otherwise.
You seem to believe this is not a common choice? I've seen plenty of programs crash on being unable to open a socket/file.
I'm not saying this is a good thing, just that your rhetorical implication that it is unusual seems misplaced.
And it's not always even that bad. It is true that something can be done, yes, but that something often requires massive fundamental rearchitecting of the core paradigm of the system or something. In most modern languages it isn't that easy to try to deal with resources problems. Erlang could probably do it, and... uhh... I'm already running out of possibilities here.
If you can't allocate memory, can your app work (... example ...)? I guess something like trying to load a huge file (say spreadsheet) in excel-type of app.
A more realistic example might be if you have an application that allocates a large chunk of memory up front for processing later. If it fails, you can stall until a point where you know the memory will be available, or you could retry with a smaller allocation.
One of the nice things about C++ is that the STL isn't using special case backdoors in the language, so if it doesn't work for your use case you can write your own data structures and have full language support.
If you want some other strategy then you have no choice but to implement your own containers with APIs that can support such operation.
Linux - malloc never fails, so how would you know?
Xbox/PS2/PS3/Wii/etc. - bring up a screen that says "disc error"
iOS - the system will already have killed the process long before it's an issue
It's important (as many posters in this thread have already noted) to understand that C++ is many languages, and that bare-metal C++ is not necessarily the same language you would use elsewhere.
The fact remains, however, that in the right hands it can be an excellent tool for the task, and certainly more expressive and capable than its predecessor.
Plenty of issues with C++, but it is not a problem to write bare metal applications or a kernel with it, at least for the last 10 years.
Only Linux is really purely C-based, since Linus really hates C++ with a passion. But there's a reason why even the GCC project has moved over to C++.
http://lordjeb.com/2013/09/19/modern-c-in-the-windows-kernel...
XNU does not appear to have any Objective-C in the kernel, and it has no C++ in the scheduler, syscall handler, or any other critical section. I doubt Windows has C++ in any such place either.
Inside the XNU tarball (the actual kernel inside OS X) there are 1,078 C source code files containing 652,124 source (not comment, not whitespace) lines. There are exactly 10 Objective-C files containing a total of 1787 code lines, and it's not certain they are even built as part of the kernel binary.
To be fair, there is some amount of C++ in XNU these days. XNU contains 97 C++ files totalling 68,673 code lines, it seems mostly in iokit[50kSLOC] and libkern[17kSLOC] (infrastructure to support the iokit C++ code, maybe).
https://msdn.microsoft.com/en-us/library/jj620896.aspx
Hint: This exists for a reason.
Another note: Window's Kernel exposes an API for custom user-mode task-scheduling (Called UMS). Its API is accessible from C++: https://msdn.microsoft.com/en-us/library/windows/desktop/dd6... . When you get to the ConcRT stuff (which is built on top of UMS), its C++ code for sure: https://msdn.microsoft.com/en-us/library/ee207192.aspx
Nearly direct-access to the GPU is also done through a little C++ API called DirectX.
https://msdn.microsoft.com/en-us/library/windows/desktop/dn7...
DirectX is very C++, using namespaces and stuff. Its a bit wonky because DirectX itself is older than C++98, but its clearly a C++-based API. Its very efficient and performant too.
Not sure what you consider "near", but I certainly wouldn't consider even Direct3D 12 to be anywhere "near" direct access to the GPU. It doesn't expose the shader ISA, it doesn't expose the command stream format, it doesn't expose the register sets.
The user mode scheduler is not the system scheduler, thus my statement holds.
Of course, none of those are important.
The important bit to expose are exposed. IE: The memory-model of the GPU. CPU memory space vs GPU memory space, such as Residency: https://msdn.microsoft.com/en-us/library/windows/desktop/mt1....
Indeed, the ability for C++ to create abstractions that can track memory spaces in DirectX is proof positive that C++ has useful features applicable to the OS developer.
https://msdn.microsoft.com/en-us/library/windows/desktop/mt6...
-------
Note that both CUDA and OpenCL support C++ features as well. The RAII feature of C++ alone is a huge benefit to anybody tracking what bits are where in OS settings. Have you tried to use C++ in the last 10 years?
Also, it's not really C++, it's a pretty serious subset (lacking templates, multiple inheritance, exceptions, reflection, funky new casts [reinterpret_cast, static_cast, dynamic_cast, const_cast], and even namespaces(!)).
Oh come on. Its not like you see C99 dynamic arrays used very much in Kernel mode.
That's the joy of programming languages. Good programmers use the good parts and ignore the bad parts. C++ has a lot of bad parts (with regards to kernel programming. Such as how most of the STL automatically reallocates memory on its own whims).
So, you ignore the parts that don't work for your task, and the work with the parts that are useful. Very few people have good uses of multiple inheritance anyway.
----------
> Also, it's not really C++, it's a pretty serious subset (lacking templates, multiple inheritance, exceptions, reflection, funky new casts [reinterpret_cast, static_cast, dynamic_cast, const_cast], and even namespaces(!)).
BTW: The -kernel flag in Visual Studio only disables:
* Dynamic Casts (Reflection/RTTI is disabled), Exceptions, and the DEFAULT New / Delete.
Templates, Static_cast / Const_cast and all that good stuff are still available.
This is a similar "subset" of C++ that has been chosen for GCC development: https://lwn.net/Articles/542457/
The danger of quick edits. : )
> Drivers are not really kernel per se, there are plenty of systems which have no drivers in the kernel (usually except RTC, which is in kernel in XNU anyway).
I mean, except more than half of the OSs you listed. And of those you listed that are microkernels, only QNX has gotten any serious traction.
> Also, it's not really C++, it's a pretty serious subset (lacking templates, multiple inheritance, exceptions, reflection, funky new casts [reinterpret_cast, static_cast, dynamic_cast, const_cast], and even namespaces(!)).
Yeah, compilers have come a really long way in the nearly 20 years since that was designed, but they want to keep ABI compatibility.