Android 8.0 系统启动流程之Linux内核启动--kernel_init进程(三)

    在上一篇文章中详细的分析了kthreadd进程的启动,init进程也是有idle进程去触发启动的,init进程分为前后两部分,前一部分是在内核启动的,主要是完成创建和内核初始化工作,内容都是跟Linux内核相关的;后一部分是在用户空间启动的,主要完成Android系统的初始化工作。
    本文着重分析init进程的前一部分,init进程的后一部分将在下一篇文章中讲述。

1、init进程启动

在rest_init函数中启动了init进程,定义在kernel/msm-4.4/init/main.c中

static noinline void __init_refok rest_init(void)
{
    ......
    //用kernel_thread方式创建kthreadd进程,CLONE_FILES 子进程与父进程共享相同的文件描述符(file descriptor)表
    kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
    ......
}

    kernel_thread会调用do_fork函数用于创建进程,进程创建成功后会通过函数指针回调执行kernel_init函数;进程创建过程在此就不做分析,着重来分析一下kernel_init函数

2、kernel_init

定义在kernel/msm-4.4/init/main.c中

static int __ref kernel_init(void *unused) //__ref 这个跟之前讲的__init作用一样
{
    int ret;

    kernel_init_freeable(); //进行init进程的一些初始化操作
    /* need to finish all async __init code before freeing the memory */
    // 等待所有异步调用执行完成,,在释放内存前,必须完成所有的异步 __init 代码
    async_synchronize_full();
    // 释放所有init.* 段中的内存
    free_initmem();
    mark_readonly();
    system_state = SYSTEM_RUNNING;// 设置系统状态为运行状态
    numa_default_policy(); // 设定NUMA系统的默认内存访问策略

    flush_delayed_fput();// 释放所有延时的struct file结构体

    if (ramdisk_execute_command) { //ramdisk_execute_command的值为"/init"
        ret = run_init_process(ramdisk_execute_command);//运行根目录下的init程序
        if (!ret)
            return 0;
        pr_err("Failed to execute %s (error %d)\n",
               ramdisk_execute_command, ret);
    }

    /*
     * We try each of these until one succeeds.
     *
     * The Bourne shell can be used instead of init if we are
     * trying to recover a really broken machine.
     */
    if (execute_command) {//execute_command的值如果有定义就去根目录下找对应的应用程序,然后启动
        ret = run_init_process(execute_command);
        if (!ret)
            return 0;
        panic("Requested init %s failed (error %d).",
              execute_command, ret);
    }
    //如果ramdisk_execute_command和execute_command定义的应用程序都没有找到,
    //就到根目录下找 /sbin/init,/etc/init,/bin/init,/bin/sh 这四个应用程序进行启动
    if (!try_to_run_init_process("/sbin/init") ||
        !try_to_run_init_process("/etc/init") ||
        !try_to_run_init_process("/bin/init") ||
        !try_to_run_init_process("/bin/sh"))
        return 0;

    panic("No working init found.  Try passing init= option to kernel. "
          "See Linux Documentation/init.txt for guidance.");
}

    kernel_init主要工作是完成一些init的初始化操作,然后去系统根目录下依次找ramdisk_execute_command和execute_command设置的应用程序,如果这两个目录都找不到,就依次去根目录下找 /sbin/init,/etc/init,/bin/init,/bin/sh 这四个应用程序进行启动,只要这些应用程序有一个启动了,其他就不启动了

    ramdisk_execute_command和execute_command的值是通过bootloader传递过来的参数设置的,ramdisk_execute_command通过”rdinit”参数赋值,execute_command通过”init”参数赋值,ramdisk_execute_command如果没有被赋值,kernel_init_freeable函数会赋一个初始值”/init”
定义在device/qcom/XXX/BoardConfig.mk

ifeq ($(BOARD_KERNEL_CMDLINE),)
ifeq ($(TARGET_KERNEL_VERSION),4.4)
     BOARD_KERNEL_CMDLINE += console=ttyMSM0,115200,n8 init=/init androidboot.console=ttyMSM0 earlycon=msm_serial_dm,0xc170000
else
     BOARD_KERNEL_CMDLINE += console=ttyHSL0,115200,n8 init=/init androidboot.console=ttyHSL0 earlycon=msm_hsl_uart,0xc1b0000
