Android源码学习---init

init,是linux系统中用户空间的第一个进程,也是Android系统中用户空间的第一个进程。

位于/system/core/init目录下。

分析init

int main(int argc, char **argv)
{
//设置子进程退出的信号处理函数 sigchld_handler
act.sa_handler = sigchld_handler;

//解析init.rc文件
parse_config_file("/init.rc");
//通过这个函数读取/proc/cpuinfo得到机器的HardWare名,
get_hardware_name();
snprintf(tmp, sizeof(tmp), "/init.%s.rc", hardware);
//解析这个和机器相关的配置文件
parse_config_file(tmp);


//执行各个阶段的工作 共分为4个阶段
/**
 * ① early-init阶段完成的动作
 */
action_for_each_trigger("early-init", action_add_queue_tail);
drain_action_queue();

//初始化和属性相关的资源
property_init();

/**INIT_IMAGE_FILE定义为“initlogo.rle”
 *  这个函数将加载这个文件作为系统开机的画面
 *  如果加载文件initlogo.rle失败就会打开/dev/tty0设备,将输出“ANDROID”的字样作为开机画面
 */

if( load_565rle_image(INIT_IMAGE_FILE) ) {
fd = open("/dev/tty0", O_WRONLY);
if (fd >= 0) {
const char *msg;
msg = "\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"  // console is 40 cols x 30 lines
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"             A N D R O I D ";
write(fd, msg, strlen(msg));
close(fd);
}
}

/**
  * ②init
  */
 action_for_each_trigger("init", action_add_queue_tail);
 drain_action_queue();

//启动属性服务
 property_set_fd = start_property_service();

/**
  * ③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();

//boot char是一个小工具 对系统性能进行分析 帮助提升系统的启动速度
#if BOOTCHART
bootchart_count = bootchart_init();
if (bootchart_count < 0) {
……
#endif


/**
  * 进入init循环
  */

for(;;) {
int nr, i, timeout = -1;

for (i = 0; i < fd_count; i++)
ufds[i].revents = 0;

drain_action_queue();
//重启死亡的进程
restart_processes();
//调用poll等待一些事情的发生
nr = poll(ufds, fd_count, timeout);

总结:init工作主要先初始化两个配置文件,然后执行四个阶段的动作,接下来调用property_init相关的属性资源,并通过property_start_service启动属性服务,然后init将进入一个无限循环,并等待一些事情的发生。

解析配置文件

init会解析两个配置文件一个是系统配置文件init_rc,另一个是与硬件平台相关的配置文件,解析调用的是parse_config_file函数,然后看看这个函数里做了什么?

 /system/core/init下的parser.c文件

int parse_config_file(const char *fn)
{
    parse_config(fn, data);
    return 0;
}
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;
        case T_NEWLINE:
            if (nargs) {
                //得到关键字类型
                int kw = lookup_keyword(args[0]);
                //判断关键字是不是SECTION
                if (kw_is(kw, SECTION)) {
                    state.parse_line(&state, 0, 0);
                    //解析这个SECTION
                    parse_new_section(&state, kw, nargs, args);
                } else {
                    state.parse_line(&state, nargs, args);
                }
                nargs = 0;
            }
            break;
        case T_TEXT:
            if (nargs < SVC_MAXARGS) {
                args[nargs++] = state.text;
            }
            break;
        }
    }
}

实际上,parse_config首先会找到配置文件中的section,然后根据不同的section使用不同的解析函数去解析。section是什么?在parse.h里包含了keywords.h脚本文件

 

第一次包含keywords.h时声明了一些函数,定义了一个枚举值是K_class等关键字

 第二次包含keywords.h后,得到了一个keyword_info的结构体数组,以前面对应的枚举值为索引,存储对应的关键字信息,这些信息包括关键字名称、处理函数、函数参数个数以及属性。根据keywords.h中的定义,on和service就可以表示成section。

 

init.rc的解析:

1 一个section的内容的从这个标识section的关键字开始,到下一个标识section的关键字结束。

2 init.rc中出现的名字为boot和init的section就是前面介绍的四个动作执行阶段中的boot和init,也就是说,在boot这个阶段执行的动作都是由boot这个section定义的。

然后再来解析init.rc文件

 

总结:

1. 一个section的内容是从这个标识section的关键字开始,直到下一个标识section的地方结束。on和service都是section开始的标识。

2. 这里出现了名为boot和init的section,这就是前面四个动作中执行的init和boot,init和boot阶段执行的动作都是在这里定义的

Zygote对应的是service,zygote的section是这样的

