Análisis relacionado con el inicio del proceso de inicio de Android

Tabla de contenido

1. Información general:

2. Arquitectura

2.1 ¿Cómo se inicia el proceso Init?

2.2 ¿Qué hizo el proceso Init después de que comenzó?

3. Análisis del código fuente del proceso de inicio del kernel

3.1 kernel_init

3.2 do_basic_setup

4. Análisis del código fuente de inicio del proceso Init

4.1 Entrada del proceso de inicio

4.2 ueventd_main

4.3 El proceso init inicia la primera etapa

4.3.1 Primera Etapa Principal

4.4 Cargar reglas de SELinux

4.4.1 Configuración de Selinux

4.4.2 Inicializar Selinux()

4.5 El proceso init inicia la segunda fase

4.5.1 Segunda Etapa Principal

5. Procesamiento de señales

5.1 InstalarSignalFdHandler

5.2 Controlador de registros

5.3 ManejarSeñalFd

5.4 Proceso ReapOne

6. Servicio de atributos

6.1 inicio_propiedad

6.2 Iniciar servicio de propiedad

6.3 handle_property_set_fd

7. La tercera etapa init.rc

7.1 Acción

7.2 Comando

7.3 Servicio

7.4 Opciones

7.5 importación

7.6 proceso de análisis de init.rc

7.6.1 Cargar guiones de arranque

7.6.2 Ejecución de acciones

7.6.3 Inicio de cigoto

8. Resumen


1. Información general:
 

El proceso init es el primer proceso en el espacio de usuario en el sistema Linux y el número de proceso es 1.

  • ROM de arranque: cuando el teléfono está apagado, mantenga presionado el botón de encendido para encenderlo, el chip de arranque comienza a ejecutarse desde el código preestablecido solidificado en la ROM y luego carga el programa de arranque en la RAM;
  • Boot Loader: este es el programa de arranque antes de iniciar el sistema Android, principalmente para verificar la memoria RAM, inicializar los parámetros de hardware y otras funciones.

Cuando se inicie el cargador de arranque, inicie el kernel.Después de que se inicie el kernel, inicie el proceso de inicio en el espacio del usuario y luego lea la configuración relevante en init.rc a través del proceso de inicio, para iniciar otros procesos relacionados y otras operaciones.

El proceso init recibe mucho trabajo importante. El inicio del proceso init se divide principalmente en dos etapas:

La primera fase completa lo siguiente:

Salto de uevent/watchdogd y configuración de variables de entorno
Montar el sistema de archivos y crear un directorio
Inicializar la salida del registro, montar el dispositivo de partición
Habilitar la política de seguridad de SELinux
Iniciar los preparativos antes de la segunda etapa

La segunda fase completa lo siguiente:

Inicialice el sistema de atributos.
Ejecute la segunda etapa de SELinux y restaure algunos contextos de seguridad de archivos.
Cree un nuevo epoll e inicialice la función de procesamiento de señales de finalización del proceso secundario
. Configure otros atributos del sistema y habilite los servicios de atributos.

2. Arquitectura


2.1 ¿Cómo se inicia el proceso Init?


El proceso Init es el primer proceso de espacio de usuario iniciado después de que se inicie el Kernel, con PID 1.

Después de que se inicie kernel_init, complete algunas operaciones de inicialización de init y luego vaya al directorio raíz del sistema para encontrar las aplicaciones configuradas por ramdisk_execute_command y execute_command a su vez. Si no se pueden encontrar estos dos directorios, vaya al directorio raíz para encontrar /sbin/init, /etc/init, /bin/init,/bin/sh estas cuatro aplicaciones se inician, siempre que se inicie una de estas aplicaciones, las demás no se iniciarán.

El sistema Android generalmente coloca un archivo ejecutable de inicio en el directorio raíz, es decir, el proceso de inicio del sistema Linux ejecuta directamente el archivo de inicio después de que se completa la inicialización del kernel.

2.2 ¿Qué hizo el proceso Init después de que comenzó?


Después de que comience el proceso Init, primero monte el sistema de archivos, luego monte la partición correspondiente, inicie la política de seguridad de SELinux, inicie el servicio de atributos, analice el archivo rc e inicie el proceso de servicio de atributos correspondiente, inicialice epoll y establezca la señal, propiedad y acorde de tecla en secuencia La función de devolución de llamada correspondiente cuando un fd es legible. Ingrese al bucle inalámbrico, utilizado para responder a los cambios y reconstrucciones de cada proceso.

3. Análisis del código fuente del proceso de inicio del kernel
 

3.1 kernel_init


núcleo/msm-4.19/init/main.c

kernel/msm-4.19/init/main.c
kernel_init()
  |
run_init_process(ramdisk_execute_command) //ejecutar el archivo ejecutable, iniciar el proceso de inicio
static int __ref kernel_init(void *unused)
{     kernel_init_freeable(); //realizar un proceso de inicio La operación de inicialización     /* necesita finalizar todo el código __init asíncrono antes de liberar la memoria */     async_synchronize_full();// Espere a que se completen todas las llamadas asíncronas, antes de liberar la memoria, todos los códigos __init asíncronos deben completarse     free_initmem();// Liberar todo memoria en el segmento init.*     mark_rodata_ro(); // arm64 implementación vacía     system_state = SYSTEM_RUNNING;// establecer el estado del sistema en estado de ejecución     numa_default_policy(); // establecer la política de acceso a la memoria predeterminada del sistema NUMA     flush_delayed_fput(); // Liberar todas las estructuras de archivo struct retrasadas     if (ramdisk_execute_command) { //El valor de ramdisk_execute_command es "/init"







 

 

        if (!run_init_process(ramdisk_execute_command)) //ejecutar el programa init en el directorio raíz
            return 0;
        pr_err("Error al ejecutar %s\n", ramdisk_execute_command);
    }
 
    /*
     * Probamos cada uno de estos hasta que uno tenga éxito.
     *
     * El shell Bourne se puede usar en lugar de init si estamos
     * tratando de recuperar una máquina realmente dañada
     */
    if (execute_command) { //Si el valor de execute_command está definido, vaya al directorio raíz para encontrar la aplicación correspondiente, y luego comience
        si (!run_init_process(execute_command))
            devuelva 0;
        pr_err("Error al ejecutar %s. Intentando valores predeterminados...\n",
            execute_command);
    }
    if (!run_init_process("/sbin/init")
    || ,/bin/sh Estas cuatro aplicaciones se inician
 
        !run_init_process("/etc/init") ||
        !run_init_process("/bin/init") ||
        !run_init_process ("/bin/sh"))
        return 0;
 
    panic("No se encontró init. Intente pasar la opción init= al kernel. "
          "Consulte la documentación de Linux/init.txt para obtener orientación.");
}


3.2 do_basic_setup


kernel_init_freeable()
|
do_basic_setup()
 

