Android启动流程分析(1)-init进程

启动过程

老是有在群里看到大佬们讨论Android系统的第一个始祖进程是init进程,对于有过多年开发经验的我,应用程序开发跟init进程是扯不上关系的,为了跟上大佬的脚步,不得不花时间看了一些列Android启动流程的文章,认认真真的跟着源码走了一遍,虽然对于实际开发应用没有什么提升,感觉也没啥鸟用,但走一遍确实对于整个Android的分层结构有了更深刻的认识,可以跟上群里大佬吹逼的脚步了,接下来就把我梳理的流程记录一下

从按开机键上电开始,Android在上电后会通过汇编指令去加载uboot引导程序,然后由uboot从分区中加载内核镜像,并启动内核,Android的内核使用的是Linux内核,内核这部分对于应用开发涉及不多,但是需要知道的是,Linux内核启动主要涉及到3个特殊的进程

  • idle进程(pid=0)->Linux系统第一个进程,是init进程和kthread进程的父进程

  • init进程(pid=1)-> 正式进入用户态的第一个用户进程,也是Android系统应用程序的第一个进程

  • kthreadd进程(pid=2)->这个还是Linux的内核态进程,系统内核的管家

这三个进程是内核的基础,init进程是Android用户空间的始祖进程,让代码从kernel层运行到native层的代码,然后再通过fork去孵化Java进程的始祖进程zygote,后续的app进程的启动都是由zygote进行fork孵化出来
启动流程.png

结合Android的系统分层架构,我们知道架构层级关系是kernel->native->framwork->apps,所以启动流程需要去分析其中关键的init进程和zygote进程,通过adb shell ps从当前手机的进程的关系可以很明显看出上面的父子关系
pspid.png

Init(pid=1)用户空间的始祖进程,孵化出了zygote进程(zygote进程有兼容的64位和32位,所以当前手机是2个zygote进程),然后所有的用户的apps(类似微信,系统相册,照相机等)都是由zygote进程孵化产生的。

init进程

接下来分析init进程的启动流程

内核代码初始化会执行到kernel/common/init/main.c ->kernel_init()

static int __ref kernel_init(void *unused){
    ...
    if (!try_to_run_init_process("/sbin/init") ||
        !try_to_run_init_process("/etc/init") ||
        !try_to_run_init_process("/bin/init") ||   //后续流程传入参数是"/bin/init"
        !try_to_run_init_process("/bin/sh"))
        return 0;
}

static int try_to_run_init_process(const char *init_filename)
{
    int ret;
    ret = run_init_process(init_filename);  //执行run_init_process方法
    return ret;
}

static int run_init_process(const char *init_filename)
{
  //执行kernel_execve指令,这个指令就是内核空间去执行用户空间的程序
    return kernel_execve(init_filename, argv_init, envp_init); 
}
Android.bp

/system/core/init

执行用户空间的init,会执行init文件夹下面的Android.bp脚本文件,里面可以可以看到init的执行入口函数在main.cpp代码中

cc_binary {
    name: "init_second_stage",
    recovery_available: true,
    stem: "init",
    defaults: ["init_defaults"],
    static_libs: ["libinit"],
    required: [
        "e2fsdroid",
        "init.rc",
        "mke2fs",
        "sload_f2fs",
        "make_f2fs",
        "ueventd.rc",
    ],
    srcs: ["main.cpp"],
    symlinks: ["ueventd"],
    target: {
        recovery: {
            cflags: ["-DRECOVERY"],
            exclude_shared_libs: [
                "libbinder",
                "libutils",
            ],
        },
    },
}
main.cpp

init的代码位于/system/core/init 下面,找到main函数的执行逻辑

//C++主函数有两个参数,argc表示参数的个数,第二个是参数的列表,具体的参数
int main(int argc, char** argv) { 
   //strcmp是是String的函数,比较字符串
   //basename是C的一个函数,得到特定路径中最后一个/后的内容
    if (!strcmp(basename(argv[0]), "ueventd")) {
        return ueventd_main(argc, argv);
    }

    if (argc > 1) {
        if (!strcmp(argv[1], "subcontext")) {
            android::base::InitLogging(argv, &android::base::KernelLogger);
            const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap();

            return SubcontextMain(argc, argv, &function_map);
        }

        if (!strcmp(argv[1], "selinux_setup")) {
            return SetupSelinux(argv);
        }

        if (!strcmp(argv[1], "second_stage")) {
            return SecondStageMain(argc, argv);
        }
    }

    return FirstStageMain(argc, argv);
}

从这段代码看,主要执行的流程就是FirstStageMain/SetupSelinux/SecondStageMain/SubcontextMain

- FirstStageMain

第一次执行main函数的时候,没有携带任何参数,所以会最先执行FirstStageMain方法

