Platform addition or NDK
There are two ways of bringing native code to an Android device, either add it to the platform itself and integrate it with the framework, or include it with an application package. The latter method have evolved a lot and with the release of NDK version 5 even allows you to hook directly into the application lifecycle http://developer.android.com/reference/android/app/NativeActivity.html from the NDK. The NDK is useful for any application where you have need of native performance, have portable C libriaries you want to reuse or just some legacy native code that could be included in your application. The NDK integrates well with the Android SDK and is a great way to include native functionality in your application. It should be the preferred way for any application that needs to be reusable across a lot of Android devices.
The other option is to include your functionality, it may be native or Java, as an API extension for all applications to use. This will only work on devices that implement these extensions and it may be a suitable option for device builders. This is the variant that we aim for here.
Analyze the existing project
Porting native code to Android is not always straight forward, especially if we are talking about C++ code due to the fact that Android uses its own c-runtime with limited support for exceptions among other things. If you want to know more about the details of bionic there is an overview in the NDK docs.
The code I wanted to port for this project was the Enea LINX for Linux framework which is a fast IPC framework. My purpose was to be able to interact with control systems running our OSE real time operating system which also implements this kind of IPC. LINX consists of a couple of kernel driver modules, a user space library and some configuration and control utilities. It is written in C. I had created a small demo with LINX in Android before where I compiled it separately and used static linking but for this project I wanted a complete port to the Android build system. It did not have any issues with bionic compatability so the port should be straight forward.
I just want to add a short disclaimer about LINX. I use it here since it is a good example of integrating a solution into Android from kernel drivers up to the API levels. This particular piece of code does add additional IPC mechanisms to the systems which more or less messes up the security model so do not use it unless you are aware of the implications. The steps needed to port code to Android described in this post are however applicable for any type of driver/framework/library that you may want to include on your product.
Adding kernel driver modules
The first step was to add the kernel modules to the Android build. One way would have built a new kernel and include them directly but for this project I chose to keep them as separate modules. Building for the kernel is not handled by the Android build system meaning that we build them as we would do with any Linux system. The target is an Atmel based development board and in the LINX module build I provide the headers and cross compilation toolchain for that kernel and architecture.
Now for the Android specific parts. We need to add the compiled kernel modules to the platform build system in some way and create an Android.mk file that includes them in the system image when we build. Add a folder in the source tree where your project will go, device or external are suitable candidates. I created a folder called linx that will hold the entire linx port and in that I added a subfolder called modules where I place the prebuilt kernel modules. Now what we need is an Android makefile to copy them to the suitable place in the out folder for system image generation. This will look like:
LOCAL_PATH := $(my-dir) include $(CLEAR_VARS) LOCAL_MODULE := linx.ko LOCAL_MODULE_CLASS := SHARED_LIBRARY # This will copy the file in /system/lib/modules # LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/modules LOCAL_SRC_FILES := $(LOCAL_MODULE) include $(BUILD_PREBUILT)
The standard location for modules on the Android system image is System/lib/modules so that is where we copy them. If we build the platform now the build system will copy our precompiled module linx.ko to the system image that we use for our device. The next step is to make sure that we have the module installed on the system when we run it. This can either be done manually via the shell or via a script that we run during init.
In this case I have created a shell script to be launched from init.rc with the following content:
#linx init insmod /lib/modules/linx.ko insmod /lib/modules/linx_tcp_cm.ko netcfg eth0 up ifconfig eth0 192.168.1.12 mktcpcon --ipaddr=192.168.1.21 ControlConn mklink --connection=tcpcm/ControlConn control_link
This includes installing the modules and configuring the network and LINX-link. We launched this from init.rc by adding:
... #linx init script service linx-setup /system/etc/linx_setup.sh oneshot ...
The setup script is added to the system image in the same way by including it as a prebuilt target.
LOCAL_PATH := $(my-dir) include $(CLEAR_VARS) LOCAL_MODULE := linx_setup.sh LOCAL_MODULE_CLASS := ETC LOCAL_MODULE_PATH := $(TARGET_OUT)/etc LOCAL_SRC_FILES := $(LOCAL_MODULE) include $(BUILD_PREBUILT)
Creating Android make files for the user space code
Now that we have the drivers in place the next step is to look at porting the user space libraries. The default LINX build system uses standard GNU make files but we need to create new ones adapted to the Android build system. Start out by adding the source files needed to the linx directory created in the Android source tree. This gives the following structure:
Android.mk include liblinx linx_basic linxcfg linx_setup.sh modules
I have the linx setup script and the main Android.mk file in the top directory and then we have the source files in separate folders and the include files in the include folder. To illustrate how the Android make files for each source component is created we can use liblinx as an example. The Android.mk file looks like:
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_SRC_FILES := linx.c LOCAL_C_INCLUDES += $(LOCAL_PATH)/../include LOCAL_MODULE := liblinx LOCAL_PRELINK_MODULE := false include $(BUILD_SHARED_LIBRARY)
We set our sources by specifying LOCAL_SRC_FILES and the name of the library by specifying LOCAL_MODULE. We also need to supply the header files in the include directory by specifying LOCAL_C_INCLUDES. Finally this is a shared library that we are porting so use the BUILD_SHARED_LIBRARY template. This will build the library with the Android build system and add it to the system image as a shared library with the name liblinx.so.
The rest of the code is moved to the Android build system in the same way, by creating Android.mk files and specifying type and any dependencies. As another example we may look at the syntax for building the mktcpcon configuration program. This depends on the library we just created and hence the makefile looks entry looks like:
LOCAL_SRC_FILES := mktcpcon.c LOCAL_C_INCLUDES += $(LOCAL_PATH)/../include LOCAL_STATIC_LIBRARIES += liblinxcfg LOCAL_SHARED_LIBRARIES += liblinx LOCAL_MODULE := mktcpcon include $(BUILD_EXECUTABLE)
Here we use the BUILD_EXECUTABLE template and we also need to specify static and shared libraries that we link against.
I hope that provides some insight in how you setup the build for an existing linux project to run on an Android system. The steps to follow are:
- Build any kernel related things using the correct kernel build system and config for your device
- Add the kernel modules (and/or kernel) to the platform build system and create Android.mk files for them using the prebuilt template.
- Create config and intialization services for your drivers if needed and add them to init.
- Move the rest of your code (user space) to the Android source tree and create Android.mk files for them.
- If you encounter build errors work them out in the source code and see what incompatabilities your code have with the specifics of the Android C-runtime.
That wraps up my post for today. Having done this we are now able to use our added drivers and API:s from native programs running in the shell. The next step is to create a JNI layer and java library to allow regular Android applications to make use of our platform additions.
I have been away for half a year on paternal leave (nice Swedish benefit) but now it is full time Android hacking again and pushing the team to publish things. Hopefully you will see more activity here including a follow up on this post discussing applications APIs.