《深入理解Android》导读之init

原创 2012年04月02日 17:09:38

1. 概述

        init做了很多事情,在很多修改驱动,或者对系统作一些修改的时候,经常会对init.rc进行修改,因此,无论做系统研究,还是作平台性的修改,这部分都很重要。另外,《深入理解》是根据2.3的版本写的代码分析,我再根据4.0相应的代码做了类似的分析和修改。

        这章主要讲了两个方面,init如何创建zygote和init的属性服务是如何工作的。我个人最大的收获,是系统地读了下init的工作流程,当然,原来看过对init.rc的语法分析的东西,相对有点帮助,可以看得稍微快一点。

        init的源文件多放在system/core/init下, init.rc放在system/core/rootdir/init.rc下,当然,不同平台(device)的init.rc放在device/下。


2. init 分析

        整理init.c 里的main函数,按顺序做了下面这些事情(没有一件一件罗列,只是把关注的和主要的列了下):

        a. 建立一些根目录下必需的目录,其他目录从init.rc中读取建立 ——>

        b. 重定向标注输入/输出/错误输出到/dev/_null_ (open_devnull_stdio()) ——>

        c. 设置init的日志输出设备(klog_fd)为/dev/__kmsg__,设置完后马上unlink,其他进程就无法打开这个文件读取日志信息了(klog_init())——>

        d. 分析init.rc以及init.hardware.rc得到一系列的action(parse_config_file())——>

        e. 把early-init阶段的action加到全局定义的action_queue (init_parser.c) 里——>

        f.  通过调用queue_builtin_action()把wait_for_coldboot_done_action, property_init_action, keychord_init_action, logo_init_action, console_init_action, set_init_propertyes_action加到action_queue 里 ——>

        g. 把init段的action加到action_queue里(action_for_each_trigger("init", action_add_queue_tail);)——>

        h. 把property_service_init_action, signal_init_action, check_startup_action, 加到action_queue 里 ——>

         i. 如果为charger模式,则把charger加到action_queue里,否则把early-boot, boot加到action_queue里 ——>

        j.  把queue_property_triggers_action加到action_queue里,否则把early-boot, boot加到action_queue里 ——>

        k. 进入无限for循环,通过执行execute_one_command()按序执行action_queue里的action,每执行完一个action,就会调用一次restart_processes(), (如果action_queue里的action已经完成,则不做action,只是不断监听事件,作为“天字一号”程序运行),只是如下:

       

       execute_one_command();
        restart_processes();

        并且根据需要来设置ufds[], 分别监听来自属性服务器,由soketpair创建的另一个socket,keychord设备这三个事件


        if (!property_set_fd_init && get_property_set_fd() > 0) {
            ufds[fd_count].fd = get_property_set_fd();
            ufds[fd_count].events = POLLIN;
            ufds[fd_count].revents = 0;
            fd_count++;
            property_set_fd_init = 1;
        }
        if (!signal_fd_init && get_signal_fd() > 0) {
            ufds[fd_count].fd = get_signal_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();
            ufds[fd_count].events = POLLIN;
            ufds[fd_count].revents = 0;
            fd_count++; 
            keychord_fd_init = 1;
        }
           然后调用poll等待监听事情的发生,如果有来自上面监听的事件,则处理事件,否则,返回for循环,做下一个action

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

        由上面的分析可以知道,其实e到f的步骤都只是按照一定的执行顺序把init.rc里的action加到action_queue里,然后在for循环里一边执行一边监听3类事件,等所有action完成后,继续一直停留在for循环里监听3类事件。各个section的工作顺序可以从这里看得出来。跟2.3不一样的地方是,2.3是先做完early-init action, 再做property_init, 再加载开机第一画面,再做init action,然后启动属性服务器,然后建立sockpair对, 然后执行early-boot 和 boot action,再打开3类监听器,然后进入for循环,执行其他aciton, 并监听3类事件。在4.0里,开机画面加载(load_565rle_image(INIT_IMAGE_FILE))放在console_init_action里,然后所有的action都放在for循环中执行


2.1 解析配置文件

        init的main函数里调用init_parse_config_file来处理init.rc或和硬件平台相关的init.**.rc, init_parse_config()主要调用了parse_config()来分析,如下:

