天天看点

android启动过程分析--启动init进程

每当我们学习一门新的语言时,我们总是以HelloWorld来开始我们的学习之旅,每当我们分析一个应用程序时,我们也总会找到main函数入口,学习一个系统,最好的办法也是先把它的启动流程弄清楚,这样,后面分析起来才能更好的把握。

android系统比较复杂,它的启动涉及的东西也比较多,但是只要我们把一点点都弄明白 ,最后整个流程起来再看的话也就会明了很多,我们先看下init启动过程中主要做的事。

Android从Linux系统启动有4个步骤:

(1)、init进程启动

(2)、Native服务启动

(3)、System Server、Android服务启动

(4)、Home启动

总体启动框架图如:

android启动过程分析--启动init进程

首先我们看下init进程的启动过程中都做了哪些事情:

android启动过程分析--启动init进程

代码路径:system/init

下面我们一步步看它都 做了哪些事情:

1、准备系统启动所需要的最小文件目录:

创建必要的文件夹,挂载所需文件系统,重定位标准输入输出错误到/dev/__null__,以下是相关代码:

int main(int argc, char **argv)
{
    int fd_count = 0;
    struct pollfd ufds[4];
    char *tmpdev;
    char* debuggable;
    char tmp[32];
    int property_set_fd_init = 0;
    int signal_fd_init = 0;
    int keychord_fd_init = 0;

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

    /* 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", 0, "mode=0755");
    mkdir("/dev/pts", 0755);
    mkdir("/dev/socket", 0755);
    mount("devpts", "/dev/pts", "devpts", 0, NULL);
    mount("proc", "/proc", "proc", 0, NULL);
    mount("sysfs", "/sys", "sysfs", 0, NULL);

        /* We must have some place other than / to create the
         * device nodes for kmsg and null, otherwise we won't
         * be able to remount / read-only later on.
         * Now that tmpfs is mounted on /dev, we can actually
         * talk to the outside world.
         */
    open_devnull_stdio();//需要在后面的程序中看到打印的话需要屏蔽这个函数

           

我们再看下open_devnull_stdio 函数

void open_devnull_stdio(void)
{
    int fd;
    static const char *name = "/dev/__null__";
    if (mknod(name, S_IFCHR | 0600, (1 << 8) | 3) == 0) {
        fd = open(name, O_RDWR);
        unlink(name);
        if (fd >= 0) {
            dup2(fd, 0);
            dup2(fd, 1);
            dup2(fd, 2);
            if (fd > 2) {
                close(fd);
            }
            return;
        }
    }

    exit(1);
}
           

这里调用dup函数把标准输入,输出,错误输出都重定位到/dev/__null__

2、初始化log系统

调用log_init对log系统进行初始化,我们看下这个函数:

void log_init(void)
{
    static const char *name = "/dev/__kmsg__";
    if (mknod(name, S_IFCHR | 0600, (1 << 8) | 11) == 0) {
        log_fd = open(name, O_WRONLY);
        fcntl(log_fd, F_SETFD, FD_CLOEXEC);
        unlink(name);
    }
}
           

这里创建并打开了一个设备节点,用于后面的log信息处理,如写日志等就往这个节点里面写.

.3、解析init.rc

这里调用了parse_config去解析init.rc。

static void parse_config(const char *fn, char *s)
{
    struct parse_state state;
    char *args[INIT_PARSER_MAXARGS];
    int nargs;

    nargs = 0;
    state.filename = fn;
    state.line = 1;
    state.ptr = s;
    state.nexttoken = 0;
    state.parse_line = parse_line_no_op;
    for (;;) {
        switch (next_token(&state)) {
        case T_EOF:
            state.parse_line(&state, 0, 0);
            return;
        case T_NEWLINE:
            if (nargs) {
                int kw = lookup_keyword(args[0]);
                if (kw_is(kw, SECTION)) {
                    state.parse_line(&state, 0, 0);
                    parse_new_section(&state, kw, nargs, args);
                } else {
                    state.parse_line(&state, nargs, args);
                }
                nargs = 0;
            }
            break;
        case T_TEXT:
            if (nargs < INIT_PARSER_MAXARGS) {
                args[nargs++] = state.text;
            }
            break;
        }
    }
}
           

调用next_token对下一个字符进行解析,这里分成了三类,文件结束,换行,还有就是字符 ,它们的处理也不一样。

1、如果是文件结束符,直接调用parse_line进行处理

2、如果是字符,则保存到args变量中

3、如果是换行,首先查关键字,如果是SECTION,说明是一个新的段的开始,先调用parse_line处理(因为第二个参数为0,其实什么也没有做),然后调用parse_new_section对这新的一个段的开始一行进行解析;否则直接调用parse_line解析。

再看一下parse_new_section

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;
    }
    state->parse_line = parse_line_no_op;
}
           

这里也是对keyword分两种情况进行了处理,一个是service,另一个是on