service zygote /system/bin/app_process -Xzygote /system/bin =zygote \
--start system server
socket zygote stream 666 
onrestart write 
onrestart write
onrestart restart media 

 解析section的入口函数是parse_new_section,在这里,解析service时用到了parse_service和parse_line_service这两个函数,

先来看Service结构体,是什么样子的?

struct service {
    struct listnode slist;//将结构体连接成一个双向链表
    const char *name; //service的名字 这里就是zygote
    const char *classname; //service所属的class的名字
    unsigned flags;//service属性
    pid_t pid;//进程号
    time_t time_started;   //上一次启动时间
    time_t time_crashed;  //上一次死亡的时间
    int nr_crashed;       //死亡的次数   
    uid_t uid;
    gid_t gid;
    gid_t supp_gids[NR_SVC_SUPP_GIDS];
    size_t nr_supp_gids;
struct socketinfo *sockets; //用来描述socket信息的结构体
//service一般运行在一个单独的线程中,envvars表示创建这个进程需要的环境变量
struct svcenvinfo *envvars;
    struct action onrestart;  /* Actions to execute on restart. */ 
    /* 与keywords相关的信息 */
    int *keycodes;
    int nkeycodes;
int keychord_id; 
//IO优先级
    int ioprio_class;
    int ioprio_pri;
int nargs; //参数个数
    char *args[1];
}

现在已经了解了service中的结构体,再来看zygote中的三个onrestart是什么,先看action结构体:

struct action {
     /*一个action结构体可以存放在三个双向链表中 */
    struct listnode alist; //存放所有的action
    struct listnode qlist;//链接等待执行的action
    struct listnode tlist; //链接那些待某些条件满足后需要执行的action

    unsigned hash;
    const char *name;
    /*对应的是command列表 zygote有三个onrestart就会创建三个command结构体*/
    struct listnode commands;
    struct command *current;
};

接下来,在parse_service和parse_line_service里,parse_service搭建了一个service的架子,parse_line_service里:

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_oneshot: //service的属性
        svc->flags |= SVC_ONESHOT;/*有五个属性 SVC_ONESHOT代表退出后不需要重启*/
//SVC_DISABLED 不随class自动启动;SVC_RUNNING 正在运行;SVC_RESTARING:等待重启;
//SVC_CONSOLE 该service需要使用控制台;SVC_CRITICAL 如果在规定时间内,该service不断重启则系统会重启进入恢复模式。
   ......
List_add_tail(&svc->onrestart.commands, &cmd->clist);// 把新建的command加入到双向链表中
   ......

由此可见,parse_line_service是根据配置文件的内容填充service结构体。

总结:

service_list解析后将service连接到一起,是一个双向链表的形式。

socket_info也是一个双向链表。

onrestart通过command指向一个command链表,zygote有三个command。

init控制service

Init.rc目录是system/core/rootdir/init,rc

在init.rc的解析中,包含了这样的内容:

 class_start标识一个COMMAND,对应的处理函数是do_class_start,位于boot section里,在init的main函数里,boot阶段是这样的,

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

do_class_start函数位于system/core/init/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*/
//zygote的classname 就是default
    service_for_each_class(args[1], service_start_if_not_disabled);
    return 0;
}

service_start_if_not_disabled里调用了service_start函数,代码分析如下:

1 启动zygote,Zygote是由fork和execve共同创建的

void service_start(struct service *svc, const char *dynamic_args)
{
...
 if (svc->flags & SVC_RUNNING) {
        return; //service已经在运行,不用处理
}
/*service一般运行在另外一个进程中,这个进程也是init的子进程,*/
    if (stat(svc->args[0], &s) != 0) {
        ERROR("cannot find '%s', disabling '%s'\n", svc->args[0], svc->name);
        svc->flags |= SVC_DISABLED;
        return;
    }
//调用fork创建子进程
pid = fork();
//Pid为0的时候,表明运行在子线程中
 if (pid == 0) {//pid为0 表明运行在子进程中
       ......
//添加环境变量信息 socket等

 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));
            }
        } 
}
//父进程的init的处理
svc->time_started = gettime();
    svc->pid = pid;
    svc->flags |= SVC_RUNNING;
......
}

2 重启zygote

onrestart方法顾名思义是在zygote重启的时候调用,zygote死亡后,父进程init:

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

signal_fd就是在init中通过socketpair创建的两个当中的一个,会往这个socket里发送数据另一个socket就一定能收到,这样就会导致init从poll函数中返回,在init.c中,

    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
}

那么zygote在哪里重启呢?答案是init.c的main函数里.