static void parse_config(const char *fn, char *s)
{
    struct parse_state state;
    char *args[INIT_PARSER_MAXARGS];
    int nargs;

    nargs = 0;
    state.filename = fn;
    state.line = 0;
    state.ptr = s;
    state.nexttoken = 0;
    state.parse_line = parse_line_no_op;
    for (;;) {
        switch (next_token(&state)) {
        case T_EOF:
            state.parse_line(&state, 0, 0);
            return;
        case T_NEWLINE:
            state.line++;
            if (nargs) {
                int kw = lookup_keyword(args[0]);
                if (kw_is(kw, SECTION)) {
                    state.parse_line(&state, 0, 0);
                    parse_new_section(&state, kw, nargs, args);
                } else {
                    state.parse_line(&state, nargs, args);
                }
                nargs = 0;
            }
            break;
        case T_TEXT:
            if (nargs < INIT_PARSER_MAXARGS) {
                args[nargs++] = state.text;
            }
            break;
        }
    }
}

        其中的lookupkeyword在下面的2.1.1会详细讲到。找到keyword后,如果不是section属性,则调用parse_new_line(), 调用parse_new_section(), parse_new_section()分析了关键字 on,  service, import三个属性的section,如下:

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);
        if (state->context) {
            state->parse_line = parse_line_action;
            return;
        }
        break;
    case K_import:
        if (nargs != 2) {
            ERROR("single argument needed for import\n");
        } else {
            int ret = init_parse_config_file(args[1]);
            if (ret)
                ERROR("could not import file %s\n", args[1]);
        }
    }
    state->parse_line = parse_line_no_op;
}
      

        其中service是系统服务,on是section的起始标志,import是引进别的类似init.rc(这是加入硬件平台的init.rc的另外一种常用方法)。

2.1.1 关键字定义

        关键字定义在system/core/init/keywords.h文件里,文件首先定义了action实际执行的函数,部分代码:

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

        然后通过##来定义一些keyword枚举,如:

   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,       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(rm,          COMMAND, 1, do_rm)
    KEYWORD(rmdir,       COMMAND, 1, do_rmdir)
    KEYWORD(service,     SECTION, 0, 0)
    KEYWORD(setenv,      OPTION,  2, 0)
        注意keywords.h里有#ifndef KEYWORD的条件,这个巧妙的应用(如何巧妙可看书),在配合parser.c里的代码:


#define SECTION 0x01
#define COMMAND 0x02
#define OPTION  0x04

#include "keywords.h"

#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"
};
#undef KEYWORD

#define kw_is(kw, type) (keyword_info[kw].flags & (type))
#define kw_name(kw) (keyword_info[kw].name)
#define kw_func(kw) (keyword_info[kw].func)
#define kw_nargs(kw) (keyword_info[kw].nargs)
        这样,就形成了一个一个keyword_info结构提数组,里面的内容为[{on, null, 1, SECTION}, {class_start, do_class_start, 2, COMMAND}, {class, null, 1, OPTION}, ......]等。总结起来有两点:

         第一次包含keywords.h时,它声明了一些诸如do_class_start的函数,另外还定义了一个枚举,枚举值为K_class,K_mkdir等关键字。第二次包含keywords.h后,得到了keyword_info的结构体数组,以前面的枚举值为索引,存储对应的关键字信息,包括关键字名称,处理函数,处理函数的参数个数,以及属性。

         2.3中,只有当symbol为on和service为SECTION,4.0中增加了import也为section,用于添加分析新的init.rc设置。

      

2.2.2 解析init.rc

        粘贴部分init.rc如下:

on early-init
    # Set init and its forked children's oom_adj.
    write /proc/1/oom_adj -16

    start ueventd

# create mountpoints
    mkdir /mnt 0775 root system

on init

sysclktz 0

loglevel 3

# setup the global environment
    export PATH /sbin:/vendor/bin:/system/sbin:/system/bin:/system/xbin
    export LD_LIBRARY_PATH /vendor/lib:/system/lib
    export ANDROID_BOOTLOGO 1
    export ANDROID_ROOT /system
......
on fs
# mount mtd partitions
    # Mount /system rw first to give the filesystem a chance to save a checkpoint
    mount yaffs2 mtd@system /system
    mount yaffs2 mtd@system /system ro remount
    mount yaffs2 mtd@userdata /data nosuid nodev
    mount yaffs2 mtd@cache /cache nosuid nodev

on post-fs
    # once everything is setup, no need to modify /
    mount rootfs rootfs / ro remount

    # We chown/chmod /cache again so because mount is run as root + 