int FirstStageMain(int argc, char** argv) {
   
    //挂载 创建 文件
    CHECKCALL(mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755"));
    CHECKCALL(mkdir("/dev/pts", 0755));
    CHECKCALL(mkdir("/dev/socket", 0755));
    ...
   
    //重定向 输入输出
    SetStdioToDevNull(argv);
  
    //初始化内核的日志打印
    InitKernelLogging(argv);

    //启动 selinux_setup
    const char* path = "/system/bin/init";
    const char* args[] = {path, "selinux_setup", nullptr};
    auto fd = open("/dev/kmsg", O_WRONLY | O_CLOEXEC);
    dup2(fd, STDOUT_FILENO);
    dup2(fd, STDERR_FILENO);
    close(fd);
    execv(path, const_cast<char**>(args));

    return 1;
}
- SetupSelinux

接下来再次执行main.cpp中的main函数,传入了selinux_setup参数

int SetupSelinux(char** argv) {
    //重复第一步里面的重定向输入输出和日志打印
    SetStdioToDevNull(argv);
    InitKernelLogging(argv);

    ......
    const char* path = "/system/bin/init";
    const char* args[] = {path, "second_stage", nullptr};
    execv(path, const_cast<char**>(args));

    return 1;
}

这部分的作用主要是Linux的安全策略,管理Android系统的权限,保证系统内核代码运行的稳定

- SecondStageMain

接下来再次执行main.cpp中的main函数,传入了second_stage参数

//$init.cpp
int SecondStageMain(int argc, char** argv) {
  //再一次出现了这两个
    SetStdioToDevNull(argv);
    InitKernelLogging(argv);
  
  ......
    //创建一块共享的内存空间,初始化属性域
    PropertyInit();

  ......
    Epoll epoll;
    //处理子进程终止信号,杀死僵尸进程
    //初始化子进程退出信号处理函数,并调用epoll_ctl设置 property fd可读的回调函数
    InstallSignalFdHandler(&epoll);
    InstallInitNotifier(&epoll);
    //启动属性服务,调用epoll_ctl设置 property fd 可读的回调函数
    StartPropertyService(&property_fd);

  ......
    //匹配命令和函数之间的关系
    const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap();
    Action::set_function_map(&function_map);

  ......
    ActionManager& am = ActionManager::GetInstance();
    ServiceList& sm = ServiceList::GetInstance();

    //解析init.rc
    LoadBootScripts(am, sm);
  ......
    //类似Looper循环,死循环进入等待
    while (true) {
        auto pending_functions = epoll.Wait(epoll_timeout);
    }

    return 0;
}

//解析init.rc文件
static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) {
    //创建解析器
    Parser parser = CreateParser(action_manager, service_list);
 
    ......
    parser.ParseConfig("/system/etc/init/hw/init.rc");
}

//.rc文件的语法里面就有service on import等关键字,创造一个解析器
Parser CreateParser(ActionManager& action_manager, ServiceList& service_list) {
    Parser parser;

    parser.AddSectionParser("service", std::make_unique<ServiceParser>(
                                               &service_list, GetSubcontext(), std::nullopt));
    parser.AddSectionParser("on", std::make_unique<ActionParser>(&action_manager, GetSubcontext()));
    parser.AddSectionParser("import", std::make_unique<ImportParser>(&parser));

    return parser;
}

//parse.cpp
bool Parser::ParseConfig(const std::string& path) {
    if (is_dir(path.c_str())) {
        return ParseConfigDir(path);
    }
    return ParseConfigFile(path);
}

bool Parser::ParseConfigDir(const std::string& path) {
    for (const auto& file : files) {
        if (!ParseConfigFile(file)) {
        }
    }
    return true;
}

bool Parser::ParseConfigFile(const std::string& path) {
    ParseData(path, &config_contents.value());
    return true;
}

//解析init文件
void Parser::ParseData(const std::string& filename, std::string* data) {
    for (;;) {
        switch (next_token(&state)) {
            case T_EOF:
                return;
            case T_NEWLINE: {
                break;
            }
            case T_TEXT:
                break;
        }
    }
}

//init.rc
import /system/etc/init/hw/init.${ro.zygote}.rc //${ro.zygote}由厂商定义,与平台相关

on late-init
    # Now we can start zygote for devices with file based encryption
    trigger zygote-start

on zygote-start && property:ro.crypto.state=unencrypted
    # A/B update verifier that marks a successful boot.
    exec_start update_verifier_nonencrypted
    start statsd  
    start netd      //start 对应的映射关系定义于 /system/core/init/builtins.cpp中
    start zygote    //调用 start 对应的处理函数,启动名为 zygote 的服务 (传入init.zygote.rc中定义的参数)
    start zygote_secondary
小结

init进程会去解析android.bp文件,从会找到执行入口是main.cpp的main()函数,而这一块会根据不同的入参分步进行初始化过程

  • 第一步会创建挂载文件,重定向输入输出,初始化内容日志打印等

  • 第二步会启动selinux安全策略

  • 第三步会初始化一些属性域,处理僵尸进程,匹配native函数和内核命令的关系,解析init.rc文件,也会有一个类似Looper的epoll循环机制

解析init.rc文件里面会有start zygote命令,然后就会去执行zygote进程的启动流程,这部分在下一篇进行整理

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

推荐阅读更多精彩内容