Android 源码分析 --                   (一) Android 启动过程

                             royalxw@gmail.com

1. 源码文件路径:           platform/system/core/init/init.c
    0) 这个代码文件主要用于实现 Android 系统中 init 进程 (init 进程为 Android 系统中用
       户空间启动的第一个进程,其作用相当于 Linux 系统中的 init 进程)
       NOTE: 如果调用此文件生成的可执行文件的第一个参数为“ueventd”,          那么此文件
       将实现 Android 系统中的 “ueventd” 进程。
    1) 在进行编译后,此文件生成的可执行程序名称为”init”,同时会生成一个指向此文
       件的软链接: “ueventd”




int main(int argc, char **argv)
{

    2) 基于 C 语言的风格,在函数入口处声明一些后续会使用的变量。

     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;



    3) 如果执行此文件生成的可执行程序的方式类似于: “ueventd xxx”。也即是基于执行
    此 文 件 对 应 的 软 链 接 : /sbin/ueventd 时 会 调 用 ”ueventd_main” 函 数 , 进 而 生
    成”ueventd” 进程。
    4) Android 系统中的 ueventd 进程用于实现用户态进程与内核进行数据通信。
    5) 在 Android 系统的 init.rc 文件: platform/system/core/rootdir/init.rc 中通过如下方式
    来启动 ueventd 进程:
    on early-init
        # Set init and its forked children's oom_adj.
        write /proc/1/oom_adj -16
        start ueventd



     if (!strcmp(basename(argv[0]), "ueventd"))
return ueventd_main(argc, argv);



 6)        生成 Android 系统中的一些基本的系统目录并挂载对应的文件系统。

      /* 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.
          */
      mkdir("/dev", 0755);
      mkdir("/proc", 0755);
      mkdir("/sys", 0755);
      mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "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);
      /* indicate that booting is in progress to background fw loaders, etc
*/
      close(open("/dev/.booting", O_WRONLY | O_CREAT, 0000));
      /* 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.
          */


 7) 生成 “/dev/__null__” 虚拟设备(类似于 Linux 系统中的 /dev/null 设备)并将
 stdin/stdout/stderr 三个文件重定向到 “/dev/__null__”

      open_devnull_stdio();


 8)        生成 ” /dev/__kmsg__” 虚拟设备用于记录 log。
           Klog_init 实现文件: system/core//libcutils/klog.c

      klog_init();
      INFO("reading config filen");




     9)    解析 init.rc (platform/system/core/rootdir/init.rc)。
init_parse_config_file("/init.rc");
   /* pull the kernel commandline and ramdisk properties file in */

  10) 从 “/proc/cmdline” 中读取内核命令行参数,
  对应函数实现路径: platform/system/core/init/util.c
   import_kernel_cmdline(0, import_kernel_nv);



  11) 在第 10 步读取完 /proc/cmdline 中的参数后,修改此文件的权限,禁止非授权
  用户操作此文件。

   /* don't expose the raw commandline to nonpriv processes */
   chmod("/proc/cmdline", 0440);


  12) 从 “/proc/cpuinfo” 中读取系统的 CPU 硬件信息。
  对应函数实现路径: platform/system/core/init/util.c

   get_hardware_name(hardware, &revision);


  13) 基于第 12 步中读取的硬件信息来解析特定于硬件的配置信息。

   snprintf(tmp, sizeof(tmp), "/init.%s.rc", hardware);
   init_parse_config_file(tmp);


 14) 基 于 ”early-init”,”property_init”,”keychord_init”,”console_init” 标 识 , 使 用 ”
 queue_builtin_action”来过滤上述操作中解析的 init.rc 文件中的 action,                    并将符合
 条 件 的 action 添 加 到 对 应 的                         action_queue 中 , 然 后 调 用 ”
 action_for_each_trigger”来运行这些 actions(实际上是 action 在 list 中的分类移
 动操作)。
 对应函数实现路径: platform/system/core/init/init_parser.c
 基于下面的运行逻辑可以看出,运行”init.rc”中的 action 的顺序为:
 “early-init” -> “init” -> “early-boot” -> “boot”



   action_for_each_trigger("early-init", action_add_queue_tail);
   queue_builtin_action(wait_for_coldboot_done_action,
"wait_for_coldboot_done");
   queue_builtin_action(property_init_action, "property_init");
   queue_builtin_action(keychord_init_action, "keychord_init");
   queue_builtin_action(console_init_action, "console_init");
   queue_builtin_action(set_init_properties_action,
"set_init_properties");
   /* execute all the boot actions to get us started */
   action_for_each_trigger("init", action_add_queue_tail);