......

## Daemon processes to be run by init.
##
service ueventd /sbin/ueventd
    class core
    critical

service console /system/bin/sh
    class core
    console
    disabled
    user shell
    group log

on property:ro.debuggable=1
    start console

# adbd is controlled via property triggers in init.<platform>.usb.rc
service adbd /sbin/adbd
    class core
    disabled

# adbd on at boot in emulator
on property:ro.kernel.qemu=1
    start adbd
        如上所分析,on代表一个section,因此一开始就是early-init 和 init 两个 section,后来还有很多section, 例如fs section, post-fs section等,每个on section里有一些command,例如mount,export, start等,service是另外一类section,service后跟这个service的名字,对应的执行文件,以及属性(OPTION)设置。

2.2 解析 service

2.2.1 service 结构体

        以zygote为例(虽然zygote是虚拟机的始祖,但是如果不是系统的重度开发者,是不会涉及到zygote的修改吧,所以我的关注点并不是zygote本身,而是透过书上的这个例子看一下典型的service的启动过程,当然,zygote的崩溃导致死机或者系统重启的现象和原因,有助于当产线上老化机器出现死机或者系统自动重启问题时候,对问题的debug),看一下service到底是怎么一回事,先看下init.rc里关于zygote的设置:

service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
    class main
    socket zygote stream 666
    onrestart write /sys/android_power/request_state wake
    onrestart write /sys/power/state on
    onrestart restart media
    onrestart restart netd

       上面里,service是一个section的开始,zygote是service的名字,/system/bin/app_process是zygote对应的可执行文件,后面是执行参数。下面都是OPTION。class main 让service的calssname属性为main, 这是4.0的新特性,2.3中这个属性默认为default,以在do_class_start()中默认启动服务。socket是建立名字为zygote,类型为stream,读写权限是666的socket。onrestart是一个属性,这个属性附加了4个command。parse_new_section()通过parse_service()生成service结构体,查找是否冲突,否则把下图的service结构体加入到链表service_list(), 并且把service的classname属性设置为“default”,以保证在do_class_start中被匹配启动。返回新生成的结构体。然后,通过调用parse_line_service(),读取service的各种属性,生成socket(如果需要),填入service结构体,生成的service机构体如下:



        a. servide_list链表将解析后的service全部链接到了一起,并且是一个双向链表,前向节点用prev表示,后向节点用next表示。

        b. socketinfo也是一个双向链表,因为zygote只有一个socket,所以画了一个虚框socket作为链表的示范。

        c. onrestart通过commands指向一个commands链表, zygote有4个commands, 图中按照2.3来只画了3个。

2.3 init控制service

        上面讲了init.rc的service是如何匹配到service结构体,下面讲一下parse_new_section()后,init是怎么启动service的。

2.3.1 启动 zygote

        init.rc中有COMMAND class_start的时候,会执行do_class_start的函数,2.3中class_start 只会有一个default参数,会把所有classname为default的service启动(通常是放在boot section), 4.0中class_start加了core, main, charger, late_start这4个类型。do_class_start()中调用service_start_if_not_disabled(), 再调用service_start(),用fork() + execve()来创建子进程并执行service指定的可执行文件来终于启动了service。这个过程会根据一些service的属性,例如SVC_DISABLED等来决定是否进行启动service。

2.3.2 重启 zygote

        在signal_init()(函数在core/init/signal_handle.c,通过queue_builtin_action(signal_init_action, "signal_init");加入到init的action_list中)函数中,通过socketpair()把signal_fd和signal_recv_fd配对。当子进程退出时,会执行sigchld_handle()函数,往signal_fd写数据,而signal_init()调用的handle_signal()函数通过读取signal_recv_fd,调用wait_for_one_process(),这个函数通过Pid找到对应的service,然后根据SVC_ONESHOT, SVC_RESET, SVC_DISABLED等的值,来决定如何处理。需要关注的是,如果设置了SVC_ONESHOT,那么会调用kill(-pid, SIGKELL)把所有该Pid创建的所有子进程都杀死,zygote就是设置了这个属性。如果设置了SVC_CRITICAL,那么4分钟内该服务的次数不能超过4次,否则机器会在重启时进入recovery模式。像servicemanager就有这个设置。如果SVC_RESTARTING,那么属性onrestart的command就会执行。


