精华内容
下载资源
问答
  • init进程

    2016-03-16 17:15:14
     init进程,是内核启动的第一个用户级进程。init有许多很重要的任务,比如象启动getty(用于用户登录)、实现运行级别、以及处理孤立进程。  对于Linux系统的运行来说,init程序是最基本的程序之一。但你仍可以大...

           介绍

           init进程,是内核启动的第一个用户级进程。init有许多很重要的任务,比如象启动getty(用于用户登录)、实现运行级别、以及处理孤立进程。

      对于Linux系统的运行来说,init程序是最基本的程序之一。但你仍可以大部分的忽略它。一个好的Linux发行版本通常随带有一个init的配置,这个配置适合于绝大多数系统的工作,在这样一些系统上不需要对init做任何事。通常,只有你在碰到诸如串行终端挂住了、拨入(不是拨出)调制解调器、或者你希望改变缺省的运行级别时你才需要关心init。

      当内核启动了自己之后(已被装入内存、已经开始运行、已经初始化了所有的设备驱动程序和数据结构等等),通过启动用户级程序init来完成引导进程的内核部分。因此,init总是第一个进程(它的进程号总是1)。

      内核在几个位置上来查寻init,这几个位置以前常用来放置init,但是init的最适当的位置(在Linux系统上)是/sbin/init。如果内核没有找到init,它就会试着运行/bin/sh,如果还是失败了,那么系统的启动就宣告失败了。

      当init开始运行,它通过执行一些管理任务来结束引导进程,例如检查文件系统、清理/tmp、启动各种服务以及为每个终端和虚拟控制台启动getty,在这些地方用户将登录系统。

      在系统完全起来之后,init为每个用户已退出的终端重启getty(这样下一个用户就可以登录)。init同样也收集孤立的进程:当一个进程启动了一个子进程并且在子进程之前终止了,这个子进程立刻成为init的子进程。对于各种技术方面的原因来说这是很重要的,知道这些也是有好处的,因为这便于理解进程列表和进程树图。init的变种很少。绝大多数Linux发行版本使用sysinit(由Miguel van Smoorenburg著),它是基于System V的init设计。UNIX的BSD版本有一个不同的init。最主要的不同在于运行级别:System V有而BSD没有(至少是传统上说)。

      启动(引导)进入单用户模式

      一个很重要的运行级别是单用户模式(single user mode)(运行级别1),在这个模式中只有系统管理员在使用机器并且只有很少的系统服务在运行,如登录服务。对于一些管理任务来说单用户模式是必须的,如在/usr分区上运行fsck,因为这需要该分区没被加载,除非几乎所有的系统服务都被终止了,否则不可能会有这种情况。通过telinit请求运行级别1,一个运行着的系统可以转换到单用户模式。在启动时,可以通过在内核的命令行上给出single或emergency来进入单用户模式:内核同样也将命令行给init,init会理解那个单词并且不会使用缺省的运行级别。(内核命令行输入的方法依赖于系统是如何引导的。)在加载文件系统之前,引导进入单用户模式有时是需要的,这样就可以手工运行fsck命令了,否则的话很可能损坏/usr分区(在一个有问题的文件系统上的任何操作会更进一步地损坏它,所以fsck要尽早地运行)。如果启动时fsck的自动检查失败了,启动描述文件init就会自动地进入单用户模式。这是试图避免系统使用一个文件系统,这个文件系统损坏的太严重以至于fsck都不能够自动地修复它。这样的毁坏情况是相当少的,通常是硬盘有问题或是在试验一个内核版本,但是有准备总比没有好。作为一个安全措施,一个正确配置的系统应该在运行单用户模式的shell之前要求口令。否则的话,只要给LILO输入适当的一行参数就很容易地以root身份进入系统。(当然,如果由于文件系统的问题而使/etc/passwd毁坏时,就不是这样了。如果是这样的话,你手头最好有张引导软盘。)

           启动顺序

           启动Init进程,其实质是执行/sbin/init程序,即Init进程可以启动其它子进程,或启动shell,本质上是因为内核执行了可执行序/sbin/init.因此,可以说Init进程的作用只不过是可执行程序/sbin/init的作用.可以这样来理解,当执行可执行程序/sbin/init时,该程序就是一进程,名字是Init.当然,用户也可以根据需要编写自己的/sbin/init程序,或者传入命令行参数"init=xxxx"指定某个程序作为Init进程运行.在嵌入式领域内,一般来说多使用Busybox集成的Init程序.

             内核启动的最后一步就是启动Init进程.该Init进程中,它主要做了以下几项工作:
       <1>读取系统初始化表格文件/etc/inittab.(BusyBox init及inittabl文件
       <2>建立getty进程,在终端上显示login提示符,以等待用户的登录.
       <3>进程getty会调用exec以执行login程序.该程序将会核对用户账号和密码.
       <4>login程序会调用exec以执行shell命令解释器,也可执行X-windows.
       <5>shell命令解释器会读取系统环境配置脚本/etc/profile.

    展开全文
  • 日期 内核版本 架构 作者 GitHub CSDN 2016-05-29 Linux-4.5 X86 & arm gatieme LinuxDeviceDrivers ...前言Linux下有3个特殊的进程,idle进程(PID=0PID = 0), init进程(PID=1PID = 1)和kthreadd(PID=2P
    日期 内核版本 架构 作者 GitHub CSDN
    2016-05-29 Linux-4.5 X86 & arm gatieme LinuxDeviceDrivers Linux进程管理与调度-之-进程的创建

    前言


    Linux下有3个特殊的进程,idle进程(PID=0), init进程(PID=1)和kthreadd(PID=2)


    * idle进程由系统自动创建, 运行在内核态

    idle进程其pid=0,其前身是系统创建的第一个进程,也是唯一一个没有通过fork或者kernel_thread产生的进程。完成加载系统后,演变为进程调度、交换


    * init进程由idle通过kernel_thread创建,在内核空间完成初始化后, 加载init程序, 并最终用户空间

    由0进程创建,完成系统的初始化. 是系统中所有其它用户进程的祖先进程
    Linux中的所有进程都是有init进程创建并运行的。首先Linux内核启动,然后在用户空间中启动init进程,再启动其他系统进程。在系统启动完成完成后,init将变为守护进程监视系统其他进程。


    * kthreadd进程由idle通过kernel_thread创建,并始终运行在内核空间, 负责所有内核线程的调度和管理

    它的任务就是管理和调度其他内核线程kernel_thread, 会循环执行一个kthread的函数,该函数的作用就是运行kthread_create_list全局链表中维护的kthread, 当我们调用kernel_thread创建的内核线程会被加入到此链表中,因此所有的内核线程都是直接或者间接的以kthreadd为父进程

    我们下面就详解分析1号进程的前世(kernel_init)今生(init进程)

    Linux系统中的init进程(pid=1)是除了idle进程(pid=0,也就是init_task)之外另一个比较特殊的进程,它是Linux内核开始建立起进程概念时第一个通过kernel_thread产生的进程,其开始在内核态执行,然后通过一个系统调用,开始执行用户空间的/sbin/init程序,期间Linux内核也经历了从内核态到用户态的特权级转变,/sbin/init极有可能产生出了shell,然后所有的用户进程都有该进程派生出来

    1号进程


    前面我们了解到了0号进程是系统所有进程的先祖, 它的进程描述符init_task是内核静态创建的, 而它在进行初始化的时候, 通过kernel_thread的方式创建了两个内核线程,分别是kernel_init和kthreadd,其中kernel_init进程号为1

    start_kernel在其最后一个函数rest_init的调用中,会通过kernel_thread来生成一个内核进程,后者则会在新进程环境下调 用kernel_init函数,kernel_init一个让人感兴趣的地方在于它会调用run_init_process来执行根文件系统下的 /sbin/init等程序:

    kernel_init


    0号进程创建1号进程的方式如下

    kernel_thread(kernel_init, NULL, CLONE_FS);

    我们发现1号进程的执行函数就是kernel_init, 这个函数被定义init/main.c中,如下所示

    kernel_init函数将完成设备驱动程序的初始化,并调用init_post函数启动用户空间的init进程。

    由0号进程创建1号进程(内核态),1号内核线程负责执行内核的部分初始化工作及进行系统配置,并创建若干个用于高速缓存和虚拟主存管理的内核线程。

    init进程


    随后,1号进程调用do_execve运行可执行程序init,并演变成用户态1号进程,即init进程。

    init进程是linux内核启动的第一个用户级进程。init有许多很重要的任务,比如像启动getty(用于用户登录)、实现运行级别、以及处理孤立进程。

    它按照配置文件/etc/initab的要求,完成系统启动工作,创建编号为1号、2号…的若干终端注册进程getty。

    每个getty进程设置其进程组标识号,并监视配置到系统终端的接口线路。当检测到来自终端的连接信号时,getty进程将通过函数do_execve()执行注册程序login,此时用户就可输入注册名和密码进入登录过程,如果成功,由login程序再通过函数execv()执行shell,该shell进程接收getty进程的pid,取代原来的getty进程。再由shell直接或间接地产生其他进程。

    上述过程可描述为:0号进程->1号内核进程->1号用户进程(init进程)->getty进程->shell进程

    注意,上述过程描述中提到:1号内核进程调用执行init函数并演变成1号用户态进程(init进程),这里前者是init是函数,后者是进程。两者容易混淆,区别如下:

    1. kernel_init函数在内核态运行,是内核代码

    2. init进程是内核启动并运行的第一个用户进程,运行在用户态下。

    3. 一号内核进程调用execve()从文件/etc/inittab中加载可执行程序init并执行,这个过程并没有使用调用do_fork(),因此两个进程都是1号进程。

    当内核启动了自己之后(已被装入内存、已经开始运行、已经初始化了所有的设备驱动程序和数据结构等等),通过启动用户级程序init来完成引导进程的内核部分。因此,init总是第一个进程(它的进程号总是1)。

    当init开始运行,它通过执行一些管理任务来结束引导进程,例如检查文件系统、清理/tmp、启动各种服务以及为每个终端和虚拟控制台启动getty,在这些地方用户将登录系统。

    在系统完全起来之后,init为每个用户已退出的终端重启getty(这样下一个用户就可以登录)。init同样也收集孤立的进程:当一个进程启动了一个子进程并且在子进程之前终止了,这个子进程立刻成为init的子进程。对于各种技术方面的原因来说这是很重要的,知道这些也是有好处的,因为这便于理解进程列表和进程树图。init的变种很少。绝大多数Linux发行版本使用sysinit(由Miguel van Smoorenburg著),它是基于System V的init设计。UNIX的BSD版本有一个不同的init。最主要的不同在于运行级别:System V有而BSD没有(至少是传统上说)。这种区别并不是主要的。在此我们仅讨论sysvinit。 配置init以启动getty:/etc/inittab文件

    关于init程序


    1号进程通过execve执行init程序来进入用户空间,成为init进程,那么这个init在哪里呢

    内核在几个位置上来查寻init,这几个位置以前常用来放置init,但是init的最适当的位置(在Linux系统上)是/sbin/init。如果内核没有找到init,它就会试着运行/bin/sh,如果还是失败了,那么系统的启动就宣告失败了。

    因此init程序是一个可以又用户编写的进程, 如果希望看init程序源码的朋友,可以参见

    init包 说明 学习链接
    sysvinit 早期一些版本使用的初始化进程工具, 目前在逐渐淡出linux历史舞台, sysvinit 就是 system V 风格的 init 系统,顾名思义,它源于 System V 系列 UNIX。它提供了比 BSD 风格 init 系统更高的灵活性。是已经风行了几十年的 UNIX init 系统,一直被各类 Linux 发行版所采用。 浅析 Linux 初始化 init 系统(1):sysvinit
    upstart debian, Ubuntu等系统使用的initdaemon 浅析 Linux 初始化 init 系统(2): UpStart
    systemd Systemd 是 Linux 系统中最新的初始化系统(init),它主要的设计目标是克服 sysvinit 固有的缺点,提高系统的启动速度 浅析 Linux 初始化 init 系统(3) Systemd

    Ubuntu等使用deb包的系统可以通过dpkg -S查看程序所在的包

    这里写图片描述

    CentOS等使用rpm包的系统可以通过rpm -qf查看系统程序所在的包

    旧版CentOS

    新版CentOS

    参见

    Linux下查看并下载命令源码包(根据命令/应用程序逆向获取并且安装其所属源码包)

    附录


    kernel_init_freeable流程分析


    static noinline void __init kernel_init_freeable(void)
    {
        /*
         * Wait until kthreadd is all set-up.
         */
        wait_for_completion(&kthreadd_done);
    
        /* Now the scheduler is fully set up and can do blocking allocations */
        gfp_allowed_mask = __GFP_BITS_MASK;
    
        /*
         * init can allocate pages on any node
         */
        set_mems_allowed(node_states[N_MEMORY]);
        /*
         * init can run on any cpu.
         */
        set_cpus_allowed_ptr(current, cpu_all_mask);
    
        cad_pid = task_pid(current);
    
        smp_prepare_cpus(setup_max_cpus);
    
        do_pre_smp_initcalls();
        lockup_detector_init();
    
        smp_init();
        sched_init_smp();
    
        page_alloc_init_late();
    
        do_basic_setup();
    
        /* Open the /dev/console on the rootfs, this should never fail */
        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
         */
    
        if (!ramdisk_execute_command)
                ramdisk_execute_command = "/init";
    
        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();
     }
    执行流程 说明
    wait_for_completion 实例在kernel/sched/completion.c中, 等待Kernel Thread kthreadd (PID=2)创建完毕
    gfp_allowed_mask __GFP_BITS_MASK;设置bitmask, 使得init进程可以使用PM并且允许I/O阻塞操作
    set_mems_allowed(node_states[N_MEMORY]); init进程可以分配物理页面
    set_cpus_allowed_ptr 通过设置cpu_bit_mask, 可以限定task只能在特定的处理器上运行, 而initcurrent进程此时必然是init进程,设置其cpu_all_mask即使得init进程可以在任意的cpu上运行
    task_pid 设置到目前运行进程init的pid号给cad_pid(cad_pid是用来接收ctrl-alt-del reboot signal的进程, 如果设置C_A_D=1就表示可以处理来自ctl-alt-del的动作), 最后会调用 ctrl_alt_del(void)并确认C_A_D是否为1,确认完成后将执行cad_work=deferred_cad,执行kernel_restart
    smp_prepare_cpus 体系结构相关的函数,实例在arch/arm/kernel/smp.c中,调用smp_prepare_cpus时,会以全局变量setup_max_cpus为函式参数max_cpus,以表示在编译核心时,设定支援的最大CPU数量
    do_pre_smp_initcalls 实例在init/main.c中, 会透过函式do_one_initcall,执行Symbol中 __initcall_start与__early_initcall_end之间的函数
    smp_init 实例在kernel/smp.c中, 函数主要是由Bootstrap处理器,进行Active多核心架构下其它的处理器. 如果发生Online的处理器个数(from num_online_cpus)超过在核心编译时,所设定的最大处理器个数 setup_max_cpus (from NR_CPUS),就会终止流程.如果该处理器目前属於Present (也就是存在系统中),但尚未是Online的状态,就会呼叫函式cpu_up(in kernel/cpu.c)来啟动该处理器.
    sched_init_smp 实例在kernel/sched.c中, (1), 呼叫get_online_cpus,如果目前CPU Hotplug Active Write行程是自己,就直接返回.反之就把 cpu_hotplug.refcount加1 (表示多一个Reader)
    (2),取得Mutex Lock “sched_domains_mutex”
    (3),呼叫arch_init_sched_domains,设定scheduler domains与groups,参考Linux Documentation/scheduler/sched-domains.txt文件,一个Scheduling Domain会包含一个或多个CPU Groups,排程的Load-Balance就会根据Domain中的Groups来做调整.
    (4),释放Mutex Lock “sched_domains_mutex”
    (5),呼叫put_online_cpus,如果目前CPU Hotplug Active Writer行程是自己,就直接返回.反之就把 cpu_hotplug.refcount减1,如果 cpu_hotplug.refcount减到為0,表示没有其他Reader,此时如果有CPU Hotplug Active Writer行程在等待,就会透过wake_up_process唤醒该行程,以便让等待中的Writer可以被执行下去.(也可以参考_cpu_up中对於函式cpu_hotplug_begin的说明).
    (6)注册CPU Notifier cpuset_cpu_active/cpuset_cpu_inactive/update_runtime
    (7),呼叫set_cpus_allowed_ptr,透过这函式可以设定CPU bitmask,限定Task只能在特定的处理器上运作.在这会用参数”non_isolated_cpus”,也就是会把init指定给non-isolated CPU. Linux Kernel可以在啟动时,透过Boot Parameters “isolcpus=“指定CPU编号或是范围,让这些处理器不被包含在Linux Kernel SMP balancing/scheduling算法内,可以在啟动后指派给特定的Task运作.而不在 “isolcpus=“ 指定范围内的处理器就算是non-isolated CPU.
    (8),呼叫sched_init_granularity,透过函式update_sysctl,让sysctl_sched_min_granularity=normalized_sysctl_sched_min_granularity,sysctl_sched_latency=normalized_sysctl_sched_latency,sysctl_sched_wakeup_granularity=normalized_sysctl_sched_wakeup_granularit
    do_basic_setup 实例在init/main.c中,
    1,diaousermodehelper_init (in kernel/kmod.c),产生khelper workqueue.
    2,调用init_tmpfs (in mm/shmem.c),对VFS注册Temp FileSystem.
    3,呼叫driver_init (in drivers/base/init.c),初始化Linux Kernel Driver System Model.
    4,呼叫init_irq_proc(in kernel/irq/proc.c),初始化 “/proc/irq”与其下的File Nodes.
    5,呼叫do_ctors (in init/main.c),执行位於Symbol __ctors_start 到 __ctors_end间属於Section “.ctors” 的Constructor函式.
    6,透过函式do_initcalls,执行介於Symbol __early_initcall_end与__initcall_end之间的函式呼叫,
    sys_open 实例在fs/fcntl.c中,”SYSCALL_DEFINE1(dup, unsigned int, fildes)”,在这会连续执行两次sys_dup,复制两个sys_open开啟/dev/console所產生的档案描述0 (也就是会多生出两个1与2),只是都对应到”/dev/console”,我们在System V streams下的Standard Stream一般而言会有如下的对应
    0:Standard input (stdin)
    1:Standard output (stdout)
    2:Standard error (stderr)
    (為方便大家参考,附上Wiki URL http://en.wikipedia.org/wiki/Standard_streams )
    ramdisk_execute_command与prepare_namespace 1,如果ramdisk_execute_command為0,就设定ramdisk_execute_command = “/init”
    2,如果sys_access确认档案ramdisk_execute_command 失败,就把ramdisk_execute_command 设定為0,然后呼叫prepare_namespace去mount root FileSystem.
    integrity_load_keys 至此我们初始化工作完成, 文件系统也已经准备好了,那么接下来加载 load integrity keys hook
    load_default_modules 加载基本的模块

    kernel_init分析


    static int __ref kernel_init(void *unused)
    {
        int ret;
    
        kernel_init_freeable();
        /* need to finish all async __init code before freeing the memory */
        async_synchronize_full();
        free_initmem();
        mark_rodata_ro();
        system_state = SYSTEM_RUNNING;
        numa_default_policy();
    
        flush_delayed_fput();
    
        rcu_end_inkernel_boot();
    
        if (ramdisk_execute_command) {
                ret = run_init_process(ramdisk_execute_command);
                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) {
                ret = run_init_process(execute_command);
                if (!ret)
                        return 0;
                panic("Requested init %s failed (error %d).",
                      execute_command, ret);
        }
        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_freeable 调用kernel_init_freeable完成初始化工作,准备文件系统,准备模块信息
    async_synchronize_full 用以同步所有非同步函式呼叫的执行,在这函数中会等待List async_running与async_pending都清空后,才会返回. Asynchronously called functions主要设计用来加速Linux Kernel开机的效率,避免在开机流程中等待硬体反应延迟,影响到开机完成的时间
    free_initmem free_initmem(in arch/arm/mm/init.c),释放Linux Kernel介於__init_begin到 __init_end属于init Section的函数的所有内存.并会把Page个数加到变量totalram_pages中,作为后续Linux Kernel在配置记忆体时可以使用的Pages. (在这也可把TCM范围(__tcm_start到__tcm_end)释放加入到总Page中,但TCM比外部记忆体有效率,适合多媒体,中断,…etc等对效能要求高的执行需求,放到总Page中,成为可供一般目的配置的存储范围
    system_state 设置运行状态SYSTEM_RUNNING
    加载init进程,进入用户空间 a,如果ramdisk_execute_command不為0,就执行该命令成為init User Process.
    b,如果execute_command不為0,就执行该命令成為init User Process.
    c,如果上述都不成立,就依序執行如下指令
    run_init_process(“/sbin/init”);
    run_init_process(“/etc/init”);
    run_init_process(“/bin/init”);
    run_init_process(“/bin/sh”);
    也就是说会按照顺序从/sbin/init, /etc/init, /bin/init 與 /bin/sh依序执行第一个 init User Process.
    如果都找不到可以執行的 init Process,就會進入Kernel Panic.如下所示panic(“No init found. Try passing init= option to kernel. ”“See Linux Documentation/init.txt for guidance.”);
    展开全文
  • Linux下有3个特殊的进程,idle进程(PID=0PID=0), init进程(PID=1PID=1)和kthreadd(PID=2PID=2)* idle进程由系统自动创建, 运行在内核态 idle进程其pid=0,其前身是系统创建的第一个进程,也是唯一一个没有通过fork...

    Linux下有3个特殊的进程,idle进程(PID=0PID=0), init进程(PID=1PID=1)和kthreadd(PID=2PID=2)


    * idle进程由系统自动创建, 运行在内核态 

    idle进程其pid=0,其前身是系统创建的第一个进程,也是唯一一个没有通过fork或者kernel_thread产生的进程。完成加载系统后,演变为进程调度、交换


    * init进程由idle通过kernel_thread创建,在内核空间完成初始化后, 加载init程序, 并最终用户空间 

    由0进程创建,完成系统的初始化. 是系统中所有其它用户进程的祖先进程 
    Linux中的所有进程都是有init进程创建并运行的。首先Linux内核启动,然后在用户空间中启动init进程,再启动其他系统进程。在系统启动完成完成后,init将变为守护进程监视系统其他进程。


    * kthreadd进程由idle通过kernel_thread创建,并始终运行在内核空间, 负责所有内核线程的调度和管理 

    它的任务就是管理和调度其他内核线程kernel_thread, 会循环执行一个kthread的函数,该函数的作用就是运行kthread_create_list全局链表中维护的kthread, 当我们调用kernel_thread创建的内核线程会被加入到此链表中,因此所有的内核线程都是直接或者间接的以kthreadd为父进程 

    我们下面就详解分析1号进程的前世(kernel_init)今生(init进程)

    Linux系统中的init进程(pid=1)是除了idle进程(pid=0,也就是init_task)之外另一个比较特殊的进程,它是Linux内核开始建立起进程概念时第一个通过kernel_thread产生的进程,其开始在内核态执行,然后通过一个系统调用,开始执行用户空间的/sbin/init程序,期间Linux内核也经历了从内核态到用户态的特权级转变,/sbin/init极有可能产生出了shell,然后所有的用户进程都有该进程派生出来

    1号进程


    前面我们了解到了0号进程是系统所有进程的先祖, 它的进程描述符init_task是内核静态创建的, 而它在进行初始化的时候, 通过kernel_thread的方式创建了两个内核线程,分别是kernel_init和kthreadd,其中kernel_init进程号为1

    start_kernel在其最后一个函数rest_init的调用中,会通过kernel_thread来生成一个内核进程,后者则会在新进程环境下调 用kernel_init函数,kernel_init一个让人感兴趣的地方在于它会调用run_init_process来执行根文件系统下的 /sbin/init等程序:

    kernel_init


    0号进程创建1号进程的方式如下

    kernel_thread(kernel_init, NULL, CLONE_FS);
    • 1

    我们发现1号进程的执行函数就是kernel_init, 这个函数被定义init/main.c中,如下所示

    kernel_init函数将完成设备驱动程序的初始化,并调用init_post函数启动用户空间的init进程。

    由0号进程创建1号进程(内核态),1号内核线程负责执行内核的部分初始化工作及进行系统配置,并创建若干个用于高速缓存和虚拟主存管理的内核线程。

    init进程


    随后,1号进程调用do_execve运行可执行程序init,并演变成用户态1号进程,即init进程。

    init进程是linux内核启动的第一个用户级进程。init有许多很重要的任务,比如像启动getty(用于用户登录)、实现运行级别、以及处理孤立进程。

    它按照配置文件/etc/initab的要求,完成系统启动工作,创建编号为1号、2号…的若干终端注册进程getty。

    每个getty进程设置其进程组标识号,并监视配置到系统终端的接口线路。当检测到来自终端的连接信号时,getty进程将通过函数do_execve()执行注册程序login,此时用户就可输入注册名和密码进入登录过程,如果成功,由login程序再通过函数execv()执行shell,该shell进程接收getty进程的pid,取代原来的getty进程。再由shell直接或间接地产生其他进程。

    上述过程可描述为:0号进程->1号内核进程->1号用户进程(init进程)->getty进程->shell进程

    注意,上述过程描述中提到:1号内核进程调用执行init函数并演变成1号用户态进程(init进程),这里前者是init是函数,后者是进程。两者容易混淆,区别如下:

    1. kernel_init函数在内核态运行,是内核代码

    2. init进程是内核启动并运行的第一个用户进程,运行在用户态下。

    3. 一号内核进程调用execve()从文件/etc/inittab中加载可执行程序init并执行,这个过程并没有使用调用do_fork(),因此两个进程都是1号进程。

    当内核启动了自己之后(已被装入内存、已经开始运行、已经初始化了所有的设备驱动程序和数据结构等等),通过启动用户级程序init来完成引导进程的内核部分。因此,init总是第一个进程(它的进程号总是1)。

    当init开始运行,它通过执行一些管理任务来结束引导进程,例如检查文件系统、清理/tmp、启动各种服务以及为每个终端和虚拟控制台启动getty,在这些地方用户将登录系统。

    在系统完全起来之后,init为每个用户已退出的终端重启getty(这样下一个用户就可以登录)。init同样也收集孤立的进程:当一个进程启动了一个子进程并且在子进程之前终止了,这个子进程立刻成为init的子进程。对于各种技术方面的原因来说这是很重要的,知道这些也是有好处的,因为这便于理解进程列表和进程树图。init的变种很少。绝大多数Linux发行版本使用sysinit(由Miguel van Smoorenburg著),它是基于System V的init设计。UNIX的BSD版本有一个不同的init。最主要的不同在于运行级别:System V有而BSD没有(至少是传统上说)。这种区别并不是主要的。在此我们仅讨论sysvinit。 配置init以启动getty:/etc/inittab文件

    关于init程序


    1号进程通过execve执行init程序来进入用户空间,成为init进程,那么这个init在哪里呢

    内核在几个位置上来查寻init,这几个位置以前常用来放置init,但是init的最适当的位置(在Linux系统上)是/sbin/init。如果内核没有找到init,它就会试着运行/bin/sh,如果还是失败了,那么系统的启动就宣告失败了。

    因此init程序是一个可以又用户编写的进程, 如果希望看init程序源码的朋友,可以参见

    init包说明学习链接
    sysvinit早期一些版本使用的初始化进程工具, 目前在逐渐淡出linux历史舞台, sysvinit 就是 system V 风格的 init 系统,顾名思义,它源于 System V 系列 UNIX。它提供了比 BSD 风格 init 系统更高的灵活性。是已经风行了几十年的 UNIX init 系统,一直被各类 Linux 发行版所采用。浅析 Linux 初始化 init 系统(1):sysvinit
    upstartdebian, Ubuntu等系统使用的initdaemon浅析 Linux 初始化 init 系统(2): UpStart
    systemdSystemd 是 Linux 系统中最新的初始化系统(init),它主要的设计目标是克服 sysvinit 固有的缺点,提高系统的启动速度浅析 Linux 初始化 init 系统(3) Systemd

    Ubuntu等使用deb包的系统可以通过dpkg -S查看程序所在的包

    这里写图片描述

    CentOS等使用rpm包的系统可以通过rpm -qf查看系统程序所在的包

    旧版CentOS

    新版CentOS

    参见

    Linux下查看并下载命令源码包(根据命令/应用程序逆向获取并且安装其所属源码包)

    附录


    kernel_init_freeable流程分析


    static noinline void __init kernel_init_freeable(void)
    {
        /*
         * Wait until kthreadd is all set-up.
         */
        wait_for_completion(&kthreadd_done);
    
        /* Now the scheduler is fully set up and can do blocking allocations */
        gfp_allowed_mask = __GFP_BITS_MASK;
    
        /*
         * init can allocate pages on any node
         */
        set_mems_allowed(node_states[N_MEMORY]);
        /*
         * init can run on any cpu.
         */
        set_cpus_allowed_ptr(current, cpu_all_mask);
    
        cad_pid = task_pid(current);
    
        smp_prepare_cpus(setup_max_cpus);
    
        do_pre_smp_initcalls();
        lockup_detector_init();
    
        smp_init();
        sched_init_smp();
    
        page_alloc_init_late();
    
        do_basic_setup();
    
        /* Open the /dev/console on the rootfs, this should never fail */
        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
         */
    
        if (!ramdisk_execute_command)
                ramdisk_execute_command = "/init";
    
        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();
     }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    执行流程说明
    wait_for_completion实例在kernel/sched/completion.c中, 等待Kernel Thread kthreadd (PID=2)创建完毕
    gfp_allowed_mask__GFP_BITS_MASK;设置bitmask, 使得init进程可以使用PM并且允许I/O阻塞操作
    set_mems_allowed(node_states[N_MEMORY]);init进程可以分配物理页面
    set_cpus_allowed_ptr通过设置cpu_bit_mask, 可以限定task只能在特定的处理器上运行, 而initcurrent进程此时必然是init进程,设置其cpu_all_mask即使得init进程可以在任意的cpu上运行
    task_pid设置到目前运行进程init的pid号给cad_pid(cad_pid是用来接收ctrl-alt-del reboot signal的进程, 如果设置C_A_D=1就表示可以处理来自ctl-alt-del的动作), 最后会调用 ctrl_alt_del(void)并确认C_A_D是否为1,确认完成后将执行cad_work=deferred_cad,执行kernel_restart
    smp_prepare_cpus体系结构相关的函数,实例在arch/arm/kernel/smp.c中,调用smp_prepare_cpus时,会以全局变量setup_max_cpus为函式参数max_cpus,以表示在编译核心时,设定支援的最大CPU数量
    do_pre_smp_initcalls实例在init/main.c中, 会透过函式do_one_initcall,执行Symbol中 __initcall_start与__early_initcall_end之间的函数
    smp_init实例在kernel/smp.c中, 函数主要是由Bootstrap处理器,进行Active多核心架构下其它的处理器. 如果发生Online的处理器个数(from num_online_cpus)超过在核心编译时,所设定的最大处理器个数 setup_max_cpus (from NR_CPUS),就会终止流程.如果该处理器目前属於Present (也就是存在系统中),但尚未是Online的状态,就会呼叫函式cpu_up(in kernel/cpu.c)来啟动该处理器.
    sched_init_smp实例在kernel/sched.c中, (1), 呼叫get_online_cpus,如果目前CPU Hotplug Active Write行程是自己,就直接返回.反之就把 cpu_hotplug.refcount加1 (表示多一个Reader)
    (2),取得Mutex Lock “sched_domains_mutex”
    (3),呼叫arch_init_sched_domains,设定scheduler domains与groups,参考Linux Documentation/scheduler/sched-domains.txt文件,一个Scheduling Domain会包含一个或多个CPU Groups,排程的Load-Balance就会根据Domain中的Groups来做调整.
    (4),释放Mutex Lock “sched_domains_mutex”
    (5),呼叫put_online_cpus,如果目前CPU Hotplug Active Writer行程是自己,就直接返回.反之就把 cpu_hotplug.refcount减1,如果 cpu_hotplug.refcount减到為0,表示没有其他Reader,此时如果有CPU Hotplug Active Writer行程在等待,就会透过wake_up_process唤醒该行程,以便让等待中的Writer可以被执行下去.(也可以参考_cpu_up中对於函式cpu_hotplug_begin的说明).
    (6)注册CPU Notifier cpuset_cpu_active/cpuset_cpu_inactive/update_runtime 
    (7),呼叫set_cpus_allowed_ptr,透过这函式可以设定CPU bitmask,限定Task只能在特定的处理器上运作.在这会用参数”non_isolated_cpus”,也就是会把init指定给non-isolated CPU. Linux Kernel可以在啟动时,透过Boot Parameters “isolcpus=“指定CPU编号或是范围,让这些处理器不被包含在Linux Kernel SMP balancing/scheduling算法内,可以在啟动后指派给特定的Task运作.而不在 “isolcpus=“ 指定范围内的处理器就算是non-isolated CPU.
    (8),呼叫sched_init_granularity,透过函式update_sysctl,让sysctl_sched_min_granularity=normalized_sysctl_sched_min_granularity,sysctl_sched_latency=normalized_sysctl_sched_latency,sysctl_sched_wakeup_granularity=normalized_sysctl_sched_wakeup_granularit
    do_basic_setup实例在init/main.c中,
    1,diaousermodehelper_init (in kernel/kmod.c),产生khelper workqueue.
    2,调用init_tmpfs (in mm/shmem.c),对VFS注册Temp FileSystem.
    3,呼叫driver_init (in drivers/base/init.c),初始化Linux Kernel Driver System Model.
    4,呼叫init_irq_proc(in kernel/irq/proc.c),初始化 “/proc/irq”与其下的File Nodes.
    5,呼叫do_ctors (in init/main.c),执行位於Symbol __ctors_start 到 __ctors_end间属於Section “.ctors” 的Constructor函式.
    6,透过函式do_initcalls,执行介於Symbol __early_initcall_end与__initcall_end之间的函式呼叫,
    sys_open实例在fs/fcntl.c中,”SYSCALL_DEFINE1(dup, unsigned int, fildes)”,在这会连续执行两次sys_dup,复制两个sys_open开啟/dev/console所產生的档案描述0 (也就是会多生出两个1与2),只是都对应到”/dev/console”,我们在System V streams下的Standard Stream一般而言会有如下的对应
    0:Standard input (stdin)
    1:Standard output (stdout)
    2:Standard error (stderr)
    (為方便大家参考,附上Wiki URL http://en.wikipedia.org/wiki/Standard_streams )
    ramdisk_execute_command与prepare_namespace1,如果ramdisk_execute_command為0,就设定ramdisk_execute_command = “/init”
    2,如果sys_access确认档案ramdisk_execute_command 失败,就把ramdisk_execute_command 设定為0,然后呼叫prepare_namespace去mount root FileSystem.
    integrity_load_keys至此我们初始化工作完成, 文件系统也已经准备好了,那么接下来加载 load integrity keys hook
    load_default_modules加载基本的模块

    kernel_init分析


    static int __ref kernel_init(void *unused)
    {
        int ret;
    
        kernel_init_freeable();
        /* need to finish all async __init code before freeing the memory */
        async_synchronize_full();
        free_initmem();
        mark_rodata_ro();
        system_state = SYSTEM_RUNNING;
        numa_default_policy();
    
        flush_delayed_fput();
    
        rcu_end_inkernel_boot();
    
        if (ramdisk_execute_command) {
                ret = run_init_process(ramdisk_execute_command);
                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) {
                ret = run_init_process(execute_command);
                if (!ret)
                        return 0;
                panic("Requested init %s failed (error %d).",
                      execute_command, ret);
        }
        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.");
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    执行流程说明
    kernel_init_freeable调用kernel_init_freeable完成初始化工作,准备文件系统,准备模块信息
    async_synchronize_full用以同步所有非同步函式呼叫的执行,在这函数中会等待List async_running与async_pending都清空后,才会返回. Asynchronously called functions主要设计用来加速Linux Kernel开机的效率,避免在开机流程中等待硬体反应延迟,影响到开机完成的时间
    free_initmemfree_initmem(in arch/arm/mm/init.c),释放Linux Kernel介於__init_begin到 __init_end属于init Section的函数的所有内存.并会把Page个数加到变量totalram_pages中,作为后续Linux Kernel在配置记忆体时可以使用的Pages. (在这也可把TCM范围(__tcm_start到__tcm_end)释放加入到总Page中,但TCM比外部记忆体有效率,适合多媒体,中断,…etc等对效能要求高的执行需求,放到总Page中,成为可供一般目的配置的存储范围
    system_state设置运行状态SYSTEM_RUNNING
    加载init进程,进入用户空间a,如果ramdisk_execute_command不為0,就执行该命令成為init User Process.
    b,如果execute_command不為0,就执行该命令成為init User Process.
    c,如果上述都不成立,就依序執行如下指令
    run_init_process(“/sbin/init”);
    run_init_process(“/etc/init”);
    run_init_process(“/bin/init”);
    run_init_process(“/bin/sh”);
    也就是说会按照顺序从/sbin/init, /etc/init, /bin/init 與 /bin/sh依序执行第一个 init User Process.
    如果都找不到可以執行的 init Process,就會進入Kernel Panic.如下所示panic(“No init found. Try passing init= option to kernel. ”“See Linux Documentation/init.txt for guidance.”);
    展开全文
  • Android Init进程分析

    千次阅读 2013-04-03 17:47:04
    之前在看android启动过程总是带着完成工作任务的目的去分析代码,但是对于一些代码的细节并不是很清楚,在这里就分析一下Init进程的执行过程。 Init进程是android系统起来之后启动的第一个进程,对于研究android系统...

    之前在看android启动过程总是带着完成工作任务的目的去分析代码,但是对于一些代码的细节并不是很清楚,在这里就分析一下Init进程的执行过程。

    以下框图简要描述系统进程间层次关系


    Init进程是android系统起来之后启动的第一个进程,对于研究android系统的启动过程很重要。它是android系统中的始祖进程,USB守护进程(usbd),挂载守护进程(vold),无线接口守护进程(rild)等都是init进程的子进程。以下截图是我手机的运行进程情况,可以明显看出进程间的关系

    还有一个PID=2的kthread进程,该进程用来创建内核空间的其它进程




    直接根据代码来分析整个进程的执行过程。

    int main(int argc, char **argv)
    {
        int fd_count = 0;
        struct pollfd ufds[4];//存放pollfd
        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);//ueventd是init的软链接,执行这个进程的时候相当于执行init进程,然后根据进程名进入相应的执行流程
    
        /* 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.
             */
    

    以上主要创建一些文件系统目录并挂载相应的文件系统,proc文件系统是重要的内核数据的接口,可以通过它读取一些系统信息还能操作内核参数

        open_devnull_stdio();//重定向标准输入,输入,错误到/dev/__null__(dup2复制文件句柄,0,1,2分别代表标准输入 输出 错误) 屏蔽标准输入输出
        log_init();//设置log信息输出设备/dev/__kmsg__,unlink之后其他进程无法访问,阅读源码定向到printk函数输出 初始化log系统
      
        property_init();//初始化属性系统,这个可以以后分析
    
    
        get_hardware_name(hardware, &revision);
    
    
        process_kernel_cmdline();
    
    
    #ifdef HAVE_SELINUX
        INFO("loading selinux policy\n");
        selinux_load_policy();
    #endif
    
    
        is_charger = !strcmp(bootmode, "charger");
    
    
        INFO("property init\n");
        if (!is_charger)
            property_load_boot_defaults();

    这里导入相应的处理函数,分析执行过程

    static void import_kernel_cmdline(int in_qemu)
    {
        char cmdline[1024];
        char *ptr;
        int fd;
    
        fd = open("/proc/cmdline", O_RDONLY);
        if (fd >= 0) {
            int n = read(fd, cmdline, 1023);
            if (n < 0) n = 0;
    
            /* get rid of trailing newline, it happens */
            if (n > 0 && cmdline[n-1] == '\n') n--;
    	//读取/proc/cmdline中的信息,存放在cmdline字符数组并进行处理
            cmdline[n] = 0;
            close(fd);
        } else {
            cmdline[0] = 0;
        }
    
        ptr = cmdline;
        while (ptr && *ptr) {
            char *x = strchr(ptr, ' ');
            if (x != 0) *x++ = 0;
            import_kernel_nv(ptr, in_qemu);//根据' '间断符逐行分析文本
            ptr = x;
        }
    
            /* don't expose the raw commandline to nonpriv processes */
        chmod("/proc/cmdline", 0440);
    }
    
    static void import_kernel_nv(char *name, int in_qemu)
    {
        char *value = strchr(name, '=');
    
        if (value == 0) {
    	if (!strcmp(name, "calibration"))
    	    calibration = 1;//表示要校准还是什么?
    	return;
        }
        *value++ = 0;
        if (*name == 0) return;
    
        if (!in_qemu)
        {
            /* on a real device, white-list the kernel options */
            if (!strcmp(name,"qemu")) {
                strlcpy(qemu, value, sizeof(qemu));
            } else if (!strcmp(name,"androidboot.console")) {
                strlcpy(console, value, sizeof(console));
            } else if (!strcmp(name,"androidboot.mode")) {
                strlcpy(bootmode, value, sizeof(bootmode));//启动模式
            } else if (!strcmp(name,"androidboot.serialno")) {
                strlcpy(serialno, value, sizeof(serialno));
            } else if (!strcmp(name,"androidboot.baseband")) {
                strlcpy(baseband, value, sizeof(baseband));//基带
            } else if (!strcmp(name,"androidboot.carrier")) {
                strlcpy(carrier, value, sizeof(carrier));
            } else if (!strcmp(name,"androidboot.bootloader")) {
                strlcpy(bootloader, value, sizeof(bootloader));
            } else if (!strcmp(name,"androidboot.hardware")) {
                strlcpy(hardware, value, sizeof(hardware));
            }//将以上设备信息存放在定义的字符数组中
        } else {
            /* in the emulator, export any kernel option with the
             * ro.kernel. prefix */
            char  buff[32];
            int   len = snprintf( buff, sizeof(buff), "ro.kernel.%s", name );
            if (len < (int)sizeof(buff)) {
                property_set( buff, value );
            }
        }
    }
    
    

        get_hardware_name(hardware, &revision);
        snprintf(tmp, sizeof(tmp), "/init.%s.rc", hardware);
        init_parse_config_file(tmp);//分析相应硬件版本的rc文件
    
    

    init.rc文件有自己相应的语法,分析rc文件也是根据对应的语法来分析,这里引入一片简单介绍init.rc语法的文章

    Android init.rc脚本解析

    int init_parse_config_file(const char *fn)
    {
        char *data;
        data = read_file(fn, 0);//这里通过read_file函数将fn文件中的数据全部读取到data缓冲区中,malloc分配空间
        if (!data) return -1;
        //这里开始真正分析脚本中的命令
        parse_config(fn, data);
        DUMP();
        return 0;
    }
    

    解析过程会先将init.rc文件action与service进行解析,然后插入到链表中依次执行,查看源码中对链表的定义

    #ifndef _CUTILS_LIST_H_
    #define _CUTILS_LIST_H_
    
    #include <stddef.h>
    
    #ifdef __cplusplus
    extern "C" {
    #endif /* __cplusplus */
    //声明一个双向链表
    struct listnode
    {
        struct listnode *next;
        struct listnode *prev;
    };
    //计算结构体数据变量相对于结构体首地址的偏移量,这个很重要
    #define node_to_item(node, container, member) \
        (container *) (((char*) (node)) - offsetof(container, member))
    //声明一个双向链表,并且指向自己
    #define list_declare(name) \
        struct listnode name = { \
            .next = &name, \
            .prev = &name, \
        }
    //遍历链表
    #define list_for_each(node, list) \
        for (node = (list)->next; node != (list); node = node->next)
    //反向遍历链表
    #define list_for_each_reverse(node, list) \
        for (node = (list)->prev; node != (list); node = node->prev)
    
    void list_init(struct listnode *list);//初始化一个双向链表
    void list_add_tail(struct listnode *list, struct listnode *item);//将结点添加至双向链表尾部
    void list_remove(struct listnode *item);
    
    #define list_empty(list) ((list) == (list)->next)
    #define list_head(list) ((list)->next)
    #define list_tail(list) ((list)->prev)
    
    #ifdef __cplusplus
    };
    #endif /* __cplusplus */
    
    #endif
    

    这里声明了三个链表

    static list_declare(service_list);
    static list_declare(action_list);
    static list_declare(action_queue);
    


    static void parse_config(const char *fn, char *s)
    {
        struct parse_state state;
        char *args[INIT_PARSER_MAXARGS];//允许解析出来的命令行最多有64个参数
        int nargs;
    
        nargs = 0;
        state.filename = fn;//文件名
        state.line = 0;
        state.ptr = s;//data
        state.nexttoken = 0;
        state.parse_line = parse_line_no_op;//此时解析函数是空操作
        for (;;) {
            switch (next_token(&state)) {//通过next_token函数来寻找字符数组中的关键标记
            //这里面省略了一些字符的处理(如‘\r’, '\t', '"', ' '等),只针对有效字符进行处理('\0', '\n'等)
            //#define T_EOF 0    #define T_TEXT 1    #define T_NEWLINE 2
            case T_EOF:
                state.parse_line(&state, 0, 0);                                                                                                     goto parser_done;
                return;
            case T_NEWLINE:
                if (nargs) {
                    int kw = lookup_keyword(args[0]);//这里将分析第一个参数所代表的关键字
                    //根据字符匹配返回已定义好的宏定义
                    if (kw_is(kw, SECTION)) {//当关键字是on或service或import
                        state.parse_line(&state, 0, 0); //此时相当于什么都没做
                        parse_new_section(&state, kw, nargs, args);//对state.parse_line进行填充
                    } else {
                        state.parse_line(&state, nargs, args);//对于NEWLINE不是on service import的调用parse_line,而在后面的填充中                                                                     //parse_line函数即parse_line_action
    
                        //回调相应的处理函数
                    }
                    nargs = 0;
                }
                break;
            case T_TEXT://不处理
                if (nargs < INIT_PARSER_MAXARGS) {
                    args[nargs++] = state.text;
                }
                break;
            }
        }                                                                                                                                   parser_done:
        list_for_each(node, &import_list) {//文件解析结束后解析新导入的rc文件
             struct import *import = node_to_item(node, struct import, list);
             int ret;
          
             //循环取出rc文件的路径   
             INFO("importing '%s'", import->filename);
             ret = init_parse_config_file(import->filename);//重新解析rc文件
             if (ret)
                 ERROR("could not import file '%s' from '%s'\n",
                       import->filename, fn);
        }
    }
    

    首先查看一下keywords.h这个文件,对分析过程有帮助

    #ifndef KEYWORD//防止重复定义
    int do_chroot(int nargs, char **args);
    int do_chdir(int nargs, char **args);
    int do_class_start(int nargs, char **args);
    int do_class_stop(int nargs, char **args);
    int do_class_reset(int nargs, char **args);
    int do_domainname(int nargs, char **args);
    int do_exec(int nargs, char **args);
    int do_export(int nargs, char **args);
    int do_hostname(int nargs, char **args);
    int do_ifup(int nargs, char **args);
    int do_insmod(int nargs, char **args);
    int do_mkdir(int nargs, char **args);
    int do_mount_all(int nargs, char **args);
    int do_mount(int nargs, char **args);
    int do_restart(int nargs, char **args);
    int do_restorecon(int nargs, char **args);
    int do_rm(int nargs, char **args);
    int do_rmdir(int nargs, char **args);
    int do_setcon(int nargs, char **args);
    int do_setenforce(int nargs, char **args);
    int do_setkey(int nargs, char **args);
    int do_setprop(int nargs, char **args);
    int do_setrlimit(int nargs, char **args);
    int do_setsebool(int nargs, char **args);
    int do_start(int nargs, char **args);
    int do_stop(int nargs, char **args);
    int do_trigger(int nargs, char **args);
    int do_symlink(int nargs, char **args);
    int do_sysclktz(int nargs, char **args);
    int do_write(int nargs, char **args);
    int do_copy(int nargs, char **args);
    int do_chown(int nargs, char **args);
    int do_chmod(int nargs, char **args);
    int do_loglevel(int nargs, char **args);
    int do_load_persist_props(int nargs, char **args);
    int do_wait(int nargs, char **args);
    int do_ubiattach(int argc, char **args);
    int do_ubidetach(int argc, char **args);
    #define __MAKE_KEYWORD_ENUM__
    #define KEYWORD(symbol, flags, nargs, func) K_##symbol,//#与##是宏定义的连接符
    enum {
        K_UNKNOWN,
    #endif
        KEYWORD(capability,  OPTION,  0, 0)//这里返回的相当于K_capabikity
        KEYWORD(chdir,       COMMAND, 1, do_chdir)//K_chdir
        KEYWORD(chroot,      COMMAND, 1, do_chroot)//K_chroot
        KEYWORD(class,       OPTION,  0, 0)
        KEYWORD(class_start, COMMAND, 1, do_class_start)
        KEYWORD(class_stop,  COMMAND, 1, do_class_stop)
        KEYWORD(class_reset, COMMAND, 1, do_class_reset)
        KEYWORD(console,     OPTION,  0, 0)
        KEYWORD(critical,    OPTION,  0, 0)
        KEYWORD(dalvik_recache,    OPTION,  0, 0)
        KEYWORD(disabled,    OPTION,  0, 0)
        KEYWORD(domainname,  COMMAND, 1, do_domainname)
        KEYWORD(exec,        COMMAND, 1, do_exec)
        KEYWORD(export,      COMMAND, 2, do_export)
        KEYWORD(group,       OPTION,  0, 0)
        KEYWORD(hostname,    COMMAND, 1, do_hostname)
        KEYWORD(ifup,        COMMAND, 1, do_ifup)
        KEYWORD(insmod,      COMMAND, 1, do_insmod)
        KEYWORD(import,      SECTION, 1, 0)
        KEYWORD(keycodes,    OPTION,  0, 0)
        KEYWORD(mkdir,       COMMAND, 1, do_mkdir)
        KEYWORD(mount_all,   COMMAND, 1, do_mount_all)
        KEYWORD(mount,       COMMAND, 3, do_mount)
        KEYWORD(on,          SECTION, 0, 0)
        KEYWORD(oneshot,     OPTION,  0, 0)
        KEYWORD(onrestart,   OPTION,  0, 0)
        KEYWORD(restart,     COMMAND, 1, do_restart)
        KEYWORD(restorecon,  COMMAND, 1, do_restorecon)
        KEYWORD(rm,          COMMAND, 1, do_rm)
        KEYWORD(rmdir,       COMMAND, 1, do_rmdir)
        KEYWORD(seclabel,    OPTION,  0, 0)
        KEYWORD(service,     SECTION, 0, 0)
        KEYWORD(setcon,      COMMAND, 1, do_setcon)
        KEYWORD(setenforce,  COMMAND, 1, do_setenforce)
        KEYWORD(setenv,      OPTION,  2, 0)
        KEYWORD(setkey,      COMMAND, 0, do_setkey)
        KEYWORD(setprop,     COMMAND, 2, do_setprop)
        KEYWORD(setrlimit,   COMMAND, 3, do_setrlimit)
        KEYWORD(setsebool,   COMMAND, 1, do_setsebool)
        KEYWORD(socket,      OPTION,  0, 0)
        KEYWORD(start,       COMMAND, 1, do_start)
        KEYWORD(stop,        COMMAND, 1, do_stop)
        KEYWORD(trigger,     COMMAND, 1, do_trigger)
        KEYWORD(symlink,     COMMAND, 1, do_symlink)
        KEYWORD(sysclktz,    COMMAND, 1, do_sysclktz)
        KEYWORD(user,        OPTION,  0, 0)
        KEYWORD(wait,        COMMAND, 1, do_wait)
        KEYWORD(write,       COMMAND, 2, do_write)
        KEYWORD(copy,        COMMAND, 2, do_copy)
        KEYWORD(chown,       COMMAND, 2, do_chown)
        KEYWORD(chmod,       COMMAND, 2, do_chmod)
        KEYWORD(loglevel,    COMMAND, 1, do_loglevel)
        KEYWORD(load_persist_props,    COMMAND, 0, do_load_persist_props)
        KEYWORD(ubiattach,   COMMAND, 1, do_ubiattach)
        KEYWORD(ubidetach,   COMMAND, 1, do_ubidetach)
        KEYWORD(ioprio,      OPTION,  0, 0)
    #ifdef __MAKE_KEYWORD_ENUM__
        KEYWORD_COUNT,
    };
    #undef __MAKE_KEYWORD_ENUM__
    #undef KEYWORD
    #endif
    
    以上通过枚举建立key-value映射,也就相当于map的功能,我们会通过查找key来执行对应的操作

    再来查看init_parser.c这个文件,在其中两次include keywords.h这个文件

    #include "keywords.h"//这是得到enum{K_UNKNOWN,K_capability,K_chdir,K_chroot......}
    
    #define KEYWORD(symbol, flags, nargs, func) \
        [ K_##symbol ] = { #symbol, func, nargs + 1, flags, },
    
    struct {
        const char *name;
        int (*func)(int nargs, char **args);
        unsigned char nargs;
        unsigned char flags;
    } keyword_info[KEYWORD_COUNT] = {
        [ K_UNKNOWN ] = { "unknown", 0, 0, 0 },
    #include "keywords.h"//之前已经include得过,此时为[ K_capability ] = { "capability", 0, 1, OPTION }                                                         //[ K_chdir ] = { "chdir", do_chdir, 2, COMMAND }                                                                                   //[ K_chroot ] = { "chroot", do_chroot, 3, COMMAND}
    };
    此时keyword_info保存的关于键值对的结构体数组

    实际上上面两次include的代码如下

    int do_chroot(int nargs, char **args);
    … …
    enum
    {
    K_UNKNOWN,
    K_ capability,
    K_ chdir,
    … …
    }
    #define KEYWORD(symbol, flags, nargs, func) \
        [ K_##symbol ] = { #symbol, func, nargs + 1, flags, },
    struct {
        const char *name;
        int (*func)(int nargs, char **args);
        unsigned char nargs;
        unsigned char flags;
    } keyword_info[KEYWORD_COUNT] = {
        [ K_UNKNOWN ] = { "unknown", 0, 0, 0 },
        [K_ capability] = {" capability ", 0, 1, OPTION },
        [K_ chdir] = {"chdir", do_chdir ,2, COMMAND},
        … …
    };

    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);//分析对应的on判断  其中nargs与args对应于命令的参数个数和参数列表,类似main函数
            if (state->context) {
                state->parse_line = parse_line_action;//赋值给每个新行的parse_line
                return;
            }
            break;
        case K_import:
            parse_import(state, nargs, args);
            break;
        }
        state->parse_line = parse_line_no_op;
    }
    
    

    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;
        }//限定nargs只能等于2
        act = calloc(1, sizeof(*act));
        act->name = args[1];
        list_init(&act->commands);//初始化一个commands链表
        list_add_tail(&action_list, &act->alist);//将当前act->alist结点添加到action_list链表尾部
            /* XXX add to hash */
        return act;
    }
    
    static void parse_line_action(struct parse_state* state, int nargs, char **args)
    {
        struct command *cmd;
        struct action *act = state->context;
        int (*func)(int nargs, char **args);
        int kw, n;
    
        if (nargs == 0) {
            return;
        }
    
        kw = lookup_keyword(args[0]);
        if (!kw_is(kw, COMMAND)) {//查找关键字是否为COMMAND,不是的就返回
            parse_error(state, "invalid command '%s'\n", args[0]);
            return;
        }
    
        n = kw_nargs(kw);
        if (nargs < n) {
            parse_error(state, "%s requires %d %s\n", args[0], n - 1,
                n > 2 ? "arguments" : "argument");
            return;
        }
        cmd = malloc(sizeof(*cmd) + sizeof(char*) * nargs);
        cmd->func = kw_func(kw);//这个时候就有对应的处理函数
        cmd->nargs = nargs;
        memcpy(cmd->args, args, sizeof(char*) * nargs);
        list_add_tail(&act->commands, &cmd->clist);//将新建的cmd->clist节点添加到commands尾部                                              }             
    对应的链表结构图如下

    通常我们定义链表都是将结构体变量与指针放在一起,如下所示:

    typedef struct DulNode{

         ElemType data;

         struct DulNode *prev;

         struct DulNode *next;

    }DulNode, *DuLinkList;

    源码中这种建立链表的方式有些特别,建立只有指针的链表,将链表中的结点放在结构体中,通过求偏移量来访问结构体变量,提高了效率,值得借鉴

    offsetof与container_of可以自己查阅学习


    这里还涉及到一些结构体Action及对应的Command,Service也是如此

    struct command
    {
            /* list of commands in an action */
        struct listnode clist;
    
    
        int (*func)(int nargs, char **args);
        int nargs;
        char *args[1];
    };
        
    struct action {
            /* node in list of all actions */
        struct listnode alist;
            /* node in the queue of pending actions */
        struct listnode qlist;
            /* node in list of actions for a trigger */
        struct listnode tlist;
    
    
        unsigned hash;
        const char *name;
        
        struct listnode commands;
        struct command *current;
    };
    
    
    struct socketinfo {
        struct socketinfo *next;//这里用到单链表形式的结构体指针,用来管理多个socket
        const char *name;
        const char *type;
        uid_t uid;
        gid_t gid;
        int perm;
    };
    
    
    struct svcenvinfo {
        struct svcenvinfo *next;//这里用到单链表形式的结构体指针,管理多个env
    
        const char *name;
        const char *value;
    };
    
    
    struct service {
            /* list of all services */
        struct listnode slist;//链表结点
    
    
        const char *name;//服务名
        const char *classname;//class名  如class main等
    
    
        unsigned flags;//标志
        pid_t pid;//分配的进程号
        time_t time_started;    /* time of last start *///service启动的时间
        time_t time_crashed;    /* first crash within inspection window *///崩溃过程时间
        int nr_crashed;         /* number of times crashed within window *///崩溃次数
        
        uid_t uid;//分配的用户id
        gid_t gid;//分配的组id
        gid_t supp_gids[NR_SVC_SUPP_GIDS];
        size_t nr_supp_gids;
    
    
    #ifdef HAVE_SELINUX
        char *seclabel;
    #endif
    
    
        struct socketinfo *sockets;//socket信息结构体
        struct svcenvinfo *envvars;//环境变量结构体
    
    
        struct action onrestart;  /* Actions to execute on restart. *///restart时需执行的action
        
        /* keycodes for triggering this service via /dev/keychord */
        int *keycodes;
        int nkeycodes;
        int keychord_id;
    
    
        int ioprio_class;
        int ioprio_pri;
    
    
        int nargs;
        /* "MUST BE AT THE END OF THE STRUCT" */
        char *args[1];
    }; /*     ^-------'args' MUST be at the end of this struct! */                                                                                                                                 

    查看分析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);//初始化一个action onrestart的commands双向链表
        list_add_tail(&service_list, &svc->slist);//将当前svc->slist结点添加至service_list链表
        return svc;
    }
    
    static void parse_line_service(struct parse_state *state, int nargs, char **args)
    {
        struct service *svc = state->context;
        struct command *cmd;
        int i, kw, kw_nargs;
    
        if (nargs == 0) {
            return;
        }
    
        svc->ioprio_class = IoSchedClass_NONE;
    
        kw = lookup_keyword(args[0]);
        switch (kw) {
        case K_capability:
            break;
        case K_class:
            if (nargs != 2) {
                parse_error(state, "class option requires a classname\n");
            } else {
                svc->classname = args[1];
            }
            break;
        case K_console:
            svc->flags |= SVC_CONSOLE;//设置flags为SVC_CONSOLE
            break;
        case K_disabled:
            svc->flags |= SVC_DISABLED;//设置flags
            svc->flags |= SVC_RC_DISABLED;
            break;
        case K_ioprio:
            if (nargs != 3) {
                parse_error(state, "ioprio optin usage: ioprio <rt|be|idle> <ioprio 0-7>\n");
            } else {
                svc->ioprio_pri = strtoul(args[2], 0, 8);
    
                if (svc->ioprio_pri < 0 || svc->ioprio_pri > 7) {
                    parse_error(state, "priority value must be range 0 - 7\n");
                    break;
                }
    
                if (!strcmp(args[1], "rt")) {
                    svc->ioprio_class = IoSchedClass_RT;
                } else if (!strcmp(args[1], "be")) {
                    svc->ioprio_class = IoSchedClass_BE;
                } else if (!strcmp(args[1], "idle")) {
                    svc->ioprio_class = IoSchedClass_IDLE;
                } else {
                    parse_error(state, "ioprio option usage: ioprio <rt|be|idle> <0-7>\n");
                }
            }
            break;
        case K_group:
            if (nargs < 2) {
                parse_error(state, "group option requires a group id\n");
            } else if (nargs > NR_SVC_SUPP_GIDS + 2) {
                parse_error(state, "group option accepts at most %d supp. groups\n",
                            NR_SVC_SUPP_GIDS);
            } else {
                int n;
                svc->gid = decode_uid(args[1]);
                for (n = 2; n < nargs; n++) {
                    svc->supp_gids[n-2] = decode_uid(args[n]);
                }
                svc->nr_supp_gids = n - 2;
            }
            break;
        case K_keycodes:
            if (nargs < 2) {
                parse_error(state, "keycodes option requires atleast one keycode\n");
            } else {
                svc->keycodes = malloc((nargs - 1) * sizeof(svc->keycodes[0]));
                if (!svc->keycodes) {
                    parse_error(state, "could not allocate keycodes\n");
                } else {
                    svc->nkeycodes = nargs - 1;
                    for (i = 1; i < nargs; i++) {
                        svc->keycodes[i - 1] = atoi(args[i]);
                    }
                }
            }
            break;
        case K_oneshot:
            svc->flags |= SVC_ONESHOT;
            break;
        case K_onrestart:
            nargs--;
            args++;
            kw = lookup_keyword(args[0]);
            if (!kw_is(kw, COMMAND)) {
                parse_error(state, "invalid command '%s'\n", args[0]);
                break;
            }
            kw_nargs = kw_nargs(kw);
            if (nargs < kw_nargs) {
                parse_error(state, "%s requires %d %s\n", args[0], kw_nargs - 1,
                    kw_nargs > 2 ? "arguments" : "argument");
                break;
            }
    
            cmd = malloc(sizeof(*cmd) + sizeof(char*) * nargs);
            cmd->func = kw_func(kw);
            cmd->nargs = nargs;
            memcpy(cmd->args, args, sizeof(char*) * nargs);
            list_add_tail(&svc->onrestart.commands, &cmd->clist);
            break;
        case K_critical:
            svc->flags |= SVC_CRITICAL;
            break;
        case K_dalvik_recache:
            svc->flags |= SVC_DALVIK_RECACHE;
            break;
        case K_setenv: { /* name value */
            struct svcenvinfo *ei;
            if (nargs < 2) {
                parse_error(state, "setenv option requires name and value arguments\n");
                break;
            }
            ei = calloc(1, sizeof(*ei));
            if (!ei) {
                parse_error(state, "out of memory\n");
                break;
            }
            ei->name = args[1];
            ei->value = args[2];
            ei->next = svc->envvars;//单链表操作
            svc->envvars = ei;//单链表操作
            break;
        }
        case K_socket: {/* name type perm [ uid gid ] */
            struct socketinfo *si;
            if (nargs < 4) {
                parse_error(state, "socket option requires name, type, perm arguments\n");
                break;
            }
            if (strcmp(args[2],"dgram") && strcmp(args[2],"stream")
                    && strcmp(args[2],"seqpacket")) {
                parse_error(state, "socket type must be 'dgram', 'stream' or 'seqpacket'\n");
                break;
            }
            si = calloc(1, sizeof(*si));
            if (!si) {
                parse_error(state, "out of memory\n");
                break;
            }
            si->name = args[1];
            si->type = args[2];
            si->perm = strtoul(args[3], 0, 8);
            if (nargs > 4)
                si->uid = decode_uid(args[4]);
            if (nargs > 5)
                si->gid = decode_uid(args[5]);
            si->next = svc->sockets;//这种插入方式是逆序插入
            svc->sockets = si;//将新链表表头赋值给sockets
            break;
        }
        case K_user:
            if (nargs != 2) {
                parse_error(state, "user option requires a user id\n");
            } else {
                svc->uid = decode_uid(args[1]);
            }
            break;
        case K_seclabel:
    #ifdef HAVE_SELINUX
            if (nargs != 2) {
                parse_error(state, "seclabel option requires a label string\n");
            } else {
                svc->seclabel = args[1];
            }
    #endif
            break;
    
        default:
            parse_error(state, "invalid option '%s'\n", args[0]);
        }
    }
    
    

    同理,根据service解析代码我们也能画出service_list的结构图,不过比较特殊的是service  option项比较多  情况各异  有很大出入



    从以上代码可以看出,paser_action主要解析一个Action刚开始的情况并添加到action_list链表,paser_line_action则解析Action中的command并添加到command链表

    service的解析函数亦同理

    最后分析import加入的新rc文件,init.rc文件便解析完成,并将所有的action和service分别添加到action_list和service_list链表


    跟随代码,下面执行这些函数,这里可能有些疑惑,上面明显声明了三个链表,但是一直都没有涉及到action_queue这个链表。

        
        action_for_each_trigger("early-init",action_add_queue_tail);
        queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done");
      

    分析这两个个函数看到底做了什么处理,其中wait_for_coldboot_done_action是一个执行函数

    void action_for_each_trigger(const char *trigger,
                                 void (*func)(struct action *act))
    {
        struct listnode *node;
        struct action *act;
        list_for_each(node, &action_list) {//遍历已经完整的action_list链表,查找early-init action
            act = node_to_item(node, struct action, alist);
            if (!strcmp(act->name, trigger)) {
                func(act);//执行action_add_queue_tail
            }
        }
    }
    
    void action_add_queue_tail(struct action *act)
    {
        list_add_tail(&action_queue, &act->qlist);//将early-init action中的qlist结点添加到action_queue链表中(这里开始涉及到action_queue链表)
    }                                                                                                                                                                                                                                                                        void queue_builtin_action(int (*func)(int nargs, char **args), char *name)
    {
        struct action *act;
        struct command *cmd;
    
    
        act = calloc(1, sizeof(*act));//首先新建一个action
        act->name = name;
        list_init(&act->commands);//初始化commands链表
    
    
        cmd = calloc(1, sizeof(*cmd));//新建一个command结构体
        cmd->func = func;
        cmd->args[0] = name;
        list_add_tail(&act->commands, &cmd->clist);//将cmd->clist结点添加到commands链表尾部
    
    
        list_add_tail(&action_list, &act->alist);//将act->alist结点添加到上面的action_list尾部
        action_add_queue_tail(act);//将这个action添加到action_queue链表尾部
    }                                                                                                                                      
    

    从以上代码分析可看出action_for_each_trigger函数实现查找action_list中的action,并将其添加到action_queue尾部

    queue_builtin_action则是新建一个action,将其分别添加到action_list和action_queue链表尾部

        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(keychord_init_action, "keychord_init");
        queue_builtin_action(console_init_action, "console_init");
    
    
        /* execute all the boot actions to get us started */
        action_for_each_trigger("init", action_add_queue_tail);
    
    
        /* skip mounting filesystems in charger mode */
        if (!is_charger) {
            action_for_each_trigger("early-fs", action_add_queue_tail);
            action_for_each_trigger("fs", action_add_queue_tail);
            action_for_each_trigger("post-fs", action_add_queue_tail);
            action_for_each_trigger("post-fs-data", action_add_queue_tail);
        }
    
    
        queue_builtin_action(property_service_init_action, "property_service_init");
        queue_builtin_action(signal_init_action, "signal_init");
        queue_builtin_action(check_startup_action, "check_startup");
    
    
        if (is_charger) {
            action_for_each_trigger("charger", action_add_queue_tail);
        } else {
            action_for_each_trigger("early-boot", action_add_queue_tail);
            queue_builtin_action(ubootenv_init_action, "ubootenv_init");
            action_for_each_trigger("boot", action_add_queue_tail);
        }
    
    
            /* run all property triggers based on current state of the properties */
        queue_builtin_action(queue_property_triggers_action, "queue_property_triggers");
    
    
    
    
    #if BOOTCHART
        queue_builtin_action(bootchart_init_action, "bootchart_init");
    #endif
    
    

    从以上的代码实现的功能都是类似的

    接着阅读init.c后面的源码

        for(;;) {
            int nr, i, timeout = -1;
    
            execute_one_command();//从链表中取出结点相应执行然后remove
            //分析过这个函数,在这里还有个疑问,该函数都是从action队列中去结点执行,但是系统的service是怎么执行的
            //难道service链表不可能只注册不执行
            //这里注意on boot section中最后一个command(class_start default),最终调用do_class_start
    


      
    
    static struct command *get_first_command(struct action *act)
    {
        struct listnode *node;
        node = list_head(&act->commands);
        if (!node || list_empty(&act->commands))
            return NULL;
    
    
        return node_to_item(node, struct command, clist);
    }                                                                                                                                                                                                                                                             static struct command *get_next_command(struct action *act, struct command *cmd)
    {
        struct listnode *node;
        node = cmd->clist.next;
        if (!node)
            return NULL;
        if (node == &act->commands)
            return NULL;
    
        return node_to_item(node, struct command, clist);
    }
    
    static int is_last_command(struct action *act, struct command *cmd)
    {
        return (list_tail(&act->commands) == &cmd->clist);//判断cmd->clist结点是否为act->commands链表最后一个
    }
    
    void execute_one_command(void)
    {
        int ret;
        //第一次执行cur_action是action结构体指针,cur_command是command结构体指针,都为null
        if (!cur_action || !cur_command || is_last_command(cur_action, cur_command)) {
            cur_action = action_remove_queue_head();//从非空action_queue链表中取出头部结点并移除
            cur_command = NULL;
            if (!cur_action)//cur_action为null时返回
                return;
            INFO("processing action %p (%s)\n", cur_action, cur_action->name);
            cur_command = get_first_command(cur_action);//从cur_action中取出第一个command
        } else {
            cur_command = get_next_command(cur_action, cur_command);//依次取出后面的command
        }
    
        if (!cur_command)//cur_command为null时返回
            return;
    
        ret = cur_command->func(cur_command->nargs, cur_command->args);//这里才开始执行command操作
        INFO("command '%s' r=%d\n", cur_command->args[0], ret);
    }
       
    当执行完一个action中的所有command后,通过for(;;)再依次执行action_queue中的下一个action

    因此action_queue链表中的顺序就是系统真正的执行顺序,如图所示



    到这里,大体上弄清楚了init的执行过程,但是这里有个疑问,所有的action都已经执行完毕,根本没有涉及到service

    查看init.rc文件我们可以看到在on boot这个action中对应的command为 

        class_start core
        class_start main

    根据之前的parse_line_action我们可以跟踪到do_class_start函数

    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) {//遍历service_list链表
            svc = node_to_item(node, struct service, slist);//从service_list链表中返回对应的service结构体
            if (!strcmp(svc->classname, classname)) {//比较classname是否为core,main等
                func(svc);
            }
        }
    }
    以上的classname为parse_line_service函数中解析的,即service中class一项
    static void service_start_if_not_disabled(struct service *svc)
    {
        if (!(svc->flags & SVC_DISABLED)) {//判断svc的flags是否为DISABLED
            service_start(svc, NULL);
        }
    }
    
    void service_start(struct service *svc, const char *dynamic_args)
    {
        struct stat s;
        pid_t pid;
        int needs_console;
        int n;
    
            /* starting a service removes it from the disabled or reset
             * state and immediately takes it out of the restarting
             * state if it was in there
             */
        svc->flags &= (~(SVC_DISABLED|SVC_RESTARTING|SVC_RESET));
        svc->time_started = 0;
    
            /* running processes require no additional work -- if
             * they're in the process of exiting, we've ensured
             * that they will immediately restart on exit, unless
             * they are ONESHOT
             */
        if (svc->flags & SVC_RUNNING) {
            return;
        }
    
        needs_console = (svc->flags & SVC_CONSOLE) ? 1 : 0;
        if (needs_console && (!have_console)) {
            ERROR("service '%s' requires console\n", svc->name);
            svc->flags |= SVC_DISABLED;
            return;
        }
    
        if (stat(svc->args[0], &s) != 0) {
            ERROR("cannot find '%s', disabling '%s'\n", svc->args[0], svc->name);
            svc->flags |= SVC_DISABLED;
            return;
        }
    
        if ((!(svc->flags & SVC_ONESHOT)) && dynamic_args) {
            ERROR("service '%s' must be one-shot to use dynamic args, disabling\n",
                   svc->args[0]);
            svc->flags |= SVC_DISABLED;
            return;
        }
    
        NOTICE("starting '%s'\n", svc->name);
        //以上主要设置该服务的一些标志
        pid = fork();//通过fork()创建子进程
    
        if (pid == 0) {//此为子进程
            struct socketinfo *si;
            struct svcenvinfo *ei;
            char tmp[32];
            int fd, sz;
    
            if (properties_inited()) {
                get_property_workspace(&fd, &sz);//获取属性系统句柄
                sprintf(tmp, "%d,%d", dup(fd), sz);
                add_environment("ANDROID_PROPERTY_WORKSPACE", tmp);
            }
    
            for (ei = svc->envvars; ei; ei = ei->next)
                add_environment(ei->name, ei->value);//为该服务添加环境变量
    
            for (si = svc->sockets; si; si = si->next) {
                int socket_type = (
                        !strcmp(si->type, "stream") ? SOCK_STREAM :
                            (!strcmp(si->type, "dgram") ? SOCK_DGRAM : SOCK_SEQPACKET));
                int s = create_socket(si->name, socket_type,//创建通信socket,相当于每个service都创建socket,用来与其它进程通信
                                      si->perm, si->uid, si->gid);
                if (s >= 0) {
                    publish_socket(si->name, s);//将句柄s添加到环境变量中,该环境变量为ANDROID_SOCKET_XXX
                }
            }
    
            if (svc->ioprio_class != IoSchedClass_NONE) {
                if (android_set_ioprio(getpid(), svc->ioprio_class, svc->ioprio_pri)) {//设置pid
                    ERROR("Failed to set pid %d ioprio = %d,%d: %s\n",
                          getpid(), svc->ioprio_class, svc->ioprio_pri, strerror(errno));
                }
            }
    
            if (needs_console) {
                setsid();
                open_console();//打开控制台
            } else {
                zap_stdio();
            }
    
    #if 0
            for (n = 0; svc->args[n]; n++) {
                INFO("args[%d] = '%s'\n", n, svc->args[n]);
            }
            for (n = 0; ENV[n]; n++) {
                INFO("env[%d] = '%s'\n", n, ENV[n]);
            }
    #endif
            //配置进程id和组
            setpgid(0, getpid());
    
        /* as requested, set our gid, supplemental gids, and uid */
            if (svc->gid) {
                if (setgid(svc->gid) != 0) {
                    ERROR("setgid failed: %s\n", strerror(errno));
                    _exit(127);
                }
            }
            if (svc->nr_supp_gids) {
                if (setgroups(svc->nr_supp_gids, svc->supp_gids) != 0) {
                    ERROR("setgroups failed: %s\n", strerror(errno));
                    _exit(127);
                }
            }
            if (svc->uid) {
                if (setuid(svc->uid) != 0) {
                    ERROR("setuid failed: %s\n", strerror(errno));
                    _exit(127);
                }
            }
    
            if (!dynamic_args) {
                if (execve(svc->args[0], (char**) svc->args, (char**) ENV) < 0) {
                    ERROR("cannot execve('%s'): %s\n", svc->args[0], strerror(errno));
                }
            } else {
                char *arg_ptrs[INIT_PARSER_MAXARGS+1];
                int arg_idx = svc->nargs;
                char *tmp = strdup(dynamic_args);
                char *next = tmp;
                char *bword;
    
                /* Copy the static arguments */
                memcpy(arg_ptrs, svc->args, (svc->nargs * sizeof(char *)));
    
                while((bword = strsep(&next, " "))) {
                    arg_ptrs[arg_idx++] = bword;
                    if (arg_idx == INIT_PARSER_MAXARGS)
                        break;
                }
                arg_ptrs[arg_idx] = '\0';
                execve(svc->args[0], (char**) arg_ptrs, (char**) ENV);//执行新进程调用的函数
            }
            _exit(127);
        }
    
        if (pid < 0) {//fork()错误
            ERROR("failed to start '%s'\n", svc->name);
            svc->pid = 0;
            return;
        }
    
        svc->time_started = gettime();
        svc->pid = pid;
        svc->flags |= SVC_RUNNING;
    
        if (properties_inited())
            notify_service_state(svc->name, "running");//设置服务运行状态
    }
    

    从上面可以看出service的运行过程,同理,service_list链表也得到执行

    execute_one_command函数在for循环中一直查找action_queue链表中是否为空,不为空的情况下就移除队首的结点并执行,否则就直接返回


            restart_processes();//判断是否有进程需要重启
    
            if (!property_set_fd_init && get_property_set_fd() > 0) {//系统属性
                ufds[fd_count].fd = get_property_set_fd();//获取property系统fd
                ufds[fd_count].events = POLLIN;
                ufds[fd_count].revents = 0;
                fd_count++;
                property_set_fd_init = 1;//设置标志,下一次循环不会执行
            }
            if (!signal_fd_init && get_signal_fd() > 0) {//进程间通信
                ufds[fd_count].fd = get_signal_fd();//获取子进程信号处理fd
                ufds[fd_count].events = POLLIN;
                ufds[fd_count].revents = 0;
                fd_count++;
                signal_fd_init = 1;
            }
            if (!keychord_fd_init && get_keychord_fd() > 0) {//组合键检测(系统刷机按键等)
                ufds[fd_count].fd = get_keychord_fd();//获取组合键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;
    }
    

    static void restart_processes()
    {
        process_needs_restart = 0;
        service_for_each_flags(SVC_RESTARTING,
                               restart_service_if_needed);
    }
                                                                                                                                        void service_for_each_flags(unsigned matchflags,
                                void (*func)(struct service *svc))
    {
        struct listnode *node;
        struct service *svc;
        list_for_each(node, &service_list) {//遍历service_list链表
            svc = node_to_item(node, struct service, slist);
            if (svc->flags & matchflags) {//判断服务标志是否为RESTARTING,成立就回调函数执行
                func(svc);
            }
        }
    }                                                                                                                              
                                                                                                                                        static void restart_service_if_needed(struct service *svc)
    {
        time_t next_start_time = svc->time_started + 5;
    
        if (next_start_time <= gettime()) {//当前时间不小于启动时间就重启该服务
            svc->flags &= (~SVC_RESTARTING);//清除RESTARTING标志
            service_start(svc, NULL);
            return;
        }
    
        if ((next_start_time < process_needs_restart) ||
            (process_needs_restart == 0)) {
            process_needs_restart = next_start_time;
        }
    }
    

    这样一个服务就会被重启,但是死亡的服务它的标志是怎么样被设置成RESTARTING,这里有疑惑

    从后面的代码可以看出,inti采用I/O多路服用才监听3个句柄的情况,当可读时做相应的处理

    在多路服用中,如果timeout==0 poll就不阻塞;如果timeout>0,poll只有当等待时间超时或有事件发生时才返回;如果timeout==-1就有事件发生才会返回


    if (process_needs_restart) {
                timeout = (process_needs_restart - gettime()) * 1000;//设置等待时间
                if (timeout < 0)
                    timeout = 0;
            }
    
            if (!action_queue_empty() || cur_action)//如果action_queue和cur_action都不为空,timeout设为0,此时不阻塞,相当于此时不执行poll操作
                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)//如果超时等待,就不执行后面的处理,直接跳到for循环开始处,执行action或者重启service
                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();
                }
            }
        }
    
    这里主要处理三大事件:属性设置事件,按键组合事件,子进程信号事件。前两者这里不做讨论,按键组合事件也只有在调试模式下才处理

    从上面的分析中我们知道service是init调用fork创建的子进程,在Linux进程间通信中,可以通过SIGCHLD信号来通知子进程的状态

    在之前的action_queue已经进行signal_init初始化

    void signal_init(void)
    {
        int s[2];
    
        struct sigaction act;
    
        act.sa_handler = sigchld_handler;//信号处理函数
        act.sa_flags = SA_NOCLDSTOP;
        act.sa_mask = 0;
        act.sa_restorer = NULL;
        sigaction(SIGCHLD, &act, 0);//安装SIGCHLD信号处理器
    
        /* create a signalling mechanism for the sigchld handler */
        if (socketpair(AF_UNIX, SOCK_STREAM, 0, s) == 0) {//用于init进程中双端之间通信
            signal_fd = s[0];//发送端socket fd
            signal_recv_fd = s[1];//接收端socket fd,并且被注册到poll系统监听
            fcntl(s[0], F_SETFD, FD_CLOEXEC);//设置fd的属性
            fcntl(s[0], F_SETFL, O_NONBLOCK);//非阻塞
            fcntl(s[1], F_SETFD, FD_CLOEXEC);
            fcntl(s[1], F_SETFL, O_NONBLOCK);
        }
    
        handle_signal();
    }
    
    static void sigchld_handler(int s)
    {
        write(signal_fd, &s, 1);
    }
    
    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))//一直执行wait_for_one_process,直到返回非0
            ;
    }
    
    套接字可用于网络通信,也可以用于本机内的进程通信。由于本机内进程的IP地址都相同,因此只需要进程号来确定通信的双方。非网络通信套接字在Android系统中应用很多。Linux环境下使用socketpair函数创造一对未命名的、相互连接的UNIX域套接字。

    在init进程中初始化signal_init,在init子进程死亡后会向init进程发送SIGCHLD信号,在init进程中已经注册该信号处理器sigchld_handler,在该函数中会向signal_fd发送信号的编号,而在另一端则接收这个数据,由于signal_recv_fd已注册在poll中,因此会调用handle_signal进行处理



    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;
        //waitpid函数停止当前进程,等待子进程的结束,-1表示等待任何子进程,WNOHANG表示返回该进程的id,status为返回状态
        while ( (pid = waitpid(-1, &status, block ? 0 : WNOHANG)) == -1 && errno == EINTR );
        if (pid <= 0) return -1;//进程号不可能为负数,此时while循环退出
        INFO("waitpid returned pid %d, status = %08x\n", pid, status);
    
        svc = service_find_by_pid(pid);//通过pid从service_list中查找结点
        if (!svc) {
            ERROR("untracked pid %d exited\n", pid);
            return 0;
        }
    
        NOTICE("process '%s', pid %d exited\n", svc->name, pid);
        //判断service是否为oneshot,如果是代表只运行一次,则不需要再重启
        if (!(svc->flags & SVC_ONESHOT)) {//如果service不为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);//释放该服务占用的所有socket资源
    
        }
    
        svc->pid = 0;//设置进程号为0
        svc->flags &= (~SVC_RUNNING);//清除状态标志RUNNING
    
            /* oneshot processes go into the disabled state on exit */
        if (svc->flags & SVC_ONESHOT) {//如果设置了状态ONESHOT,则不需要重启,设置为DISABLED
            svc->flags |= SVC_DISABLED;
        }
    
            /* disabled and reset processes do not get restarted automatically */
        if (svc->flags & (SVC_DISABLED | SVC_RESET) )  {//如果状态设置了DISABLED或者RESET,则不需要重启
            notify_service_state(svc->name, "stopped");//设置状态属性值为stopped
            return 0;
        }
    
        now = gettime();
        if (svc->flags & SVC_CRITICAL) {//如果service标志为CRITICAL
            if (svc->time_crashed + CRITICAL_CRASH_WINDOW >= now) {//如果崩溃时间超过4分钟
                if (++svc->nr_crashed > CRITICAL_CRASH_THRESHOLD) {//如果崩溃次数超过4次
                    ERROR("critical process '%s' exited %d times in %d minutes; "
                          "rebooting into recovery mode\n", svc->name,
                          CRITICAL_CRASH_THRESHOLD, CRITICAL_CRASH_WINDOW / 60);
                    android_reboot(ANDROID_RB_RESTART2, 0, "recovery");//重启手机
                    return 0;
                }
            } else {//重置状态值
                svc->time_crashed = now;
                svc->nr_crashed = 1;
            }
        }else if (svc->flags & SVC_DALVIK_RECACHE) {
            if (svc->time_started + RECACHE_ENABLE_PHASE >= now) {
                ERROR("recacheabl process '%s' exited at(%lu) ,start(%lu)",
                      svc->name, now, svc->time_started);
                system("/system/xbin/busybox rm /data/dalvik-cache/*");
                //android_reboot(ANDROID_RB_RESTART, 0, 0);
            }
        }
    
        svc->flags |= SVC_RESTARTING;//设置service标志为RESTARTING,待restart_processes()函数重启该服务
    
        /* 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");//修改状态属性值为restarting
        return 0;
    }
    

    init进程进入死循环,监听三大事件,并查询action_queue与service_list链表,是否有action需要执行,是否有service需要重启,并进行处理。

    至此,分析完毕。



    1.三大链表action_list,action_queue,service_list, action_queue才是真正用来查询执行的,因此它决定执行顺序

    2.注意源码中node_to_item,该宏通过链表节点返回结构体引用

    3.service_list中的service是何时才被执行,怎样执行的

    4.init进程死循环中,execute_one_command()和restart_processes()函数是怎么执行action和重启service_list服务的,尤其是对service重启的处理




    展开全文
  • Linux init进程详解

    千次阅读 2018-04-05 13:14:32
     所谓的init进程,它是一个由内核启动的用户级进程。 内核自行启动(已经被载入内存,开始运行,并已初始化所有的设备驱动程序和数据结构等)之后,就通过启动一个用户级程序init的方式,完成引导进程。所以,init...
  • Android init进程启动

    万次阅读 2012-07-12 23:30:38
    一、Android Init.c执行流程Android中的内核启动后,kernel启动第一个用户级别的进程init,它是一个由内核启动的用户级进程。内核自行启动(已经被载入内存,开始运行,并已初始化所有的设备驱动程序和数据结构...
  • Init进程在初始化过程中启动很多重要的守护进程,因此,了解Init进程的启动过程有助于我们更好的理解Android系统。 在介绍Init进程前,我们先简单介绍下Android的启动过程。从系统角度看,Android的启动过程可分为...
  • Android系统init进程启动及init.rc全解析

    万次阅读 多人点赞 2017-11-23 16:14:40
    system/core/init/init.c文件main函数中parse_config_file(init.rc)读取并解析init.rc文件内容。将service信息放置到system/core/init/init_parser.cpp的service_list中 system/core/init/init.c文
  • Android Init进程源码分析

    千次阅读 2016-08-18 19:12:40
    启动过程介绍一文中介绍了Android系统的各个启动阶段,init进程启动后读取init.rc配置文件,通过fork系统调用启动init.rc文件中配置的各个Service进程。init进程首先启动启动android的服务大管家
  • android启动之init进程详解

    千次阅读 2013-05-10 23:39:02
    Android系统的初始化过程是init进程开始的。它在加载linux基本内核后就开始运行。那么系统怎么样知道它应该加载init进程呢?原来是从android加载linux内核时,就设置了下面的参数: Kernelcommand line: noinitrd...
  • Android Framework - 开机启动 Init 进程

    千次阅读 2019-08-04 18:30:30
    相关文章链接: ...2. Android FrameWork - 开机启动 Init 进程 相关源码文件: /system/core/init/Init.cpp /system/core/rootdir/init.rc /system/core/init/init_parser.cpp /system/core/init/builtins.cp...
  • ueventd主要是负责设备节点的创建、权限设定等一些列工作。服务通过使用uevent,监控驱动发送的消息,做进一步处理。 ueventd实际和init是同一个binary,只是走了不同分支,可参看前一部分。 ueventd的整体代码比较...
  • init进程详细分析--基于android 10

    千次阅读 2020-03-16 14:20:02
    init进程详细分析 概述 android设备上电,引导程序引导进入boot(通常是uboot),加载initramfs、kernel镜像,启动kernel后,进入用户态程序。第一个用户空间程序是init,PID固定是1.在android系统上,init的代码位于/...
  • Android系统启动流程(一)解析init进程启动过程

    万次阅读 多人点赞 2017-02-07 11:29:48
    作为“Android框架层”这个大系列中的第一个系列,我们首先要了解的是Android系统启动流程,在这个流程中涉及到很多重要的知识点,这个系列我们就来一一讲解它们,这一篇我们就来学习init进程
  • init进程会孵化出ueventd、logd、healthd、installd、adbd、lmkd等用户守护进程 init进程还启动servicemanager(binder服务管家)、bootanim(开机动画)等重要服务 init进程孵化出Zygote进程,Zygote进程是Android...
  • android init进程分析 基本流程

    千次阅读 2015-07-07 11:02:10
    android设备上电,引导程序引导进入boot(通常是uboot),加载initramfs、kernel镜像,启动kernel后,进入用户态程序。第一个用户空间程序是init, PID固定是1.在android系统上,init的代码位于/...init进程的系统初始化
  • init进程,是一个由内核启动的用户级进程,内核自行启动后,就通过启动 init来完成引导进程。所以,init始终是第一个进程(其进程编号始终为1)。 init 进程首先进行一系列的硬件初始化,并挂载根文件系统。最后 ...
  • Android init进程分析

    千次阅读 2012-12-27 12:59:55
    <!-- @page {margin:0.79in} p ...Android中的内核启动后,kernel启动第一个用户级别的进程init,它是一个由内核启动的用户级进程,它的进程号是1,它以一个守护进程的方式运行,主要提供
  • init进程会孵化出ueventd、logd、healthd、installd、adbd、lmkd等用户守护进程 init进程还启动servicemanager(binder服务管家)、bootanim(开机动画)等重要服务 init进程孵化出Zygote进程,Zygote进程是Android...
  • Android 10.0系统启动之init进程-[Android取经之路]

    万次阅读 多人点赞 2019-12-15 16:12:11
    摘要:init进程是linux系统中用户空间的第一个进程,进程号为1.当bootloader启动后,启动kernel,kernel启动完后,在用户空间启动init进程,再通过init进程,来读取init.rc中的相关配置,从而来启动其他相关进程以及...
  • Android开机流程分析 -- init进程

    千次阅读 2015-05-08 09:12:18
    init进程(system\core\init)是Linux Kernal启动之后,运行的第一个进程。init进程在Android系统启动过程中起到至关重要的作用,init进程都被赋予了哪些职责呢,下面进行详细分析。
  •       Android P之init进程启动源码分析指南之二 前言   在上一篇章Android P之init进程启动源码分析指南之一中我们讲解了init启动的第一阶段工作,在第一阶段中init主要做了如下几方面的工作: ueventd...
  • android init进程分析 ueventd

    千次阅读 2015-07-07 11:04:53
    ueventd实际和init是同一个binary,只是走了不同分支,可参看前一部分。 ueventd的整体代码比较简单,主要是三部分:  解析ueventd.rc 初始化设备信息 循环polling uevent消息 主函数及相关功能如下如下: int ...
  •       Android P之init进程启动源码分析指南 前言   此时的我吃着火锅唱着歌,进行着Android P(此P非彼P,Android 9)的适配工作。我真的只能说每次Android版本的迭代更新,都是对我们的一次炼狱般的摧残啊...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 82,398
精华内容 32,959
关键字:

init进程会管理设备