static void __init do_basic_setup(void)
{     cpuset_init_smp();//Para sistemas SMP, inicialice el subsistema cpuset del grupo de control del kernel.     usermodehelper_init();// Crear una cola de trabajo de subproceso único khelper para ayudar a crear y ejecutar programas de espacio de usuario     shmem_init();//Inicializar la memoria compartida     driver_init();//Inicializar los controladores de dispositivo     init_irq_proc();//Crear /proc / irq, e inicializar los subdirectorios correspondientes a todas las interrupciones en el sistema     do_ctors();// ejecutar el constructor del kernel     usermodehelper_enable();// habilitar usermodehelper     do_initcalls();// atravesar la matriz initcall_levels, llamar a la función initcall interna, aquí principalmente es para inicializar el dispositivo, el controlador y el sistema de archivos,     //todas las funciones se encapsulan en una matriz para el recorrido, principalmente con el propósito de extender     random_int_secret_init();//inicializar el grupo de generación de números aleatorios }









 


 

4. Análisis del código fuente de inicio del proceso Init


Analizamos principalmente el código de inicio de Android Q (10.0).

Archivos de código fuente involucrados:

plataforma/sistema/núcleo/init/main.cpp
plataforma/sistema/núcleo/init/init.cpp
plataforma/sistema/núcleo/init/ueventd.cpp
plataforma/sistema/núcleo/init/selinux.cpp
plataforma/sistema/núcleo/ plataforma init/subcontext.cpp
/system/core/base/logging.cpp
plataforma/system/core/init/first_stage_init.cpp
plataforma/system/core/init/first_stage_main.cpp
plataforma/system/core/init/first_stage_mount.cpp
plataforma /system/core/init/keyutils.h
plataforma/sistema/core/init/property_service.cpp
plataforma/external/selinux/libselinux/src/label.c
plataforma/sistema/core/init/signal_handler.cpp
plataforma/sistema/núcleo /init/servicio.cpp

4.1 Entrada del proceso de inicio


El proceso init se ha iniciado a través de kernel_init, y el proceso init pertenece a un proceso daemon, para ser precisos, es el primer proceso controlado por el usuario en el sistema Linux, y su número de proceso es 1. Su ciclo de vida recorre todo el kernel de Linux. El creador común de todos los demás procesos en Android es el proceso init.

Puede verificar el número de proceso de init a través del comando "adb shell ps |grep init".

La función de entrada de inicio de Android Q (10.0) se ajusta del init.cpp original a main.cpp, lo que separa las operaciones de cada etapa y hace que el código sea más conciso. A continuación, comenzaremos a aprender de la función principal.

 [sistema/núcleo/init/principal.cpp]

/*
 * 1. El primer parámetro argc indica el número de parámetros, el segundo parámetro es la lista de parámetros, es decir, los parámetros específicos * 2.
 La función principal tiene cuatro entradas de parámetros,
 * uno está ueventd en el parámetro, ingrese ueventd_main
 * La segunda es que hay subcontexto en el parámetro, ingrese InitLogging y SubcontextMain
 * La tercera es que hay selinux_setup en el parámetro, ingrese SetupSelinux
 * La cuarta es que hay second_stage en el parámetro, ingrese SecondStageMain

 * 3. La secuencia de ejecución de main es la siguiente:
   * (1) El proceso de inicio ueventd_main crea el proceso secundario ueventd,
   * y confía el trabajo de crear el archivo de nodo de dispositivo a ueventd, y ueventd crea el archivo de nodo de dispositivo de dos maneras
   * (2) FirstStageMain inicia la primera fase 1
   * (3) SetupSelinux carga las reglas de selinux, establece los registros de selinux y completa el trabajo relacionado con SELinux
   * (4) SecondStageMain inicia la segunda fase
 */
int main(int argc, char** argv) {     //Cuando argv[0] Cuando el contenido es ueventd, el valor de strcmp es 0,! strcmp es 1     //1 significa verdadero, por lo que se ejecuta ueventd_main, ueventd es principalmente responsable de la creación de nodos de dispositivos, configuración de permisos y otros trabajos     if (!strcmp(basename(argv[0]), "ueventd")) {         return ueventd_main(argc, argv);     }    //Cuando el número de parámetros pasados ​​es mayor que 1, realice las siguientes operaciones     if (argc > 1) {         //El parámetro es subcontexto, inicialice el sistema de registro,         if (!strcmp( argv [1], "subcontexto")) {





 




            android::base::InitLogging(argv, &android::base::KernelLogger);
            const BuiltinFunctionMap function_map;
            return SubcontextMain(argc, argv, &function_map);
        }
 
      //El parámetro es "selinux_setup", inicie la política de seguridad de Selinux
        si ( !strcmp (argv[1], "selinux_setup")) {             return SetupSelinux(argv);         }        //El parámetro es "segunda_etapa", inicia la segunda etapa del proceso de inicio         si (!strcmp(argv[1], "segunda_etapa ")) {             return SecondStageMain(argc, argv);         }     }  // Inicia la primera etapa del proceso de inicio de forma predeterminada     return FirstStageMain(argc, argv); }










4.2 ueventd_main


Ruta del código: plataforma/sistema/núcleo/init/ueventd.cpp

El directorio "/dev" no existe en la imagen del sistema de archivos raíz de Android. Este directorio se crea dinámicamente después de que se inicia el proceso de inicio.

Por lo tanto, la pesada tarea de establecer el archivo de nodo del dispositivo en Android también recae en el proceso de inicio. Con este fin, el proceso init crea un proceso secundario ueventd y le confía a ueventd el trabajo de crear archivos de nodo de dispositivo.

ueventd crea archivos de nodo de dispositivo de dos maneras.

El primer método corresponde a "Cold Plug", es decir, en función de la información del dispositivo predefinida, cuando se inicia ueventd, el archivo de nodo del dispositivo se crea de manera uniforme. Este tipo de archivo de nodo de dispositivo también se denomina archivo de nodo estático.

El segundo método corresponde a "Hot Plug", es decir, cuando el sistema está funcionando, cuando se inserta un dispositivo en el puerto USB, ueventd recibirá este evento y creará dinámicamente un archivo de nodo de dispositivo para el dispositivo insertado. Este tipo de archivo de nodo de dispositivo también se denomina archivo de nodo dinámico.

int ueventd_main(int argc, char** argv) {     //Establecer el valor predeterminado del nuevo archivo, que es opuesto a chmod, aquí es equivalente al permiso después del nuevo archivo es 666     umask(000);      //Inicializar el registro del kernel, ubicado en node/dev/kmsg, en este momento los procesos logd y logcat aún no se han iniciado,     //use el sistema de registro del kernel, abra el nodo del dispositivo /dev/kmsg, luego puede obtener el registro del kernel por cat /dev/kmsg.     android::base::InitLogging(argv, &android::base::KernelLogger);     //Registrar la función de devolución de llamada relacionada con selinux para imprimir registros     SelinuxSetupKernelLogging();      SelabelInitialize();     //Analizar xml y obtenerlo de acuerdo con diferentes SOC fabricantes Diferentes archivos de hardware rc     auto ueventd_configuration = ParseConfig({"/ueventd.rc", "/vendor/ueventd.rc",                                               "/odm/ueventd.rc", "/ueventd." + hardware + ".rc"}) ;     //Arranque en frío     si (acceso(COLDBOOT_DONE,


 



 



 



 


        ColdBoot cold_boot(uevent_listener, uevent_handlers);
        cold_boot.Run();
    }
    for (auto& uevent_handler : uevent_handlers) {         uevent_handler->ColdbootDone();     }     //Ignorar la señal de finalización del proceso secundario signal     (SIGCHLD, SIG_IGN);     // Cosechar y hijos pendientes que salieron entre la última llamada a waitpid() y la configuración de SIG_IGN //     para     (        SIGCHLD anterior     }     //Escuche el uevent del controlador y realice el procesamiento de "intercambio en caliente"     uevent_listener.Poll([&uevent_handlers](const uevent& uevent) {         for (auto& uevent_handler : uevent_handlers) {


 







 



            uevent_handler->HandleUevent(uevent); //Inicio en caliente, crear dispositivo
        }
        return ListenerAction::kContinue;
    });
    return 0;
}


4.3 El proceso init inicia la primera etapa


Ruta del código: plataforma\sistema\core\init\first_stage_init.cpp

El trabajo principal de la primera etapa del proceso de inicio es montar particiones, crear nodos de dispositivos y algunos directorios clave, inicializar el sistema de salida de registros y habilitar las políticas de seguridad de SELinux.

La primera fase completa lo siguiente:

/* 01. Crear un directorio de sistema de archivos y montar el sistema de archivos relacionado*/

/* 02. Proteger la entrada y salida estándar/inicializar el sistema de registro del kernel*/

4.3.1 Primera Etapa Principal

