【我的车载技术】 Android AutoMotive 之 init与zygote内核原理

news2024/11/15 18:05:17

init概述

init是一个进程,确切地说,它是Linux系统中用户空间的第一个进程。由于Android是基于Linux内核的,所以init也是Android系统中用户空间的第一个进程,它的进程号是1。作为天字第一号的进程,init被赋予了很多极其重要的工作职责,本章将关注其中两个比较重要的职责:

  • init进程负责创建系统中的几个关键进程,尤其是下一章要介绍的Zygote,它更是Java世界的开创者。那么,init进程是如何创建Zygote的呢?
  • Android系统有很多属性,于是init就提供了一个property service(属性服务)来管理它们。那么这个属性服务是怎么工作的呢?

如上所述,本章将通过下面两方面内容来分析init:

  • init如何创建zygote。
  • init的属性服务是如何工作的。

init分析

init进程的入口函数是main,它的代码如下所示:

init.c

int main(int argc, char **argv)
{
    int device_fd = -1;
    int property_set_fd = -1;
    int signal_recv_fd = -1;
    int keychord_fd = -1;
    int fd_count;
    int s[2];
    int fd;
    struct sigaction act;
    char tmp[PROP_VALUE_MAX];
    struct pollfd ufds[4];
    char *tmpdev;
    char *debuggable;
   
    //设置子进程退出的信号处理函数,该函数为sigchld_handler。
    act.sa_handler = sigchld_handler;
    act.sa_flags = SA_NOCLDSTOP;
    act.sa_mask = 0;
    act.sa_restorer = NULL;
    sigaction(SIGCHLD, &act, 0);
  
    ......//创建一些文件夹,并挂载设备,这些是和Linux相关的,不拟做过多讨论。
    mkdir("/dev/socket", 0755);
    mount("devpts", "/dev/pts", "devpts", 0,NULL);
    mount("proc", "/proc", "proc", 0, NULL);
    mount("sysfs", "/sys", "sysfs", 0, NULL);
​
    //重定向标准输入/输出/错误输出到/dev/_null_。
    open_devnull_stdio();
    /*
    设置init的日志输出设备为/dev/__kmsg__,不过该文件打开后,会立即被unlink了,
    这样,其他进程就无法打开这个文件读取日志信息了。
    */
    log_init();
​
    //上面涉及很多和Linux系统相关的知识,不熟悉的读者可自行研究,它们不影响我们的分析
    //解析init.rc配置文件
    parse_config_file("/init.rc");
    
    ......
    //下面这个函数通过读取/proc/cpuinfo得到机器的Hardware名,我的HTCG7手机为bravo。
    get_hardware_name();
    snprintf(tmp,sizeof(tmp), "/init.%s.rc", hardware);
    //解析这个和机器相关的配置文件,我的G7手机对应文件为init.bravo.rc。
    parse_config_file(tmp);
​
    /*
    解析完上述两个配置文件后,会得到一系列的Action(动作),下面两句代码将执行那些处于
    early-init阶段的Action。init将动作执行的时间划分为四个阶段:early-init、init、
    early-boot、boot。由于有些动作必须在其他动作完成后才能执行,所以就有了先后之分。哪些
    动作属于哪个阶段由配置文件决定。后面会介绍配置文件的相关知识。
    */
    action_for_each_trigger("early-init", action_add_queue_tail);
    drain_action_queue();
​
    /*
    创建利用Uevent和Linux内核交互的socket。关于Uevent的知识,第9章中对
    Vold进行分析时会做介绍。
    */
    device_fd = device_init();
    //初始化和属性相关的资源
    property_init();
    //初始化/dev/keychord设备,这和调试有关,本书不讨论它的用法。读者可以自行研究,
    //内容比较简单。
    keychord_fd = open_keychord();
​
    ......
        /*
        INIT_IMAGE_FILE定义为”/initlogo.rle”,下面这个函数将加载这个文件作为系统的开机
        画面,注意,它不是开机动画控制程序bootanimation加载的开机动画文件。
        */
        if(load_565rle_image(INIT_IMAGE_FILE) ) {
            /*
            如果加载initlogo.rle文件失败(可能是没有这个文件),则会打开/dev/ty0设备,并
            输出”ANDROID”的字样作为开机画面。在模拟器上看到的开机画面就是它。
            */
        ......
        }
   }
    if(qemu[0])
       import_kernel_cmdline(1);
   ......
    //调用property_set函数设置属性项,一个属性项包括属性名和属性值。
    property_set("ro.bootloader", bootloader[0] ? bootloader :"unknown");
​
    ......//执行位于init阶段的动作
    action_for_each_trigger("init", action_add_queue_tail);
    drain_action_queue();
​
    //启动属性服务
    property_set_fd = start_property_service();
​
    /*
    调用socketpair函数创建两个已经connect好的socket。socketpair是Linux的系统调用,
    不熟悉的读者可以利用man socketpair查询相关信息。后面就会知道它们的用处了。
    */
    if(socketpair(AF_UNIX, SOCK_STREAM, 0, s) == 0) {
        signal_fd = s[0];
        signal_recv_fd = s[1];
        ......
    }
​
    ......
    //执行配置文件中early-boot和boot阶段的动作。
    action_for_each_trigger("early-boot", action_add_queue_tail);
    action_for_each_trigger("boot", action_add_queue_tail);
    drain_action_queue();
​
    ......    
    //init关注来自四个方面的事情。
    ufds[0].fd = device_fd;//device_fd用于监听来自内核的Uevent事件
    ufds[0].events = POLLIN;
    ufds[1].fd = property_set_fd;//property_set_fd用于监听来自属性服务器的事件
    ufds[1].events = POLLIN;
    //signal_recv_fd由socketpair创建,它的事件来自另外一个socket。
    ufds[2].fd = signal_recv_fd;
    ufds[2].events = POLLIN;
    fd_count = 3;
    if(keychord_fd > 0) {
        //如果keychord设备初始化成功,则init也会关注来自这个设备的事件。
        ufds[3].fd = keychord_fd;
        ufds[3].events = POLLIN;
        fd_count++;
    }
    ......
​
#if BOOTCHART
    ......//与Boot char相关,不做讨论了。
    /*
    Boot chart是一个小工具,它能对系统的性能进行分析,并生成系统启动过程的图表,
    以提供一些有价值的信息,而这些信息最大的用处就是帮助提升系统的启动速度。
    */
#endif
    for(;;) {
        //从此init将进入一个无限循环。
        int nr, i, timeout = -1;
​
        for (i = 0; i < fd_count; i++)
            ufds[i].revents = 0;
        
        //在循环中执行动作
        drain_action_queue();
        restart_processes(); //重启那些已经死去的进程
......
#if BOOTCHART
        ...... // Boot Chart相关
#endif
        //调用poll等待一些事情的发生
        nr= poll(ufds, fd_count, timeout);
        ......
        //ufds[2]保存的是signal_recv_fd,用于接收来自socket的消息。
        if(ufds[2].revents == POLLIN) {
            //有一个子进程去世,init要处理这个事情
            read(signal_recv_fd, tmp, sizeof(tmp));
            while (!wait_for_one_process(0))
               ;
            continue;
        }
 
        if(ufds[0].revents == POLLIN)
           handle_device_fd(device_fd);//处理Uevent事件
        if(ufds[1].revents == POLLIN)
           handle_property_set_fd(property_set_fd);//处理属性服务的事件。
        if(ufds[3].revents == POLLIN)
           handle_keychord(keychord_fd);//处理keychord事件。
    }
    return 0;
}
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169

从上面的代码中可知,init的工作任务还是很重的。上面的代码虽已省略了不少行,可结果还是很长,不过从本章要分析的两个知识点来看,可将init的工作流程精简为以下四点:

  • 解析两个配置文件,其中,将分析对init.rc文件的解析。
  • 执行各个阶段的动作,创建Zygote的工作就是在其中的某个阶段完成的。
  • 调用property_init初始化属性相关的资源,并且通过property_start_service启动属性服务。
  • init进入一个无限循环,并且等待一些事情的发生。重点关注init如何处理来自socket和来自属性服务器相关的事情。

精简工作流程,是以后分析代码时常用的方法。读者在分析代码的过程中,也可使用这种方法。

解析配置文件

根据上面的代码可知,在init中会解析两个配置文件,其中一个是系统配置文件init.rc,另外一个是和硬件平台相关的配置文件。以HTC G7手机为例,这个配置文件名为init.bravo.rc,其中bravo是硬件平台的名称。对这两个配置文件进行解析,调用的是同一个parse_config_file函数。下面就来看这个函数,在分析过程中以init.rc为主。

parser.c

