Lang leve Linux – sorry, Android!

Reading time: 7 minutes

Author:

De embedded-wereld omarmt Android als logische opvolger van embedded Linux. Maar het platform is daar niet in eerste instantie voor bedoeld en kent de nodige beperkingen. Hoe programmeer je Android – speciaal met het oog op moderne multicore systemen?

Op Linuxcon Europe in november 2012 verklaarde de bekende Linux-trainer en consultant Chris Simmonds embedded Linux halfdood en Android de gedoodverfde opvolger. Simmonds had een aantal goede argumenten waarom het Google-OS steeds breder wordt toegepast: het is de mobieltjes allang voorbij.

Elk apparaat moet tegenwoordig gekoppeld zijn aan internetdiensten: de weegschaal maakt via een webdienst grafiekjes en de thermostaat kan op afstand lager worden gezet. Dit was de oorspronkelijke reden om Linux in apparaten te gaan gebruiken; USB- of WLan-ondersteuning inbouwen in simpelere OS‘en was domweg te veel werk. Maar een tweede revolutie lijkt nu daar weer een eind aan te maken.

De tijd dat wasmachines toe konden met een enkele draaiknop is passé. Nu moeten apparaten worden uitgerust met een touchscreen. En zoals enkele fabrikanten tot hun spijt hebben moeten constateren, verwachten de meeste gebruikers dan meteen een touchscreen dat zich gedraagt als een Iphone. Dus mét animaties en mét handbewegingen als tikken, vegen en knijpen. ’Gewoon‘ embedded Linux kan daar niet automatisch aan voldoen. Met uitzondering van Qt zijn geen van de bestaande grafische toolkits toereikend voor een flitsende Gui. Dus ondanks dat Canonical denkt een Ubuntu Phone OS te kunnen bouwen, lijkt een ’gewoon‘ Linux het niet te gaan halen op een touchscreen.

Blijft over: Android. Dat heeft een aantal voor- en nadelen ten opzichte van een ’normale‘ embedded Linux-variant. De zeer uitgebreide UI-bibliotheken omvatten niet alleen graphics en netwerkinterfaces, maar bijvoorbeeld ook aspecten als geolocatie, beweging, camera, spraakherkenning en contactgegevens. In Android zijn relatief complexe applicaties in beperkte tijd te bouwen. Nadeel van deze ’rijke‘ omgeving is dat zelfs de kleinste Android-configuratie nog steeds 160 MB flash vereist. En cynici roepen dat je met die 160 MB vooral géén embedded Linux krijgt – er zit bijvoorbeeld maar een zeer beperkte shell-scriptingomgeving in en lang niet de gehele Posix-C-bibliotheek is beschikbaar. ’Even‘ een bestaande C/C++-applicatie crosscompileren voor Android is er dan ook meestal niet bij.

De verschillende mogelijkheden voor applicatieontwikkeling met Android

Type software ’eenvoudige‘ apps gemengde apps games platformsoftware device-drivers
taal Java Java en C/C++ C/C++ C/C++ C
technologie Java JNI (eventloop in Java) NativeActivity (eventloop in C/C++) Android-platform Linux-kernel
ontwikkelgereedschappen Eclipse + SDK Eclipse + SDK + NDK NDK + alle editors commandoregel commandoregel
multithreadingmodel java.util.concurrent PThreads PThreads PThreads n.v.t.
GPGPU-gebruik Renderscript Renderscript of OpenCL Renderscript of OpenCL OpenCL n.v.t.

Tabel 1: De meeste app-ontwikkelaars kunnen met Java uit de voeten. Als de prestaties niet toereikend zijn, zijn er diverse opties beschikbaar om in C en C++ te ontwikkelen.

Niks in Java

Maar Android doet een aantal dingen beter dan de gebruikelijke embedded-Linux-bouwsystemen als Open Embedded, PTXDist of Buildroot. Allereerst leveren de chip- of bordfabrikanten vaak al kant-en-klare Android-omgevingen mee. Ten tweede heeft Android al een software-updatemechanisme ingebouwd.