NULO)); #define MAKE_STR(x) __STRING(x)







 


 






    CHECKCALL(mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC)));
#undef MAKE_STR
 
    // 非特权应用不能使用Andrlid cmdline
    CHECKCALL(chmod("/proc/cmdline", 0440));
    gid_t grupos[] = {AID_READPROC};
    CHECKCALL(setgroups(tamaño de matriz(grupos), grupos));
    CHECKCALL(mount("sysfs", "/sys", "sysfs", 0, NULL));
    CHECKCALL(mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL));
 
    CHECKCALL(mknod("/dev/kmsg", S_IFCHR | 0600, makedev(1, 11)));
 
    if constexpr (WORLD_WRITABLE_KMSG) {         CHECKCALL(mknod("/dev/kmsg_debug", S_IFCHR | 0622, makedev(1, 11)));     }     CHECKCALL(mknod("/desarrollo/aleatorio", S_IFCHR | 0666, makedev(1, 8)));


 

    CHECKCALL(mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9))); //
 
 
    Esto es necesario para el contenedor de registro, que se llama antes de que ueventd ejecute
    CHECKCALL(mknod("/dev/ptmx" , S_IFCHR | 0666, makedev(5, 2)));
    CHECKCALL(mknod("/dev/null", S_IFCHR | 0666, makedev(1, 3))); //
 
 
    colgar en tmpfs, mnt/vendor, mount/ partición de productos. No es necesario cargar otras particiones en la primera etapa,
    //solo es necesario cargarlas analizando el archivo rc en la segunda etapa.
    CHECKCALL(mount("tmpfs", "/mnt", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
                    "mode=0755,uid=0,gid=1000"));
    
    //Cree un directorio de proveedores de lectura y escritura
    CHECKCALL ( mkdir("/mnt/vendor", 0755));
    // /mnt/product se usa para montar particiones específicas del producto que no pueden ser
    // parte de la partición del producto, por ejemplo
    CHECKCALL(mkdir("/mnt/product", 0755));
 
    // Monte APEX, que se introdujo especialmente en Android 10.0 para resolver el problema de fragmentación, similar a un método de componentes, una mejora de Treble, // no, no lo
    es necesario actualizar completamente la versión del sistema completo para escribir actualizaciones especiales de Google, al igual que actualizar APK, realizar la actualización del componente APEX
    CHECKCALL(mount("tmpfs", "/apex", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
                    "mode=0755 ,uid=0,gid=0"));
 
    // /debug_ramdisk se utiliza para conservar archivos adicionales del ramdisk de depuración
    CHECKCALL(mount("tmpfs", "/debug_ramdisk", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
                    " modo=0755,uid=0,gid=0"));
#undef CHECKCALL
 
    //Redirige la entrada estándar, la salida estándar y el error estándar al archivo de dispositivo vacío "/dev/null"
    SetStdioToDevNull(argv);
    //Monta tmpfs y kmsg 
    //De esta manera, el /kernel El sistema de registro se puede inicializar para que los usuarios impriman registros
antigua_raíz_info.st_dev);     }
 

 







 





 



 
    SetInitAvbVersionInRecovery();
 
    constexpr estático uint32_t kNanosegundos por milisegundo = 1e6;
    uint64_t start_ms = start_time.time_since_epoch().count() / kNanosecondsPerMillisecond;
    setenv("INIT_STARTED_AT", std::to_string(start_ms).c_str(), 1);
 
    //启动init进程,传入参数selinux_steup
    // 执行命令: /system/bin/init selinux_setup
    const char* ruta = "/system/bin/init";
    const char* args[] = {ruta, "selinux_setup", nullptr};
    execv(ruta, const_cast<char**>(args));
    PLOG(FATAL) << "execv(\"" << ruta << "\") falló";
 
    devolver 1;
}


4.4 Cargar reglas de SELinux


SELinux es la abreviatura de "Security-Enhanced Linux", que es la Agencia de Seguridad Nacional "NSA=La Agencia de Seguridad Nacional"

Un módulo de seguridad de control de acceso obligatorio extendido para Linux desarrollado por SCC (Secure Computing Corporation).

Bajo la restricción de este sistema de control de acceso, un proceso solo puede acceder a aquellos archivos necesarios para su tarea.

selinux tiene dos modos de trabajo:

Permisivo, todas las operaciones están permitidas (es decir, sin MAC), pero si se viola el permiso, se registrará el registro. Generalmente, la aplicación
se usa en el modo de ingeniería, y se verificará el permiso de todas las operaciones. Generalmente, los modos de usuario y de depuración de usuario se utilizan
para operar el archivo /sys/fs/selinux/enforce, ya sea security_setenforce o security_getenforce, 0 significa permisivo 1 significa cumplimiento

4.4.1 Configuración de Selinux

Descripción: inicialice selinux, cargue las reglas de SELinux, configure la salida de registro relacionada con SELinux e inicie la segunda etapa

Ruta del código: plataforma\sistema\núcleo\init\selinux.cpp

/* Esta función inicializa selinux, luego ejecuta init para ejecutarse en init selinux */
int SetupSelinux(char** argv) {        //Inicializa el registro del Kernel     InitKernelLogging(argv);        //Reinicia el gestor de arranque cuando la versión de depuración init falla     if ( REBOOT_BOOTLOADER_ON_PANIC) {         InstallRebootSignalHandlers();     }     //Registre una devolución de llamada para configurar el registro de selinux que debe escribirse en kmsg     SelinuxSetupKernelLogging();      //Cargar las reglas de SELinux     SelinuxInitialize();     /*        *Estamos en el dominio del kernel y queremos cambiar al dominio inicial. Los sistemas de archivos que almacenan selabels en sus xattrs (como ext4) no requieren una restauración explícita,        * pero otros sistemas de archivos sí. Especialmente para ramdisks, como imágenes de recuperación para dispositivos a/b, este es un paso necesario.        *De hecho, actualmente se encuentra en el dominio del kernel. Después de cargar Seliux, debe volver a ejecutar init para cambiar al modo de usuario de C space        */


 




 


   


 





    if (selinux_android_restorecon("/system/bin/init", 0) == -1) {         PLOG(FATAL) << "falló la restauración de /system/bin/init";     }   //Prepárese para iniciar el proceso innit, pasar el parámetro second_stage     const char* ruta = "/system/bin/init";     const char* args[] = {path, "second_stage", nullptr};     execv(path, const_cast<char**>(args));     /*        *Ejecutar /system/bin/init second_stage, ingresar a la segunda etapa        */     PLOG(FATAL) << "execv(\"" << ruta << "\") falló";     return 1; }


 




 




 


4.4.2 Inicializar Selinux()

                        "falso") << ") falló";         }     }





 


 


 







 
    if (resultado automático = WriteFile("/sys/fs/selinux/checkreqprot", "0"); !result) {         LOG(FATAL) << "No se puede escribir en /sys/fs/selinux/checkreqprot: " << result.error();     } } /*  * Cargar reglas de SELinux  * Aquí se distinguen dos casos, estos dos casos solo distinguen dónde cargar el archivo de política de seguridad,  * el primero se lee desde /vendor/etc/selinux/precompiled_sepolicy Toma,  *El segundo se lee desde /sepolicy, todos terminan llamando al método selinux_android_load_policy_from_fd  */ bool LoadPolicy() {     return IsSplitPolicyDevice() ? LoadSplitPolicy() : LoadMonolithicPolicy(); }












4.5 El proceso init inicia la segunda fase


El contenido principal de la segunda etapa:

Crear una clave de sesión de proceso e inicializar el sistema de atributos. Realizar la
segunda etapa de SELinux y restaurar algunos contextos de seguridad de archivos . , vea la Sección 6 para más detalles Sección-Propiedad Servicio Analizar init.rc y otros archivos, crear acción y servicio del archivo rc, iniciar otros procesos, ver Sección 7-análisis de archivos rc para más detalles



 