/* skip mounting filesystems in charger mode */
     if (strcmp(bootmode, "charger") != 0)
     {
         action_for_each_trigger("early-fs", action_add_queue_tail);
         action_for_each_trigger("fs", action_add_queue_tail);
         action_for_each_trigger("post-fs", action_add_queue_tail);
         action_for_each_trigger("post-fs-data", action_add_queue_tail);
     }
     queue_builtin_action(property_service_init_action,
"property_service_init");
     queue_builtin_action(signal_init_action, "signal_init");
     queue_builtin_action(check_startup_action, "check_startup");
     if (!strcmp(bootmode, "charger"))
     {
         action_for_each_trigger("charger", action_add_queue_tail);
     }
     else
     {
         action_for_each_trigger("early-boot", action_add_queue_tail);
         action_for_each_trigger("boot", action_add_queue_tail);
     }
     /* run all property triggers based on current state of the properties
*/
     queue_builtin_action(queue_property_triggers_action,
"queue_propety_triggers");
#if BOOTCHART
     queue_builtin_action(bootchart_init_action, "bootchart_init");
#endif



 15) “init” 进程开始进行”循环事件处理”逻辑。


     for (;;)
     {
         int nr, i, timeout = -1;


 16) 运行第 14 步操作中所分类整理的 action_queue 中对应的 action。

         execute_one_command();

 17) 查看当前的服务进程状态,如果有服务进程退出,重启对应服务进程。
         restart_processes();
18) 基于 property_service 的事件句柄填充 poll event 结构体,用于后续 poll 操作。

    if (!property_set_fd_init && get_property_set_fd() > 0)
    {
        ufds[fd_count].fd = get_property_set_fd();
        ufds[fd_count].events = POLLIN;
        ufds[fd_count].revents = 0;
        fd_count++;
        property_set_fd_init = 1;
    }

19) 处理当前 init 进程上接收到的 signal,主要是处理 SIGCHLD。
    if (!signal_fd_init && get_signal_fd() > 0)
    {
        ufds[fd_count].fd = get_signal_fd();
        ufds[fd_count].events = POLLIN;
        ufds[fd_count].revents = 0;
        fd_count++;
        signal_fd_init = 1;
    }

20) 基于 keychord service 的事件句柄填充 poll event 结构体,用于后续 poll 操作。

    if (!keychord_fd_init && get_keychord_fd() > 0)
    {
        ufds[fd_count].fd = get_keychord_fd();
        ufds[fd_count].events = POLLIN;
        ufds[fd_count].revents = 0;
        fd_count++;
        keychord_fd_init = 1;
    }

21) 如果有所监控的子进程退出,此时 init 进程需要负责重新启动这些退出的服务进
程,因此下面调用 poll 时的 timeout 设置为 0,这样就不会因为 poll 在无事件激发时而
阻塞导致当前的 init 进程对”重启服务进程”处理的的延迟,从而可以尽快恢复退出的
服务进程。

    if (process_needs_restart)
    {
        timeout = (process_needs_restart - gettime()) * 1000;
        if (timeout < 0)
           timeout = 0;
    }