int parse_config_file(const char *fn)
{
    char *data;
    data = read_file(fn, 0);//读取配置文件的内容,这个文件是init.rc。
    if (!data) return -1;
    parse_config(fn,data); //调用parse_config做真正的解析
    return 0;
}
12345678

读取完文件的内容后,将调用parse_config进行解析,这个函数的代码如下所示:

parser.c

static void parse_config(const char *fn, char*s)
{
struct parse_state state;
char *args[SVC_MAXARGS];
int nargs;
​
nargs = 0;
state.filename = fn;
state.line = 1;
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;
      caseT_NEWLINE:
           if (nargs) {
              //得到关键字的类型
               int kw = lookup_keyword(args[0]);
               if (kw_is(kw, SECTION)) { //判断关键字类型是不是SECTION。
                    state.parse_line(&state,0, 0);
                   parse_new_section(&state,kw, nargs, args);//解析这个SECTION。
               } else {
                   state.parse_line(&state, nargs, args);
               }
               nargs = 0;
           }
           break;
       case T_TEXT:
          ......
           break;
        }
    }
}
123456789101112131415161718192021222324252627282930313233343536

上面就是parse_config函数,代码虽短,实际却比较复杂。从整体来说,parse_config首先会找到配置文件的一个section,然后针对不同的 section使用不同的解析函数来解析。那么,什么是section呢?这和init.rc文件的组织结构有关。先不必急着去看init.rc,还是先到代码中去寻找答案。

关键字定义

keywords.h这个文件定义了init中使用的关键字,它的用法很有意思,先来看这个文件,代码如下所示:

keywords.h

#ifndef KEYWORD //如果没有定义KEYWORD宏,则走下面的分支
......//声明一些函数,这些函数就是前面所说Action的执行函数。
int do_class_start(int nargs, char **args);
int do_class_stop(int nargs, char **args);
......
int do_restart(int nargs, char **args);
......
#define __MAKE_KEYWORD_ENUM__  //定义一个宏
​
/*
定义KEYWORD宏,虽然有四个参数,不过这里只用第一个,其中K_##symbol中的##表示连接
的意思,即最后得到的值为K_symbol。symbol其实就是init.rc中的关键字
*/
#define KEYWORD(symbol, flags, nargs, func)K_##symbol,
enum { //定义一个枚举,这个枚举定义了各个关键字的枚举值。
   K_UNKNOWN,
#endif
    ......
    //根据上面KEYWORD的定义,这里将得到一个枚举值K_class,
    KEYWORD(class,       OPTION,  0, 0)
    KEYWORD(class_start, COMMAND, 1, do_class_start)//K_class_start,
    KEYWORD(class_stop,  COMMAND, 1, do_class_stop)//K_class_stop,
    KEYWORD(on,          SECTION, 0, 0)//K_on,
    KEYWORD(oneshot,     OPTION,  0, 0)
    KEYWORD(onrestart,   OPTION,  0, 0)
    KEYWORD(restart,     COMMAND, 1,do_restart)
    KEYWORD(service,     SECTION, 0,0)
    ......
    KEYWORD(socket,      OPTION,  0, 0)
    KEYWORD(start,       COMMAND, 1,do_start)
    KEYWORD(stop,        COMMAND, 1,do_stop)
    ......
#ifdef __MAKE_KEYWORD_ENUM__
    KEYWORD_COUNT,
};
#undef __MAKE_KEYWORD_ENUM__
#undef KEYWORD //取消KEYWORD宏定义
#endif
1234567891011121314151617181920212223242526272829303132333435363738

keywords.h好像没什么奇特,不过是个简单的头文件。为什么说它的用法很有意思呢?来看代码中是如何使用它的,如下所示:

parser.c