4.5.1 Segunda Etapa Principal
 

int SecondStageMain(int argc, char** argv) {     /* 01. Cree una clave de sesión de proceso e inicialice el atributo system*/     keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 1);     //Cree un archivo /dev/.booting, que es una marca , lo que indica que el arranque está en curso     close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));     // Inicializar el sistema de propiedades y leer la propiedad     property_init() del archivo especificado;     /* 02. Conducta la segunda etapa de SELinux Y restaurar algún contexto de seguridad de archivos */         SelinuxRestoreContext();     /* 03. Crear un nuevo epoll e inicializar la función de procesamiento de la señal de finalización del proceso secundario */     Epoll epoll;     if (auto result = epoll.Open(); !resultado) {         PLOG(FATAL) << resultado.error();     }              InstallSignalFdHandler(&epoll);     /* 04. Establecer otras propiedades del sistema e iniciar el servicio de propiedades del sistema*/     StartPropertyService(&epoll);


 


 


 


 





 

 


 
           /* 05 Analizar archivos como init.rc, crear acciones y servicios de archivos rc e iniciar otros procesos*/
    ActionManager& am = ActionManager::GetInstance();
    ServiceList& sm = ServiceList::GetInstance();
    LoadBootScripts(am, sm );
}
Análisis detallado del flujo de código:

int SecondStageMain(int argc, char** argv) {     /*      *Reiniciar el cargador de arranque cuando se bloquea el inicio     * Esta función se utiliza principalmente para establecer el comportamiento de varios semáforos, como SIGABRT, SIGBUS, etc., en SA_RESTART, una vez que estas señales son monitoreados Ejecutar reinicio del sistema     */     if (REBOOT_BOOTLOADER_ON_PANIC) {         InstallRebootSignalHandlers();     }     //redirigir la entrada estándar, la salida estándar y el error estándar al archivo de dispositivo vacío "/dev/null"     SetStdioToDevNull(argv);     //bajo el directorio /dev Montar tmpfs y kmsg      //De esta manera, el sistema de registro /kernel se puede inicializar para que los usuarios impriman el registro     InitKernelLogging(argv);     LOG(INFO) << "init, segunda etapa iniciada!";     // 01. Crear una clave de sesión de proceso e inicialice el sistema de atributos     keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 1);     //Cree un archivo /dev/.booting, que es una marca que indica que el arranque está en curso







 






 


 

    close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000)); //
 
    Inicializar el sistema de propiedades y leer la propiedad
    property_init() del archivo especificado;
 
    /*
     * 1. Si los parámetros son lea desde la línea de comando al mismo tiempo Páselo con DT, la prioridad de DT siempre es más alta que la de la línea de comando
     * 2. DT es árbol de dispositivos, lo que significa árbol de dispositivos en chino, que registra su propia configuración de hardware y parámetros operativos del sistema,
     */
    process_kernel_dt(); // Procesar las propiedades de DT
    process_kernel_cmdline(); // Procesar las propiedades de la línea de comandos
 
    // Procesar algunas otras propiedades
    export_kernel_boot_props();
 
    // Hacer que la hora en que se inició init esté disponible para bootstat en log.property_set
    ("ro.boottime.init", getenv( "INIT_STARTED_AT"));
    property_set("ro.boottime.init.selinux", getenv("INIT_SELINUX_TOOK"));
 
    // Establecer la versión de libavb para la coincidencia OTA solo de Framework en Treble construir.
    const char* avb_version = getenv("INIT_AVB_VERSION");
    if (avb_version) property_set("ro.boot.avb_version", avb_version);
 
    // Vea si es necesario cargar accesorios de depuración para permitir la raíz adb, cuando el dispositivo está desbloqueado.
    const char* force_debuggable_env = getenv("INIT_FORCE_DEBUGGABLE");
    if (force_debuggable_env && AvbHandle::IsDeviceUnlocked()) {         load_debug_prop = "true"s == force_debuggable_env;     }     // 基于cmdline设置memcg属性     bool memcg_enabled = android::base::GetBoolProperty("ro.boot.memcg",false);     if (memcg_enabled) {        // control de memoria raíz cgroup        mkdir("/dev/memcg", 0700);        chown("/dev/memcg",AID_ROOT,AID_SYSTEM);


 






    SelinuxSetupKernelLogging();     SelabelInitialize();     /*






 





 



 

     * 02. Realice la segunda etapa de SELinux y restaure algunos contextos de seguridad de archivos 
     * Restaure el contexto de seguridad de los archivos relacionados, ya que estos archivos se crean antes de que se inicialice el mecanismo de seguridad de SELinux, *
     por lo que es necesario restaurar el contexto
     */
    SelinuxRestoreContext() ;
 
   /*
    * 03. Cree un nuevo epoll e inicialice la función de procesamiento de la señal de terminación del proceso secundario
    * Cree una instancia de epoll y devuelva el descriptor de archivo de epoll
    */
    Epoll epoll;
    if (resultado automático = epoll.Open(); !result) {         PLOG(FATAL) << result.error();     }     /*       *Principalmente crea un manejador para manejar la señal de finalización del proceso secundario, registra una señal en epoll para monitorear      * para procesamiento de subherencia      */     InstallSignalFdHandler(&epoll) ;     // Hacer el trabajo relacionado con la configuración de propiedad predeterminada     property_load_boot_defaults( load_debug_prop);     UmountDebugRamdisk();


 





 



    fs_mgr_vendor_overlay_mount_all();
    export_oem_lock_status();
 
    /*
     *04. Establezca otras propiedades del sistema y habilite los servicios de propiedad del sistema
     */
    StartPropertyService(&epoll);
    MountHandler mount_handler(&epoll);
 
    //Establezca el controlador udc para almacenamiento USB, sys/class/udc
    set_usb_controller ();
 
    // hacer coincidir la correspondencia entre comandos y funciones
    const BuiltinFunctionMap function_map;
    Action::set_function_map(&function_map);
 
    if (!SetupMountNamespaces()) {         PLOG(FATAL) << "SetupMountNamespaces falló";     }     // archivo de inicialización Context     subcontexts = InitializeSubcontexts();    /*      *05 Analizar init.rc y otros archivos, crear acciones y servicios de archivos rc e iniciar otros procesos      */


 


 



    ActionManager& am = ActionManager::GetInstance();
    ServiceList& sm = ServiceList::GetInstance();
 
    LoadBootScripts(am, sm);
 
    // Activar esto y dejar que se descarte el registro INFO agrega 0.2s al
    // tiempo de arranque de Nexus 9, por lo que está deshabilitado de manera predeterminada.
    if (false) DumpState();
 
    // Cuando se ejecuta el script GSI, asegúrese de que el estado GSI esté disponible.
    if (android::gsi::IsGsiRunning()) {         property_set("ro.gsid .image_running", "1");     } else {         property_set("ro.gsid.image_running", "0");     }     am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups");     // Ejecutar la declaración cuyo disparador está en Early- init en el archivo rc     am.QueueEventTrigger("early-init");




 
 

 


 
    // Espere a que se complete la inicialización del dispositivo de conexión en frío
    am.QueueBuiltinAction(wait_for_coldboot_done_action, "wait_for_coldboot_done");
 
    // Comience a consultar acciones desde /dev
    am.QueueBuiltinAction(MixHwrngIntoLinuxRngAction, "MixHwrngIntoLinuxRng");
    am.QueueBuiltinAction(SetMmapRndBit sA ción , "SetMmapRndBits") ;
    am.QueueBuiltinAction(SetKptrRestrictAction, "SetKptrRestrict");
 
    // Operación de inicialización del dispositivo keychords
    keychords;
    am.QueueBuiltinAction(
        [&epoll, &keychords](const BuiltinArguments& args) -> Result<Success> {             for (const auto& svc: ServiceList::GetInstance()) {                 keychords.Register(svc->keycodes());             }



            keychords.Start(&epoll, HandleKeychord);
            return Success();
        },
        "KeychordInit");
 
    //Muestra el LOGOTIPO estático de Android en la pantalla
    am.QueueBuiltinAction(console_init_action, "console_init");
 
    // Ejecuta el disparador en el rc archivo como La declaración en init
    am.QueueEventTrigger("init");
 
    // Inicio de la autoprueba BoringSSL, para el cumplimiento de la certificación NIAP.
    am.QueueBuiltinAction(StartBoringSslSelfTest, "StartBoringSslSelfTest");
 
    // Repetir mix_hwrng_into_linux_rng en caso de /dev/hw_rand om o /dev /random
    // no estaba listo inmediatamente después de wait_for_coldboot_done
    am QueueBuiltinAction(MixHwrngIntoLinuxRngAction, "MixHwrngIntoLinuxRng");
 
    // Inicializar binder antes de abrir otros servicios del sistema
    am.QueueBuiltinAction(InitBinder, "InitBinder");
 
    // Cuando el dispositivo está en modo de carga, no es necesario montar el sistema de archivos ni iniciar los servicios del sistema
    // En modo de carga, el cargador se ejecutará como una cola, de lo contrario, ponga late-init si la cola de ejecución
    std::string bootmode = GetProperty("ro.bootmode", "");
    if (bootmode == "charger") {         am.QueueEventTrigger( "charger");     } else {         am .QueueEventTrigger("late-init");     }     // Ejecuta todos los activadores de propiedad según el estado actual de la propiedad.     am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers");     while (true) {         // Por defecto, dormir hasta que algo suceda.




 


 


        auto epoll_timeout = std::opcional<std::crono::milisegundos>{};
 
        if (do_shutdown && !shutting_down) {             do_shutdown = false;             if (HandlePowerctlMessage(shutdown_command)) {                 shutting_down = true;             }         } // ejecutar cada An action lleva la función de ejecución correspondiente al comando         if (!(waiting_for_prop || Service::is_exec_service_running())) {             am.ExecuteOneCommand();         }         if (!(waiting_for_prop || Service::is_exec_service_running())) {             if ( !shutting_down) {                 auto next_process_action_time = HandleProcessActions();





 







 
                // Si hay un proceso que necesita reiniciarse, despierta a tiempo para eso.
                if (next_process_action_time) {                     epoll_timeout = std::chrono::ceil<std::chrono::milliseconds>(                             *next_process_action_time - boot_clock::now());                     si (*epoll_timeout < 0ms) epoll_timeout = 0ms;                 }             }             // Si hay más trabajo que hacer, despierta inmediatamente.             if (am.HasMoreCommands()) epoll_timeout = 0ms;         }         // 循环等待事件发生         if (resultado automático = epoll.Wait(epoll_timeout); !result) {             LOG(ERROR) << result.error();         }





 



 




    }
 
    devuelve 0;
}
 