static int wait_for_one_process(int block)
{
......
   svc = service_find_by_pid(pid);//找到死掉的service
//杀掉zygote创建的所有子进程,这就是zygote死后Java世界崩溃的原因
   if (!(svc->flags & SVC_ONESHOT)) {
        kill(-pid, SIGKILL);
        NOTICE("process '%s' killing any children in process group\n", svc->name);
}
//如果设置了SVC_CRITICAL模式,则四分钟内重启的次数不能超过四次否则机器
//会在重启后进入recovery模式
    if (svc->flags & SVC_CRITICAL) {
        if (svc->time_crashed + CRITICAL_CRASH_WINDOW >= now) {
            if (++svc->nr_crashed > CRITICAL_CRASH_THRESHOLD) {
                ERROR("critical process '%s' exited %d times in %d minutes; "
                      "rebooting into recovery mode\n", svc->name,
                      CRITICAL_CRASH_THRESHOLD, CRITICAL_CRASH_WINDOW / 60);
                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_RESTARTING,然后执行service onrestart中的COMMAND
svc->flags |= SVC_RESTARTING;
    }

属性服务

Windows平台有个叫注册表的东西,可以存储一些类似key/value的键值对,包含了系统的属性,即使系统在重启后也能根据注册表中的属性进行初始化工作,Android平台这种类似的机制叫做属性服务---property service 如下是该手机的部分属性

 属性服务的初始化

init.c中有个函数初始化属性服务函数 property_init(),这里先调用init_property_area()函数进行初始化属性存储区域,

static int init_property_area(void)
{
    prop_area *pa;
    if(pa_info_array)
        return -1;

/*initworkspace是一块内存 PA_SIZE是存储空间的总大小 a_workspace是workspace的结构体*/
    if(init_workspace(&pa_workspace, PA_SIZE))
        return -1;

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;

        /*这个变量由bionic libc库输出 */
    __system_property_area__ = pa;

    return 0;
}

为什么在最后要给变量赋值呢?属性虽然由init创建,但是Android希望其他进程也能读取到这块内存里的东西所以做了以下工作:

  1. 把属性区域建共享内存上,共享内存是可以跨进程的。
  2. 利用gcc的constructor属性,这个属性指明了一个_libc_prenit函数,当bionic libc这个库被加载时,就会自动调用_libc_prenit,这个函数内部就将完成共享内存到本地进程的映射工作

客户端进程获取存储空间

在bionic/libc/bionic/libc_init_common.c的libc_init_dynamic.c函数中,constructor属性指示加载器加载该库后,首先调用_libc_prenit函数,然后调用libc_init_common函数,最后一行就是初始化客户端的属性存储区域。

int __system_properties_init(void)
{
...
//zygote中添加的环境变量  在这里取出
env = getenv("ANDROID_PROPERTY_WORKSPACE"); 
/*映射init创建的那块内存到本地进程空间,本地进程就可以使用这个共享内存了 指定了PROT_READ属性是只读不允许设置*/
pa = mmap(0, sz, PROT_READ, MAP_SHARED, fd, 0);
}

 

属性服务器的分析

init进程会启动一个属性服务器来与与客户端交互设置客户端属性,属性服务器由start_property_service函数启动

int start_property_service(void)
{
    int fd;
//加载属性文件,解析出来设置到属性空间中
    load_properties_from_file(PROP_PATH_SYSTEM_BUILD);
    load_properties_from_file(PROP_PATH_SYSTEM_DEFAULT);
    load_properties_from_file(PROP_PATH_LOCAL_OVERRIDE);
//创建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;
}

属性服务器接受到客户端请求后会在init进程中,调用handle_property_set_fd进行处理。检查客户端是否有足够的权限,有则调用property_set(位于properties_service.c下)函数进行相关处理。

那么客户端是如何设置属性的?

客户端调用property_set(位于properties.c下)进行处理,在libcutils库下,

static int send_prop_msg(prop_msg *msg)
{    //建立和属性服务器的socket链接
    s = socket_local_client(PROP_SERVICE_NAME, 
                            ANDROID_SOCKET_NAMESPACE_RESERVED,
                            SOCK_STREAM);
   //通过socket发出去
    while((r = send(s, msg, sizeof(prop_msg), 0)) < 0) {
        if((errno == EINTR) || (errno == EAGAIN)) continue;
        break;
    }
}
int property_set(const char *key, const char *value)
{   ......
    msg.cmd = PROP_MSG_SETPROP;//设置消息码为PROP_MSG_SETPROP
    return send_prop_msg(&msg);......
}

至此,讲解了init如何解析配置文件,解析zygote,以及属性服务。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值