Android5.0开关机模块——init进程

本文详细介绍了Android5.0开关机模块中的init进程,从硬件层到应用层逐步解析Android启动流程。重点分析了init进程的启动、设备节点创建、文件系统挂载、属性服务初始化以及init.rc配置文件的解析。通过init进程,理解Android系统如何从内核层面逐步构建到用户界面。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

    最近项目组把Android5.0的开关机模块分给我,以前只是很简单的接触过Android的开关机流程,刚好借助这次机会,深入研究一下Android5.0的开关机模块。
    Android的开关机流程可以按照整个手机设备的分层结构来理解:
    1、硬件层(Hardware)。用户手指长按电源键触发硬件产生电信号
    2、内核层(Kernel)。硬件的电信号会触发bootloader,bootloader会引导kernel的启动
    3、系统层(System)。Kernel起来以后,才是真正的Android系统被引导起来
    4、应用层(App)。Android系统起来后,用户最终看到的App才会显示在桌面(Launcher)上
    这4步是对Android启动流程的简要概括。第一步和第二步主要由硬件工程师和驱动工程师来负责,我只关注第三、四步。
    Kernel起来后,会启动Android系统的第一个进程——init进程
    从init进程的ID号也可以看出,它的进程ID是1,说明是第一个进程
    root      1     0     948    704   c01f80d0 00028100 S /init
    就从init进程开始入手分析,init进程的源代码在system/core/init/init.c中
    分析init进程从main函数入手
    main函数在定义相关的变量之后,首先就是uevent和watchdog
<span style="font-size:18px;">    if (!strcmp(basename(argv[0]), "ueventd"))
        return ueventd_main(argc, argv);

    if (!strcmp(basename(argv[0]), "watchdogd"))
        return watchdogd_main(argc, argv);</span>
system/core/init/Android.mk是init进程的编译脚本,它有下面一段话
    # Make a symlink from /sbin/ueventd and /sbin/watchdogd to /init
SYMLINKS := \
	$(TARGET_ROOT_OUT)/sbin/ueventd \
	$(TARGET_ROOT_OUT)/sbin/watchdogd
    这说明ueventd和watchdogd是init的两个软连接,当执行ueventd或者watchdogd这两个服务时,执行的还是init进程,init会根据命令行的参数名决定是否进入ueventd_main或者watchdogd_main
    ueventd伺服程序将解析/ueventd.rc文件,并创建相应的设备结点。watchdogd伺服程序是一个看门狗程序,它的任务就是定期向看门狗设备文件执行写操作,以判断系统是否正常运行。
    接下来就是创建几个必须的目录,并挂载tmpfs,devpts,proc,sysfs文件系统,将通过创建文件"/dev/.booting"来表示目前正处于启动中的状态。
    /* 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));</span>
接下来,将创建两个设备结点:/dev/__null__以及/dev/__kmsg__,并打开标准输入流,输出流以及错误输出流,并将它们重定向到/dev/__null__,所以,此时是不能直接调用printf系列的函数直接打印Log输出,而是利用klog输出日志。
    open_devnull_stdio();
    klog_init();
    property_init();</span>
void klog_init(void)
{
    static const char *name = "/dev/__kmsg__";

    if (klog_fd >= 0) return; /* Already initialized */

    if (mknod(name, S_IFCHR | 0600, (1 << 8) | 11) == 0) {
        klog_fd = open(name, O_WRONLY);
        if (klog_fd < 0)
                return;
        fcntl(klog_fd, F_SETFD, FD_CLOEXEC);
        unlink(name);
    }
}

上述实现看出内核的log输出是通过文件描述符log_fd写入的,那到底写入到什么设备呢?/dev/kmsg,这个设备则会把它收到的任何写入都作为printk的输出。printk函数是内核中运行的向控制台输出显示的函数。
       接下来,property_init初始化属性服务所需要的基本空间。首先创建一个/dev/__properties__文件,然后通过对应的文件描述映射一块共享内存,大小PA_SIZE(49152),映射的地址和相应的文件描述符保存在struct workspace中。相当于在内存中初始化一块共享内存区域,用来存储系统属性,方便其他进程在执行过程中读取。
       接着获取硬件信息,将处理传递给内核的命令行参数
    get_hardware_name(hardware, &revision);

    process_kernel_cmdline();