......//parser.c中将包含keywords.h头文件,而且还不只一次!!
//第一次包含keywords.h,根据keywords.h的代码,我们首先会得到一个枚举定义
#include "keywords.h"
/*
重新定义KEYWORD宏,这回四个参数全用上了,看起来好像是一个结构体。其中#symbol表示
一个字符串,其值为“symbol”。
*/
#define KEYWORD(symbol, flags, nargs, func) \
    [K_##symbol ] = { #symbol, func, nargs + 1, flags, },
​
//定义一个结构体keyword_info数组,它用来描述关键字的一些属性,请注意里面的注释内容。
struct {
    constchar *name;  //关键字的名。
    int(*func)(int nargs, char **args);//对应关键字的处理函数。
    unsignedchar nargs;//参数个数,每个关键字的参数个数是固定的。
    //关键字的属性,有三种属性,COMMAND、OPTION和SECTION。其中COMMAND有对应的处理函数
    unsigned char flags;
} keyword_info[KEYWORD_COUNT] = {
[ K_UNKNOWN ] = { "unknown", 0, 0, 0},
​
/*
第二次包含keywords.h,由于已经重新定了KEYWORD宏,所以以前那些作为枚举值的关键字
现在变成keyword_info数组的索引了。
*/
#include "keywords.h"   
};
#undef KEYWORD
 
//一些辅助宏,帮助我们快速操作keyword_info中的内容。
#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)
123456789101112131415161718192021222324252627282930313233

现在领略了keywords.h的神奇之处了吧?原来它干了两件事情:

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

目前,关键字信息中最重要的就是symbol和flags了。什么样的关键字被认为是section呢?根据keywords.h的定义,symbol为下面两个的关键字表示section:

KEYWORD(on,          SECTION, 0, 0)
KEYWORD(service,     SECTION, 0, 0)
12

有了上面的知识,再来看配置文件init.rc的内容。

init.rc的解析

init.rc的内容如下所示:(我们截取了部分内容,注意,其中的注释符号是#。)

init.rc

on init  #根据上面的分析,on关键字标示一个section,对应的名字是”init”
 ......  #下面所有的内容都属于这个section,直到下一个section开始时。
 export PATH /sbin:/system/sbin:/system/bin:/system/xbin
 export LD_LIBRARY_PATH /system/lib
 export ANDROID_BOOTLOGO 1 #根据keywords.h的定义,export表示一个COMMAND
 export ANDROID_ROOT /system
 export ANDROID_ASSETS /system/app
...... #省略部分内容
on boot  #这是一个新的section,名为”boot”
   ifup lo#这是一个COMMAND
   hostname localhost
   domainname localdomain
    ......
   #class_start也是一个COMMAND,对应函数为do_class_start,很重要,切记。
    class_start default 
    ......
    #下面这个section的意思是:待属性persist.service.adb.enable的值变为1后,
    #需要执行对应的COMMAND,这个COMMAND是start adbd
     on property:persist.service.adb.enable=1
         start adbd //start是一个COMMAND
     on property:persist.service.adb.enable=0
         stop adbd
    ......
#service也是section的标示,对应section的名为“zygote“
service zygote /system/bin/app_process -Xzygote/system/bin –zygote \
 --start-system-server
    socketzygote stream 666  #socket关键字表示OPTION
    onrestart write /sys/android_power/request_state wake #onrestart也是OPTION
    onrestart write /sys/power/state on
    onrestart restart media
#一个section,名为”media”
service media /system/bin/mediaserver
    user media
    group system audio camera graphics inet net_bt net_bt_admin net_raw
iopriort 4
1234567891011121314151617181920212223242526272829303132333435

从上面对init.rc的分析中可知:

  • 一个section的内容从这个标示section的关键字开始,到下一个标示section的地方结束。
  • init.rc中出现了名为boot和init的section,这里的boot和init,就是前面介绍的动作执行四个阶段中的boot和init。也就是说,在boot阶段执行的动作都是由boot这个section定义的。

另外还可发现,zygote被放在了一个servicesection中。下面以zygote这个section为例,介绍service是如何解析的。

解析service

zygote对应的service section内容是:

init.rc::zygote

service zygote /system/bin/app_process -Xzygote/system/bin –zygote \
--start-system-server
    socketzygote stream 666  #socket是OPTION
    #下面的onrestart是OPTION,而write和restart是COMMAND
    onrestart write /sys/android_power/request_state wake
    onrestart write /sys/power/state on
    onrestart restart media
1234567

解析section的入口函数是parse_new_section,它的代码如下所示:

parser.c

void parse_new_section(struct parse_state*state, int kw,
                       int nargs, char **args)
{
    switch(kw) {
    case K_service:  //解析service,用parse_service和parse_line_service
        state->context = parse_service(state, nargs, args);
        if(state->context) {
            state->parse_line = parse_line_service;
            return;
        }
        break;
    case K_on: //解析on section
        ......//读者可以自己研究
        break;
    }
    state->parse_line = parse_line_no_op;
}
1234567891011121314151617

其中,service解析时,用到了parse_service和parse_line_service两个函数,在分别介绍它们之前,先看init是如何组织这个service的。

service结构体

init中使用了一个叫service的结构体来保存和service section相关的信息,不妨来看这个结构体,代码如下所示:

init.h::service结构体定义

struct service {
  //listnode是一个特殊的结构体,在内核代码中用得非常多,主要用来将结构体链接成一个
  //双向链表。init中有一个全局的service_list,专门用来保存解析配置文件后得到的service。
    struct listnode slist; 
    constchar *name; //service的名字,对应我们这个例子就是”zygote”。
    constchar *classname; //service所属class的名字,默认是”defult”
    unsigned flags;//service的属性
    pid_t pid;    //进程号
    time_t time_started;   //上一次启动的时间
    time_t time_crashed;  //上一次死亡的时间
    int nr_crashed;        //死亡次数
    uid_t uid;     //uid,gid相关
    gid_t gid;
    gid_t supp_gids[NR_SVC_SUPP_GIDS];
    size_t nr_supp_gids;
    /*
    有些service需要使用socket,下面这个socketinfo用来描述socket的相关信息。
    我们的zygote也使用了socket,配置文件中的内容是socket zygote stream 666。
    它表示将创建一个AF_STREAM类型的socket(其实就是TCP socket),该socket的名为“zygote”,
    读写权限是666。
    */
    struct socketinfo *sockets; 
    //service一般运行在单独的一个进程中,envvars用来描述创建这个进程时所需的环境变量信息。
    struct svcenvinfo *envvars; 
    /*
    虽然关键字onrestart标示一个OPTION,可是这个OPTION后面一般跟着COMMAND,
    下面这个action结构体可用来存储command信息,马上就会分析到它。
    */
    struct action onrestart;
​
    //和keychord相关的内容
    int *keycodes;
    int nkeycodes;
    int keychord_id;
    //io优先级设置
    int ioprio_class;
    int ioprio_pri;
    //参数个数
    int nargs;
    //用于存储参数
    char *args[1];
}; 
123456789101112131415161718192021222324252627282930313233343536373839404142

我们现在已了解的service的结构体,相对来说还算是清晰易懂的。而zygote中的那三个onrestart该怎么表示呢?请看service中使用的这个action结构体:

init.h::action结构体定义

struct action {
    /*
    一个action结构体可存放在三个双向链表中,其中alist用于存储所有action,
    qlist用于链接那些等待执行的action,tlist用于链接那些待某些条件满足后
    就需要执行的action。
    */
    struct listnode alist;
    struct listnode qlist;
    struct listnode tlist;
​
    unsigned hash;
    const char *name;
   
    //这个OPTION对应的COMMAND链表,以zygote为例,它有三个onrestart option,所以
    //它对应会创建三个command结构体。
    struct listnode commands;
    struct command *current;
};
123456789101112131415161718

了解了上面的知识后,你是否能猜到parse_service和parse_line_service的作用了呢?马上就来看它们。

parse_service

parse_service的代码如下所示:

parser.c

static void *parse_service(struct parse_state*state, int nargs, char **args)
{
    struct service *svc; //声明一个service结构体
    ......
    //init维护了一个全局的service链表,先判断是否已经有同名的service了。
    svc =service_find_by_name(args[1]);
    if(svc) {
       ......  //如果有同名的service,则不能继续后面的操作。
       return 0;
    }
   
    nargs-= 2;
    svc = calloc(1, sizeof(*svc) + sizeof(char*) * nargs);
    ......
    svc->name = args[1];
    svc->classname = "default";//设置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);
    //把zygote这个service加到全局链表service_list中。
    list_add_tail(&service_list, &svc->slist);
    return svc;
}
1234567891011121314151617181920212223242526

parse_service函数只是搭建了一个service的架子,具体的内容尚需由后面的解析函数来填充。来看service的另外一个解析函数parse_line_service。

了解parse_line_service

parse_line_service的代码如下所示:

parser.c

static void parse_line_service(structparse_state *state, int nargs, char **args)
{
    struct service *svc = state->context;
    struct command *cmd;
    int i,kw, kw_nargs;
    ......
    svc->ioprio_class = IoSchedClass_NONE;
    //其实还是根据关键字来做各种处理。
    kw =lookup_keyword(args[0]);
    switch(kw) {
    case K_capability:
        break;
    case K_class:
        if(nargs != 2) {
            ......
        }else {
            svc->classname = args[1];
        }
        break;
    ......
    case K_oneshot:
        /*
        这是service的属性,它一共有五个属性,分别为:
        SVC_DISABLED:不随class自动启动。下面将会看到class的作用。
        SVC_ONESHOT:退出后不需要重启,也就是这个service只启动一次就可以了。
        SVC_RUNNING:正在运行,这是service的状态。
        SVC_RESTARTING:等待重启,这也是service的状态。
        SVC_CONSOLE:该service需要使用控制台 。
        SVC_CRITICAL:如果在规定时间内该service不断重启,则系统会重启并进入恢复模式。
        zygote没有使用任何属性,这表明它:会随着class的处理自动启动;
        退出后会由init重启;不使用控制台;即使不断重启也不会导致系统进入恢复模式。
        */
        svc->flags |= SVC_ONESHOT;
        break;
    case K_onrestart: //根据onrestart的内容,填充action结构体的内容
        nargs--;
        args++;
        kw= lookup_keyword(args[0]);
        ......
        //创建command结构体
        cmd = malloc(sizeof(*cmd) + sizeof(char*) * nargs);
        cmd->func = kw_func(kw);
        cmd->nargs = nargs;
        memcpy(cmd->args, args, sizeof(char*) * nargs);
        //把新建的command加入到双向链表中。
        list_add_tail(&svc->onrestart.commands, &cmd->clist);
        break;
    ......
    case K_socket: { //创建socket相关信息
        struct socketinfo *si;
        ......
        si= calloc(1, sizeof(*si));
        if(!si) {
            parse_error(state, "out of memory\n");
            break;
        }
        si->name = args[1]; //socket的名字
        si->type = args[2]; //socket的类型
        si->perm = strtoul(args[3], 0, 8); //socket的读写权限
        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;
        break;
    }
    ......
    default:
        parse_error(state, "invalid option '%s'\n", args[0]);
    }
}
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172

parse_line_service将根据配置文件的内容填充service结构体,那么,zygote解析完后会得到什么呢?图3-1表示了zygote解析后的结果:

图3-1 zygote解析结果示意图

从上图中可知:

  • service_list链表将解析后的service全部链接到了一起,并且是一个双向链表,前向节点用prev表示,后向节点用next表示。
  • socketinfo也是一个双向链表,因为zygote只有一个socket,所以画了一个虚框socket做为链表的示范。
  • onrestart通过commands指向一个commands链表,zygote有三个commands。

zygote这个service解析完了,现在就是“万事俱备,只欠东风”了。接下来要了解的是,init是如何控制service的。

init控制service

先看service是如何启动的。

启动zygote

init.rc中有这样一句话:

#class_start是一个COMMAND,对应的函数为do_class_start,很重要,切记。
class_start default
12

class_start标示一个COMMAND,对应的处理函数为do_class_start,它位于boot section的范围内。为什么说它很重要呢?

还记得init进程中的四个执行阶段吗?当init进程执行到下面几句话时,do_class_start就会被执行了。

//将bootsection节的command加入到执行队列
action_for_each_trigger("boot",action_add_queue_tail);
//执行队列里的命令,class可是一个COMMAND,所以它对应的do_class_start会被执行。
drain_action_queue();
1234

下面来看do_class_start函数:

builtins.c

int do_class_start(int nargs, char **args)
{
    /*
    args为do_class_start的参数,init.rc中只有一个参数,就是default。
    下面这个函数将从service_list中寻找classname为”default”的service,然后
    调用service_start_if_not_disabled函数。现在读者明白了service结构体中
    classname的作用了吗?
    */
    service_for_each_class(args[1],service_start_if_not_disabled);
    return 0;
}
1234567891011

我们已经知道,zygote这个service的classname的值就是“default”,所以会针对这个service调用service_start_if_not_disabled,这个函数的代码是:

parser.c

static void service_start_if_not_disabled(struct service *svc)
{
    if (!(svc->flags & SVC_DISABLED)) {
         service_start(svc,NULL); //zygote可没有设置SVC_DISABLED
    }
}
123456

service_start函数的代码如下所示:

init.c

void service_start(struct service *svc, constchar *dynamic_args)
{
    struct stat s;
    pid_t pid;
    int needs_console;
    int n;
 
    svc->flags &= (~(SVC_DISABLED|SVC_RESTARTING));
    svc->time_started = 0;
   
    if(svc->flags & SVC_RUNNING) {
       return;//如果这个service已在运行,则不用处理
​
    }
    /*
    service一般运行于另外一个进程中,这个进程也是init的子进程,所以启动service前需要判断
    对应的可执行文件是否存在,zygote对应的可执行文件是/system/bin/app_process
    */
    if(stat(svc->args[0], &s) != 0) {
        svc->flags |= SVC_DISABLED;
        return;
    }
    ......
    pid =fork(); //调用fork创建子进程
    if(pid == 0) {
        //pid为零,我们在子进程中
       struct socketinfo *si;
       struct svcenvinfo *ei;
       char tmp[32];
       int fd, sz;
​
        //得到属性存储空间的信息并加到环境变量中,后面在属性服务一节中会碰到使用它的地方。
        get_property_workspace(&fd, &sz);
        add_environment("ANDROID_PROPERTY_WORKSPACE", tmp);
        //添加环境变量信息
        for (ei = svc->envvars; ei; ei = ei->next)
            add_environment(ei->name, ei->value);
            //根据socketinfo创建socket
            for (si = svc->sockets; si; si = si->next) {
                int s = create_socket(si->name,
                                  !strcmp(si->type,"dgram") ?
                                  SOCK_DGRAM :SOCK_STREAM,
                                  si->perm,si->uid, si->gid);
               if (s >= 0) {
                   //在环境变量中添加socket信息。
                    publish_socket(si->name, s);
               }
        }
       ......//设置uid,gid等
        setpgid(0, getpid());
       if(!dynamic_args) {
        /*
        执行/system/bin/app_process,这样就进入到app_process的main函数中了。
        fork、execve这两个函数都是Linux系统上常用的系统调用。
        */
            if (execve(svc->args[0], (char**)svc->args, (char**) ENV) < 0) {
                ......
            }
        } else {
          ......
    }
    ......//父进程init的处理,设置service的信息,如启动时间、进程号,以及状态等。
    svc->time_started = gettime();
    svc->pid = pid;
    svc->flags |= SVC_RUNNING;
    //每一个service都有一个属性,zygote的属性为init.svc.zygote,现在设置它的值为running
    notify_service_state(svc->name, "running");
}
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768

原来,zygote是通过fork和execv共同创建的!但service结构中的那个onrestart好像没有派上用场,原因何在?

重启zygote

根据名字,就可猜到onrestart应该是在zygote重启时用的。下面先看在zygote死后,它的父进程init会有什么动作:

init.c

static void sigchld_handler(int s)
{  //当子进程退出时,init的这个信号处理函数会被调用
   write(signal_fd, &s, 1); //往signal_fd write数据
}
1234

signal_fd,就是在init中通过socketpair创建的两个socket中的一个,既然会往这个signal_fd中发送数据,那么另外一个socket就一定能接收到,这样就会导致init从poll函数中返回:

init.rc::main函数代码片断

 nr =poll(ufds, fd_count, timeout);
 ......
 if(ufds[2].revents == POLLIN) {
   read(signal_recv_fd, tmp, sizeof(tmp));
       while (!wait_for_one_process(0))//调用wait_for_one_process函数处理
         ;
       continue;
 }
 ......
//直接看这个wait_for_one_process函数:
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;
​
    while( (pid = waitpid(-1, &status, block ? 0 : WNOHANG)) == -1 && errno == EINTR );
    if(pid <= 0) return -1;
    //找到死掉的那个service,现在应该找到了代表zygote的那个service。
    svc = service_find_by_pid(pid);
    ......
​
    if(!(svc->flags & SVC_ONESHOT)) {
        //杀掉zygote创建的所有子进程,这就是zygote死后,Java世界崩溃的原因。
        kill(-pid, SIGKILL);
    }
 
    //清理socket信息,不清楚的读者可以通过命令man 7 AF_UNIX查询一下相关知识。
    for(si = svc->sockets; si; si = si->next) {
        char tmp[128];
        snprintf(tmp, sizeof(tmp), ANDROID_SOCKET_DIR"/%s",si->name);
        unlink(tmp);
    }
 
    svc->pid = 0;
    svc->flags &= (~SVC_RUNNING);
 
    if(svc->flags & SVC_ONESHOT) {
       svc->flags |= SVC_DISABLED;
    }
    ......
    now = gettime();
    /*
    如果设置了SVC_CRITICAL标示,则4分钟内该服务重启次数不能超过4次,否则
    机器会重启进入recovery模式。根据init.rc的配置,只有servicemanager进程
    享有此种待遇。
    */
    if(svc->flags & SVC_CRITICAL) {
        if(svc->time_crashed + CRITICAL_CRASH_WINDOW >= now) {
           if (++svc->nr_crashed > CRITICAL_CRASH_THRESHOLD) {
              ......
               sync();
               __reboot(LINUX_REBOOT_MAGIC1,LINUX_REBOOT_MAGIC2,
                        LINUX_REBOOT_CMD_RESTART2, "recovery");
               return 0;
           }
        } else {
           svc->time_crashed = now;
           svc->nr_crashed = 1;
        }
    }
​
    svc->flags |= SVC_RESTARTING;
    //设置标示为SVC_RESTARTING,然后执行该service onrestart中的COMMAND,这些内容就
    //非常简单了,读者可以自行学习。
    list_for_each(node, &svc->onrestart.commands) {
        cmd = node_to_item(node, struct command, clist);
        cmd->func(cmd->nargs, cmd->args);
    }
    //设置init.svc.zygote的值为restarting。
    notify_service_state(svc->name, "restarting");
    return 0;
}
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677

通过上面的代码,可知道onrestart的作用了,但zygote本身又在哪里重启的呢?答案就在下面的代码中:

init.c::main函数代码片断

for(;;) {
       int nr, i, timeout = -1;
       for (i = 0; i < fd_count; i++)
           ufds[i].revents = 0;
       drain_action_queue(); //poll函数返回后,会进入下一轮的循环
       restart_processes(); //这里会重启所有flag标志为SVC_RESTARTING的service。
       ......
}
12345678

这样,zygote又回来了!

属性服务

我们知道,Windows平台上有一个叫注册表的东西。注册表可以存储一些类似key/value的键值对。一般而言,系统或某些应用程序会把自己的一些属性存储在注册表中,即使下次系统重启或应用程序重启,它还能够根据之前在注册表中设置的属性,进行相应的初始化工作。Android平台也提供了一个类型机制,可称之为属性服务(property service)。应用程序可通过这个属性机制,查询或设置属性。读者可以用adb shell登录到真机或模拟器上,然后用getprop命令查看当前系统中有哪些属性。即如我的HTC G7测试结果,如图3-2所示:(图中只显示了部分属性)

图3-2 HTC G7属性示意图

这个属性服务是怎么实现的呢?下面来看代码,其中与init.c和属性服务有关的代码有下面两行:

property_init();
property_set_fd = start_property_service();
12

分别来看看它们。

属性服务初始化

(1)创建存储空间 先看property_init函数,代码如下所示:

property_service.c

void property_init(void)
{
    init_property_area();//初始化属性存储区域
    //加载default.prop文件
    load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT);
}
123456

在properyty_init函数中,先调用init_property_area函数,创建一块用于存储属性的存储区域,然后加载default.prop文件中的内容。再看init_property_area是如何工作的,它的代码如下所示:

property_service.c

static int init_property_area(void)
{
    prop_area *pa;
​
    if(pa_info_array)
        return -1;
        /*
        初始化存储空间,PA_SIZE是这块存储空间的总大小,为32768字节,pa_workspace
        为workspace类型的结构体,下面是它的定义:
        typedef struct {
            void *data;   //存储空间的起始地址
            size_tsize;  //存储空间的大小
            int fd;   //共享内存的文件描述符
        } workspace;
        init_workspace函数调用Android系统提供的ashmem_create_region函数创建一块
        共享内存。关于共享内存的知识我们在第7章会接触,这里,只需把它当做一块普通的内存就
        可以了。
        */
        if(init_workspace(&pa_workspace, PA_SIZE))
            return -1;
 
        fcntl(pa_workspace.fd, F_SETFD, FD_CLOEXEC);
​
        //在32768个字节的存储空间中,有PA_INFO_START(1024)个字节用来存储头部信息
        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;
            //__system_property_area__这个变量由bionic libc库输出,有什么用呢?
            __system_property_area__ = pa;
​
        return0;
}
12345678910111213141516171819202122232425262728293031323334

上面的内容比较简单,不过最后的赋值语句可是大有来头。system_property_area是bionic libc库中输出的一个变量,为什么这里要给它赋值呢?

原来,虽然属性区域是由init进程创建,但Android系统希望其他进程也能读取这块内存里的东西。为做到这一点,它便做了以下两项工作:

  • 把属性区域创建在共享内存上,而共享内存是可以跨进程的。这一点,已经在上面的代码中见到了,init_workspace函数内部将创建这个共享内存。
  • 如何让其他进程知道这个共享内存呢?Android利用了gcc的constructor属性,这个属性指明了一个libc_prenit函数,当bionic libc库被加载时,将自动调用这个libc_prenit,这个函数内部就将完成共享内存到本地进程的映射工作。

(2)客户端进程获取存储空间 关于上面的内容,来看相关代码:

libc_init_dynamic.c

//constructor属性指示加载器加载该库后,首先调用__libc_prenit函数。这一点和Windows上
//动态库的DllMain函数类似
void __attribute__((constructor))__libc_prenit(void);
void __libc_prenit(void)
{
    ......
     __libc_init_common(elfdata); //调用这个函数
    ......
}
123456789

__libc_init_common函数为:

libc_init_common.c

void __libc_init_common(uintptr_t *elfdata)
{
   ......
   __system_properties_init();//初始化客户端的属性存储区域
}
12345

system_properties.c

int __system_properties_init(void)
{
    prop_area *pa;
    int s,fd;
    unsigned sz;
    char *env;
​
    .....
    //还记得在启动zygote一节中提到的添加环境变量的地方吗?属性存储区域的相关信息
    //就是在那儿添加的,这里需要取出来使用了。
    env = getenv("ANDROID_PROPERTY_WORKSPACE");
    //取出属性存储区域的文件描述符。关于共享内存的知识,第7章中将会进行介绍。
    fd = atoi(env);
    env = strchr(env, ',');
    if (!env) {
        return -1;
    }
    sz = atoi(env + 1);
    //映射init创建的那块内存到本地进程空间,这样本地进程就可以使用这块共享内存了。
    //注意,映射的时候指定了PROT_READ属性,所以客户端进程只能读属性,而不能设置属性。
    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;
}
1234567891011121314151617181920212223242526272829303132

上面代码中很多地方和共享内存有关,在第7章中会对与共享内存有关问题进行介绍,读者也可先行学习有关共享内存的知识。

总之,通过这种方式,客户端进程可以直接读取属性空间,但没有权限设置属性。客户端进程又是如何设置属性呢?

启动属性服务器

(1)启动属性服务器 init进程会启动一个属性服务器,而客户端只能通过和属性服务器交互才能设置属性。先来看属性服务器的内容,它由start_property_service函数启动,代码如下所示:

Property_servie.c

int start_property_service(void)
​
{
    int fd;
​
    /*
    加载属性文件,其实就是解析这些文件中的属性,然后把它设置到属性空间中去。Android系统
    一共提供了四个存储属性的文件,它们分别是:
    #definePROP_PATH_RAMDISK_DEFAULT    "/default.prop"
    #define PROP_PATH_SYSTEM_BUILD      "/system/build.prop"
    #define PROP_PATH_SYSTEM_DEFAULT    "/system/default.prop"
    #define PROP_PATH_LOCAL_OVERRIDE    "/data/local.prop"
    */
  
    load_properties_from_file(PROP_PATH_SYSTEM_BUILD);
    load_properties_from_file(PROP_PATH_SYSTEM_DEFAULT);
    load_properties_from_file(PROP_PATH_LOCAL_OVERRIDE);
    //有一些属性是需要保存到永久介质上的,这些属性文件则由下面这个函数加载,这些文件
    //存储在/data/property目录下,并且这些文件的文件名必须以persist.开头。这个函数
    //很简单,读者可自行研究。
    load_persistent_properties();
   //创建一个socket,用于IPC通信。
    fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM, 0666, 0, 0);
    if( fd< 0) return -1;
    fcntl(fd, F_SETFD, FD_CLOEXEC);
    fcntl(fd, F_SETFL, O_NONBLOCK);
    listen(fd, 8);
    return fd;
​
}
123456789101112131415161718192021222324252627282930