Welke programmeertaal is het beste voor een project dat ook nog snelheidseisen heeft? Zoals in Tabel 1 te zien is, zijn er meerdere ontwikkelmodellen. De meeste Android-toepassingen vallen in de tweede kolom, waarbij de gehele app is ontwikkeld in Java.

De derde kolom bevat applicaties die gebruik maken van reeds bestaande C/C++-library‘s die naar Android moeten worden overgezet. In dit geval moet naast de Android-SDK ook de NDK (Native Development Kit) worden geïnstalleerd. Omdat deze applicaties gebruikmaken van gecompileerde code, is de binary specifiek voor een processorarchitectuur: Arm of X86, of misschien zelfs specifieke processorversies. Voor elk van deze platforms dient er opnieuw te worden gecompileerd en gepackaged.

De NDK genereert een hoeveelheid glue-code (Java Native Interface, JNI) die ervoor zorgt dat de C-functies en variabelen vanuit Java te gebruiken zijn. Hiermee is de applicatie dus gemengd: sommige delen in C/C++ en sommige in Java. De afhandeling van gebeurtenissen door interactie met de gebruiker (de eventloop) bevindt zich nog altijd in Java.

Bij bijvoorbeeld een port van een bestaande applicatie zoals een spelletje kan er ook voor worden gekozen om zelfs helemaal niks in Java te schrijven. Dit is de vierde kolom in Tabel 1. De Java-code die Android nodig heeft om de levenscyclus van de applicatie te beheren, wordt dan gegenereerd als een zogenaamde NativeActivity. Op dat moment komt de eventloop naar de C-code. De daadwerkelijke applicatie krijgt de verschijningsvorm van een Linux-bibliotheek (een bestand met de .so-extensie) en is natuurlijk processorspecifiek. Dalvik, de Java-VM van Android, moet de startende applicatie nog wel steeds inbinden vanuit de gegenereerde Java-code, net als in de eerste twee situaties.

Al deze applicaties zijn direct installeerbaar via Google Play: het zijn gewoon apps. Bij de categorie uit de vijfde kolom is dat niet langer het geval. Hierin vallen applicaties en bibliotheken die de hardwarefabrikant op het apparaat installeert en die gebruikers niet kunnen verwijderen. Zulke bibliotheken zijn beschikbaar vanuit elke applicatie. Als het embedded systeem nieuwe randapparatuur moet aansturen, valt de ontwikkeling van besturingssoftware op kernelniveau in de vijfde kolom.

Deze laatste kolom is met name van belang voor embedded-systeemontwikkeling en -configuratie. Omdat het Android-systeem enkele flinke beperkingen heeft, kan ontwikkelen, debuggen of profilen van dit soort toepassingen echter moeilijk worden. Allereerst heeft Android het gewone Unix-concept van verschillende gebruikers overboord gegooid – dit mechanisme wordt nu ’misbruikt‘ om de toegangsrechten van verschillende gedownloade apps te regelen. Ten tweede is er geen systeem van zoekpaden: alle applicaties worden geforkt vanaf een proces genaamd ’Zygote‘. Bij het opstarten van Android laadt en opent Zygote alvast alle veelgebruikte bibliotheken. Een fork naar een nieuwe applicatie kost dan heel weinig tijd en voorkomt dat de toepassing zelf nog eerst al deze library‘s moet gaan laden en runtime moet linken. Deze Zygote-truc verhindert helaas wel om een debug- en een releaseversie van dezelfde library op het systeem te hebben.

Later dit jaar op de markt

