Init研究报告

本文先说明init的大致流程,然后择重点说明其重要部分
一.Init整体流程
Init进程主函数main定义于system/core/init/init.c中,从该目录的Android.mk中我们知道该文件最终被编译为Init命令,也就是内核启动的第一个命令。它的进程号是1,用于系统的用户层初始化和启动各种服务进程。
1)配置规则
Init读取两个配置文件,/init.rc和/init.goldfish.rc,这两个配置文件存储在ramdisk的根目录中,在android的源代码结构中的位置是system/core/rootdir和system/core/rootdir/etc,配置文件中包含了若干条规则,有两种类型的规则,他们的定义如下:
1.action
on <trigger>
   <command>
   <command>
   <command>
表示在发生某个trigger条件的时候,执行下面的command
2.service
service <name> <pathname> [ <argument> ]*
   <option>
   <option>
启动服务,服务的名称为name,执行路径是pathname,参数为argument,option是对服务的进一步说明
system/core/init/readme.txt文件中有规则的详细说明
2)流程伪代码
创建并绑定不同的文件系统
解析并加载init.rc和init.goldfish.rc配置文件
加载内置规则
Init进程执行这两个文件解析出来的内容,配置文件中的trigger按照early-init,init,early-boot,boot的顺序进入执行序列中执行。排序后的action triger如下:
early-init
wait_for_coldboot_done
property_init
keychord_init
console_init
set_init_properties
init
property_service_init
signal_init
check_startup
early-boot
boot
queue_propety_trigger
进入以下的循环事件等待,该循环实际上是等待三个不同的事件,property,signal,keychord,然后分别处理
    for(;;) {
        execute_one_command();//一次执行一个action_queue中的命令
        restart_processes();

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

二.rc文件的解析
init_parser.c
 init_parse_config_file
    ->parse_config该函数是解析的主函数,循环解析token,然后设置进一步解析action或者service的函数
         ->parse_new_section
                ->parse_service负责解析service后面的名字和执行路径,参数等信息,并填写到service结构中,最后放入到service_list全局链表中,并设置parse_line_service作为解析service后续的option的函数
               -> parse_action负责解析action中on后面的触发条件,创建并填写action结构,并把这个结构放入到action_list中,同时设置parse_line_action作为解析action后续的command的函数
action,service,command的结构定义于system/core/init/init.h
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 service {
        /* list of all services */
    struct listnode slist;

    const char *name;
    const char *classname;

    unsigned flags;
    pid_t pid;
    time_t time_started;    /* time of last start */
    time_t time_crashed;    /* first crash within inspection window */
    int nr_crashed;         /* number of times crashed within window */
   
    uid_t uid;
    gid_t gid;
    gid_t supp_gids[NR_SVC_SUPP_GIDS];
    size_t nr_supp_gids;

    struct socketinfo *sockets;
    struct svcenvinfo *envvars;

    struct action onrestart;  /* Actions to execute on restart. */
   
    /* 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! */

特别需要注意的是在system/core/init/init_parser.c中定义的如下变量:
static list_declare(service_list);
static list_declare(action_list);
static list_declare(action_queue);
service_list把所有rc中所有的服务都加入到这个list中,在main的for循环中依次启动
action_list把rc中所有的action都加入到这个list,但是由于编写rc的时候,可能不是按照init期望运行的顺序编写的,所以这里的list都是乱序的
action_queue按照init期望启动的顺序early-init,init,early-boot,boot,把action加入到这个list中,同时也加入了一些builtin的action,然后在main的for循环中依次启动.

三.service分析
1.service的启动时机
在进入循环之前按照early-init,init,early-boot,boot的triger排序action,查看rc文件,在on boot的action中有
class_start core
class_start main
class_start对应的是system/core/init/builtins.c文件的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;
}
static void service_start_if_not_disabled(struct service *svc)
{
    if (!(svc->flags & SVC_DISABLED)) {
        service_start(svc, NULL);
    }
}
其中service_start定义于system/core/init.c中
也就是说,上述action,启动了service队列中的core和main两个class的service。
2.启动service
system/core/init/init.c中的service_start函数是用来启动rc文件中指定的服务的,启动一个服务的基本流程如下:
1)fork一个子进程
2)在子进程中
         if (properties_inited()) {
            get_property_workspace(&fd, &sz);
            sprintf(tmp, "%d,%d", dup(fd), sz);
            //向环境变量表中加入 ANDROID_PROPERTY_WORKSPACE=fd,size 的环境变量
            add_environment("ANDROID_PROPERTY_WORKSPACE", tmp);
        }

        //根据service的envvars向环境变量表中添加service指定的环境变量
        for (ei = svc->envvars; ei; ei = ei->next)
            add_environment(ei->name, ei->value);

        //根据service中的socket options指定的name,type,perm,uid,gid,创建socket,并向环境变量表中添加ANDROID_SOCKET_name=socket_id的环境变量
        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,
                                  si->perm, si->uid, si->gid);
            if (s >= 0) {
                //添加环境变量,特别注意在其中调用了fcntl(fd, F_SETFD, 0);表示没有设置socket id的CLOSE_ON_EXEC,也就是说着个id会被子进程继承的。
                publish_socket(si->name, s);
            }
        }