属性服务创建了一个用来接收请求的socket,可这个请求在哪里被处理呢?事实上,在init中的for循环那里已经进行相关处理了。

(2)处理设置属性请求 接收请求的地方是在init进程中,代码如下所示:

init.c::main函数片断

if (ufds[1].revents == POLLIN)
           handle_property_set_fd(property_set_fd);
12

当属性服务器收到客户端请求时,init会调用handle_property_set_fd进行处理。这个函数的代码如下所示:

property_service.c

void handle_property_set_fd(int fd)
{
    prop_msg msg;
    int s;
    int r;
    int res;
    struct ucred cr;
    struct sockaddr_un addr;
    socklen_t addr_size = sizeof(addr);
    socklen_t cr_size = sizeof(cr);
    //先接收TCP连接
    if ((s= accept(fd, (struct sockaddr *) &addr, &addr_size)) < 0) {
       return;
    }
​
    //取出客户端进程的权限等属性。
    if(getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) {
        ......
        return;
    }
    //接收请求数据
    r = recv(s,&msg, sizeof(msg), 0);
    close(s);
    ......
    switch(msg.cmd) {
    case PROP_MSG_SETPROP:
       msg.name[PROP_NAME_MAX-1] = 0;
       msg.value[PROP_VALUE_MAX-1] = 0;
        /*
        如果是ctl开头的消息,则认为是控制消息,控制消息用来执行一些命令,例如用
        adb shell登录后,输入setprop ctl.start bootanim就可以查看开机动画了,
        关闭的话就输入setpropctl.stop bootanim,是不是很有意思呢?
        */
       if(memcmp(msg.name,"ctl.",4) == 0) {
           if (check_control_perms(msg.value, cr.uid, cr.gid)) {
               handle_control_message((char*) msg.name + 4, (char*) msg.value);
           }
           ......
        }else {
           //检查客户端进程是否有足够的权限
           if (check_perms(msg.name, cr.uid, cr.gid)) {
               //然后调用property_set设置。
               property_set((char*) msg.name, (char*) msg.value);
           }
           ......
        }
       break;
   default:
       break;
    }
}
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051