1、如果是service,则调用parse_service进行处理,并将state的parse_line设置为parse_line_service;

2、如果是On,则调用parse_action进行解析,并将state的parse_line设置为parse_line_action;

再来看一下parse_service

static void *parse_service(struct parse_state *state, int nargs, char **args)
{
    struct service *svc;
    if (nargs < 3) {
        parse_error(state, "services must have a name and a program\n");
        return 0;
    }
    if (!valid_name(args[1])) {
        parse_error(state, "invalid service name '%s'\n", args[1]);
        return 0;
    }

    svc = service_find_by_name(args[1]);
    if (svc) {
        parse_error(state, "ignored duplicate definition of service '%s'\n", args[1]);
        return 0;
    }

    nargs -= 2;
    svc = calloc(1, sizeof(*svc) + sizeof(char*) * nargs);
    if (!svc) {
        parse_error(state, "out of memory\n");
        return 0;
    }
    svc->name = args[1];
    svc->classname = "default";
    memcpy(svc->args, args + 2, sizeof(char*) * nargs);
    svc->args[nargs] = 0;
    svc->nargs = nargs;
    svc->onrestart.name = "onrestart";
    list_init(&svc->onrestart.commands);
    list_add_tail(&service_list, &svc->slist);
    return svc;
}
           

这里首先看这个service是否已经存在,存在说明重复了,直接返回错误,

不存在的话根据这个service的参数等分配空间,并利用传进来的参数进行初始化,最后把它加到service_list列表。

再看看parse_action

static void *parse_action(struct parse_state *state, int nargs, char **args)
{
    struct action *act;
    if (nargs < 2) {
        parse_error(state, "actions must have a trigger\n");
        return 0;
    }
    if (nargs > 2) {
        parse_error(state, "actions may not have extra parameters\n");
        return 0;
    }
    act = calloc(1, sizeof(*act));
    act->name = args[1];
    list_init(&act->commands);
    list_add_tail(&action_list, &act->alist);
        /* XXX add to hash */
    return act;
}
           

这里主要把命令加入到action_list中。

4、添加一些其它的命令到action_queue和action_queue

主要调用queue_builtin_action把action_list中的一些命令添加到action_queue,

调用queue_builtin_action一些命令到action_list

5、进入循环处理

1)、调用execute_one_command执行一个命令

void execute_one_command(void)
{
    int ret;

    if (!cur_action || !cur_command || is_last_command(cur_action, cur_command)) {
        cur_action = action_remove_queue_head();
        cur_command = NULL;
        if (!cur_action)
            return;
        INFO("processing action %p (%s)\n", cur_action, cur_action->name);
        cur_command = get_first_command(cur_action);
    } else {
        cur_command = get_next_command(cur_action, cur_command);
    }

    if (!cur_command)
        return;

    ret = cur_command->func(cur_command->nargs, cur_command->args);
    INFO("command '%s' r=%d\n", cur_command->args[0], ret);
}
           

这里主要是从action_queue的队列头弹出一个命令并调用它的func函数执行,这里的func函数 一般是do_***,在keywords.h中定义的。

2)、restart_processes

查看是否有服务需要重启,如果有重启它。

3)、把基于property、signal、keychord的句柄加入poll检测,调用poll等待事件到来。

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

分别调用相应的函数进行处理,这里稍微分析下这三个事件。

1、handle_property_set_fd

这个函数主要用来处理属性事件,包括属性设置和启动一些service,部分代码

if(memcmp(msg.name,"ctl.",4) == 0) {
            if (check_control_perms(msg.value, cr.uid, cr.gid)) {
                handle_control_message((char*) msg.name + 4, (char*) msg.value);
            } else {
                ERROR("sys_prop: Unable to %s service ctl [%s] uid: %d pid:%d\n",
                        msg.name + 4, msg.value, cr.uid, cr.pid);
            }
        } else {
            if (check_perms(msg.name, cr.uid, cr.gid)) {
                property_set((char*) msg.name, (char*) msg.value);
            } else {
                ERROR("sys_prop: permission denied uid:%d  name:%s\n",
                      cr.uid, msg.name);
            }
        }
           

如果是ctl开头的,说明是控制信息,启动或停止某个service

我们看在开机界面的启动,这个也是一个services,在init.rc中加进去了,但是没有启动

service bootanim /system/bin/bootanimation
    user graphics
    group graphics
    disabled
    oneshot
           

然后在status_t SurfaceFlinger::readyToRun()中有如下代码:

 property_set("ctl.start", "bootanim");

bootanim 就是上面 /system/bin/bootanimation 这个service的名字,

我们看到property_set最终调用了send_prop_msg,看下这个函数:

