Android FrameWork - 开机启动 Init 进程

相关文章链接:

1. Android FrameWork - 学习启动篇
2. Android FrameWork - 开机启动 Init 进程
3. Android FrameWork - 开机启动 Zygote 进程

相关源码文件:

/system/core/init/Init.cpp
/system/core/rootdir/init.rc
/system/core/init/init_parser.cpp
/system/core/init/builtins.cpp
/system/core/init/signal_handler.cpp

操作系统本身也是一个程序,只是这个程序是用来管理我们 App 应用程序的。不知大家是否了解计算机的启动过程?当然不了解也没关系这里并不影响。如果大家想要了解操作系统的基本原理,可以去听听国内外的一些公开课,推荐清华大学的《计算机操作系统》公开课。Android 系统虽然也是基于 Linux 系统的,但是由于 Android 属于移动设备,并没有像 PC 那样的 BIOS 程序。 取而代之的是 BootLoader (系统启动加载器)。 它类似于 BIOS,在系统加载前,用以初始化硬件设备,建立内存空间的映像图,为最终调用系统内核准备好环境。 在 Android 里没有硬盘,而是 ROM,它类似于硬盘存放操作系统,用户程序等。 ROM 跟硬盘一样也会划分为不同的区域,用于放置不同的程序。当 Linux 内核启动后会初始化各种软硬件环境,加载驱动程序,挂载根文件系统,Linux 内核加载的准备完毕后就开始加载一些特定的程序(进程)了。第一个加载的就是 init 进程,我们就从这里开始分析,至于之前的过程大家感兴趣可以自行研究下,作为一个 App 应用开发者不了解也没关系。

Init 进程

我们应该都知道不管是 Java 还是 C/C++ 去运行某一个程序(进程)都是 XXX.xxx 的 main 方法作为入口,相信有很多大佬都跟我一样,App 开发做久了渐渐就忘记了还有个 main 方法。因此我们找到 /system/core/init/Init.cpp 的 main() 方法:

int main(int argc, char** argv) {
    ...
    // 初始化 signal handler
    signal_handler_init();
    // 解析 init.rc 脚本文件
    init_parse_config_file("/init.rc"); 
    // 将解析脚本中对应的操作添加到 action_queue 队列中
    action_for_each_trigger("early-init", action_add_queue_tail);
    // Queue an action that waits for coldboot done so we know ueventd has set up all of /dev...
    queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done");
    // ... so that we can start queuing up actions that require stuff from /dev.
    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");
    
    ...
    // 这里是个死循环
    while (true) {
        if (!waiting_for_exec) {
            // 执行命令
            execute_one_command();
            restart_processes();
        }
        ...
    }
    return 0;
}

main 方法里面有 148 行代码(不包括子函数代码)具体分为四个步骤:1.创建目录,挂载分区,2.解析启动脚本,3.启动解析的服务,4.守护解析的服务。init.rc 文件是 Android 系统的重要配置文件,位于 /system/core/rootdir/init.rc

// 导入其它的一些脚本
import /init.environ.rc
import /init.usb.rc
// 当前硬件版本的脚本
import /init.${ro.hardware}.rc
import /init.${ro.zygote}.rc
import /init.trace.rc

on early-init
...
on init
...
// 服务 服务名称 执行文件路径 执行参数
// 有几个重要的服务
service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server
service servicemanager /system/bin/servicemanager
service surfaceflinger /system/bin/surfaceflinger
service media /system/bin/mediaserver
service installd /system/bin/installd

上面主要分为有 import 导入、on 命令和 service 服务,最主要的几个服务有 zygote、servicemanager、surfaceflinger 、media 、installd (后面还会分析到),接着往下看是如何解析的:

int init_parse_config_file(const char* path) {
    INFO("Parsing %s...\n", path);
    Timer t;
    std::string data;
    if (!read_file(path, &data)) {
        return -1;
    }

    data.push_back('\n'); // TODO: fix parse_config.
    // 解析
    parse_config(path, data);
    dump_parser_state();

    NOTICE("(Parsing %s took %.2fs.)\n", path, t.duration());
    return 0;
}

static void parse_config(const char *fn, const std::string& data)
{
    struct listnode import_list;
    struct listnode *node;
    
    for (;;) {
        // next_token 内部实现是一个字符一个字符去解析
        // 0:T_EOF 文件结尾
        // \n: T_NEWLINE
        switch (next_token(&state)) {
        case T_EOF:
            // 文件结尾,跳转到 parser_done
            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);
                    // 解析三种 Section:parse_service、parse_action、parse_import
                    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;
        }
    }

parser_done:
    // 循环解析 import 
    list_for_each(node, &import_list) {
         struct import *import = node_to_item(node, struct import, list);
         int ret;

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

接下来看下是如何执行的,这里我们主要了解服务是如何启动的:

void execute_one_command() {
    Timer t;

    char cmd_str[256] = "";
    char name_str[256] = "";

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

        build_triggers_string(name_str, sizeof(name_str), cur_action);

        INFO("processing action %p (%s)\n", cur_action, name_str);
        cur_command = get_first_command(cur_action);
    } else {
        cur_command = get_next_command(cur_action, cur_command);
    }

    if (!cur_command) {
        return;
    }
    // 服务的 func 跳转到 service_for_each_class
    int result = cur_command->func(cur_command->nargs, cur_command->args);
    ...
}

int do_class_start(int nargs, char **args)
{
   /* Starting a class does not start services
   * which are explicitly disabled.  They must
   * be started individually.
   */
    service_for_each_class(args[1], service_start_if_not_disabled);
    return 0;
}

