Android init启动分析(1)init启动

free-jdx 2021-04-15 14:56:16 4607
1. 前言

总结下android init启动流程;
为平时debug梳理思路

2.Init代码结构

Init 源代码目录:system/core/init/ , 源文件如下:
├── Android.mk
├── bootchart.c
├── bootchart.h
├── builtins.c
├── devices.c
├── devices.h
├── drv_display.h
├── fswatcherd.c
├── fswatcherd.h
├── grab-bootchart.sh
├── init.c
├── init_disp.c
├── init_disp.h
├── init.h
├── init_parser.c
├── init_parser.h
├── keychords.c
├── keychords.h
├── keywords.h
├── log.h
├── parser.c
├── parser.h
├── property_service.c
├── property_service.h
├── signal_handler.c
├── signal_handler.h
├── sunxi_display2.h
├── ueventd.c
├── ueventd.h
├── ueventd_keywords.h
├── ueventd_parser.c
├── ueventd_parser.h
├── util.c
├── util.h
├── watchdogd.c
└── watchdogd.h

3. Init启动前准备

Android Init是Kernel启动阶段结束后启动的第一个用户空间进程。Android镜像会将init可执行文件放置在内存/目录下,Kernel初始化结束阶段会加载并运行/init文件,处理流程如下:
start_kernel()-->reset_init()-->kernel_init()-->kernel_init_freeable():

static noinline void __init kernel_init_freeable(void)
{
... .... .... .... 
    if (!ramdisk_execute_command)
        ramdisk_execute_command = "/init";  //检查ramdisk_execute_command如果为空,强制赋值”/init” 
    if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
        ramdisk_execute_command = NULL;
      /* 检查ramdisk_execute_command字符串内容路径文件是否存在,如果不存在,为NULL */
        prepare_namespace();
    }
} 

Kernel 启动结束阶段start_kernel()-->reset_init()-->kernel_init()函数中会做如下判断:

static int __ref kernel_init(void *unused)
{
    kernel_init_freeable();
    async_synchronize_full();
    free_initmem();
    mark_rodata_ro();
    system_state = SYSTEM_RUNNING;
    numa_default_policy();
    flush_delayed_fput();
    if (ramdisk_execute_command) { 
        if (!run_init_process(ramdisk_execute_command))  // 启动“/init”
            return 0;
        pr_err("Failed to execute %s\n", ramdisk_execute_command);
   }
    if (execute_command) {  //如果ramdisk_execute_command启动失败,则再次尝试从execute_command启动
        if (!run_init_process(execute_command))
            return 0;
}
.... .... .... .... .... .... .... ....
.... .... .... .... .... .... .... ....
}

Bootload退出前传递给Kernel的cmdline中含有字符串”init=/init”, Kernel启动初始化cmdline过程中会将”/init” 赋值到execute_command变量中.

4. Init启动过程

Android init代码很长,入口函数为源文件init.c的main()函数,分阶段对init启动代码处理过程进行分析.
(1) 区分init和ueventd/watchdogd/fswatcherd守护进程

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

Android.mk文件中将/sbin/ueventd 、/sbin/watchdogd 和/sbin/fswatcherd守护进程编译成软链接指向init 二进制文件本身,主要考虑到守护进程与init代码重复度很高,可以直接加载init二进制本身进行运行。
init进程执行init.rc命令过程中会启动守护进程,由于main()入口处有区分,因此不同守护进程分别执行对应的流程。具体分析在后面章节中.

(2) 设置init进程创建文件和目录属性
umask(000);
头文件#include<sys/stat.h> 定义函数mode_t umask(mode_t mask);
文件权限读(4)+写(2)+可执行(1),Umask缺省值为022,建立文件的默认权限是(6-0,6-2,6-2)644
建立目录的默认权限是(7-0,7-2,7-2)755. umask()可以更改umask的值.
当umask更改为000时,创建文件权限为666,创建目录权限为777.

(3) 创建目录和挂载linux文件系统到节点

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

创建目录,并经tmpfs文件系统、proc文件系统和sysfs文件系统到对应节点

(4) 创建/dev/.booting临时文件

close(open("/dev/.booting", O_WRONLY | O_CREAT, 0000));

表明当前init正在启动初始化过程,init启动结束后,会删除该临时文件.

(5)打开/dev/null节点

open_devnull_stdio();

函数定义在util.c文件中,打开/dev/null空设备节点,把标准输入、标准输出和标准错误重定向输入到该节点.守护进程常用的手段.

(6)打开"/dev/kmsg"节点,将init 打印输出到内核打印buf中

klog_init();

Init进程打印宏定义如下:
system/core/init/log.h

#define ERROR(x...)   KLOG_ERROR("init", x)
#define NOTICE(x...)  KLOG_NOTICE("init", x)
#define INFO(x...)    KLOG_INFO("init", x)

system/core/include/cutils/klog.h

