Skip to content

C# on embedded ARM Linux systems is now practical

Update: Michael Dominic K. has kindly provided feedback on this article and has some additional points about Mono and embedded systems.

Mono has included support for ARM systems for some time, but with the release of Mono 1.2.6, and its inclusion in OpenEmbedded, it is now practical and easy to build and run C# applications on your everyday ARM Linux system.  Several ARM issues in 1.2.5 have been fixed, and thus far it seems the average program runs quite well.  In this article, we’ll explore what advantages C# offers over more traditional languages such as C or Python, and how OpenEmbedded simplifies adding C# support to your embedded system.

Why C#

Why is C# an attractive language for embedded Linux systems?  There are many reasons including memory protection and exception handling, native types, good threading support, extensive library, and developer productivity.  In the past, I’ve typically used C or Python for implementing large applications on embedded Linux systems.  This section will compare C# to my experiences with both languages.

A typical experience when implementing a large application in an embedded system is to deploy the system for field testing (before it is ready) and start getting reports that the application is crashing.  Somewhere in the mass of C code your team just wrote there is a stray pointer clobbering something it should not.  Of course this does not happen in the lab where you have access to the machine, but in some piece of industrial equipment many miles away.  And, you cannot reproduce the problem in the lab because with embedded systems, simulating a real world situation is challenging because it is difficult to simulate real I/O.  This is less of a problem with desktop applications where you are less dependent on external I/O.  Sound familiar?  Even if you can reproduce the problem on a machine you have access to (perhaps even remotely), the problem can be caused by a stray pointer corrupting data in a completely unrelated piece of code.  So while the debugger may tell you what got clobbered, you still don’t know exactly who did it.  If you have lots of time and a team to do lots of testing, this is less of an issue, but available resources and time to market sometimes don’t give you all you need to do the job right before it gets to the field.  One solution to this problem is use a language that has exception handling (I’ve used Python a lot in the past), and log all exceptions.  If something bad happens in the field, I ask for the log.  With the stack trace in the log, and some focused testing I can usually find the problem relatively quickly.  Add in the memory protection and things get a lot easier.  For small applications this is not so much of an issue, but lets face it, now that we have access to very capable hardware at a low cost, applications in embedded systems tend to get large and complex.  C# gives you exception handling and memory protection that makes problems much easier to find and debug; especially during field testing.

While its hard to beat Python or Ruby for developer productively and ease of use, there are performance issues like threading support, and support for static types that must be weighed in the balance.  With Python, everything is an object or a reference.  The checking is done at run time to make sure a variable is of the correct type.  With C#, this is done at compile time (much like C).  While it is generally more work to write code in C# than Python, the compiler tends to catch more errors for you.  There is probably also a performance advantage to being able to implement simple variables statically.  Threading support is another consideration and is often critical in embedded systems because you are usually tending to I/O, processing data, running a user interface, etc.  This type of problem is well suited to a threaded application.  Threading in Python is implemented using a global interpreter lock mechanism that is inefficient in some situations.  C# threading support is more similar to C.  While it may require you to do more manual locking than Python, it gives you more control.

When it comes to developer productivity, in my experience C# is more work to write than Python or Ruby, but less work than C.  So it seems to fall somewhere in the middle of the spectrum.

So, C# seems to fall nicely into the middle ground between C and very high level languages such as Python and Ruby.  It offers many of the advantages at both ends of the spectrum and seems to be a nice balance for programming embedded systems.

Mono

What is Mono?  From the Mono web site :

Mono provides the necessary software to develop and run .NET client and server applications on Linux, Solaris, Mac OS X, Windows, and Unix.

Mono includes all the necessary components to build and run C# applications on Linux systems including embedded ARM Linux systems.   The Mono source tree is fairly clean and is nicely segmented between native and managed code.  This is critical when it comes to cross compiling.  The Mono team has also stuck with traditional build tools such as autotools which makes the project a lot easier to cross compile for an embedded target.

Mono includes a very extensive C# library that is comparable to Microsoft’s implementation.  Most of what you might ever need is built right into the Mono class library; much like Python or Java.  With C or C++, you typically need to add a number of additional libraries to the system.