当客户端的权限满足要求时,init就调用property_set进行相关处理,这个函数比较简单,代码如下所示:

property_service.c

int property_set(const char *name, const char*value)
{
    prop_area *pa;
    prop_info *pi;
​
    int namelen = strlen(name);
    int valuelen = strlen(value);
    ......
    //从属性存储空间中寻找是否已经存在该属性
    pi = (prop_info*) __system_property_find(name);
 
    if(pi!= 0) {
        //如果属性名以ro.开头,则表示是只读的,不能设置,所以直接返回。
        if(!strncmp(name, "ro.", 3)) return -1;
        pa = __system_property_area__;
        //更新该属性的值
        update_prop_info(pi, value, valuelen);
        pa->serial++;
        __futex_wake(&pa->serial, INT32_MAX);
    }else {
        //如果没有找到对应的属性,则认为是增加属性,所以需要新创建一项。注意,Android支持
        //最多247项属性,如果目前属性的存储空间中已经有247项,则直接返回。
        pa = __system_property_area__;
        if(pa->count == PA_COUNT_MAX) return -1;
​
        pi = pa_info_array + pa->count;
        pi->serial = (valuelen << 24);
        memcpy(pi->name, name, namelen + 1);
        memcpy(pi->value, value, valuelen +1);
​
        pa->toc[pa->count] =
           (namelen << 24) | (((unsigned) pi) - ((unsigned) pa));
​
       pa->count++;
       pa->serial++;
       __futex_wake(&pa->serial, INT32_MAX);
    }
    //有一些特殊的属性需要特殊处理,这里,主要是以net.change开头的属性。
    if(strncmp("net.", name, strlen("net.")) == 0)  {
        if(strcmp("net.change", name) == 0) {
            return 0;
        }
        property_set("net.change", name);
    } elseif (persistent_properties_loaded &&
        strncmp("persist.", name,strlen("persist.")) == 0) {
        //如果属性名以persist.开头,则需要把这些值写到对应文件中去。
        write_persistent_property(name, value);
    }
    /*
    还记得init.rc中的下面这句话吗?
    on property:persist.service.adb.enable=1
         startadbd
    当persist.service.adb.enable属性置为1后,就会执行start adbd这个command,
    这是通过property_changed函数来完成的,它非常简单,读者可以自己阅读。
    */
    property_changed(name, value);
    return 0;
}
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758

好,属性服务端的工作已经了解了,下面看客户端是如何设置属性的。

(3)客户端发送请求 客户端通过property_set发送请求,property_set由libcutils库提供,代码如下所示:

properties.c

int property_set(const char *key, const char*value)
{
    prop_msg msg;
    unsigned resp;
​
    ......
    msg.cmd = PROP_MSG_SETPROP;//设置消息码为PROP_MSG_SETPROP。
    strcpy((char*) msg.name, key);
    strcpy((char*) msg.value, value);
    //发送请求
    returnsend_prop_msg(&msg);
}
​
static int send_prop_msg(prop_msg *msg)
{
    int s;
    int r;
    //建立和属性服务器的socket连接
    s = socket_local_client(PROP_SERVICE_NAME,
                           ANDROID_SOCKET_NAMESPACE_RESERVED,
                            SOCK_STREAM);
    if(s < 0) return -1;
    //通过socket发送出去
    while((r = send(s, msg, sizeof(prop_msg), 0)) < 0) {
        if((errno == EINTR) || (errno == EAGAIN)) continue;
        break;
    }
​
    if(r == sizeof(prop_msg)) {
        r = 0;
    } else{
        r = -1;
    }
​
    close(s);
    return r;
}
12345678910111213141516171819202122232425262728293031323334353637