5. Procesamiento de señales


init es un proceso demonio Para evitar que el proceso secundario de init se convierta en un proceso zombi (proceso zombi), init necesita obtener el código final del proceso secundario cuando finaliza el proceso secundario y eliminar el proceso secundario en el programa tabla a través del código final para evitar que se convierta en un proceso zombi. El proceso secundario del proceso zombi ocupa el espacio de la tabla del programa (cuando el espacio de la tabla del programa alcanza el límite superior, el sistema ya no puede iniciar nuevos procesos, lo que causará serios problemas en el sistema).

El proceso de reinicio del proceso hijo se muestra en la siguiente figura:

El trabajo principal del procesamiento de señales:

Inicializar señal manejador de señal
Proceso secundario de procesamiento de bucle
Registrar epoll manejador Manejar
terminación de proceso secundario
Nota: EPOLL es similar a POLL, que se utiliza para activar eventos en Linux, similar a la función EventBus. Linux ha estado utilizando select para la activación de eventos durante mucho tiempo. Se procesa mediante sondeo. Cuantos más fds se sondeen, más tiempo consumirá. Para procesar una gran cantidad de descriptores, EPOLL tiene más ventajas

5.1 InstalarSignalFdHandler


En Linux, el proceso principal conoce el final del proceso secundario al capturar la señal SIGCHLD. La señal SIGCHLD se enviará cuando finalice el proceso secundario. Después de comprender estos antecedentes, echemos un vistazo a cómo el proceso init maneja esta señal.

Primero, cree una nueva estructura sigaction, sa_handler es una función de procesamiento de señales, que apunta al puntero de función SIG_DFL especificado por el kernel es diferente de Android 9.0 y versiones anteriores, aquí ya no recibe señales a través del controlador de lectura y escritura del socket, pero cámbielo al procesamiento de señales de la función del kernel SIG_DFL.
Luego, sigaction(SIGCHLD, &act, nullptr) es establecer una relación de enlace de señal, es decir, cuando la señal SIGCHLD es monitoreada, será procesada por la estructura sigaction de act. Finalmente, el rol de RegisterHandler es signal_read_fd (el anterior s[1]
) Después de recibir la señal, active handle_signal.Como
se mencionó anteriormente, la función de la función InstallSignalFdHandler es activar HandleSignalFd para el procesamiento de la señal cuando se recibe la señal SIGCHLD.

                                   Diagrama esquemático del procesamiento de señales:

Ruta del código: plataforma/sistema/núcleo/init.cpp

Descripción: La función principal de esta función es inicializar el proceso de procesamiento de la señal de terminación del proceso secundario.

static void InstallSignalFdHandler(Epoll* epoll) {     // SA_NOCLDSTOP hace que el proceso de inicio reciba la señal SIGCHLD solo cuando su proceso secundario termina     const struct sigaction act { .sa_handler = SIG_DFL, .sa_flags = SA_NOCLDSTOP };     sigaction(SIGCHLD, &act, nullptr );     sigset_t mask;     sigemptyset(&mask);     sigaddset(&mask, SIGCHLD);      if (!IsRebootCapable()) {         // Si init no tiene la capacidad de CAP_SYS_BOOT, actualmente se está ejecutando en el contenedor         // en este escenario } if (sigprocmask(SIG_BLOCK, &         mask,     nullptr     ) == -1) {         PLOG(FATAL) << "no se pudieron bloquear las señales";     }     // registre el controlador para desbloquear las señales en los procesos secundarios
 



 



 





 



 

    const int resultado = pthread_atfork(nullptr, nullptr, &UnblockSignals);
    if (resultado!= 0) {         LOG(FATAL) << "Error al registrar un manejador de bifurcación: " << strerror(resultado);     }     //crear manejador de señal     signal_fd = signalfd(-1, &mask, SFD_CLOEXEC);     if (signal_fd == -1) {         PLOG(FATAL) << "no se pudo crear signalfd";     }     //Registro de señal, cuando signal_fd recibe una señal, activa HandleSignalFd     if ( auto resultado = epoll->RegisterHandler(signal_fd, HandleSignalFd); !resultado) {         LOG(FATAL) << resultado.error();     } }


 





 





5.2 Controlador de registros
 

Ruta del código: /plataforma/sistema/core/epoll.cpp

Descripción: registro de señal, agregue el identificador fd a la cola de escucha de epoll_fd_

Result<void> Epoll::RegisterHandler(int fd, std::function<void()> handler, uint32_t events) {     if (!events) {         return Error() << "Debe especificar eventos";     }     auto [it, insertado] = epoll_handlers_.emplace(fd, std::move(handler));     if (!inserted) {         return Error() << "No se pueden especificar dos manejadores de epoll para un FD dado";     }     epoll_event ev;     ev.events = eventos;     // Los iteradores de std::map no se invalidan hasta que se borran, por lo que usamos el     // puntero a la función std::en el mapa directamente para epoll_ctl.     ev.data.ptr = reinterpret_cast<void*>(&it->segundo);     // 将fd的可读事件加入到epoll_fd_的监听队列中













    if (epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, fd, &ev) == -1) {         Result<void> result = ErrnoError() << "epoll_ctl no pudo agregar fd";         epoll_handlers_.erase(fd);         resultado devuelto;     }     devolver {}; }






 

