Entendendo o processo de boot do Android

- por Sergio Prado

Categorias: Android, Beagleboard-xM Tags: , ,

No artigo passado dei uma introdução ao funcionamento interno do sistema operacional Android, sua arquitetura, as alterações no kernel e seus principais componentes de software. Neste artigo vamos estudar o processo de boot que dá vida ao robozinho do Google.

Os testes que realizei foram todos baseados no Gingerbread (Android 2.3) rodando na BeagleBoard-xM. Para quem ainda não leu, uns tempos atrás escrevi um artigo sobre como compilar e executar o Android na BeagleBoard.

VISÃO GERAL

O kernel do Linux chama o processo init, que faz a configuração básica do sistema e inicia processos e serviços, incluindo o zygote, responsável por inicializar a máquina virtual Dalvik, e todos os processos e serviços Java.

Satizfeito com a versão resumida? Aposto que não! Então vamos para uma versão um pouco mais longa…

OS PRIMEIROS ELÉTRONS DE VIDA

Os primeiros elétrons a circularem nas veias do robozinho são totalmente dependentes do hardware. Normalmente a CPU possui um código de boot residente em ROM (bootloader de primeiro nível), responsável por carregar um  bootloader de segundo nível para a memória. Este bootloader pode ainda carregar um outro bootloader de terceiro nível antes de iniciar o kernel do Linux. Na Beagleboard, é isso que acontece:


1. O bootloader de primeiro estágio, residente em ROM, procura por imagens de boot em diversos dispositivos de armazenamento e carrega para a SRAM (limite de 64KB). No nosso caso, ele irá encontrar o X-Loader na flash NAND.

2. O X-Loader, bootloader de segundo estágio, é carregado para a SRAM. Ele inicializa a controladora da DRAM, flash NAND e leitora MMC, e carrega o bootloader de terceiro estágio, que no nosso caso é o U-Boot.

3. O U-Boot, bootloader de terceiro estágio, roda da RAM. Ele inicializa alguns dispositivos de hardware (rede, USB, etc.), carrega a imagem do kernel do Linux na RAM e passa o controle para ele.

4. O kernel roda da RAM e assume o controle do sistema. A partir deste ponto, os bootloaders viraram passado.

Sob o ponto de vista do sistema operacional, o que importa é saber que um bootloader em algum momento será carregado, irá inicializar o hardware, carregar o kernel para a memória e executá-lo.

DENTRO DO KERNEL

O processo de boot do kernel do Linux é idêntico, independentemente da plataforma ser Android, Meego ou Ubuntu. O importante aqui é saber que o kernel fará seu trabalho normalmente, inicializando processos, memória virtual, drivers, dispositivos, sistemas de arquivo, rootfs, etc. Para quem quiser uma descrição do que acontece “under the hood” no kernel, pode dar uma lida no artigo do Gustavo Duarte e da IBM DeveloperWorks.

O que importa é que o kernel, após montar o rootfs, irá chamar um processo de inicialização, que será o processo-pai de todos os processos rodando em userspace no Android. E este processo é o “init“, como podemos ver na linha de comandos do kernel configurada no U-Boot da Beagleboard:

# setenv bootargs 'mem=256M androidboot.console=ttyS2 console=tty0 console=ttyS2,115200n8 root=/dev/mmcblk0p2 rw init=/init rootwait omapfb.video_mode=640x480MR-16@60'

O INIT

Aqui o Android começa a se diferenciar de outros sistemas GNU/Linux. Nada de SysV ou systemd. O Android implementa seu próprio mecanismo de inicialização dos processos básicos do sistema.

A implementação do init encontra-se nos fontes do Android em “system/core/init/init.c“.

Obs: Os fontes do Android estão disponíveis no site do projeto para quem quiser se aventurar.