get_hardware_name()其实是把/proc/cpuinfo中的hardware和revision信息提取出来存在变量中,供后面使用
      process_kernel_cmdline处理内存传递上来的参数,该参数存在/proc/cmdline中
      如果启用了SELinux机制,接下来将加载selinux策略,并初始化文件安全上下文以及属性安全上下文。
    union selinux_callback cb;
    cb.func_log = log_callback;
    selinux_set_callback(SELINUX_CB_LOG, cb);

    cb.func_audit = audit_callback;
    selinux_set_callback(SELINUX_CB_AUDIT, cb);

    selinux_initialize();
    /* These directories were necessarily created before initial policy load
     * and therefore need their security context restored to the proper value.
     * This must happen before /dev is populated by ueventd.
     */
    restorecon("/dev");
    restorecon("/dev/socket");
    restorecon("/dev/__properties__");
    restorecon_recursive("/sys");
从内核参数中得出当前的bootmode,判断是不是充电模式
    is_charger = !strcmp(bootmode, "charger");
    接下来是property_load_boot_defaults();
    INFO("property init\n");
    property_load_boot_defaults();
     这个函数是从PROP_PATH_RAMDISK_DEFAULT中加载系统属性
    bionic\libc\include\sys\_system_properties.h
#define PROP_PATH_RAMDISK_DEFAULT  "/default.prop"
#define PROP_PATH_SYSTEM_BUILD     "/system/build.prop"
#define PROP_PATH_SYSTEM_DEFAULT   "/system/default.prop"
#define PROP_PATH_LOCAL_OVERRIDE   "/data/local.prop"
#define PROP_PATH_FACTORY          "/factory/factory.prop"
接下来是解析init.rc配置文件,这是init进程的重头戏
    INFO("reading config file\n");
    init_parse_config_file("/init.rc");
在讲述函数init_parse_config_file()之前,先了解下init.rc的一些简要知识
       init.rc文件基本组成单位是section, section分为三种类型,分别由三个关键字(所谓关键字即每一行的第一列)来区分,这三个关键字是on、service、import。
       on类型的section表示一系列命令的组合, 例如:

       on init
             export PATH /sbin:/system/sbin:/system/bin
             export ANDROID_ROOT /system
             export ANDROID_DATA /data
 
       这样一个section包含了三个export命令,命令的执行是以section为单位的,所以这三个命令是一起执行的,不会单独执行, 那什么时候执行呢? 这是由init.c的main()所决定的,main()里在某个时间会调用
       action_for_each_trigger("init", action_add_queue_tail);
       这就把on init开始的这样一个section里的所有命令加入到一个执行队列,在未来的某个时候会顺序执行队列里的命令,所以调用action_for_each_trigger的先后决定了命令执行的先后。

       service类型的section表示一个可执行程序,例如:
 
       service surfaceflinger /system/bin/surfaceflinger
           class main
           user system
           group graphics drmrpc
           onrestart restart zygote
 
       surfaceflinger作为一个名字标识了这个service,  /system/bin/surfaceflinger表示可执行文件的位置, class、user、group、onrestart这些关键字所对应的行都被称为options, options是用来描述的service一些特点,不同的service有着不同的options。
       service类型的section标识了一个service(或者说可执行程序), 那这个service什么时候被执行呢?是在class_start这个命令被执行的时候,class_start命令行总是存在于某个on类型的section中,“class_start core”这样一条命令被执行,就会启动类型为core的所有service。
所以可以看出android的启动过程主要就是on类型的section被执行的过程。
 
       import类型的section表示引入另外一个.rc文件,例如:
        
       import init.test.rc
 
       相当包含另外一些section, 在解析完init.rc文件后继续会调用init_parse_config_file来解析引入的.rc文件。

       接着继续说,init_parse_config_file()函数,该函数会调用parse_config函数,这才是完成解析动作的函数
       