5.3 ManejarSeñalFd
 

Ruta del código: plataforma/sistema/núcleo/init.cpp

Descripción: Supervise la señal SIGCHLD y llame a ReapAnyOutstandingChildren para finalizar el proceso secundario problemático

vacío estático HandleSignalFd() {     signalfd_siginfo siginfo;     ssize_t bytes_read = TEMP_FAILURE_RETRY(leer(signal_fd, &siginfo, sizeof(siginfo)));     if (bytes_read != sizeof(siginfo)) {         PLOG(ERROR) << "Error al leer siginfo de signal_fd";         devolver;     }    //Conmutador SIGCHLD信号     (siginfo.ssi_signo) {         case SIGCHLD:             ReapAnyOutstandingChildren();             romper;         case SIGTERM:             ManejarSigtermSignal(siginfo);             romper;         por defecto:             PLOG(ERROR) << "signal_fd: señal inesperada recibida" << siginfo.ssi_signo;






 










            romper;
    }
}
 

5.4 Proceso ReapOne
 

Ruta del código: /plataforma/sistema/core/sigchld_handle.cpp

Explicación: ReapOneProcess es la función de procesamiento final. Esta función primero usa waitpid para averiguar el pid del proceso pendiente y luego encuentra el servicio correspondiente de acuerdo con el pid.

Finalmente, llame al método Reap de Servicio para borrar recursos y decida si reiniciar la máquina o reiniciar el proceso de acuerdo con el tipo de proceso.

void ReapAnyOutstandingChildren() {     while (ReapOneProcess()) {     } } static bool ReapOneProcess() {     siginfo_t siginfo = {};     //Utilice la función waitpid para obtener el pid del proceso secundario cuyo estado ha cambiado     //El indicador de waitpid es WNOHANG, es decir, sin bloqueo Si devuelve un valor positivo, significa que un proceso se ha colgado     si (TEMP_FAILURE_RETRY(waitid(P_ALL, 0, &siginfo, WEXITED | WNOHANG | WNOWAIT)) != 0) {         PLOG(ERROR) < < "waitid falló";         return false;     }     auto pid = siginfo.si_pid;     if (pid == 0) return false;     // Cuando sabemos que hay un pid zombie, usamos scopeguard para borrar el pid     auto reaper = make_scope_guard( [pid] { TEMP_FAILURE_RETRY(waitpid(pid , nullptr, WNOHANG)); });     std::string name;



 








 


 


 

    std::cadena espera_cadena;
    Servicio* servicio = nullptr;
 
    if (SubcontextChildReap(pid)) {         nombre = "Subcontexto";     } else {         //通过pid找到对应的servicio         service = ServiceList::GetInstance().FindService(pid, &Service::pid);         if (servicio) {             nombre = StringPrintf("Servicio '%s' (pid %d)", servicio->nombre().c_str(), pid);             if (servicio->flags() & SVC_EXEC) {                 auto exec_duration = boot_clock::now() - service->time_started();                 auto exec_duration_ms =                     std::chrono::duration_cast<std::chrono::milliseconds>(exec_duration).count();




 






                wait_string = StringPrintf(" la espera tomó %f segundos", exec_duration_ms / 1000.0f);
            } else if (servicio->flags() & SVC_ONESHOT) {                 auto exec_duration = boot_clock::now() - service->time_started();                 auto exec_duration_ms =                         std::chrono::duration_cast<std::chrono::milliseconds>(exec_duration)                                 .count();                 wait_string = StringPrintf(" el servicio oneshot tomó %f segundos en segundo plano",exec_duration_ms / 1000.0f);             }         } else {             nombre = StringPrintf("pid sin seguimiento %d", pid);         }     }     si (info de firma.










 

        LOG(INFO) << nombre << " salió con estado " << siginfo.si_status << cadena_espera;
    } else {         LOG(INFO) << nombre << " señal recibida " << siginfo.si_status << cadena_espera;     }     / /No se encontró ningún servicio, lo que indica que terminó, salga     si (!servicio) devuelve verdadero;     servicio->Reap(siginfo);//Borrar recursos relacionados con el proceso secundario     si (servicio->flags() & SVC_TEMPORARY) {         ServiceList: : GetInstance().RemoveService(*service); //Eliminar el servicio     }     return true; }


 


 

 



 


 

6. Servicio de atributos


Durante el proceso de desarrollo y depuración, vimos que las propiedades del sistema se pueden configurar fácilmente a través de property_set, entonces, ¿por qué iniciar un servicio de propiedad aquí? En realidad, esto implica algunos problemas de permisos, ya que no todos los procesos pueden modificar las propiedades del sistema a voluntad.

Android asigna la configuración de los atributos al proceso init para su administración. Otros procesos no pueden modificar directamente los atributos, pero solo pueden notificar al proceso init para que los modifique. En este proceso, el proceso init puede realizar el control de permisos. Echemos un vistazo al proceso específico proceso.

6.1 inicio_propiedad


Ruta del código: plataforma/sistema/núcleo/property_service.cpp

Descripción: Inicialice el sistema de atributos, lea el atributo del archivo especificado, regístrese con SELinux y realice el control de permisos de atributos

Borre el caché, aquí es principalmente para borrar varias listas vinculadas y asignaciones en la memoria, cree un nuevo directorio property_filename, el valor de este directorio es /dev/_properties_

Luego, llame a CreateSerializedPropertyInfo para cargar la información de categoría de algunas propiedades del sistema y, finalmente, escriba la lista vinculada cargada en un archivo y asígnela a la memoria.

void property_init() {     //     Selinux_callback cb;     cb.func_audit = PropertyAuditCallback;     selinux_set_callback(SELINUX_CB_AUDIT, cb);     mkdir("/dev/__propiedades__", S_IRWXU | S_IXGRP | S_IXOTH);     CreateSerializedPropertyInfo();     if (__system_property_area_init()) {         LOG(FATAL) << "Error al inicializar el área de propiedad";     }     if (!property_info_area.LoadDefaultPath()) {         LOG(FATAL) << "Error al cargar el archivo de información de propiedad serializado";     } }
 




 









 

Use CreateSerializedPropertyInfo para cargar los contextos de los siguientes directorios:

1) Relacionado con SELinux

/system/etc/selinux/plat_property_contexts
 
/vendor/etc/selinux/vendor_property_contexts /
 
vendor/etc/selinux/nonplat_property_contexts /
 
product/etc/selinux/product_property_contexts
 
/odm/etc/selinux/odm_property_contexts
2)与SELinux无关

/plat_property_contexts
 
/vendor_property_contexts
/nonplat_property_contexts
 
/product_property_contexts
/odm_property_contexts
 

6.2 Iniciar servicio de propiedad


Ruta del código: plataforma/sistema/núcleo/init.cpp

Descripción: Iniciar el servicio de la propiedad

Primero cree un socket y devuelva el descriptor de archivo, y luego establezca la simultaneidad máxima en 8. Otros procesos pueden notificar al proceso init para modificar las propiedades del sistema a través de este socket.

Finalmente, registre el evento epoll, es decir, llame a handle_property_set_fd cuando se monitoree el cambio de property_set_fd