void service_for_each_class(const char *classname,void (*func)(struct service *svc))
{
    struct listnode *node;
    struct service *svc;
    list_for_each(node, &service_list) {
        svc = node_to_item(node, struct service, slist);
        // 如果名字是一致的执行 func ,这是一个回调函数也就是 service_start_if_not_disabled
        if (!strcmp(svc->classname, classname)) {
            func(svc);
        }
    }
}

static void service_start_if_not_disabled(struct service *svc)
{
    if (!(svc->flags & SVC_DISABLED)) {
        service_start(svc, NULL);
    } else {
        svc->flags |= SVC_DISABLED_START;
    }
}

void service_start(struct service *svc, const char *dynamic_args)
{
    ...
    // fork 创建了一个子进程
    pid_t pid = fork();
    if (pid == 0) {
        // 代表子进程,子进程会继承父进程的所有资源,可简单理解为读时共享写事复制
        ...
        // exc 函数族,这些都是 linux 基础知识
        execve(svc->args[0], (char**) arg_ptrs, (char**) ENV);
    }
}

最后再来看下 init 进程是如何守护子进程的:

void signal_handler_init() {
    // Create a signalling mechanism for SIGCHLD.
    int s[2];
    // 创建 socket pair 用于通信
    if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, s) == -1) {
        ERROR("socketpair failed: %s\n", strerror(errno));
        exit(1);
    }
    // 当捕获信号SIGCHLD,则写入signal_write_fd
    signal_write_fd = s[0];
    signal_read_fd = s[1];

    // Write to signal_write_fd if we catch SIGCHLD.
    struct sigaction act;
    memset(&act, 0, sizeof(act));
    act.sa_handler = SIGCHLD_handler;
    act.sa_flags = SA_NOCLDSTOP;
    // SA_NOCLDSTOP 使 init 进程只有在其子进程终止时才会受到 SIGCHLD 信号
    sigaction(SIGCHLD, &act, 0);
    // 进入 waitpid 来处理子进程是否退出的情况
    reap_any_outstanding_children();
    // 调用 epoll_ctl 方法来注册 epoll 的回调函数
    register_epoll_handler(signal_read_fd, handle_signal);
}

//读取数据
static void handle_signal() {
    // Clear outstanding requests.
    char buf[32];
    // 读取 signal_read_fd 中的数据,并放入 buf,这里读出并没有什么实际作用,只是用于阻塞等待
    read(signal_read_fd, buf, sizeof(buf));
    reap_any_outstanding_children();
}

//写入数据
static void SIGCHLD_handler(int) {
    // 向signal_write_fd写入1 
    if (TEMP_FAILURE_RETRY(write(signal_write_fd, "1", 1)) == -1) {
        ERROR("write(signal_write_fd) failed: %s\n", strerror(errno));
    }
}

static void reap_any_outstanding_children() {
    while (wait_for_one_process()) { }
}

static bool wait_for_one_process() {
    int status;
    //等待任意子进程,如果子进程没有退出则返回 0,否则则返回该子进程 pid。
    pid_t pid = TEMP_FAILURE_RETRY(waitpid(-1, &status, WNOHANG));
    if (pid == 0) {
        return false;
    } else if (pid == -1) {
        return false;
    }
    //根据 pid 查找到相应的 service
    service* svc = service_find_by_pid(pid);

    //当 flags 为 RESTART,且不是 ONESHOT 时, kill 进程组内所有的子进程或子线程
    if (!(svc->flags & SVC_ONESHOT) || (svc->flags & SVC_RESTART)) {
        kill(-pid, SIGKILL);
    }

    //移除当前服务 svc 中的所有创建过的 socket
    for (socketinfo* si = svc->sockets; si; si = si->next) {
        char tmp[128];
        snprintf(tmp, sizeof(tmp), ANDROID_SOCKET_DIR"/%s", si->name);
        unlink(tmp);
    }

    //当 flags 为 EXEC 时,释放相应的服务
    if (svc->flags & SVC_EXEC) {
        waiting_for_exec = false;
        list_remove(&svc->slist);
        free(svc->name);
        free(svc);
        return true;
    }
    ...

    // 对于 ONESHOT 服务,使其进入 disabled 状态
    if ((svc->flags & SVC_ONESHOT) && !(svc->flags & SVC_RESTART)) {
        svc->flags |= SVC_DISABLED;
    }
    // 禁用和重置的服务,都不再自动重启
    if (svc->flags & (SVC_DISABLED | SVC_RESET))  {
        svc->NotifyStateChange("stopped"); //设置相应的service状态为stopped
        return true;
    }

    // 执行当前 service 中所有 onrestart命令,这个就是重启了
    struct listnode* node;
    list_for_each(node, &svc->onrestart.commands) {
        command* cmd = node_to_item(node, struct command, clist);
        cmd->func(cmd->nargs, cmd->args);
    }
    // 设置相应的 service 状态为 restarting
    svc->NotifyStateChange("restarting");
    return true;
}

至此 init 进程已全部分析完毕,有四个步骤:1. 创建目录,挂载分区,2. 解析启动脚本,3. 启动解析的服务,4. 守护解析的服务。最需要注意的是 init 创建了 zygote(创建 App 应用的服务)、servicemanager (client 与 service 通信管理的服务)、surfaceflinger(显示渲染服务) 和 media(多媒体服务) 等 service 进程。

视频地址:https://pan.baidu.com/s/1j_wgzITcgABVbThvO0VBPA
视频密码:jj4b

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容