这里所谓的create_socket是创建一个AF_UNIX的socket,该类型socket用于进程间通信,它绑定一个/dev/socket/name的路径,并返回一个socket id。service一般会在这个socket上监听,客户端通过连接这个socket与service通信。
3)在子进程中执行execve,并把环境变量作为启动服务的参数
4)父进程也就是init进程,会在属性服务器中加入该服务器的启动状态的属性条目
总体来说就是,设置环境变量,创建socket,运行服务程序

三.属性服务分析
property service运行于init进程
1.属性服务的初始化
在action排序的时候我们可以看到property_init_action函数,该函数初始化一块属性区域用来存放系统属性,并载入缺省属性/default.prop,对应源代码目录中的out/target/product/generic/root/default.prop.
property_init_action函数最终会调用到system/core/init/property_service.c文件中的如下函数
static int init_property_area(void)
{
    prop_area *pa;

    if(pa_info_array)
        return -1;
    //初始化一块内存空间用来存放属性
    if(init_workspace(&pa_workspace, PA_SIZE))
        return -1;
    //设置CLOSE_ON_EXEC,保证了子进程不会直接得到这块空间的fd
    fcntl(pa_workspace.fd, F_SETFD, FD_CLOEXEC);

    pa_info_array = (void*) (((char*) pa_workspace.data) + PA_INFO_START);

    pa = pa_workspace.data;
    memset(pa, 0, PA_SIZE);
    pa->magic = PROP_AREA_MAGIC;
    pa->version = PROP_AREA_VERSION;

        /* plug into the lib property services */
    __system_property_area__ = pa;//全局变量用来保存这块属性区域,它定义在bionic/libc/bionic/system_properties.c
    property_area_inited = 1;//设置属性空间初始化的标志
    return 0;
}
__system_property_area__是struct prop_area类型,它定义于bionic/libc/include/sys/_system_properties.h,prop_area和prop_info的结构如下,其中结构末尾的toc[1],被用来作为变长数组:
/* (8 header words + 247 toc words) = 1020 bytes */
/* 1024 bytes header and toc + 247 prop_infos @ 128 bytes = 32640 bytes */
struct prop_area {
    unsigned volatile count;
    unsigned volatile serial;
    unsigned magic;
    unsigned version;
    unsigned reserved[4];
    unsigned toc[1];
};
struct prop_info {
    char name[PROP_NAME_MAX];
    unsigned volatile serial;
    char value[PROP_VALUE_MAX];
};
实际上代码会分配一块32768长的内存块,其结构如下:
-----------------------------------------------------------------------------------------------------
| Header =8 * 4= 32 bytes | Toc = 247 * 4 = 988 bytes | Array = 247 * prop_info |
-----------------------------------------------------------------------------------------------------
其中Header对应prop_area中除了toc以外的各个字段
Toc是247个4字节,每个4字节用来存储后面Array中对应索引item的信息,其结构为:
最高的一个字节用来表示对应item中name字符串的长度,后面三个字节用来存储这个item的首地址到prop_area首地址的偏移量
这样设计的目的是为了在根据name查找prop_info的时候,能够实现快速查找,根据prop_area的count,遍历所有的toc字段,首先比较name的长度,然后再根据偏移量找到prop_info,再比较name
Array是一个247个item的数组,用来存放name和value对
上述初始化完成之后,__system_property_area__全局变量将指向这块属性区域
如下的代码是用来创建属性的workspace的,它使用mmap创建一块可读写和共享的内存块,并返回fd,这个fd会保存在ANDROID_PROPERTY_WORKSPACE环境变量中。
static int init_workspace(workspace *w, size_t size)
{
    void *data;
    int fd;

        /* dev is a tmpfs that we can use to carve a shared workspace
         * out of, so let's do that...
         */
    fd = open("/dev/__properties__", O_RDWR | O_CREAT, 0600);
    if (fd < 0)
        return -1;

    if (ftruncate(fd, size) < 0)
        goto out;

    data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if(data == MAP_FAILED)
        goto out;

    close(fd);

    fd = open("/dev/__properties__", O_RDONLY);
    if (fd < 0)
        return -1;

    unlink("/dev/__properties__");

    w->data = data;
    w->size = size;
    w->fd = fd;
    return 0;

out:
    close(fd);
    return -1;
}
除此之外,特别需要注意的是system/core/init/property_service.c中定义的property_set函数,该函数用来根据name前缀设置name,value,如果属性值发生了改变,还会向执行队列中加入触发的action。在属性初始化结束后,在init进程中的执行队列中的set_init_properties会被执行,主要用来设置一些ro属性
3)属性服务的启动
属性服务不同于其他服务,它是单独启动的,在action列表中,我们可以找到property_service_init triger对应,property_service_init_action函数,该函数会依次载入
/system/build.prop
/system/default.prop
/data/local.prop
然后会载入/data/property目录下的属性文件,这些属性文件以属性名为文件名,属性值为文件内容
最后在/dev/socket目录下会创建一个的socket,这个socket是一个AF_UNIX socket,它用来进行进程间通信,它会绑定/dev/socket/property_service路径,创建完成后会在这个socket上listen,等待接收请求,这个socket主要用于其他进程设置属性的时候,与init进程通信。
4)获取属性
其他进程是如何获取property的值,在上述属性初始化的过程中,我们知道亮点:
1.init进程已经把保存属性的内存块设置为shared,
2.把ANDROID_PROPERTY_WORKSPACE=fd,size作为环境变量传递给子进程
同时android还做了如下的事情:
在libc_init_dynamic.c文件中声明和定义了__libc_preinit函数
/* We flag the __libc_preinit function as a constructor to ensure
 * that its address is listed in libc.so's .init_array section.
 * This ensures that the function is called by the dynamic linker
 * as soon as the shared library is loaded.
 */