Segue um trecho do init.c para os mais curiosos:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
int main(int argc, char **argv)
{
    int fd_count = 0;
    struct pollfd ufds[4];
    char *tmpdev;
    char* debuggable;
    char tmp[32];
    int property_set_fd_init = 0;
    int signal_fd_init = 0;
    int keychord_fd_init = 0;
 
    printf("bk %s %d\n",__FILE__,__LINE__);
 
    if (!strcmp(basename(argv[0]), "ueventd"))
        return ueventd_main(argc, argv);
 
    printf("bk %s %d\n",__FILE__,__LINE__);
 
    /* clear the umask */
    umask(0);
 
    /* Get the basic filesystem setup we need put
     * together in the initramdisk on / and then we'll
     * let the rc file figure out the rest.
     */
 
    printf("bk %s %d\n",__FILE__,__LINE__);
 
    mkdir("/dev", 0755);
    mkdir("/proc", 0755);
    mkdir("/sys", 0755);
 
    mount("tmpfs", "/dev", "tmpfs", 0, "mode=0755");
    mkdir("/dev/pts", 0755);
    mkdir("/dev/socket", 0755);
    mount("devpts", "/dev/pts", "devpts", 0, NULL);
    mount("proc", "/proc", "proc", 0, NULL);
    mount("sysfs", "/sys", "sysfs", 0, NULL);
 
    printf("bk pre devnull %s %d\n",__FILE__,__LINE__);
 
    /* We must have some place other than / to create the
     * device nodes for kmsg and null, otherwise we won't
     * be able to remount / read-only later on.
     * Now that tmpfs is mounted on /dev, we can actually
     * talk to the outside world.
     */
    open_devnull_stdio();
 
    ERROR("bk pos devnull, pre log init %s %d\n",__FILE__,__LINE__);
 
    log_init();
 
    ERROR("bk pos log init %s %d\n",__FILE__,__LINE__);
 
    INFO("reading config file\n");
    init_parse_config_file("/init.rc");
 
    ....

Este processo faz algumas inicializações de sistema, criando diretórios e pontos de montagem básicos do Linux como o /proc, o /sys e o /dev, inicializando o sistema de log e abrindo a console.

Uma tarefa importante deste processo é o tratamento de um arquivo de configuração chamado init.rc (veja a linha 57 do código acima). Este arquivo tem objetivos parecidos com o /etc/inittab para quem esta acostumado com o mecanismo de inicialização SysV.

É no init.rc que esta configurada boa parte do restante da inicialização do sistema, incluindo a execução dos serviços básicos do Android, dentre eles:

  • console: inicia o shell ash.
  • servicemanager: inicia o binder (responsável pela comunicação entre os processos).
  • vold: volume daemon – controla a montagem de volumes de mídia no sistema de arquivos.
  • adbd: android debugger bridge daemon – servidor para comunicação com o cliente adb. 
  • media: inicia os servidores multimedia (áudio, vídeo, etc).
  • bootsound: toca um som no boot, lendo um arquivo em /system/media/audio/ui/boot.mp3.
  • installd: servidor de instalação de pacotes/aplicações (*.apk).

Depois de interpretar este arquivo, o init entra em um loop infinito monitorando a ocorrência de eventos e a execução de processos.

Você consegue por exemplo configurar o init.rc para que um processo seja iniciado quando você conectar um pendrive na porta USB, (o init monitora a criação do device node em /dev quando um dispositivo USB for conectado).

Você consegue também configurar um processo para ficar sempre em execução. Se este processo cair (encerrar), o init reinicia ele automaticamente.

Portanto, se você quiser inserir um serviço para ser executado no boot, basta inserir as linhas abaixo no init.rc:

service meu_servico /system/bin/meu_servico
    user meu_servico
    group meu_servico
    oneshot

Neste exemplo, configuramos o serviço “meu_servico” para ser executado apenas uma vez (oneshot) com as permissões de usuário e grupo “meu_servico“.

Para saber mais sobre o init.rc, você pode dar uma olhada na especificação do Android Init Language.

Bom, sabemos então que todos os processos básicos do Android são iniciados através do init.rc. Mas um destes processos é o coração do Android merece uma atenção especial.

O ZYGOTE

O zygote é o pai dos processos Java. Tudo que roda em Java é criado por este processo, que instancia uma máquina virtual Dalvik para executar um processo ou serviço Java. No Gingerbread (Android 2.3), ele é iniciado por estas linhas no init.rc:

service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
    socket zygote stream 666
    onrestart write /sys/android_power/request_state wake
    onrestart write /sys/power/state on
    onrestart restart media

Ele esta implementado em C++, e seu código-fonte encontra-se em frameworks/base/cmds/app_process/app_main.cpp.

O zygote tem basicamente dois objetivos principais:

  1. Prover uma infraestrutura para a execução de aplicações Java. Primeiramente, ele inicia a máquina virtual Dalvik. Depois, ele inicia um servidor que abre um socket e fica aguardando requisições para execução de aplicações Java. Qualquer requisição de execução de aplicações Java passa por esse servidor, que faz um fork para executar a aplicação em uma outra instancia da VM. O código-fonte deste servidor esta disponível em
    frameworks/base/core/java/com/android/internal/os/ZygoteConnection.java.
  2. Iniciar o System Server, que gerencia a base dos serviços do sistema operacional Android.

SYSTEM SERVER

A implementação do system server encontra-se em frameworks/base/services/java/com/android/server/SystemServer.java. Ele inicia todos os serviços Java básicos do Android, e são muitos! Dentre eles:

  • Power Manager
  • Activity Manager
  • Telephony Registry
  • Package Manager
  • Context Manager
  • System Context Providers
  • Battery Service
  • Alarm Manager
  • Sensor Service
  • Window Manager
  • Bluetooth Service
  • Mount Service
  • Status Bar Service
  • Hardware Service
  • NetStat Service
  • Connectivity Service
  • Notification Manager
  • DeviceStorageMonitor Service
  • Location Manager
  • Search Service
  • Clipboard Service
  • Checkin Service
  • Wallpaper Service
  • Audio Service
  • HeadsetObserver
  • AdbSettingsObserver

Todos os serviços tem sua importância dentro do ambiente e das aplicações Android. Poderíamos levar um artigo completo para descrever em detalhes cada um deles. O mais importante aqui é entender que cada funcionalidade esta abstraída através de um serviço, e a API do Android disponibiliza funções para as aplicações poderem se comunicar com estes serviços.

Quase no final do boot, o Activity Manager inicia alguns processos básicos, dentre eles o com.android.launcher, que é a aplicação responsável pela interface gráfica do Android.

BOOTCHART

Para quem trabalha portando ou integrando o Android em diferentes hardwares, uma ferramenta interessante é o bootchart, que possibilita gerar um gráfico completo com estatísticas dos processos em execução durante o boot do Android. Quem estiver interessado nesta ferramenta pode dar uma lida neste tutorial do elinux.org.
 
O gráfico gerado é parecido com este abaixo (clique na imagem abaixo para vê-la em tamanho maior).


No próximo artigo, vou falar sobre como cross-compilar qualquer aplicação open source para o Android. Você poderá inclusive testar esta aplicação cross-compilada no seu smartphone, se tiver acesso de root. Até lá!

Um abraço,

Sergio Prado

Faça um Comentário

Navegue
Creative Commons Este trabalho de Sergio Prado é licenciado pelo
Creative Commons BY-NC-SA 3.0.