Voor veruit de meeste Android-applicaties ligt ontwikkeling via Java voor de hand: de taal geniet goede ondersteuning en er zijn uitgebreide bibliotheken beschikbaar. De runtime prestaties van Java zijn de laatste tijd aanzienlijk verbeterd en voor de meeste toepassingen voldoende. Als ze toch onvoldoende blijken, zal de belangrijkste verbetering altijd eerst gezocht moeten worden in de structuur van de applicatie zelf, zoals de keuze van het algoritme en de effectiviteit van de onderliggende datastructuren. Als verbeteringen hier niet voldoende soelaas bieden, kan worden gekeken naar een efficiëntere implementatie van het algoritme. Hiervoor zijn verschillende mogelijkheden. Ten eerste kunnen de rekenintensieve delen van de applicatie worden gecodeerd in C/C++ terwijl het algoritme gelijk wordt gehouden. De ontwikkeling schuift dus van kolom 2 naar kolom 3 van de tabel.

Ten tweede kunnen de vectorisatiemogelijkheden van moderne processoren worden gebruikt (zoals Intels SSE en AVX of Arms Neon). Met name voor het (data)parallel verwerken van simpele datatypen kan dat een aanzienlijke versnelling opleveren. Dit heeft vooral nut bij beeld- en geluidsverwerking. Vanuit Java zijn deze vectorinstructies niet rechtstreeks beschikbaar, dus ook dit vereist de overstap van kolom 2 naar kolom 3.

Het dualcore Pandaboard kan onder Android ook code gecompileerd voor de Omap4460-processor draaien, in dit geval de ’native-plasma‘-demo van de Google-NDK.

Ten derde kunnen de multicoremogelijkheden van moderne processoren worden benut. Hiervoor moet de applicatie (het algoritme) worden geparallelliseerd: er worden meerdere threads opgestart die ieder hun deel van het rekenwerk doen. In Java is hiervoor het uitstekende java.util.concurrent-package beschikbaar. In C is er standaard de zeer low-level PThreads-library. Abstracties op hoger niveau zoals C11, OpenMP of Intel TBB zijn vooralsnog niet beschikbaar voor Android.

Ten vierde kunnen delen van de applicatie worden verplaatst van de CPU naar een (general-purpose programmable) GPU. Voor de hedendaagse mobiele Android-platforms zijn zulke GPGPU‘s nog niet beschikbaar, maar de eerste geschikte apparaten zullen later dit jaar op de markt komen. De beoogde programmeerinterfaces hiervoor zijn OpenCL en Renderscript, die beide C gebruiken. Renderscript is bovendien beschikbaar vanuit Java.

Elke instructie

Deze optimalisaties hebben alleen zin als de daadwerkelijk rekenintensieve delen worden versneld. Vaak is het echter niet duidelijk welke delen dit zijn. Programmeurs staan ook niet bekend om hun goede inzicht in de efficiëntie van hun code. Er moet dus worden gemeten om effectief te kunnen werken aan prestatieverbetering.

Google levert voor Java-ontwikkelaars een trace-tool die de interacties tussen processen kan aangeven en duidelijk maakt welke functies de processor het meeste belasten. Het is echter opvallend dat er voor ontwikkeling in C en C++ nog geen echte profiling-tool bestaat. Werk om statistische profilers zoals het Perf-project van de Linux-kernelgemeenschap geschikt te maken voor Android is helaas nog niet voltooid. Ook andere bekende Linux-profilers, zoals OProfile en GProf, werken niet of zeer beperkt.

Dat gat wordt nu opgevuld door commerciële aanbieders: bij Vector Fabrics hebben we het werk om onze Pareon-tool geschikt te maken voor Android vrijwel voltooid en zijn de eerste bètaklanten ermee aan de slag gegaan. Pareon bekijkt elke instructie van het uit te voeren programma en brengt daarmee niet alleen het aantal verstookte klokcycli in kaart zoals een statistische profiler, maar vormt ook andere belangrijke parameters als cachegebruik en beschikbare bandbreedte.

Vergeleken met de gebruikelijke embedded Linux-systemen heeft Android een aantal voordelen, maar op performancegebied wordt het niet makkelijker; de mix van Java en C-code, de brede beschikbaarheid maar lage utilisatie van multicore systemen en het ontbreken van goede prestatietooling suggereren dat het platform nog niet volwassen is. Misschien is het toch nog wat te vroeg om ’embedded Linux‘ dood te verklaren.