至此,属性服务器就介绍完了。总体来说,还算比较简单。

小结

本章讲解了init进程如何解析zygote,以及属性服务器的工作原理,旨在帮助读者认识这个天字号第一进程。从整体来说,init.rc的解析难度相对最大。相信读者通过以上实例分析,已经理解了init.rc的解析原理。另外,inti涉及很多和Linux系统相关的知识,有兴趣的读者可以自行研究。

车机资料推荐:《车载开发技术手册》

zygote概述

Zygote本身是一个NATIVE层的应用程序,与驱动、内核无关。前面已经介绍过了,zygote由init进程根据init.rc配置文件创建。其实本质上来说,zygote就是app_process,这个名字在android.mk中指定,但是在运行的时候,app_process通过LINUX下的pctrl系统调用将自己的名字换成了“zygote”,所以通过ps命令就可以看到进程的名字为zygote。

Zygote的进程的主要作用只有两个:

  • 启动SystemServer进程。SystemServer是一个用于启动手机内部各种服务的进程,我们常说的PMS,AMS等都是由SystemServer所启动。
  • 在系统运行过程中,即时的去孵化APP进程,也就是我们每次点击APP图标启动的APP的时候,zygote就开始运作了。

关于孵化:

Zygote进程创建别的进程的时候,用的不是创建,而是孵化这个词,那怎么理解孵化这个意思呢?

  • 我们Android系统的程序,都是基于虚拟机所启动的,如果我们每次启动一个APP都要新启动一个虚拟机,那未免也太卡太慢了。所以为了避免这种场景,zygote进程在启动的时候,就会直接预加载虚拟机所需要的内存等资源,等后面创建应用需要用到的时候,直接共享使用,这样就避免了多次启动虚拟机的情况。
  • zygote进程在创建进程的时候,最后会调到Linux内核自带的的fork方法,来复制一个子进程,从而达到1所说的共享虚拟机内容的情况。
  • zygote进程的作用,就是生成别的进程,自己是不负责做事的。

关于fork:

fork在zygote进程中是一个很重要的概念,这是Linux内核自带的一个方法,这个方法的作用就是复制一个子进程。那么,怎么理解这个复制的意思,简单来说,就是从成员变量,到内存空间,再到当前所执行的代码指令,都会生成一个副本后放到子进程中。

当进程A调用fork后,进程A和复制的子进程B,都会得到fork方法的return值,并且继续往下执行。 返回值: 若成功调用一次则返回两个值,子进程返回0,父进程返回子进程ID;否则,出错返回-1

进程A在开辟一块内存空间之后,持有这个内存空间的引用的话,在fork之后,子进程B也会持有这个内存空间。

fork进程源码解析

Native层启动Zygote进程

由于也不怎么做C语言的开发工作,这部分就快速略过,简单来说就是init进程在读取init.rc文件之后,根据文件里面的指令:

  • 启动了虚拟机。
  • 注册了JNI(Java Native Interface,也就是我们常看见的那些native方法啥的)。
  • 启动了Zygote进程,具体启动方式就是通过反射,拿到ZygoteInit.java这个类,然后去调用里面的main方法。

启动SystemServer

我还是先搬出我的时序图,对这个启动过程先做一个总结。

fork生成SystemServer进程

public class ZygoteInit {
​
    public static void main(String argv[]) {
        // 省略了无关代码
        
        if (startSystemServer) {
           Runnable r = forkSystemServer(abiList, zygoteSocketName, zygoteServer);
    
           // 如果返回的值不为空,就代表当前处于子进程,执行子进程找到的main方法
           if (r != null) {
               r.run();
               return;
           }
       } 
    }
    
    private static Runnable forkSystemServer(String abiList, String socketName,
            ZygoteServer zygoteServer) {
        // 省略了无关代码
        
        int pid;
        /* fork生成SystemServer进程 */
        pid = Zygote.forkSystemServer(
                parsedArgs.mUid, parsedArgs.mGid,
                parsedArgs.mGids,
                parsedArgs.mRuntimeFlags,
                null,
                parsedArgs.mPermittedCapabilities,
                parsedArgs.mEffectiveCapabilities);
                
        // 0表示当前是子进程,也就是SystemServer进程
        if (pid == 0) {
            if (hasSecondZygote(abiList)) {
                waitForSecondaryZygote(socketName);
            }
    
            zygoteServer.closeServerSocket();
            return handleSystemServerProcess(parsedArgs);
        }
        // 非0表示当前是父进程,也就是Zygote进程
        return null;
    }
}
​
public class Zygote {
     static int forkSystemServer(int uid, int gid, int[] gids, int runtimeFlags,
            int[][] rlimits, long permittedCapabilities, long effectiveCapabilities) {
        ZygoteHooks.preFork();
​
        int pid = nativeForkSystemServer(
                uid, gid, gids, runtimeFlags, rlimits,
                permittedCapabilities, effectiveCapabilities);
    
        // Set the Java Language thread priority to the default value for new apps.
        Thread.currentThread().setPriority(Thread.NORM_PRIORITY);
    
        ZygoteHooks.postForkCommon();
        return pid;
    }
    
    /** 最后是调用了内核的fork方法来复制进程 */
    private static native int nativeForkSystemServer(int uid, int gid, int[] gids, int runtimeFlags,
            int[][] rlimits, long permittedCapabilities, long effectiveCapabilities);
}

这里就可以看出来,对于fork方法的基本处理,因为生成的进程和父进程唯一的区别就是return的值不一样,所以就根据这个return值来区分是父进程还是子进程,从而选择接下来要处理的逻辑。

SystemServer进程寻找main方法

从handleSystemServerProcess开始继续看。

public class ZygoteInit {
    private static Runnable handleSystemServerProcess(ZygoteArguments parsedArgs) {
        // 省略了无关代码
        return ZygoteInit.zygoteInit(parsedArgs.mTargetSdkVersion,
                parsedArgs.mDisabledCompatChanges,
                parsedArgs.mRemainingArgs, cl);
    }
​
    public static final Runnable zygoteInit(int targetSdkVersion, long[] disabledCompatChanges,
            String[] argv, ClassLoader classLoader) {
        // 省略了无关代码
        RuntimeInit.commonInit();
        ZygoteInit.nativeZygoteInit();
        return RuntimeInit.applicationInit(targetSdkVersion, disabledCompatChanges, argv,
                classLoader);
    }
}
​
public class RuntimeInit {
​
    protected static Runnable applicationInit(int targetSdkVersion, long[] disabledCompatChanges,
            String[] argv, ClassLoader classLoader) {
        // 省略了无关代码
        return findStaticMain(args.startClass, args.startArgs, classLoader);
    }
    
    /** 
     * 找到className中的静态main方法 
     * 其实就是用反射去找类中的这个方法而已
    */
    protected static Runnable findStaticMain(String className, String[] argv,
            ClassLoader classLoader) {
        Class<?> cl;
​
        try {
            cl = Class.forName(className, true, classLoader);
        } catch (ClassNotFoundException ex) {
            throw new RuntimeException(
                    "Missing class when invoking static main " + className,
                    ex);
        }
    
        Method m;
        try {
            m = cl.getMethod("main", new Class[] { String[].class });
        } catch (NoSuchMethodException ex) {
            throw new RuntimeException(
                    "Missing static main on " + className, ex);
        } catch (SecurityException ex) {
            throw new RuntimeException(
                    "Problem getting static main on " + className, ex);
        }
    
        int modifiers = m.getModifiers();
        if (! (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers))) {
            throw new RuntimeException(
                    "Main method is not public and static on " + className);
        }
    
        /** 这个return的Runnable,最终会在ZygoteInit的main方法中被执行。*/
        return new MethodAndArgsCaller(m, argv);
    }
    
    /** RuntimeInit中封装的Runnable,run方法就是去跑找到的method而已 */
    static class MethodAndArgsCaller implements Runnable {
        /** method to call */
        private final Method mMethod;
    
        /** argument array */
        private final String[] mArgs;
    
        public MethodAndArgsCaller(Method method, String[] args) {
            mMethod = method;
            mArgs = args;
        }
    
        public void run() {
            try {
                mMethod.invoke(null, new Object[] { mArgs });
            } catch (IllegalAccessException ex) {
                throw new RuntimeException(ex);
            } catch (InvocationTargetException ex) {
                Throwable cause = ex.getCause();
                if (cause instanceof RuntimeException) {
                    throw (RuntimeException) cause;
                } else if (cause instanceof Error) {
                    throw (Error) cause;
                }
                throw new RuntimeException(ex);
            }
        }
        
    }