endif
BOARD_KERNEL_CMDLINE += androidboot.hardware=qcom user_debug=31 msm_rtb.filter=0x37 ehci-hcd.park=3 lpm_levels.sleep_disabled=1 sched_enable_hmp=1 sched_enable_power_aware=1 service_locator.enable=1 swiotlb=1 androidboot.configfs=true androidboot.usbcontroller=a800000.dwc3
endif

2.1 kernel_init_freeable

定义在kernel/msm-4.4/init/main.c中

static noinline void __init kernel_init_freeable(void)
{
    /*
     * Wait until kthreadd is all set-up.
     */
    //等待&kthreadd_done这个值complete,这个在rest_init方法中有写,在ktreadd进程启动完成后设置为complete
    wait_for_completion(&kthreadd_done);

    /* Now the scheduler is fully set up and can do blocking allocations */
    gfp_allowed_mask = __GFP_BITS_MASK;//设置bitmask, 使得init进程可以使用PM并且允许I/O阻塞操作

    /*
     * init can allocate pages on any node
     */
    set_mems_allowed(node_states[N_MEMORY]);//init进程可以分配物理页面
    /*
     * init can run on any cpu.
     */
    set_cpus_allowed_ptr(current, cpu_all_mask); //init进程可以在任意cpu上执行

    //设置到init进程的pid号给cad_pid,cad就是ctrl-alt-del,设置init进程来处理ctrl-alt-del信号
    cad_pid = task_pid(current);

    //设置smp初始化时的最大CPU数量,然后将对应数量的CPU状态设置为present
    smp_prepare_cpus(setup_max_cpus);

    //调用__initcall_start到__initcall0_start之间的initcall_t函数指针
    do_pre_smp_initcalls();
    //开启watchdog_threads,watchdog主要用来监控、管理CPU的运行状态
    lockup_detector_init();

    smp_init();//启动cpu0外的其他cpu核
    sched_init_smp();//进程调度域初始化

    page_alloc_init_late();

    do_basic_setup();//初始化设备,驱动等,这个方法比较重要,将在下面单独讲

    /* Open the /dev/console on the rootfs, this should never fail */
    // 打开/dev/console,文件号0,作为init进程标准输入
    if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
        pr_err("Warning: unable to open an initial console.\n");

    (void) sys_dup(0);// 标准输入
    (void) sys_dup(0);// 标准输出
    /*
     * check if there is an early userspace init.  If yes, let it do all
     * the work
     */

    //如果 ramdisk_execute_command 没有赋值,则赋值为"/init",之前有讲到
    if (!ramdisk_execute_command)
        ramdisk_execute_command = "/init";

    // 尝试进入ramdisk_execute_command指向的文件,如果失败则重新挂载根文件系统
    if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
        ramdisk_execute_command = NULL;
        prepare_namespace();
    }

    /*
     * Ok, we have completed the initial bootup, and
     * we're essentially up and running. Get rid of the
     * initmem segments and start the user-mode stuff..
     *
     * rootfs is available now, try loading the public keys
     * and default modules
     */

    integrity_load_keys();
    load_default_modules();// 加载I/O调度的电梯算法
}

    kernel_init_freeable函数做了很多重要的事情,启动了smp,smp全称是Symmetrical Multi-Processing,即对称多处理,是指在一个计算机上汇集了一组处理器(多CPU),各CPU之间共享内存子系统以及总线结构。初始化设备和驱动程序、打开标准输入和输出、初始化文件系统等等

2.1.1 do_basic_setup

定义在kernel/msm-4.4/init/main.c中

static void __init do_basic_setup(void)
{
    cpuset_init_smp();//针对SMP系统,初始化内核control group的cpuset子系统。
    shmem_init();// 初始化共享内存
    driver_init();// 初始化设备驱动,比较重要下面单独讲
    init_irq_proc();//创建/proc/irq目录, 并初始化系统中所有中断对应的子目录
    do_ctors();// 执行内核的构造函数
    usermodehelper_enable();// 启用usermodehelper
    //遍历initcall_levels数组,调用里面的initcall函数,这里主要是对设备、驱动、文件系统进行初始化,之所有将函数封装到数组进行遍历,主要是为了好扩展
    do_initcalls();
    random_int_secret_init();//初始化随机数生成池
}