#define KLOG_ERROR(tag,x...)   klog_write(3, "<3>" tag ": " x)
#define KLOG_WARNING(tag,x...) klog_write(4, "<4>" tag ": " x)
#define KLOG_NOTICE(tag,x...)  klog_write(5, "<5>" tag ": " x)
#define KLOG_INFO(tag,x...)    klog_write(6, "<6>" tag ": " x)
#define KLOG_DEBUG(tag,x...)   klog_write(7, "<7>" tag ": " x)

最终会调用到klog_write,将进程打印信息重定向到内核kmsg中
system/core/libcutils/klog.c

void klog_write(int level, const char *fmt, ...)
{
    char buf[LOG_BUF_MAX];
... ... 
    va_start(ap, fmt);
    vsnprintf(buf, LOG_BUF_MAX, fmt, ap);
    buf[LOG_BUF_MAX - 1] = 0;
    va_end(ap);
    write(klog_fd, buf, strlen(buf));
}

(7) 创建共享内存区域用于存储Android属性值

property_init();

函数定义在源文件./system/core/init/property_service.c 中,其他关联文件如下:
./bionic/libc/include/sys/_system_properties.h
./bionic/libc/include/sys/system_properties.h
./bionic/libc/bionic/system_properties.cpp
./bionic/libc/bionic/system_properties_compat.c

(8)获取/proc/cpuinfo节点信息

get_hardware_name(hardware, &revision);

函数定义在源文件.system/core/init/util.c

(9) 获取/proc/cmdline节点信息

process_kernel_cmdline

函数主要从cmdline节点获取传参信息后进行解析,并初始化系统property属性如下:

(10) 初始化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();
restorecon("/dev");
restorecon("/dev/socket");
restorecon("/dev/__properties__");
restorecon_recursive("/sys");

./external/sepolicy/:selinux 源文件,selinux的源码分析放在后面章节

(11) 检查当前启动模式是否在充电模式

is_charger = !strcmp(bootmode, "charger");

充电模式是指设置插着充电器开机时设备进入的状态,该模式下系统只会启动kernel和init,打不部分服务不会启动.

(12)读取 /default.prop 文件中配置,设置到系统property空间中

property_load_boot_defaults();

函数主要在/system/core/init/property_service.c文件中定义实现

(13) 解析/init.rc文件,将action和service分别添加到对应的queue中等待执行

init_parse_config_file("/init.rc");
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");
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(init_set_disp_policy, "init_set_disp_policy");
queue_builtin_action(signal_init_action, "signal_init");
if (is_charger) {
    action_for_each_trigger("charger", action_add_queue_tail);
} else {
    action_for_each_trigger("late-init", action_add_queue_tail);
}
queue_builtin_action(queue_property_triggers_action, "queue_property_triggers");
#if BOOTCHART
    queue_builtin_action(bootchart_init_action, "bootchart_init");
#endif

解析函数主要在./system/core/init/init_parser.c文件中定义

(14) Init 通过for循环执行action_queue中命令和启动service_queue中服务

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

(15) Init完成系统初始化后,会退化成守护进程通过poll系统调用监听系统的property属性变化、子进程的kill信号和组合按键事件.

       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 (!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;
}
声明:本文内容由易百纳平台入驻作者撰写,文章观点仅代表作者本人,不代表易百纳立场。如有内容侵权或者其他问题,请联系本站进行删除。
free-jdx
红包 87 8 评论 打赏
评论
0个
内容存在敏感词
手气红包
    易百纳技术社区暂无数据
相关专栏
置顶时间设置
结束时间
删除原因
  • 广告/SPAM
  • 恶意灌水
  • 违规内容
  • 文不对题
  • 重复发帖
打赏作者
易百纳技术社区
free-jdx
您的支持将鼓励我继续创作!
打赏金额:
¥1易百纳技术社区
¥5易百纳技术社区
¥10易百纳技术社区
¥50易百纳技术社区
¥100易百纳技术社区
支付方式:
微信支付
支付宝支付
易百纳技术社区微信支付
易百纳技术社区
打赏成功!

感谢您的打赏,如若您也想被打赏,可前往 发表专栏 哦~

举报反馈

举报类型

  • 内容涉黄/赌/毒
  • 内容侵权/抄袭
  • 政治相关
  • 涉嫌广告
  • 侮辱谩骂
  • 其他

详细说明

审核成功

发布时间设置
发布时间:
是否关联周任务-专栏模块

审核失败

失败原因
备注
拼手气红包 红包规则
祝福语
恭喜发财,大吉大利!
红包金额
红包最小金额不能低于5元
红包数量
红包数量范围10~50个
余额支付
当前余额:
可前往问答、专栏板块获取收益 去获取
取 消 确 定

小包子的红包

恭喜发财,大吉大利

已领取20/40,共1.6元 红包规则

    易百纳技术社区