static void parse_config(const char *fn, char *s)
{
    struct parse_state state;
    struct listnode import_list;
    struct listnode *node;
    char *args[INIT_PARSER_MAXARGS];
    int nargs;

    nargs = 0;
    state.filename = fn;
    state.line = 0;
    state.ptr = s;
    state.nexttoken = 0;
    state.parse_line = parse_line_no_op;

    list_init(&import_list);
    state.priv = &import_list;

    for (;;) {
        switch (next_token(&state)) {
        case T_EOF:                        //遍历到文件结尾,然后goto解析import的.rc文件,相当于解析另一个rc文件
            state.parse_line(&state, 0, 0);
            goto parser_done;
        case T_NEWLINE:
            state.line++;
            if (nargs) {
                int kw = lookup_keyword(args[0]);                //找到这一行的关键字
                if (kw_is(kw, SECTION)) {                        //如果这是一个section的第一行
                    state.parse_line(&state, 0, 0);
                    parse_new_section(&state, kw, nargs, args);
                } else {                                         //如果这不是一个section的第一行
                    state.parse_line(&state, nargs, args);
                }
                nargs = 0;
            }
            break;
        case T_TEXT:                                             //遇到普通字符
            if (nargs < INIT_PARSER_MAXARGS) {
                args[nargs++] = state.text;
            }
            break;
        }
    }

parser_done:
    list_for_each(node, &import_list) {
         struct import *import = node_to_item(node, struct import, list);
         int ret;

         INFO("importing '%s'", import->filename);
         ret = init_parse_config_file(import->filename);
         if (ret)
             ERROR("could not import file '%s' from '%s'\n",
                   import->filename, fn);
    }
}

      next_token() 解析完init.rc中一行之后,会返回T_NEWLINE,这时调用lookup_keyword函数来找出这一行的关键字, lookup_keyword返回的是一个整型值,对应keyword_info[]数组的下标,keyword_info[]存放的是keyword_info结构体类型的数据
static struct {
    const char *name;
    int (*func)(int nargs, char **args);
    unsigned char nargs;
    unsigned char flags;
} keyword_info

因此keyword_info[]中存放的是所有关键字的信息,每一项对应一个关键字。
    KEYWORD(import,      SECTION, 1, 0)
    KEYWORD(keycodes,    OPTION,  0, 0)
    KEYWORD(mkdir,       COMMAND, 1, do_mkdir)
    KEYWORD(mount_all,   COMMAND, 1, do_mount_all)
    KEYWORD(mount,       COMMAND, 3, do_mount)
    KEYWORD(on,          SECTION, 0, 0)
    KEYWORD(oneshot,     OPTION,  0, 0)
    KEYWORD(onrestart,   OPTION,  0, 0)
    KEYWORD(powerctl,    COMMAND, 1, do_powerctl)
    KEYWORD(restart,     COMMAND, 1, do_restart)
    KEYWORD(restorecon,  COMMAND, 1, do_restorecon)
    KEYWORD(restorecon_recursive,  COMMAND, 1, do_restorecon_recursive)
    KEYWORD(rm,          COMMAND, 1, do_rm)
    KEYWORD(rmdir,       COMMAND, 1, do_rmdir)
    KEYWORD(seclabel,    OPTION,  0, 0)
    KEYWORD(service,     SECTION, 0, 0)

根据每一项的flags就可以判断出关键字的类型,如新的一行是SECTION,就调用parse_new_section()来解析这一行, 如新的一行不是一个SECTION的第一行,那么调用state.parseline()来解析(state.parseline所对应的函数会根据section类型的不同而不同),在parse_new_section()中进行动态设置。
static void parse_new_section(struct parse_state *state, int kw,
                       int nargs, char **args)
{
    printf("[ %s %s ]\n", args[0],
           nargs > 1 ? args[1] : "");
    switch(kw) {
    case K_service:
        state->context = parse_service(state, nargs, args);
        if (state->context) {
            state->parse_line = parse_line_service;
            return;
        }
        break;
    case K_on:
        state->context = parse_action(state, nargs, args);
        if (state->context) {
            state->parse_line = parse_line_action;
            return;
        }
        break;
    case K_import:
        parse_import(state, nargs, args);
        break;
    }
    state->parse_line = parse_line_no_op;
}

三种类型的section: service、on、import, 
       service对应的state.parseline为parse_line_service,
       on对应的state.parseline为parse_line_action,
       import section中只有一行所以没有对应的state.parseline。	
       service的结构体为
struct service {
        /* list of all services */
    struct listnode slist;

    const char *name;
    const char *classname;

    unsigned flags;
    pid_t pid;
    time_t time_started;    /* time of last start */
    time_t time_crashed;    /* first crash within inspection window */
    int nr_crashed;         /* number of times crashed within window */
    
    uid_t uid;
    gid_t gid;
    gid_t supp_gids[NR_SVC_SUPP_GIDS];
    size_t nr_supp_gids;

    char *seclabel;

    struct socketinfo *sockets;
    struct svcenvinfo *envvars;

    struct action onrestart;  /* Actions to execute on restart. */
    
