Last week I attended a technology event dedicated to embedded solutions. In one of the sessions, a company focused on operating system abstraction layers (OSAL) presented their take on porting.
To make a long story short, the hour was devoted to showing how porting becomes a breeze for customers using their product to the extreme – you just “use the product” and voila – no need to think about porting ever again.
As someone who’s done his share of porting and works in a company that provides portable software to its customers, I wanted to set the record straight and share a few facts you might not have known about the black art of porting.
Intro: How OSALs work?
OS abstraction is very important when writing embedded software that will be ported between different operating system.
The usual way in which OS abstraction is done is by writing your own wrapper around the system calls you need to use. Things like mutexes, timers, threads, memory allocation functions and other types of constructors will be neatly wrapped in some API, so that instead of calling the operating system directly you will call the wrapper you defined on top of it.
Then, when the time comes to port the code, you simply change the wrapper implementations according to the relevant operating system and you’re done. That’s the whole story of OSAL, and it is great.
Now we drop the marketing pitch, and get back to reality. And in reality, there are things that you might have missed in the process above.
Here are 5 examples:
1. Build environments differ between operating systems
This one is easy.
Let’s look at C or C++ development as an example:
- When you develop for Windows, you usually use Visual Studio.
- When you develop for Linux, you use gmake or some other build system.
- Android? It’s Eclipse with Android Development Tools (ADT) Plugin.
- Nucleus? Integrity? OSE? Symbian?
You get the drift.
The fact that the source code is running on top of a nice wrapper doesn’t really help you in the build process, when you want to compile the code you have.
You will have to port your build system as well, and in some cases this “porting” is actually rewriting it from scratch.
Not a big deal, but does require a bit of work.
2. Them compilers are different
Not all compilers are born equal.
They tend to throw out different types of warnings. Code written and compiled on one compiler might spew a slew of warnings on another. And while these warnings might not all be important, leaving them in the code means you might miss some real bugs that the compiler complained about later on.
If you are doing some real-time stuff, then you might also need to optimize for the new operating system parts of the code that you didn’t in the past. The easiest example here is a video codec – as it takes huge amounts of processing power, optimizing it for each processor/architecture can give real benefits in performance (which are sometimes a must), but will take a lot of time to achieve.
3. Bus errors and type definitions
Different architectures and different compilers tend to regard basic type definitions differently.
- How big is an int – is it 16 bits, 32 bits or 64 bits long? You can read what Ofer Goren has to say about type conversions and understand how changing these assumptions can cause nasty crashes in your system.
- Is a char type signed or unsigned? Do you care?!
- Can you treat any position in memory as if it is an integer, or must it be aligned on a 4 byte or 8 byte boundary? Would such an alignment speed up performance?
A thing that usually happens when we try to port our code for the first time to the Solaris OS is a crash due to a bus error. It’s easy to fix once caught (just make sure you align your data properly), but you need to actually run the code to find it.
OSALs can’t fix bus errors. They can provide some basic type definitions with clear guarantees (we offer RvInt32 and RvUint32 for example for signed and unsigned integers of 32 bits), but you still need to use these new types to make them work.
4. Drivers and peripherals
Connectivity to drivers and peripherals usually isn’t standardized in a way that you can wrap it properly.
Take a multimedia chip, for instance. Put Linux on it, plug a camera in, and now you have to tweak inside the kernel and the camera driver to make it work the way you want.
And you will do that for your next camera on the same device. And for your next device with the same camera. And you will definitely have to rework what you’ve done if you migrate to another operating system.
No OSAL solution here.
5. User interfaces
Did you build that nice user interface of yours using Qt? If you did, then it is portable. But not always: if you want to move to Android or iPhone, you will need to rewrite the user interface at the very least.
There are different user interface frameworks that you can use. None of them is perfect, and while they do offer some level of portability, there will be enough work left for you to deal with when migrating to another platform or operating system.
Moral of the Story
Make sure your code is portable. Use an abstraction layer when necessary. We do that here and we use it as a key benefit of our developer tools.
When moving to the next operating system or platform, don’t count on OSAL to save you, but it will definitely reduce your risks and workload in the process.