void StartPropertyService(Epoll* epoll) {     property_set("ro.property_service.version", "2");     // Establecer conexión de socket     si (resultado automático = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,                                    false, 0666, 0, 0 , {})) {         property_set_fd = *result;     } else {         PLOG(FATAL) << "falló la creación del socket start_property_service: " << result.error();     }     // Máximo 8 escuchas simultáneas     listen(property_set_fd, 8);     / / Registre property_set_fd, cuando se cambie el identificador, manéjelo a través de handle_property_set_fd     if (resultado automático = epoll->RegisterHandler(property_set_fd, handle_property_set_fd); ! resultado) {         PLOG(FATAL) << resultado.error();

 







 


 



    }
}


6.3 handle_property_set_fd


Ruta del código: plataforma/sistema/núcleo/property_service.cpp

Descripción: establezca una conexión de socket, luego lea la información de operación del socket y llame a HandlePropertySet para realizar operaciones específicas de acuerdo con diferentes tipos de operaciones

HandlePropertySet es la función de procesamiento final. La tecla que comienza con "ctl" realizará algunas operaciones de inicio, detención y reinicio del servicio. Otras son para llamar a property_set para establecer propiedades. Ya sea lo primero o lo último, se deben realizar verificaciones de seguridad de SELinux. Solo el proceso tiene el permiso de operación para realizar la operación correspondiente

static void handle_property_set_fd() {     static constexpr uint32_t kDefaultSocketTimeout = 2000; /* ms */     // Esperando la conexión del cliente     int s = accept4(property_set_fd, nullptr, nullptr, SOCK_CLOEXEC);     if (s == -1) {         return;     }     ucred cr;     socklen_t cr_size = sizeof(cr);     // Obtener las credenciales del proceso conectado a este socket     if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) {         close(s);         PLOG(ERROR) < < "sys_prop: no se pudo obtener SO_PEERCRED";         return;     }     // Establecer conexión de socket     SocketConnection socket(s, cr);     uint32_t timeout_ms = kDefaultSocketTimeout;

 





 








 



 
    uint32_t cmd = 0;
    // Lee la información de la operación en el socket
    if (!socket.RecvUint32(&cmd, &timeout_ms)) {         PLOG(ERROR) << "sys_prop: error al leer el comando del socket";         socket.SendUint32(PROP_ERROR_READ_CMD );         return;     }     // Realiza el procesamiento correspondiente de acuerdo con la información de la operación, la diferencia entre los dos es que uno lee en forma de caracteres y el otro lee en forma de cadena switch (     cmd) {     case PROP_MSG_SETPROP: {         char prop_name[PROP_NAME_MAX] ;         char prop_value[ PROP_VALUE_MAX];         if (!socket.RecvChars(prop_name, PROP_NAME_MAX, &timeout_ms) ||             !socket.RecvChars(prop_value, PROP_VALUE_MAX, &timeout_ms)) {




 





 


          PLOG(ERROR) << "sys_prop(PROP_MSG_SETPROP): error al leer el nombre/valor del socket";
          devolver;
        }
 
        prop_name[PROP_NAME_MAX-1] = 0;
        prop_value[PROP_VALUE_MAX-1] = 0;
 
        std::string fuente_contexto;
        if (!socket.GetSourceContext(&source_context)) {             PLOG(ERROR) << "No se puede establecer la propiedad '" << prop_name << "': getpeercon() falló";             devolver;         }         const auto& cr = socket.cred();         std::error de cadena;         uint32_t resultado = HandlePropertySet(prop_name, prop_value, source_context, cr, &error);         si (resultado!



 




            LOG(ERROR) << "No se pudo establecer la propiedad '" << prop_name << "' from uid:" << cr.uid
                       << " gid:" << cr.gid << " pid:" << cr. pid << ": " << error;
        }
 
        descanso;
      }
 
    case PROP_MSG_SETPROP2: {         std::string nombre;         std::valor de cadena;         if (!socket.RecvString(&name, &timeout_ms) ||             !socket.RecvString(&value, &timeout_ms)) {           PLOG(ERROR) << "sys_prop(PROP_MSG_SETPROP2): error al leer el nombre/valor del socket";           enchufe. SendUint32(PROP_ERROR_READ_DATA);           devolver;         }         std::string source_context;








 

        if (!socket.GetSourceContext(&source_context)) {             PLOG(ERROR) << "No se puede establecer la propiedad '" << nombre << "': getpeercon() falló";             socket.SendUint32(PROP_ERROR_PERMISSION_DENIED);             devolver;         }         const auto& cr = socket.cred();         std::error de cadena;         uint32_t resultado = HandlePropertySet(nombre, valor, source_context, cr, &error);         if (resultado != PROP_SUCCESS) {             LOG(ERROR) << "No se puede establecer la propiedad '" << nombre << "' from uid:" << cr.uid                        << " gid:" << cr.gid << "pid:" <<cr. pid << ": " << error;         }         socket.SendUint32(resultado);         romper;




 









      }
 
    predeterminado:
        REGISTRO (ERROR) << "sys_prop: comando no válido" << cmd;
        socket.SendUint32(PROP_ERROR_INVALID_CMD);
        romper;
    }
}


7. La tercera etapa init.rc


Cuando se establece el servicio de atributos, las funciones propias de init básicamente terminan y luego se deben iniciar otros procesos. Pero, ¿qué pasa con el proceso de inicio y otros procesos? El otro proceso es un archivo binario, podemos iniciarlo directamente a través del comando exec, como ./system/bin/init second_stage, para iniciar la segunda etapa del proceso init. Pero hay tantos procesos Nativos en el sistema Android, si todos ejecutan los procesos uno a uno en el código pasando exec, sin duda es un diseño nefasto.

Sobre esta base, Android introduce un mecanismo init.rc, que es similar a leer archivos de configuración para iniciar diferentes procesos. A continuación, echemos un vistazo a cómo funciona init.rc.

init.rc es un archivo de configuración que contiene scripts escritos en el lenguaje de inicio de Android.

init.rc en el directorio del teléfono móvil: ./init.rc

init.rc contiene principalmente cinco tipos de declaraciones:

Acción
Comando
Servicio
Opción
Importar

7.1 Acción


Las acciones representan un conjunto de comandos. Las acciones incluyen un activador que determina cuándo ejecutar la acción.

Acción: use el disparador disparador, es decir, la instrucción que comienza con on para determinar el tiempo de ejecución del servicio correspondiente. El tiempo específico es el siguiente:

on early-init; Activado en la etapa inicial de inicialización;
on init; Activado en la etapa de inicialización;
on late-init; Activado en la etapa tardía de inicialización;
on boot/charger: Activado cuando el sistema se inicia/carga;
en propiedad :<clave>= <valor>: Se activa cuando el valor del atributo cumple la condición;
 

7.2 Comando


command es el comando en la lista de comandos de la acción, o el comando de parámetro de la opción onrestart en el servicio, y los comandos se ejecutarán uno por uno cuando ocurra el evento correspondiente.

Los comandos de uso común se enumeran a continuación

class_start <service_class_name>: inicia todos los servicios que pertenecen a la misma clase;
class_stop <service_class_name>: detiene el servicio de la clase especificada
start <service_name>: inicia el servicio especificado, si ya se inició, omítalo;
stop <service_name>: Detenga el servicio en ejecución
setprop <nombre> <valor>: establezca el valor de la propiedad
mkdir <ruta>: cree
el enlace simbólico del directorio especificado <destino> <enlace_sim>: cree el enlace simbólico <enlace_sim> conectado a <destino>;
escriba <ruta> <cadena >: escribe en Escribe una cadena en la ruta del archivo;
exec: bifurca y ejecuta, bloqueará el proceso de inicio hasta que se complete el programa;
exprot <nombre> <nombre>: establece la variable de entorno;
loglevel <nivel>: establece el nivel de registro
nombre de host <nombre>: establecer el nombre de host
importar <nombre de archivo>: importar un archivo de configuración de inicio adicional
 

7.3 Servicio


