Android源码学习---init

news2025/1/11 20:42:20

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,以及属性服务。

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

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

相关文章

【博学谷学习记录】超强总结,用心分享丨人工智能 Python面向对象 学习总结之Python与Java的区别

目录前言简述面向对象类对象特性前言 经过学习&#xff0c;对Python面向对象部分有了一定的了解。 总结记录&#xff1a;面向对象上Python与Java的部分区别 简述 从类、对象、特性三个层面来简述其部分区别 面向对象 类 PythonJava定义class ClassName(object):passpubl…

2000-2020年各省固定资本存量数据

2000-2020年各省资本存量数据 1&#xff1a;来源&#xff1a;统计NJ、各省统计NJ 2、时间&#xff1a;2000-2020年 3、包括&#xff1a;30个省 4、数据说明&#xff1a;含原始数据和计算过程及最终结果 4、指标说明&#xff1a; 参考文献&#xff1a; 单豪杰&#xff08;…

【微服务架构组件之注册中心】注册中心选型-我只选nacos

注册中心的产生是基于用来解耦服务提供者(Provider)与消费者&#xff08;Consumer&#xff09;的关系&#xff0c;分布式设计架构下&#xff0c;众多的服务提供者的数量并不是动态不变的&#xff0c;在传统的静态LB的方案中&#xff0c;无法很好感知这种变化&#xff1b; 在分…

[附源码]java毕业设计网上宠物商店

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

进度条——不仅仅是语言层面上的小程序

文章目录\r和\n进度条完整代码\r和\n 在老式键盘上&#xff0c;回车键是这样的形状 但是该键的功能它不仅仅是回车&#xff0c;而是回车换行&#xff01; 这里需要明白两个概念&#xff1a; 回车&#xff1a;光标移动到当前行的行首 换行&#xff1a;光标移动到当前位置的…

跟艾文学编程《Python基础》Anaconda 安装

作者&#xff1a;艾文&#xff0c;计算机硕士学位&#xff0c;企业内训讲师和金牌面试官&#xff0c;公司资深算法专家&#xff0c;现就职BAT一线大厂。 邮箱&#xff1a;1121025745qq.com 博客&#xff1a;https://edu.csdn.net/lecturer/894?spm1003.2001.3001.4144 内容&am…

原生AJAX

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;微微的猪食小窝 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 微微的猪食小窝 原创 1、AJAX 简介 AJAX 全称为Asynchronous Javascript And XML,就是异步的JS 和 XML. 通过AJAX可以在浏览器中向服务器…

Vue3留言墙项目——主体部分静态、mock

文章目录主体头部主体关键部分小卡片组件创建mock数据以及使用主体头部 主体部分显示的内容&#xff0c;根据头部点击的是留言墙还是照片墙的按钮&#xff0c;显示不同的内容。 将照片墙和留言墙要渲染的数据抽取到一个js中&#xff0c;在导入的Main.vue&#xff08;主体页面&…

[go]汇编ASM简介

文章目录汇编(ASM)寄存器帧指针FP常见指令函数示例生成汇编Go汇编代码主要用于优化和与底层系统交互&#xff0c;并不会像其它的经典汇编代码那样独立运行。汇编(ASM) Go ASM是一种被Go编译器使用的特殊形式的汇编语言&#xff08;伪汇编&#xff09;&#xff0c;它基于Plan9输…

记录一次Powerjob踩的坑(Failed to deserialize message)

一. 问题描述: 在本地开发环境, server端和worker都运行正常. 但是发布到SIT环境(容器)的时候, 服务端却监测不到worker(worker可以找到服务端) 二. 问题表现: 1.服务端看不到Worker信息 2. 服务端日志信息 : Failed to deserialize message from [akka://oms111.111.111…

ECMAScript modules规范示例详解

引言 很多编程语言都有模块这一概念&#xff0c;JavaScript 也不例外&#xff0c;但在 ECMAScript 2015 规范发布之前&#xff0c;JavaScript 没有语言层面的模块语法。模块实际上是一种代码重用机制&#xff0c;要实现代码重用&#xff0c;将不同的功能划分到不同的文件中是必…

pycharm安装并加载编译器,设置背景图片,手把手详细操作

pycharm安装并加载编译器&#xff0c;设置背景图片&#xff0c;手把手详细操作 pycharm社区版&#xff08;免费&#xff09;下载官网 双击安装包&#xff0c;选择安装路径 勾选这两个&#xff0c;其实全不勾也没事 下一步默认就行&#xff0c;点install 安装完成后&#xf…

mimikatz抓取密码实战

必须下载最新版本 Releases gentilkiwi/mimikatz GitHubhttps://github.com/gentilkiwi/mimikatz/releases 有32和64之分&#xff0c;systeminfo查看自己版本 首先我们用后门得到权限&#xff0c;在用getsystem提权&#xff0c;因为mimikatz要system权限&#xff0c;getuid…

Python基础-1-环境搭建(初体验)

一&#xff1a;开发环境 Linux-5.15.0&#xff08;Ubuntu22.04&#xff09; 二&#xff1a;安装Python3 1、安装&#xff1a;sudo apt-get install python3 2、版本查询&#xff1a; python3 --version python3进入python解释器也可查询对应版本&#xff0c;按CtrlD或执行…

力扣(LeetCode)20. 有效的括号(C++)

栈模拟 一次遍历字符串 sss &#xff0c; 遇到左括号则入栈&#xff0c;遇到右括号则匹配栈顶。如果右括号匹配成功 &#xff0c; 栈顶元素弹栈 &#xff0c; 匹配不成功 &#xff0c; 则 returnfalsereturn\ \ falsereturn false 。 提示 : 当遍历完所有字符&#xff0c;记…

【计算机网络】扩展以太网方法总结

注&#xff1a;最后有面试挑战&#xff0c;看看自己掌握了吗 文章目录物理层扩展以太网链路层扩展以太网网桥网桥分类透明网桥源路由网桥多接口网桥----以太网交换机直通式交换机存储转发式交换机冲突域与广播域&#x1f343;博主昵称&#xff1a;一拳必胜客 &#x1f338;博主…

LinkedList详解

介绍 众所周知ArrayList底层数据结构是数组&#xff0c;但是数组有个缺点&#xff0c;虽然查询快&#xff0c;但是增删改会慢因为数组是在连续的位置上面储存对象的应用。当我们删除某一个元素的时候在他后面的元素的索引都会左移&#xff0c;导致开销会很大。所以LinkedList应…

Linux系统下交叉编译工具的安装实现

大家好&#xff0c;今天主要和大家聊一聊&#xff0c;如何使用Linux系统下的交叉编译工具链的方法。 目录 第一:交叉编译工具链基本简介 ​第二&#xff1a;交叉编译工具安装方法 ​第三&#xff1a;安装相关库 ​第四&#xff1a;交叉编译工具验证 第一:交叉编译工具链基…

0100 蓝桥杯真题03

import java.util.Scanner; /* * 题目描述 * 如下图所示&#xff0c;3 x 3 的格子中填写了一些整数。 --*---- |10* 1|52| --****-- |20|30* 1| *******-- | 1| 2| 3| ------ *我们沿着图中的星号线剪开&#xf…

【Redis-04】Redis两种持久化方式(RDB和AOF)

Redis是基于内存的数据结构服务器&#xff0c;保存了大量的键值对数据&#xff0c;所以持久化到磁盘是非常必要的&#xff0c;Redis提供了两种持久化的方式&#xff0c;分别是RDB和AOF。下面我们看下这两种持久化方式的具体实现原理。 1.RDB持久化 首先&#xff0c;RDB持久化方…