22) 如果当前的 action_queue 中有需要处理的 action,那么下面调用 poll 时的 timeout
设置为 0,这样就不会因为 poll 在无事件激发时而阻塞导致当前的 init 进程对 action
处理的的延迟,从而提高 init 进程对 action 处理的实时性。
if (!action_queue_empty() || cur_action)
             timeout = 0;



  23) 关于” BOOTCHART”参数的解释:
        /*
             * 如果在编译选项中添加了 BOOTCHART 参数,那么意味着在系统的启动
             * 过程中需要生成 bootchart(http://www.bootchart.org/),用于后续
             * Android 启动过程中的性能分析并生成系统启动过程的可视图表。
             * makefile 中的编译选项如下:
                   ifeq ($(strip $(INIT_BOOTCHART)),true)
                      LOCAL_SRC_FILES += bootchart.c
                      LOCAL_CFLAGS     += -DBOOTCHART=1
                      endif
                 bootchart 的实现文件: platform/system/core/init/bootchart.c
             *


#if BOOTCHART
         if (bootchart_count > 0)
         {
             if (timeout < 0 || timeout > BOOTCHART_POLLING_MS)
                 timeout = BOOTCHART_POLLING_MS;
             if (bootchart_step() < 0 || --bootchart_count == 0)
             {
                 bootchart_finish();
                 bootchart_count = 0;
             }
         }
#endif

  24) 类似于网络服务器开发中常见的基于”poll”机制来检测所关注的句柄上是否有指定
  的事件激发。


         nr = poll(ufds, fd_count, timeout);
         if (nr <= 0)
             continue;
         for (i = 0; i < fd_count; i++)
         {
  25) 如果当前所关注的事件句柄上有事件发生,进行对应的事件处理。

             if (ufds[i].revents == POLLIN)
             {
                 if (ufds[i].fd == get_property_set_fd())
handle_property_set_fd();
                else if (ufds[i].fd == get_keychord_fd())
                   handle_keychord();
                else if (ufds[i].fd == get_signal_fd())
                   handle_signal();
            }
        }
    }
    return 0;
}

Android 源码分析 -- (一) Android启动过程

  • 1.
    Android 源码分析 -- (一) Android 启动过程 royalxw@gmail.com 1. 源码文件路径: platform/system/core/init/init.c 0) 这个代码文件主要用于实现 Android 系统中 init 进程 (init 进程为 Android 系统中用 户空间启动的第一个进程,其作用相当于 Linux 系统中的 init 进程) NOTE: 如果调用此文件生成的可执行文件的第一个参数为“ueventd”, 那么此文件 将实现 Android 系统中的 “ueventd” 进程。 1) 在进行编译后,此文件生成的可执行程序名称为”init”,同时会生成一个指向此文 件的软链接: “ueventd” int main(int argc, char **argv) { 2) 基于 C 语言的风格,在函数入口处声明一些后续会使用的变量。 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; 3) 如果执行此文件生成的可执行程序的方式类似于: “ueventd xxx”。也即是基于执行 此 文 件 对 应 的 软 链 接 : /sbin/ueventd 时 会 调 用 ”ueventd_main” 函 数 , 进 而 生 成”ueventd” 进程。 4) Android 系统中的 ueventd 进程用于实现用户态进程与内核进行数据通信。 5) 在 Android 系统的 init.rc 文件: platform/system/core/rootdir/init.rc 中通过如下方式 来启动 ueventd 进程: on early-init # Set init and its forked children's oom_adj. write /proc/1/oom_adj -16 start ueventd if (!strcmp(basename(argv[0]), "ueventd"))
  • 2.
    return ueventd_main(argc, argv); 6) 生成 Android 系统中的一些基本的系统目录并挂载对应的文件系统。 /* 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. */ mkdir("/dev", 0755); mkdir("/proc", 0755); mkdir("/sys", 0755); mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "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); /* indicate that booting is in progress to background fw loaders, etc */ close(open("/dev/.booting", O_WRONLY | O_CREAT, 0000)); /* 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. */ 7) 生成 “/dev/__null__” 虚拟设备(类似于 Linux 系统中的 /dev/null 设备)并将 stdin/stdout/stderr 三个文件重定向到 “/dev/__null__” open_devnull_stdio(); 8) 生成 ” /dev/__kmsg__” 虚拟设备用于记录 log。 Klog_init 实现文件: system/core//libcutils/klog.c klog_init(); INFO("reading config filen"); 9) 解析 init.rc (platform/system/core/rootdir/init.rc)。
  • 3.
    init_parse_config_file("/init.rc"); /* pull the kernel commandline and ramdisk properties file in */ 10) 从 “/proc/cmdline” 中读取内核命令行参数, 对应函数实现路径: platform/system/core/init/util.c import_kernel_cmdline(0, import_kernel_nv); 11) 在第 10 步读取完 /proc/cmdline 中的参数后,修改此文件的权限,禁止非授权 用户操作此文件。 /* don't expose the raw commandline to nonpriv processes */ chmod("/proc/cmdline", 0440); 12) 从 “/proc/cpuinfo” 中读取系统的 CPU 硬件信息。 对应函数实现路径: platform/system/core/init/util.c get_hardware_name(hardware, &revision); 13) 基于第 12 步中读取的硬件信息来解析特定于硬件的配置信息。 snprintf(tmp, sizeof(tmp), "/init.%s.rc", hardware); init_parse_config_file(tmp); 14) 基 于 ”early-init”,”property_init”,”keychord_init”,”console_init” 标 识 , 使 用 ” queue_builtin_action”来过滤上述操作中解析的 init.rc 文件中的 action, 并将符合 条 件 的 action 添 加 到 对 应 的 action_queue 中 , 然 后 调 用 ” action_for_each_trigger”来运行这些 actions(实际上是 action 在 list 中的分类移 动操作)。 对应函数实现路径: platform/system/core/init/init_parser.c 基于下面的运行逻辑可以看出,运行”init.rc”中的 action 的顺序为: “early-init” -> “init” -> “early-boot” -> “boot” action_for_each_trigger("early-init", action_add_queue_tail); queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done"); queue_builtin_action(property_init_action, "property_init"); queue_builtin_action(keychord_init_action, "keychord_init"); queue_builtin_action(console_init_action, "console_init"); queue_builtin_action(set_init_properties_action, "set_init_properties"); /* execute all the boot actions to get us started */ action_for_each_trigger("init", action_add_queue_tail);
  • 4.
    /* skip mountingfilesystems in charger mode */ if (strcmp(bootmode, "charger") != 0) { action_for_each_trigger("early-fs", action_add_queue_tail); action_for_each_trigger("fs", action_add_queue_tail); action_for_each_trigger("post-fs", action_add_queue_tail); action_for_each_trigger("post-fs-data", action_add_queue_tail); } queue_builtin_action(property_service_init_action, "property_service_init"); queue_builtin_action(signal_init_action, "signal_init"); queue_builtin_action(check_startup_action, "check_startup"); if (!strcmp(bootmode, "charger")) { action_for_each_trigger("charger", action_add_queue_tail); } else { action_for_each_trigger("early-boot", action_add_queue_tail); action_for_each_trigger("boot", action_add_queue_tail); } /* run all property triggers based on current state of the properties */ queue_builtin_action(queue_property_triggers_action, "queue_propety_triggers"); #if BOOTCHART queue_builtin_action(bootchart_init_action, "bootchart_init"); #endif 15) “init” 进程开始进行”循环事件处理”逻辑。 for (;;) { int nr, i, timeout = -1; 16) 运行第 14 步操作中所分类整理的 action_queue 中对应的 action。 execute_one_command(); 17) 查看当前的服务进程状态,如果有服务进程退出,重启对应服务进程。 restart_processes();
  • 5.
    18) 基于 property_service的事件句柄填充 poll event 结构体,用于后续 poll 操作。 if (!property_set_fd_init && get_property_set_fd() > 0) { ufds[fd_count].fd = get_property_set_fd(); ufds[fd_count].events = POLLIN; ufds[fd_count].revents = 0; fd_count++; property_set_fd_init = 1; } 19) 处理当前 init 进程上接收到的 signal,主要是处理 SIGCHLD。 if (!signal_fd_init && get_signal_fd() > 0) { ufds[fd_count].fd = get_signal_fd(); ufds[fd_count].events = POLLIN; ufds[fd_count].revents = 0; fd_count++; signal_fd_init = 1; } 20) 基于 keychord service 的事件句柄填充 poll event 结构体,用于后续 poll 操作。 if (!keychord_fd_init && get_keychord_fd() > 0) { ufds[fd_count].fd = get_keychord_fd(); ufds[fd_count].events = POLLIN; ufds[fd_count].revents = 0; fd_count++; keychord_fd_init = 1; } 21) 如果有所监控的子进程退出,此时 init 进程需要负责重新启动这些退出的服务进 程,因此下面调用 poll 时的 timeout 设置为 0,这样就不会因为 poll 在无事件激发时而 阻塞导致当前的 init 进程对”重启服务进程”处理的的延迟,从而可以尽快恢复退出的 服务进程。 if (process_needs_restart) { timeout = (process_needs_restart - gettime()) * 1000; if (timeout < 0) timeout = 0; } 22) 如果当前的 action_queue 中有需要处理的 action,那么下面调用 poll 时的 timeout 设置为 0,这样就不会因为 poll 在无事件激发时而阻塞导致当前的 init 进程对 action 处理的的延迟,从而提高 init 进程对 action 处理的实时性。
  • 6.
    if (!action_queue_empty() ||cur_action) timeout = 0; 23) 关于” BOOTCHART”参数的解释: /* * 如果在编译选项中添加了 BOOTCHART 参数,那么意味着在系统的启动 * 过程中需要生成 bootchart(http://www.bootchart.org/),用于后续 * Android 启动过程中的性能分析并生成系统启动过程的可视图表。 * makefile 中的编译选项如下: ifeq ($(strip $(INIT_BOOTCHART)),true) LOCAL_SRC_FILES += bootchart.c LOCAL_CFLAGS += -DBOOTCHART=1 endif bootchart 的实现文件: platform/system/core/init/bootchart.c * #if BOOTCHART if (bootchart_count > 0) { if (timeout < 0 || timeout > BOOTCHART_POLLING_MS) timeout = BOOTCHART_POLLING_MS; if (bootchart_step() < 0 || --bootchart_count == 0) { bootchart_finish(); bootchart_count = 0; } } #endif 24) 类似于网络服务器开发中常见的基于”poll”机制来检测所关注的句柄上是否有指定 的事件激发。 nr = poll(ufds, fd_count, timeout); if (nr <= 0) continue; for (i = 0; i < fd_count; i++) { 25) 如果当前所关注的事件句柄上有事件发生,进行对应的事件处理。 if (ufds[i].revents == POLLIN) { if (ufds[i].fd == get_property_set_fd())
  • 7.
    handle_property_set_fd(); else if (ufds[i].fd == get_keychord_fd()) handle_keychord(); else if (ufds[i].fd == get_signal_fd()) handle_signal(); } } } return 0; }