Portrait de Philippe PradosPrados.FRProgrammation, architecture & sécurité — depuis 1998

L'architecture logicielle du système Android

Nous allons descendre dans l'architecture logicielle du système Android. Quels sont les processus en jeux, que font-ils ? Comment communiquent-ils ?

Au niveau système, le framework Android est organisé autour de plusieurs instances de la machine virtuelle Dalvik, dans différents processus.

Toutes communiquent entre elles à l'aide d'un mécanisme d'invocation inter-processus (Cf. article « Inter Process Call ») par envoi de messages.

Le framework est porté par une instance de la machine virtuelle Dalvik, qui expose des composants et des services aux applications. C'est le processus SystemServer qui doit être activé pour que les applications puissent interagir avec le système. Les librairies du framework permettent de communiquer avec cette instance (en IPC). Si cette machine virtuelle tombe, le téléphone est généralement redémarré.

Les applications peuvent être lancées suivant deux approches. Si le terminal n'accepte pas, ou ne désire pas l'exécution de plusieurs processus (via un paramètre de compilation), chaque application est isolée dans un chargeur de classe spécifique. Tous les traitements partagent alors la même instance de la machine virtuelle.

Si plusieurs processus peuvent cohabiter, ce qui est généralement le cas, le framework doit instancier des machines virtuelles pour chacune. De plus, il utilise des identifiants utilisateurs différents pour chacune. Cela permet de les isoler les unes des autres au niveau de Linux.

Pour optimiser l'instanciation des machines virtuelles, Android utilise un processus spécifique appelé Zygote, portant une première instance de Dalvik en modèle. Ce dernier écoute sur un socket local. Sur la réception d'une ligne de commande, cette dernière est interprétée par Zygote, après avoir effectué un fork() du processus. Ce mécanisme permet d'instancier rapidement de nouvelles machines virtuelles Dalvik.

Afin d'optimiser le chargement des applications, Zygote commence par pré-charger une liste de classes en mémoire, à lier les DLL JNI et à faire le ménage à l'aide du ramasse miettes. Ainsi, les applications démarrées après le fork sont déjà proches d'être exécutées et la mémoire optimisée. Chaque fork possède une copie des classes pré-chargées et du lien JNI vers le code C/C++. Zygote se charge de modifier le UID et le GID du processus avant le lancement de la méthode main() de ActivtyThread, le boot d'une application.

Le code de ActivityThread commence par instancier un Looper pour écouter une pile de messages. Les messages arrivent à l'aide du protocole IPC, via l'invocation de la méthode sendMessage() de l'interface AIDL Messenger. Certains messages sont interprétés par ActivityThread pour instancier et déclencher les activités de l'application.

SystemServer, le processus racine du framework, possède également un Looper pour recevoir des messages de la part des applications.

Pour signaler à Zygote de lancer un nouveau processus, il faut utiliser la classe android.os.Process. Cette dernière communique via un socket avec Zygote, pour demander un fork et le lancement d'une classe Java (Cf. Illustration 1).

Zygote est lancé par le script init.rc d'initialisation du téléphone. Ce script utilise une syntaxe propriétaire, permettant de décrire différentes actions et les comportements à adopter en cas de plantage d'un processus. Faut-il relancer le téléphone si le même processus échoue trop souvent ? Relancer simplement le processus ? Ne rien faire ? Tout cela est décrit dans le fichier init.rc présent dans la racine du système de fichier.

Un autre démon est présent, permettant l'installation de nouvelles applications (installd). Ce dernier récupère le fichier APK, associe un compte utilisateur et un groupe à chaque application, invoque DEXOPT pour initialiser le cache de Dalvik, propose un répertoire accessible pour l'application (ou les applications possédant le même signataire). Il est invoqué par le PackageManagerService.

Cet article à décrit plus en profondeur l'architecture logicielle d'Android, les processus majeurs et leurs interactions. Il est important d'avoir conscience de ces interactions, pour limiter autant que possible la communication entre une application et SystemServer.