Mono Support in OpenEmbedded

OpenEmbedded includes comprehensive support for Mono including crosscompiling support, granular packaging of the Mono class library, and automatic run-time dependency generation for Mono applications.  In this section, we’ll see how OpenEmbedded can make your life a lot easier.

Mono is typically cross compiled in two steps :

  • The managed portions are generated by compiling Mono for the host machine architecture.  A very nice feature of the Mono build process is that it can bootstrap itself.
  • The native only portion of Mono is then cross compiled over top of the above output and the native host binaries will then get replaced with the cross compiled target binaries.

To implement this, there are two recipes implemented in OpenEmbedded.  The mono-mcs-intermediate is used to generate all the managed binaries, while the mono recipe builds only the native portion of Mono.  The first step when building the mono cross recipe is to extract the results of the mono-mcs-intermediate in the install directory and then proceed to overlay the native binaries produced by the mono recipe in that same directory.  As mentioned before, the use of standard tools like autotools makes all this work pretty well.  All of this is automated in OpenEmbedded and happens when you run “bitbake mono”.

One issue with embedded systems is you typically have limited flash/disk space.  So you only want to include that pieces of Mono in your target image that your application needs.  The OE build of Mono makes this very easy by packaging up the Mono class library into a number of different packages.  The dependencies between packages is automatically tracked so that if you add a Mono package to an OE build, its dependencies will automatically be included with no extra work on your part.  This mechanism uses the monodis tool to figure out what each Mono library provides and the dependencies of each library.  This saves a huge amount of very tedious manual work to have all this automated.

Building and Running your Mono application

Now that we have Mono running on our target system, there are a number of ways to build Mono applications to run on the target.  The quick-n-dirty method is to simply build the mono binary on any system, copy it to your embedded system and run it.  Building and running your applications is easy as Mono binaries are platform independent and will run on any system that supports Mono.  So, you don’t need to worry about cross compiling, toolchains, etc.

The quick-n-dirty method works well for getting started, experimenting, etc, but there are several advantages to integrating your Mono application into an OpenEmbedded build, even though you technically don’t need to worry about cross compiling.  The first reason is packaging.  A very simple OpenEmbedded recipe provides an easy way to automatically fetch your project from a separate version control system, build it, and package it properly into the OS image for your target.  It also gives you the ability to deploy updates to target systems using the ipkg package manager.   The second reason is automatic run-time dependencies.  If you inherit the mono OpenEmbedded class, all of the Mono components your application needs are automatically added to the image.  An example OpenEmbedded recipe for a Mono application is shown below:

DESCRIPTION = "My Application"

DEPENDS = "mono"

SRCREV = "${AUTOREV}"
PV = "1.0+svnr${SRCREV}"
PR = "r1"

SRC_URI = "svn://svn.mycompany.com/svn/myproject/;module=myapplication;proto=https"

S = ${WORKDIR}/myapplication
inherit autotools mono

The above recipe assume you have set up your application to be built with autotools, which is a good idea.  This page provides lots of good information on building Mono applications using autotools.  You can also look at a number of open source applications such as Tomboy for additional clues about how to use autotools with Mono.  For a simple application that I built, the following run time dependencies where automatically determined by OpenEmbedded: mono, libmono0, libmono2.0-cil, libmono-corlib2.0-cil. Each of these dependencies may have additional dependencies, and in this case a dozen or so mono packages ended up being installed.

Summary

As embedded systems become larger and more capable, we can expect that higher level languages such as C# and Java will start to become more common.  In the past, the focus on embedded systems programming was often efficiency, minimizing resource utilization, etc.  Now that very capable hardware is becoming cheaper, the focus in some systems is shifting to features and time to market.  Operating systems like Linux and languages like C# help us reach those goals and develop competitive products     quickly with small teams.  C# provides the features needed to rapidly develop, deploy, and debug complex applications that still perform reasonably well.  OpenEmbedded provides a way to manage builds, so that you can focus on real work instead of tedious build issues.