2.1.2 driver_init

定义在kernel/msm-4.4/drivers/base/init.c中

void __init driver_init(void)
{
    /* These are the core pieces */
    devtmpfs_init();// 注册devtmpfs文件系统,启动kdevtmpfs进程
    devices_init();// 初始化驱动模型中的部分子系统,kset:devices 和 kobject:dev、 dev/block、 dev/char
    buses_init();// 初始化驱动模型中的bus子系统,kset:bus、devices/system
    classes_init();// 初始化驱动模型中的class子系统,kset:class
    firmware_init();// 初始化驱动模型中的firmware子系统 ,kobject:firmware
    hypervisor_init();// 初始化驱动模型中的hypervisor子系统,kobject:hypervisor

    /* These are also core pieces, but must come after the
     * core core pieces.
     */
    // 初始化驱动模型中的bus/platform子系统,这个节点是所有platform设备和驱动的总线类型,即所有platform设备和驱动都会挂载到这个总线上     
    platform_bus_init();
    cpu_dev_init();// 初始化驱动模型中的devices/system/cpu子系统,该节点包含CPU相关的属性
    memory_dev_init();//初始化驱动模型中的/devices/system/memory子系统,该节点包含了内存相关的属性,如块大小等
    container_dev_init();//初始化系统总线类型为容器
    of_core_init();//初始化创建,访问和解释设备树的过程。
}

    这个函数完成驱动子系统的构建,实现了Linux设备驱动的一个整体框架,但是它只是建立了目录结构,具体驱动的装载是在do_initcalls函数,kernel_init_freeable函数告一段落了,我们接着讲kernel_init中剩余的函数

2.2 free_initmem

定义在kernel/msm-4.4/arch/arm64/mm/init.c中中

void free_initmem(void)
{
    free_initmem_default(0);
    fixup_init();
}

void fixup_init(void)
{
    /*
     * Unmap the __init region but leave the VM area in place. This
     * prevents the region from being reused for kernel modules, which
     * is not supported by kallsyms.
     */
    unmap_kernel_range((u64)__init_begin, (u64)(__init_end - __init_begin));
}

    所有使用__init标记过的函数和使用__initdata标记过的数据,在free_initmem函数执行后,都不能使用,它们曾经获得的内存现在可以重新用于其他目的。

2.3 flush_delayed_fput

定义在kernel/msm-4.4/arch/arm64/mm/init.c中,

void flush_delayed_fput(void)
{
    delayed_fput(NULL);
}

static LLIST_HEAD(delayed_fput_list);
static void delayed_fput(struct work_struct *unused)
{
    struct llist_node *node = llist_del_all(&delayed_fput_list);
    struct llist_node *next;

    for (; node; node = next) {
        next = llist_next(node);
        __fput(llist_entry(node, struct file, f_u.fu_llist));//释放struct file
    }
}

    这个函数主要用于释放&delayed_fput_list这个链表中的struct file,struct file即文件结构体,代表一个打开的文件,系统中的每个打开的文件在内核空间都有一个关联的 struct file。

2.4 run_init_process

定义在kernel/msm-4.4/init/main.c中

static int run_init_process(const char *init_filename)
{
    argv_init[0] = init_filename;
    return do_execve(getname_kernel(init_filename), //do_execve就是执行一个可执行文件
        (const char __user *const __user *)argv_init,
        (const char __user *const __user *)envp_init);
}

    run_init_process就是运行可执行文件了,从kernel_init函数中可知,系统会依次去找根目录下的init,execute_command,/sbin/init,/etc/init,/bin/init,/bin/sh这六个可执行文件,只要找到其中一个,其他就不执行。

3、小结

    上述分析了kernel部分的init进程启动过程,然而在Android系统一般会在根目录下放一个init的可执行文件,也就是说Linux系统的init进程在内核初始化完成后,就直接通过run_init_process函数执行init可执行文件,该可执行文件的源代码在system/core/init/init.cpp,下一篇文章中我将以这个文件为入口,分析Android系统的init进程启动过程。

你可能感兴趣的:(Android)