void __attribute__((constructor)) __libc_preinit(void);
使用constructor表示要把__libc_preinit加入libc.so的init_array section,这样在子进程加载这个so文件的时候,该函数会被调用,libc.so是C runtime库。
__libc_preinit会调用__system_properties_init函数,该函数会读取ANDROID_PROPERTY_WORKSPACE环境变量,并根据其中的fd,使用mmap函数在本地创建一块只读的内存块,并使用变量__system_property_area__指向它。代码如下:
int __system_properties_init(void)
{
    prop_area *pa;
    int s, fd;
    unsigned sz;
    char *env;
    //确保在init进程的时候不会调用后续代码
    if(__system_property_area__ != ((void*) &dummy_props)) {
        return 0;
    }

    env = getenv("ANDROID_PROPERTY_WORKSPACE");
    if (!env) {
        return -1;
    }
    fd = atoi(env);
    env = strchr(env, ',');
    if (!env) {
        return -1;
    }
    sz = atoi(env + 1);
    
    pa = mmap(0, sz, PROT_READ, MAP_SHARED, fd, 0);
    
    if(pa == MAP_FAILED) {
        return -1;
    }

    if((pa->magic != PROP_AREA_MAGIC) || (pa->version != PROP_AREA_VERSION)) {
        munmap(pa, sz);
        return -1;
    }

    __system_property_area__ = pa;
    return 0;
}
mmap使用了PROT_READ和MAP_SHARED参数,表明这是一块共享内存,数据来源是fd,数据内容与init进程中的内存块相同,且返回的内存区域是只读的。从前面的代码中我们知道这个fd是init进程设置给ANDROID_PROPERTY_WORKSPACE环境变量的,这样子进程就有了一块包含当前属性的内存块,重要的是,当init进程中这块内存发生变化的时候,子进程的属性也会同步变化。子进程在调用system/core/libcutils/properties.c文件中的property_get函数获取属性的时候,就可以直接从本进程的__system_property_area__指向的内存块中获取,而不用进行进程间通信。
但是这里有两个问题
1.这个libc.so是在新创建的进程中载入的,它为什么能够使用init进程的fd呢?
2.在启动服务的时候,会添加ANDROID_PROPERTY_WORKSPACE等诸多环境变量给子进程,这些环境变量都是存储在一个指针数组中
static const char *ENV[32];
因为要反复启动不同的服务,这个数组不会填满么?
对于问题1,要知道每个进程都有自己的文件描述符表,只要对应的文件描述符没有设置CLOSE_ON_EXEC标志,那么这个文件描述符都会被子进程所继承。
对于问题2,由于添加环境变量的操作是在子进程中执行的,根据Linux的copy on write的特性,ENV这个静态变量在刚刚fork的时候,子进程和父进程是共享一个内存地址的,但是在进行写操作的时候,Linux会为子进程的ENV重新分配页空间,这样,对于每个进程都是在他们的进程空间,重新添加环境变量,与父进程无关了。
5)设置属性
其他进程在设置属性的时候,会调用system/core/libcutils/properties.c文件中的property_set函数,它实际上会调用bionic/libc/bionic/system_properties.c中的send_prop_msg函数,该函数会创建一个AF_LOCAL socket,并组装msg,发送给/dev/socket/property_service。这个socket文件是在启动property服务的时候创建的,这个fd会返回给init.c的main函数,并在main的for循环中等待事件,在等待到数据后,调用handle_property_set_fd函数,该函数会从socket中获取msg,这样就在init进程中设置属性
我们看到子进程读写属性的API函数是定义在system/core/libcutils/properties.c中的,而init进程则是在system/core/init/property_service.c文件中的。
6)属性API
system/core/libcutils/properties.c中对属性的设置和获取进行了封装,在init进程以外的进程中设置属性的时候,要使用这些函数
在shell中使用到的几个命令:
printenv 打印环境变量
getprop 打印所有属性
setprop 设置属性

四.参考文档
android系统启动过程

android开机启动流程初探

android开机图片修改

分析Android 根文件系统启动过程(init守护进程分析)

浅析android下propt怎么通过init进程传递和glibc库函数的添加

http://blog.chinaunix.net/uid-27024249-id-3310159.html
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值