​
}

这段代码不难看出,SystemServer进程做的事,就是通过反射去找到SystemServer.java这个类的静态main方法去执行,同时将ZygoteInit的main方法给return了。

启动APP

启动APP主要是靠ZygoteServer类,这里还是先搬出时序图

看着有点小乱,主要是因为套了一个死循环方法。

class ZygoteInit {
    public static void main(String argv[]) {
        // 省略无关代码
        ZygoteServer zygoteServer = null;
        Runnable caller;
        try {
            zygoteServer = new ZygoteServer(isPrimaryZygote);
            // SelcetLoop方法在fork之后的子进程中会return出一个caller
            // SelectLoop方法会在Zygote进程中无限循环不停止。
            caller = zygoteServer.runSelectLoop(abiList);
        } catch (Throwable ex) {
            Log.e(TAG, "System zygote died with exception", ex);
            throw ex;
        } finally {
            if (zygoteServer != null) {
                zygoteServer.closeServerSocket();
            }
        }
        // 执行子进程中return出来的Runnable方法
        if (caller != null) {
            caller.run();
        }
    }
}
​
class ZygoteServer {
​
    Runnable runSelectLoop(String abiList) {
        // 省略无关代码
    
        // 将SocketFD添加到列表头
        ArrayList<FileDescriptor> socketFDs = new ArrayList<FileDescriptor>();
        socketFDs.add(mZygoteSocket.getFileDescriptor());
        
        while (true) {
            StructPollfd[] pollFDs = null;
            pollFDs = new StructPollfd[socketFDs.size()];
    
            int pollIndex = 0;
            for (FileDescriptor socketFD : socketFDs) {
                pollFDs[pollIndex] = new StructPollfd();
                pollFDs[pollIndex].fd = socketFD;
                pollFDs[pollIndex].events = (short) POLLIN;
                ++pollIndex;
            }
            
            try {
                // 等待文件描述符上的POLLIN事件,代码会阻塞在这里直到响应之后才继续往下执行
                Os.poll(pollFDs, -1);
            } catch (ErrnoException ex) {
                throw new RuntimeException("poll failed", ex);
            }
    
            while (--pollIndex >= 0) {
    
                // 如果没有实际发生的事件并不是可读事件就跳过该文件描述符
                if ((pollFDs[pollIndex].revents & POLLIN) == 0) {
                    continue;
                }
                
                if (pollIndex == 0) {
                    // 创建一个连接
                    ZygoteConnection newPeer = acceptCommandPeer(abiList);
                    peers.add(newPeer);
                    socketFDs.add(newPeer.getFileDescriptor());
                } else if (pollIndex < usapPoolEventFDIndex) {
                    try {
                        // 获取Zygote连接
                        ZygoteConnection connection = peers.get(pollIndex);
                        // 获取一个方法,实际上就是获取子进程的静态Main方法
                        final Runnable command = connection.processOneCommand(this);
    
                        // 成员变量,当fork完子进程的时候,子进程会将该变量设置为true
                        if (mIsForkChild) {
                            if (command == null) {
                                throw new IllegalStateException("command == null");
                            }
    
                            return command;
                        else {
                            //在主进程中关闭掉该连接
                            if (connection.isClosedByPeer()) {
                               connection.closeSocket();
                               peers.remove(pollIndex);
                               socketFDs.remove(pollIndex);
                            }
                        }
                    } catch (Exception e) { }
                }
                
            }
            
        }
        
    }
​
}

这个代码看着其实和Looper.loop非常相似,都是先阻塞在一个地方,等到响应之后才开始继续往下处理。

文件描述符FileDescriptor

在runSelectLoop这个方法里面,会出现FileDescriptor或者FD,这个东西是文件描述符,我把百科的话简单提炼介绍一下:

就是我们每个进程在运行的时候,在虚拟机底层都会有个表(一段连续的存储地址),这个表专门用于记录我们每次打开文件时的信息,包括目标文件的地址,应用层对文件所进行的操作等信息。

每次在对文件进行打开操作的时候,我们都可以获得该文件在这个文件表的下标。这个下标就叫做文件描述符,我们可以根据这个下标通过LINUX系统的API来操作或者监听那些曾经被打开过的文件。

OS.poll

OS.poll,让runSelectLoop阻塞的方法,这个方法的底层调用,就是调用linux系统的poll方法,具体功能为:

等待文件描述符上的某个事件,具体等待什么事件,根据设置决定,看一下StructPollfd 这个对象

public final class StructPollfd {
 /** The file descriptor to poll. */
    public FileDescriptor fd;
​
    /**
     * The events we're interested in. POLLIN corresponds to being in select(2)'s read fd set,
     * POLLOUT to the write fd set.
     */
    public short events;
    
    /** The events that actually happened. */
    public short revents;
}

其中,fd就代表了文件描述符,event代表了关注的事件,revent代表了实际发生的事件。

套入到我们的场景当中,我们等待的就是POLLIN这个事件,也就是可读事件。

创建连接

在if (pollIndex == 0) 中看一下如何创建一个连接,主要是acceptCommandPeer这个方法,所以就从这里看起

class ZygoteServer {
    private ZygoteConnection acceptCommandPeer(String abiList) {
        try {
            // mZygoteSocket的类型为LocalServerSocket
            return createNewConnection(mZygoteSocket.accept(), abiList);
        } catch (IOException ex) {
            throw new RuntimeException(
                    "IOException during accept()", ex);
        }
    }
    
    protected ZygoteConnection createNewConnection(LocalSocket socket, String abiList)
            throws IOException {
        return new ZygoteConnection(socket, abiList);
    }
}
​
​
public class LocalServerSocket implements Closeable {
​
    private final LocalSocketImpl impl;
    
    public LocalSocket accept() throws IOException
    {
        LocalSocketImpl acceptedImpl = new LocalSocketImpl();
    
        impl.accept(acceptedImpl);
    
        return LocalSocket.createLocalSocketForAccept(acceptedImpl);
    }
}
​
class LocalSocketImpl {
    
    protected void accept(LocalSocketImpl s) throws IOException {
        if (fd == null) {
            throw new IOException("socket not created");
        }
    
        try {
            //重点就是这行Os.accept
            s.fd = Os.accept(fd, null /* address */);
            s.mFdCreatedInternally = true;
        } catch (ErrnoException e) {
            throw e.rethrowAsIOException();
        }
    }
}
​

最后核心的部分是,调用了Os.accept,这个方法的功能简单来说就是,当调用Os.accept后,就会根据参数的fd创建一个新的Socket连接,并返回该连接的FD。

启动一个新的进程流程

综合一下前面的内容,我们就可以总结出这个死循环总体的功能了