Servicio El servicio, que comienza con servicio, lo inicia el proceso init y, por lo general, se ejecuta en un subproceso de init, por lo que es necesario determinar si existe el archivo ejecutable correspondiente antes de iniciar el servicio.

命令:servicio <nombre><nombre de ruta> [ <argumento> ]* <opción> <opción>

parámetro

significado

<nombre>

Indica el nombre de este servicio.

<nombre de ruta>

Debido a que la ruta donde se encuentra este servicio es un archivo ejecutable, debe haber una ruta de almacenamiento.

<argumento>

Parámetros para iniciar el servicio

<opción>

Opciones de restricción para este servicio

El proceso secundario generado por init se define en el archivo rc, y cada servicio generará un proceso secundario por bifurcación cuando se inicie.

Por ejemplo: service servicemanager /system/bin/servicemanager significa que el nombre del servicio es servicemanager y la ruta de ejecución del servicio es /system/bin/servicemanager.

7.4 Opciones


Las opciones son opcionales para el servicio, se usan junto con el servicio

disabled: no se inicia automáticamente con la clase, solo se inicia de acuerdo con el nombre del servicio;
oneshot: no se reinicia después de que finaliza el servicio;
usuario/grupo: establece el usuario/grupo de usuarios que ejecuta el servicio, el valor predeterminado es root;
class: establece el nombre de la clase a la que pertenece, cuando Cuando la clase se inicia/sale, el servicio también se inicia/detiene, el valor predeterminado es predeterminado; onrestart: ejecuta el comando correspondiente
cuando se reinicia el servicio;
socket: crea un socket llamado /dev/socket/ <nombre>
crítico: el servicio dentro del tiempo especificado Si continúa reiniciando, el sistema se reiniciará y entrará en el modo de recuperación
predeterminado: significa desactivado = falso, oneshot = falso, crítico = falso.

7.5 importación


Se utiliza para importar otros archivos rc

Comando: importar <nombre de archivo>

7.6 proceso de análisis de init.rc

7.6.1 Cargar guiones de arranque

Ruta del código: plataforma\sistema\núcleo\init\init.cpp

Descripción: si no hay una configuración especial ro.boot.init_rc, analice ./init.rc

把/system/etc/init,/product/etc/init,/product_services/etc/init,/odm/etc/init,

/vendor/etc/init Estas rutas se agregan a las rutas analizadas después de init.rc, después de que se complete el análisis de init.rc, analice los archivos rc en estos directorios

static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) {     Parser parser = CreateParser(action_manager, service_list);     std::string bootscript = GetProperty("ro.boot.init_rc", "");     if (bootscript.empty()) {         parser.ParseConfig("/init.rc");         if (!parser.ParseConfig("/system/etc/init")) {             late_import_paths.emplace_back("/system/etc/init");         }         if (!parser.ParseConfig("/product/etc/init")) {             late_import_paths.emplace_back("/product/etc/init");         }         if (!parser.ParseConfig("/product_services/etc/init")) {

 










            late_import_paths.emplace_back("/product_services/etc/init");
        }
        if (!parser.ParseConfig("/odm/etc/init")) {             late_import_paths.emplace_back("/odm/etc/init");         }         if (!parser.ParseConfig("/proveedor/etc/init")) {             late_import_paths.emplace_back("/vendor/etc/init");         }     } más {         parser.ParseConfig(bootscript);     } }









 

Después de Android7.0, init.rc se dividió, cada servicio tiene su propio archivo rc, básicamente se cargan en /system/etc/init, /vendor/etc/init, /odm/etc/init Espere el directorio, después el análisis init.rc se completa, analizará los archivos rc en estos directorios para realizar acciones relacionadas.

Ruta del código: plataforma\sistema\núcleo\init\init.cpp

Descripción: Cree objetos de análisis de Parser, como servicio, en, objetos de importación

Parser CreateParser(ActionManager& action_manager, ServiceList& service_list) {     Parser parser;     analizador.AddSectionParser(             "servicio", std::make_unique<ServiceParser>(&service_list, subcontexts, std::nullopt));     analizador.AddSectionParser("on", std::make_unique<ActionParser>(&action_manager, subcontexts));     analizador.AddSectionParser("importar", std::make_unique<ImportParser>(&parser));     analizador de retorno; }

 




 

7.6.2 Ejecución de acciones

Agregue acciones relacionadas a la cola de activación en orden, el orden es inicio temprano -> inicio -> inicio tardío Luego, en el ciclo, ejecute todas las funciones de ejecución con Comando en la Acción en la cola de activación.

am.QueueEventTrigger("inicio temprano");
am.QueueEventTrigger("init");
am.QueueEventTrigger("inicio tardío");
...
while (verdadero) { if (!(waiting_for_prop || Service::is_exec_service_running())) {             am.ExecuteOneCommand();         } }




 

7.6.3 Inicio de cigoto

A partir de Android 5.0, Android admite la compilación de 64 bits, por lo que el propio zygote también admite 32 y 64 bits. Utilice el atributo ro.zygote para controlar el inicio de diferentes versiones del proceso zygote.

En la sección de importación de init.rc vemos el siguiente código:

import /init.${ro.zygote}.rc // Se puede ver que init.rc ya no importa directamente un archivo fijo, sino que importa diferentes archivos según el contenido del atributo ro.zygote

 init.rc se encuentra en /system/core/rootdir. En esta ruta también se incluyen cuatro archivos rc sobre zygote.

Son init.zygote32.rc, init.zygote32_64.rc, init.zygote64.rc, init.zygote64_32.rc, cuyo archivo lo determina el hardware.

Tome el procesador de 64 bits como ejemplo, el código de init.zygote64.rc es el siguiente:

service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server
    class main # class es una opción, especificando el tipo de servicio de zygote como
            prioridad principal -20
            usuario raíz
           grupo raíz readproc Reserved_disk
            socket zygote stream 660 root system # socket palabra clave indica una opción, cree un socket llamado dev/socket/zygote, el tipo es stream, el permiso es 660 socket usap_pool_primary stream 660 root system onrestart
           write
            /sys/android_power/request_state wake # onrestart es una opción, que indica el comando que     se ejecutará cuando se reinicie     el     cigoto
            .



    onrestart reiniciar netd
    onrestart reiniciar wificond
    writepid /dev/cpuset/foreground/tasks
 

servicio zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server 解析:

servicio cigoto: Un servicio cigoto se define en init.zygote64.rc. El proceso de inicio es crear el proceso de cigoto a través de este nombre de servicio

/system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server解析:

El servicio zygote se ejecuta ejecutando /system/bin/app_process64 y pasando 4 parámetros:

Parámetro 1: -Xzygote Este parámetro se usará como el parámetro requerido cuando se inicia la máquina virtual
Parámetro 2: /system/bin representa el directorio donde se encuentra el programa de la máquina virtual
Parámetro 3: --zygote indica que la función principal en ZygoteInit La clase .java se ejecuta como una máquina virtual
Parámetro de entrada 4: --start-system-server le dice al proceso Zygote que inicie el proceso systemServer
 

8. Resumen


El trabajo principal de la primera etapa del proceso de inicio es montar particiones, crear nodos de dispositivos y algunos directorios clave, inicializar el sistema de salida de registros y habilitar las políticas de seguridad de SELinux.

El trabajo principal de la segunda etapa del proceso init es inicializar el sistema de atributos, analizar las reglas coincidentes de SELinux, procesar la señal de finalización del proceso secundario e iniciar el servicio de atributos del sistema. Se puede decir que cada elemento es muy Si la primera etapa es para el sistema de atributos, SELinux se prepara, entonces la segunda etapa es para implementar realmente estas funciones.

La tercera etapa de init es principalmente analizar init.rc para iniciar otros procesos, ingresar a un ciclo infinito y monitorear subprocesos en tiempo real.

Supongo que te gusta

Origin blog.csdn.net/s_nshine/article/details/131712990
Recomendado
Clasificación