static int send_prop_msg(prop_msg *msg)
{
    int s;
    int r;
    
    s = socket_local_client(PROP_SERVICE_NAME, 
                            ANDROID_SOCKET_NAMESPACE_RESERVED,
                            SOCK_STREAM);
    if(s < 0) return -1;
    
    while((r = send(s, msg, sizeof(prop_msg), 0)) < 0) {
        if((errno == EINTR) || (errno == EAGAIN)) continue;
        break;
    }

    if(r == sizeof(prop_msg)) {
        r = 0;
    } else {
        r = -1;
    }

    close(s);
    return r;
}
           

这里把消息发给服务端,那么服务端是什么时候监听的呢,我们看下init.rc中有如下代码。

在mina函数 中有

queue_builtin_action(property_service_init_action, "property_service_init");
           

这里面调用了start_property_service();我们看下它;

void start_property_service(void)
{
    int fd;

    load_properties_from_file(PROP_PATH_SYSTEM_BUILD);
    load_properties_from_file(PROP_PATH_SYSTEM_DEFAULT);
    load_properties_from_file(PROP_PATH_LOCAL_OVERRIDE);
    /* Read persistent properties after all default values have been loaded. */
    load_persistent_properties();

    fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM, 0666, 0, 0);
    if(fd < 0) return;
    fcntl(fd, F_SETFD, FD_CLOEXEC);
    fcntl(fd, F_SETFL, O_NONBLOCK);

    listen(fd, 8);
    property_set_fd = fd;
}
           
property
           

对的,就是在这里监听的。这样有属性设置或启动相关service时就可以通过socket通信来 实现了。

2、handle_keychord处理调试模式下的组合按键

3、handle_signal

void handle_signal(void)
{
    char tmp[32];

    /* we got a SIGCHLD - reap and restart as needed */
    read(signal_recv_fd, tmp, sizeof(tmp));
    while (!wait_for_one_process(0))
        ;
}
           

handle_signal阻塞在read上,但当有子进程退出时,sigchld_handler会write(signal_fd, &s, 1);这里signal_fd与signal_recv_fd是本了已经建立好的通信套接字,导致上面的read返回,进入 wait_for_one_process

static int wait_for_one_process(int block)
{
    pid_t pid;
    int status;
    struct service *svc;
    struct socketinfo *si;
    time_t now;
    struct listnode *node;
    struct command *cmd;

    while ( (pid = waitpid(-1, &status, block ? 0 : WNOHANG)) == -1 && errno == EINTR );
    if (pid <= 0) return -1;
    INFO("waitpid returned pid %d, status = %08x\n", pid, status);

    svc = service_find_by_pid(pid);
    if (!svc) {
        ERROR("untracked pid %d exited\n", pid);
        return 0;
    }

    NOTICE("process '%s', pid %d exited\n", svc->name, pid);

    if (!(svc->flags & SVC_ONESHOT)) {
        kill(-pid, SIGKILL);
        NOTICE("process '%s' killing any children in process group\n", svc->name);
    }

    /* remove any sockets we may have created */
    for (si = svc->sockets; si; si = si->next) {
        char tmp[128];
        snprintf(tmp, sizeof(tmp), ANDROID_SOCKET_DIR"/%s", si->name);
        unlink(tmp);
    }

    svc->pid = 0;
    svc->flags &= (~SVC_RUNNING);

        /* oneshot processes go into the disabled state on exit */
    if (svc->flags & SVC_ONESHOT) {
        svc->flags |= SVC_DISABLED;
    }

        /* disabled processes do not get restarted automatically */
    if (svc->flags & SVC_DISABLED) {
        notify_service_state(svc->name, "stopped");
        return 0;
    }

    now = gettime();
    if (svc->flags & SVC_CRITICAL) {
        if (svc->time_crashed + CRITICAL_CRASH_WINDOW >= now) {
            if (++svc->nr_crashed > CRITICAL_CRASH_THRESHOLD) {
                ERROR("critical process '%s' exited %d times in %d minutes; "
                      "rebooting into recovery mode\n", svc->name,
                      CRITICAL_CRASH_THRESHOLD, CRITICAL_CRASH_WINDOW / 60);
                sync();
                __reboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2,
                         LINUX_REBOOT_CMD_RESTART2, "recovery");
                return 0;
            }
        } else {
            svc->time_crashed = now;
            svc->nr_crashed = 1;
        }
    }

    svc->flags |= SVC_RESTARTING;

    /* Execute all onrestart commands for this service. */
    list_for_each(node, &svc->onrestart.commands) {
        cmd = node_to_item(node, struct command, clist);
        cmd->func(cmd->nargs, cmd->args);
    }
    notify_service_state(svc->name, "restarting");
    return 0;
}
           

wait_for_one_process首先会根据关闭进程的pid查找service,进行一些相关处理,关闭一些相关资源,如果是SVC_CRITICAL类型的service,则判断重启次数和时间,超过一定限制的话进入recovery模式。

然后把service标记为SVC_RESTARTING,这样在restart_processes里面会将它重启,再执行这个service相关命令。

到这里init的任务就基本完成了。