2.4 属性服务

        property service, 属性服务,类似于windows的注册表属性机制,通过getprop可以查看当前属性。property主要设计到property_init()和property_set_fd = start_property_service()两个函数。                     

2.4.1 属性服务初始化

void property_init(bool load_defaults)
{
    init_property_area();
    if (load_defaults)
        load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT);
}
        如上,在init_property_area()里面,通过ashmem_create_region() (init_workspace()函数调用) 创建大小为PA_SIZE(32768)的共享内存,然后把__system_property_area__指向这块共享内存,因为:

        a.把属性区域创建在共享内存上,而共享内存是可以跨进程的。

        b. 为了让其他进程知道这个共享内存,Android利用了gcc的constructor属性, 这个属性指明了一个__libc_prenit函数(void  __attribute__((constructor))  __libc_prenit(void)),当bionic libc库被加载时,将自动调用这个__libc_prenit,这个函数内部就将完成共享内存到本地进程的映射工作。

        c. constructor属性指示加载器加载该库后,首先调用__libc_prenit函数,和Windows上的动态库的DllMain函数类似。__libc_prenit()调用__libc_init_common()再调用__system_properties_init(),用mmap把ANDROID_PROPERTY_WORKSPACE对应的内存以只读的形式映射到本地。

2.4.2 属性服务器的分析

        start_property_service()加载 /default.prop, /system/build.prop, /system/default.prop, /data/local.prop。对于保存在永久介质上的属性文件,在/data/property目录下,并且这些文件的文件名必须以persist.开头。当属性服务器收到客户端请求时,init会调用handle_property_set_fd进行处理。客户端则通过property_set(由libcutils库提供)来向服务器发送设置属性的请求。


3.小结

        这一章详细分析了init.rc中的各项关键字,了解了SECTION,COMMAND,OPTION的意思,还有service是怎样启动的,对于增减驱动或者修改一些系统属性时修改init.rc是个启发性的阅读。另外,这一章会设计到一些socket编程的概念。


        

       



[Android5.1]开机服务启动顺序

大家知道,当Android系统启动时候,会解析init.rc文件,然后根据里面的定义,启动各种服务,如netd、zygote、servier_manager等等,但这些服务之间其实是有依赖关系的,而且...

Kitkat中对class core, class main, class late_start的简单分析

在分析Kitkat加密功能的时候,遇到了

Android init.rc文件浅析

本文主要来自$ANDROID_SOURCE/system/init/readme.txt的翻译. 1 简述 Android init.rc文件由系统第一个启动的init程序解析,此文件由语句组...

start_class core 具体执行了啥

在init.rc的 on boot最后start_class core on boot ........ class_start core on nonencrypted clas...

内核线程与用户线程的区别

根据操作系统内核是否对线程可感知,可以把线程分为内核线程和用户线程。 内核线程建立和销毁都是由操作系统负责、通过系统调用完成的,操作系统在调度时,参考各进程内的线程运行情况做出调度决定,如果一个进程...

[知其然不知其所以然-3] 为什么在高负载下cpu的温度没有显著提升

是的,根据常识,如果你在玩一个大型游戏,或者在运算一大组数据,那么cpu的温度应该是要上升许多的。 但最近在我们实验室的机器上,发现了一个问题,就是当我们用stress -c 4 -t 30进行cpu...

《深入理解Android》导读之init

1. 概述         init做了很多事情,在很多修改驱动,或者对系统作一些修改的时候,经常会对init.rc进行修改,因此,无论做系统研究,还是作平台性的修改,这部分都很重要。另外,《深...

深入理解Android之init与zygote

最近看了《深入理解Android卷I》中关于init进程与zygote进程的知识,特此写一篇博客记录一下收获。 init进程 init进程是Android系统中的用户空间的第一个进程,它的...

深入理解init_5-----属性服务(基于Android 2.2,代码源自Google)

深入理解init_5—–属性服务Android 2.2) 我们知道,windows平台上有一个叫做注册表的东西。注册表可以存储一些类似key/value的键值对。一般而言,系统或者...

android启动--深入理解init进程

init是一个进程,它是linux系统中用户空间的第一个进程,其进程PID是1,父进程为linux 系统内核的0号进程。所以其被赋予很多极其重要的职责,linux内核初始化完成后就开始执行它。 ...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:《深入理解Android》导读之init
举报原因:
原因补充:

(最多只允许输入30个字)