    /* keycodes for triggering this service via /dev/keychord */
    int *keycodes;
    int nkeycodes;
    int keychord_id;

    int ioprio_class;
    int ioprio_pri;

    int nargs;
    /* "MUST BE AT THE END OF THE STRUCT" */
    char *args[1];
}; /*     ^-------'args' MUST be at the end of this struct! */
 所有从init.rc中解析出来的service都用slist链接到service_list中。service结构体中其他变量代表了service的option选项
     action的结构体、command结构体
struct command
{
        /* list of commands in an action */
    struct listnode clist;

    int (*func)(int nargs, char **args);

    int line;
    const char *filename;

    int nargs;
    char *args[1];
};

struct action {
        /* node in list of all actions */
    struct listnode alist;
        /* node in the queue of pending actions */
    struct listnode qlist;
        /* node in list of actions for a trigger */
    struct listnode tlist;

    unsigned hash;
    const char *name;

    struct listnode commands;
    struct command *current;
};

    commands用来链接很多command结构体,也就是说,一个action中,含有很多个command。
    alist,用来链接action_list,action_list包含了所有从init.rc中解析出来的action结构体。
    qlist用来链接action_queue,后面会讲到
    command结构体中func指定的函数决定了这个command具体做什么动作,nargs,args是两个参数,传给func
    至此,init.rc的解析完成了,其实就是把init.rc中的信息放置到对应的结构体链表中,供后续使用。一个action_list包含了所有的从init.rc中解析出来的action,每一个action包含了多个command,每个command有自己的func函数和参数。一个service_list包含了所有从init.rc解析出来的service,每个service包含了自己的很多选项。
    
static list_declare(service_list);
static list_declare(action_list);
static list_declare(action_queue);

    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(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
    queue_builtin_action(keychord_init_action, "keychord_init");
    queue_builtin_action(console_init_action, "console_init");

    /* execute all the boot actions to get us started */
    action_for_each_trigger("init", action_add_queue_tail);

    /* Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random
     * wasn't ready immediately after wait_for_coldboot_done
     */
    queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
    queue_builtin_action(property_service_init_action, "property_service_init");
    queue_builtin_action(signal_init_action, "signal_init");

    /* Don't mount filesystems or start core system services if in charger mode. */
    if (is_charger) {
        action_for_each_trigger("charger", action_add_queue_tail);
    } else {
        action_for_each_trigger("late-init", action_add_queue_tail);
    }

    /* run all property triggers based on current state of the properties */
    queue_builtin_action(queue_property_triggers_action, "queue_property_triggers");


#if BOOTCHART
    queue_builtin_action(bootchart_init_action, "bootchart_init");
#endif

action_for_each_trigger是根据trigger字符串的名字,把对应的action放到action_queue队列中去
    queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done"); 把自定义的action(这些action没有在init.rc中被定义),放入action_queue中去
    接下来是一个死循环,init进程就循环在这里
for(;;) {
        int nr, i, timeout = -1;

        execute_one_command();
        restart_processes();

        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;
        }
        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;
        }
        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;
        }

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

        if (!action_queue_empty() || cur_action)
            timeout = 0;

#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

        nr = poll(ufds, fd_count, timeout);
        if (nr <= 0)
            continue;

        for (i = 0; i < fd_count; i++) {
            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;
}

execute_one_command();是执行action_queue队列中的每一个action中包含的command。action_queue是所有需要执行的action的集合,每次循环到函数execute_one_command时,就从action_queue中取出一个需要执行的command去执行。直到所有的action中的command都被执行完。当执行到class_start这个command时,会从service_list中取出service来执行。注意:如果service的option选项为disable,则不执行
restart_processes();是重启service_list队列中的所有标记为SVC_RESTARTING的service

    在for循环中,注册了三个事件
    get_property_set_fd (系统属性相关的事件)
    get_signal_fd (信号相关事件)
    get_keychord_fd (组合键相关事件)
    nr = poll(ufds, fd_count, timeout);用来轮询这三个事件,如果没有发生事件,则nr<=0,当发生事件时,nr>0,代码会跳转到下面的几个处理函数中去
    handle_property_set_fd
    handle_keychord
    handle_signal
    至此,init进程就分析完了,init进程作为一个死循环一直运行。其中还有一些小的知识点,例如轮询消息事件中的架构是如何实现的?init.rc中的具体内容的分析等,后面会慢慢介绍。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值