3.深入理解Android卷I---深入理解Init进程

本文解析了Init进程在Android系统中的核心作用,详细介绍了其通过解析init.rc文件启动和控制服务的过程,尤其是zygote服务的启动与重启机制,以及属性服务的初始化与工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

深入理解Init进程https://blog.youkuaiyun.com/Innost/article/details/47204675

关于Init进程的代码很多,而且都是C/C++语言,所以只做了大致的流程了解,没有做详细的代码笔记。


init是一个进程,确切地说,它是Linux系统中用户空间的第一个进程。由于Android是基于Linux内核的,所以init也是Android系统中用户空间的第一个进程,它的进程号是1。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]的parse_config函数。从整体来说,parse_config首先会找到配置文件的一个section,然后针对不同的 section使用不同的解析函数来解析。那么,什么是section呢?这和init.rc文件的组织结构有关。
关键字定义
keywords.h这个文件定义了init中使用的关键字,它干了两件事情:
· 第一次包含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)
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
解析section的入口函数是parse_new_section。service解析时,用到了parse_service和parse_line_service两个函数。
init中使用了一个叫service的结构体来保存和service section相关的信息;
parse_service函数只是搭建了一个service的架子,具体的内容尚需由后面的解析函数来填充;
parse_line_service将根据配置文件的内容填充service结构体,那么,zygote解析完后会得到什么呢?
· service_list链表将解析后的service全部链接到了一起,且是一个双向链表,前向节点用prev表示,后向节点用next表示。
· socketinfo也是一个双向链表,因为zygote只有一个socket,所以画了一个虚框socket做为链表的示范。
· onrestart通过commands指向一个commands链表,zygote有三个commands。
zygote这个service解析完了,现在就是“万事俱备,只欠东风”了。接下来要了解的是,init是如何控制service的。

3.2.3 init控制service
先看service是如何启动的。
1.启动zygote
service_for_each_class将从service_list中寻找classname为”default”的service,然后调用service_start_if_not_disabled函数。zygote这个service的classname的值就是“default”,所以会针对这个service调用service_start_if_not_disabled,service一般运行于另外一个进程中,这个进程也是init的子进程,所以启动service前需要判断对应的可执行文件是否存在,zygote对应的可执行文件是/system/bin/app_process。
原来,zygote是通过fork和execv共同创建的!但service结构中的那个onrestart好像没有派上用场,原因何在?
2. 重启zygote
根据名字,就可猜到onrestart应该是在zygote重启时用的。下面先看在zygote死后,它的父进程init会有什么动作:
signal_fd,就是在init中通过socketpair创建的两个socket中的一个,既然会往这个signal_fd中发送数据,那么另外一个socket就一定能接收到,这样就会导致init从poll函数中返回:[–>init.rc::main函数代码片断]

//找到死掉的那个service,现在应该找到了代表zygote的那个service。
svc = service_find_by_pid(pid);
   ......
if(!(svc->flags & SVC_ONESHOT)) {
    //杀掉zygote(死掉的service)创建的所有子进程,这就是zygote死后,Java世界崩溃的原因。
       kill(-pid, SIGKILL);
   }
/*如果设置了SVC_CRITICAL标示,则4分钟内该服务重启次数不能超过4次,否则机器会重启进入recovery模式。
  根据init.rc的配置,只有servicemanager进程享有此种待遇。相关代码未贴*/
//设置init.svc.zygote的值为restarting。
notify_service_state(svc->name, "restarting");

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

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。
       ......
}

3.2.4 属性服务
其中与init.c和属性服务有关的代码有下面两行:

property_init();
property_set_fd = start_property_service();
  1. 属性服务初始化
    (1)创建存储空间
    在properyty_init[–>property_service.c]函数中,先调用init_property_area函数,创建一块用于存储属性的存储区域,然后加载default.prop文件中的内容。
    init_property_area[–>property_service.c]函数的内容比较简单,不过最后的赋值语句可是大有来头。__system_property_area__是bionic libc库中输出的一个变量,为什么这里要给它赋值呢?
    原来,虽然属性区域是由init进程创建,但Android系统希望其他进程也能读取这块内存里的东西。为做到这一点,它便做了以下两项工作:
    · 把属性区域创建在共享内存上,而共享内存是可以跨进程的。这一点,已经在上面的代码中见到了,init_workspace函数内部将创建这个共享内存。
    · 如何让其他进程知道这个共享内存呢?Android利用了gcc的constructor属性,这个属性指明了一个__libc_prenit函数,当bionic libc库被加载时,将自动调用这个__libc_prenit,这个函数内部就将完成共享内存到本地进程的映射工作。
    (2)客户端进程获取存储空间
    关于上面的内容,来看相关代码:[–>libc_init_dynamic.c] [–>libc_init_common.c] [–>system_properties.c]
    总之,通过上述函数调用后客户端进程可以直接读取属性空间,但没有权限设置属性。客户端进程又是如何设置属性呢?

  2. 启动属性服务器
    (1)启动属性服务器
    init进程会启动一个属性服务器,而客户端只能通过和属性服务器交互才能设置属性。先来看属性服务器的内容,它由start_property_service函数启动。
    加载属性文件,其实就是解析这些文件中的属性,然后把它设置到属性空间中去。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”
    有一些属性是需要保存到永久介质上的,这些属性文件则由下面这个函数加载,这些文件存储在/data/property目录下,并且这些文件的文件名必须以persist.开头。这个函数很简单,读者可自行研究。load_persistent_properties();
    属性服务创建了一个用来接收请求的socket,可这个请求在哪里被处理呢?事实上,在init中的for循环那里已经进行相关处理了。
    (2)处理设置属性请求
    接收请求的地方是在init进程中,代码如下所示:[–>init.c::main函数片断]

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

当属性服务器收到客户端请求时,init会调用handle_property_set_fd进行处理。
如果是ctl开头的消息,则认为是控制消息,控制消息用来执行一些命令,例如用adb shell登录后,输入setprop ctl.start bootanim就可以查看开机动画了,关闭的话就输入setpropctl.stop bootanim。
当客户端的权限满足要求时,init就调用property_set进行相关处理。
(3)客户端发送请求
客户端通过property_set发送请求,property_set由libcutils库提供,[–>properties.c]。
3.3 本章小结
本章讲解了init进程如何解析zygote,以及属性服务器的工作原理,旨在帮助读者认识这个天字号第一进程。从整体来说,init.rc的解析难度相对最大。相信读者通过以上实例分析,已经理解了init.rc的解析原理。


Init进程的笔记就做到这里,目前只是大致理了一下流程,且待后看。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值