  1. 在一开始的时候,我们对mZygoteSocket的FD监听POLLIN事件(此时socketFDs 列表里面只有mZygoteSocket对象)。
  2. 另一个进程A通过ZygoteProcess.connect这个方法,来创建一个连接并发出消息(此时调用了Os.socket,创建Socket连接)。
  3. Os.poll触发,此时一定是触发if (pollIndex == 0) 的条件,然后创建一个新的Zygote连接(此时调用了Os.accept)。
  4. 进程A通过在Zygote.connect这个方法的流程中,调用LocalSocketlmpl.connectLocal方法,确认连接。
  5. 然后第二次监听POLLIN的时候,socketFDs里面就有mZygoteSocket和我们在刚刚创建的新连接。
  6. 此时触发的是else if (pollIndex < usapPoolEventFDIndex) ,去fork一个新的进程,并且找到对应的main方法运行,子进程就算创建完成了。

文章到这就是全部内容了,主要讲解车载里运用的Android AutoMotive内容的nit与zygote内核原理;车载资料参考《车载技术手册文档》

附:时序图代码

@startuml
​
participant ZygoteInit as init
participant Zygote as zygote
participant RuntimeInit as rinit
​
​
init -> init : main
activate init
init -> init : forkSystemServer
activate init
    init -> zygote : forkSystemServer
    activate zygote
    zygote -> zygote : nativeForkSystemServer\n调用内核的方法复制进程。
    activate zygote
     zygote --> zygote : return pid\n父进程返回子进程的进程id\n子进程返回0
    deactivate zygote
    zygote --> init : return pid
deactivate zygote
alt pid != 0
    init --> init :return null
else pid == 0
    init -> init : handleSystemServerProcess\n处理SystemServer进程
    activate init
        init -> init : zygoteInit
        init -> rinit : applicationInit
        activate rinit
        rinit -> rinit : findstaticMain\n寻找静态main方法
        rinit --> init : return mainMethod\n返回找到的Main方法\n就是SystemServer.Main
        deactivate rinit
end 
​
deactivate init
​
alt return null
    init -> init : 代表这是zygote进程,处理后续逻辑。
else return mainMethod
    init -> init : run MainMethod\n代表这是SystemServer进程
    init -> init : return
end
​
deactivate init
deactivate init
​
@enduml

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/382383.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

FFmpeg最常用命令参数详解及应用实例

FFMPEG堪称自由软件中最完备的一套多媒体支持库&#xff0c;它几乎实现了所有当下常见的数据封装格式、多媒体传输协议以及音视频编解码器&#xff0c;提供了录制、转换以及流化音视频的完整解决方案。 ffmpeg命令行参数解释 ffmpeg -i [输入文件名] [参数选项] -f [格式] [输出…

lambada表达式

负壹、 函数式编程 Java为什么要支持函数式编程&#xff1f; 代码简洁 函数式编程写出的代码简洁且意图明确&#xff0c;使用stream接口让你从此告别for循环。 多核友好 Java函数式编程使得编写并行程序从未如此简单&#xff0c;你需要的全部就是调用一下parallel()方法。 Jav…

C++ -- STL简介、string的使用

什么是STL STL(standard template libaray-标准模板库)&#xff1a;是C标准库的重要组成部分&#xff0c;不仅是一个可复用的组件库&#xff0c;而且是一个包罗数据结构与算法的软件框架。 STL的版本 原始版本&#xff1a;Alexander Stepanov、Meng Lee 在惠普实验室完成的原…

Person p=new student()是什么意思

记住&#xff1a;父类引用子类对象 Student t new Student(); 实例化一个Student的对象&#xff0c;这个不难理解。但当我这样定义时&#xff1a;Person p new Student(); 这代表什么意思呢&#xff1f; 很简单&#xff0c;它表示我定义了一个Person类型的引用&#xff0c;指…

内大892复试真题16年

内大892复试真题16年 1. 输出三个数中较大数2. 求两个数最大公约数与最小公倍数3. 统计字符串中得字符个数4. 输出菱形5. 迭代法求平方根6. 处理字符串(逆序、进制转换)7. 寻找中位数8. 输入十进制输出n进制1. 输出三个数中较大数 问题 代码 #include <iostream>usin…

如何保护页面端的企业邮箱

作为日常工作交流中不可或缺的工具&#xff0c;电子邮件的安全性一直是备受关注的话题。页面是企业邮箱用户日常邮箱使用的重要接入终端&#xff0c;企业邮箱承载着企业的重要业务内容和关键数据。这个环节的安全问题会对企业产生深远的影响&#xff0c;信息安全建设的重要性不…

CharGPT无法替代的职场发展底层逻辑

​ 什么是职场发展的两大底层逻辑&#xff1f; 一为对事&#xff0c;一为对人&#xff0c;二者不可偏废&#xff0c;否则就会成为“职场上的瘸子”。 01 在刚开始工作的很长一段时间里&#xff0c;我遇到了同一个问题 - 虽然我对自己的工作认真负责&#xff0c;但职业发展却并…

移动硬盘文件或目录损坏且无法读取?分享恢复数据的方法

案例&#xff1a;移动硬盘提示无法访问&#xff0c;文件或目录损坏且无法读取&#xff1f; “这个移动硬盘两年没用了&#xff0c;今天拿出来找文件&#xff0c;插上电脑后移动硬盘打不开&#xff0c;提示无法访问&#xff0c;文件或目录损坏且无法读取。硬盘里有重要文件&…

在字节跳动做了5年软件测试,12月无情被辞,想给划水的兄弟提个醒

先简单交代一下背景吧&#xff0c;某不知名 985 的本硕&#xff0c;17 年毕业加入字节&#xff0c;以“人员优化”的名义无情被裁员&#xff0c;之后跳槽到了有赞&#xff0c;一直从事软件测试的工作。之前没有实习经历&#xff0c;算是5年的工作经验吧。 这5年之间完成了一次…

SpringBoot异常处理

目录 一、 错误处理 1. 默认规则 2. 定制错误处理逻辑 二、自定义异常处理 1. 实现 ErrorController 2. RestControllerAdvice/ControllerAdvice ExceptionHandler 实现自定义异常 3. 新建 UserController.class 测试 3 种不同异常的处理 4. 最终效果如下 补充 1. 参…

【数据结构】八大经典排序总结

文章目录一、排序的概念及其运用1.排序的概念2.常见排序的分类3.排序的运用二、常见排序算法的实现1.直接插入排序1.1排序思想1.2代码实现1.3复杂度及稳定性1.4特性总结2.希尔排序2.1排序思想2.3复杂度及稳定性2.4特性总结3.直接选择排序3.1排序思想3.2代码实现3.3复杂度及稳定…

《数据治理行业实践白皮书》正式发布,开辟数据治理新范式(附下载)

近日&#xff0c;作为首届未来数商大会协办单位之一&#xff0c;袋鼠云承办“首届未来数商大会——业数融合创新论坛”&#xff0c;与参会嘉宾共同探讨数据驱动企业业务增长提效的新思路。袋鼠云联合创始人、易知微CEO 宁海元发表主题演讲《数智视融合&#xff0c;构建数字产业…

Docker安装Cassandra数据库,在SpringBoot中连接Cassandra

简介 Apache Cassandra是一个高度可扩展的高性能分布式数据库&#xff0c;旨在处理许多商用服务器上的大量数据&#xff0c;提供高可用性而没有单点故障。它是NoSQL数据库的一种。首先让我们了解一下NoSQL数据库的作用。 NoSQL 数据库 NoSQL数据库&#xff08;有时称为“Not …

YOLO5-V7.0的python代码转成exe,方便离线部署

思路&#xff1a;用Pyinstaller打包&#xff0c;但有一些坑&#xff0c;踩完坑后成功运行&#xff0c;写个踩坑指南分享下。 前提&#xff1a;已经在conda 环境下安装完可以运行的yolo5代码&#xff0c;例如你的虚拟python环境叫yolo5&#xff0c;主代码是XXX.py&#xff08;这…

数字IC手撕代码--低功耗设计 Clock Gating

背景介绍芯片功耗组成中&#xff0c;有高达 40%甚至更多是由时钟树消耗掉的。这个结果的原因也很直观&#xff0c;因 为这些时钟树在系统中具有最高的切换频率&#xff0c;而且有很多时钟 buffer&#xff0c;而且为了最小化时钟 延时&#xff0c;它们通常具有很高的驱动强度。 …

《分布式技术原理与算法解析》学习笔记Day26

流量控制 什么是流量控制&#xff1f; 网络传输中的流量控制就是让发送方发送数据的速度不要太快&#xff0c;这样可以让接收方来得及接收数据&#xff0c;通常使用滑动窗口的方式来实现。 滑动窗口是指在任意时刻&#xff0c;发送方都维持一个连续的允许发送的数据大小&…

全国CSM敏捷教练认证将于2023年3月25-26开班,报名从速!

CSM&#xff0c;即Certified Scrum Master&#xff0c;是Scrum联盟发起的Scrum认证。 CSM可以帮助团队正确使用Scrum&#xff0c;从而提高项目整体成功的可能性。 CSM深刻理解Scrum的价值观、实践以及Scrum框架。 CSM是“服务型领导”&#xff0c;帮助Scrum团队一起紧密合作。 …

QT案例 Qt Creator 使用QuaZIP加密压缩解压ZIP文件

QT开发中部分项目可能会涉及对项目数据的一些指定文件的打包压缩以及指定目录下的解压&#xff0c;此时也需要对数据数据进行加密以确保数据的安全性。此时就可以使用Quazip实现相关功能。 QuaZIP是使用Qt&#xff0c;C对ZLIB进行简单封装的用于压缩ZIP以及解压缩ZIP的开源库。…

Hive表-----数据清洗

以下内容所需要的环境 &#xff1a;hive 、beeline、Zeppelin&#xff08;可视化界面如何操作表格&#xff09; 一、准备表格 1、上传csv表格至linux目录中 百度网盘自取&#xff1a;链接&#xff1a;https://pan.baidu.com/s/1xd5MdXiBDLBUtP07kpgl5Q?pwd2ema 提取码&…

python+appium+夜神模拟器(app抓包爬虫)

安装模块 pip install appium-python-client 安装andriodSDK 官网下载&#xff1a;https://android-sdk.en.softonic.com/download 自动下载一个压缩包&#xff0c;解压后就是一个文件夹放各种需要的文件&#xff0c;将解压的路径配置到环境变量中。 然后添加到path中。 下…